@spinalfluid/ultrac 0.1.9 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -7
- package/dist/cli.js +2124 -703
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -286,7 +286,7 @@ var agentModePresets = {
|
|
|
286
286
|
"Prefer narrow, reversible changes.",
|
|
287
287
|
"Keep final answers concise and include verification status."
|
|
288
288
|
],
|
|
289
|
-
tools: [...coreTools, "list_sessions", "read_session", "search_sessions"],
|
|
289
|
+
tools: [...coreTools, "create_skill", "list_sessions", "read_session", "search_sessions"],
|
|
290
290
|
externalTools: true
|
|
291
291
|
},
|
|
292
292
|
plan: {
|
|
@@ -331,6 +331,7 @@ var agentModePresets = {
|
|
|
331
331
|
],
|
|
332
332
|
tools: [
|
|
333
333
|
...coreTools,
|
|
334
|
+
"create_skill",
|
|
334
335
|
"ast_search",
|
|
335
336
|
"repo_handbook",
|
|
336
337
|
"verification_plan",
|
|
@@ -398,6 +399,7 @@ function modePermissionAccessError(mode, toolName, permission) {
|
|
|
398
399
|
var EchoProvider = class {
|
|
399
400
|
name = "echo";
|
|
400
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 });
|
|
401
403
|
const lastUserMessage = [...request.messages].reverse().find((message) => message.role === "user");
|
|
402
404
|
return {
|
|
403
405
|
text: JSON.stringify(
|
|
@@ -1135,6 +1137,9 @@ function pinCurrentTurn(units, isUser) {
|
|
|
1135
1137
|
}
|
|
1136
1138
|
}
|
|
1137
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
|
+
}
|
|
1138
1143
|
return JSON.stringify(message)?.length ?? 0;
|
|
1139
1144
|
}
|
|
1140
1145
|
var noticeSink = (message) => {
|
|
@@ -1142,8 +1147,8 @@ var noticeSink = (message) => {
|
|
|
1142
1147
|
`);
|
|
1143
1148
|
};
|
|
1144
1149
|
var noticed = false;
|
|
1145
|
-
function setContextBudgetNotice(
|
|
1146
|
-
noticeSink =
|
|
1150
|
+
function setContextBudgetNotice(sink2) {
|
|
1151
|
+
noticeSink = sink2;
|
|
1147
1152
|
}
|
|
1148
1153
|
function resetContextBudgetNotices() {
|
|
1149
1154
|
noticed = false;
|
|
@@ -1222,20 +1227,20 @@ function compactRun(input, options = {}) {
|
|
|
1222
1227
|
}
|
|
1223
1228
|
async function compactShape(shape, options) {
|
|
1224
1229
|
const reserveChars = options.summarise ? Math.max(0, options.reserveChars ?? shape.reserveChars ?? 0) : 0;
|
|
1225
|
-
const
|
|
1230
|
+
const plain2 = planKeepingSummaries(shape, shape.messages, reserveChars);
|
|
1226
1231
|
const skip = (reason) => ({
|
|
1227
|
-
...reserveChars > 0 ? planKeepingSummaries(shape, shape.messages, shape.reserveChars ?? 0) :
|
|
1232
|
+
...reserveChars > 0 ? planKeepingSummaries(shape, shape.messages, shape.reserveChars ?? 0) : plain2,
|
|
1228
1233
|
compacted: false,
|
|
1229
1234
|
summarisedMessages: 0,
|
|
1230
1235
|
summarisedTurns: 0,
|
|
1231
1236
|
skipped: reason
|
|
1232
1237
|
});
|
|
1233
|
-
if (
|
|
1238
|
+
if (plain2.dropped.length === 0) return skip("nothing-dropped");
|
|
1234
1239
|
if (!options.summarise) return skip("no-summariser");
|
|
1235
|
-
const source =
|
|
1240
|
+
const source = plain2.dropped.filter((message2) => !isCompactionSummary(message2));
|
|
1236
1241
|
if (source.length === 0) return skip("only-summaries");
|
|
1237
1242
|
const ceiling = options.maxSummaryChars ?? DEFAULT_MAX_SUMMARY_CHARS;
|
|
1238
|
-
const slack =
|
|
1243
|
+
const slack = plain2.usage.promptLimitChars - plain2.usage.totalChars - summaryEnvelopeChars();
|
|
1239
1244
|
const maxChars = Math.min(ceiling, slack);
|
|
1240
1245
|
if (maxChars < MIN_SUMMARY_CHARS) return skip("no-room");
|
|
1241
1246
|
const transcript = renderDroppedTranscript(source, {
|
|
@@ -1260,21 +1265,21 @@ async function compactShape(shape, options) {
|
|
|
1260
1265
|
const text = truncateToLine(summary.trim(), maxChars);
|
|
1261
1266
|
if (!text) return skip("summariser-empty");
|
|
1262
1267
|
const message = compactionSummaryMessage(text);
|
|
1263
|
-
const plan = replan(shape,
|
|
1268
|
+
const plan = replan(shape, plain2, message);
|
|
1264
1269
|
if (!plan) return skip("would-drop-more");
|
|
1265
1270
|
return {
|
|
1266
1271
|
...plan,
|
|
1267
1272
|
// The re-plan saw a transcript the span was already out of, so it has
|
|
1268
1273
|
// nothing to report about it. What was dropped is what `plain` dropped.
|
|
1269
|
-
dropped:
|
|
1270
|
-
droppedMessages:
|
|
1271
|
-
droppedTurns:
|
|
1272
|
-
droppedChars:
|
|
1274
|
+
dropped: plain2.dropped,
|
|
1275
|
+
droppedMessages: plain2.droppedMessages,
|
|
1276
|
+
droppedTurns: plain2.droppedTurns,
|
|
1277
|
+
droppedChars: plain2.droppedChars,
|
|
1273
1278
|
compacted: true,
|
|
1274
1279
|
summary: text,
|
|
1275
|
-
summarisedMessages:
|
|
1276
|
-
summarisedTurns:
|
|
1277
|
-
warning: compactionWarning(plan,
|
|
1280
|
+
summarisedMessages: plain2.dropped.length,
|
|
1281
|
+
summarisedTurns: plain2.droppedTurns,
|
|
1282
|
+
warning: compactionWarning(plan, plain2)
|
|
1278
1283
|
};
|
|
1279
1284
|
}
|
|
1280
1285
|
function planKeepingSummaries(shape, messages, reserveChars) {
|
|
@@ -1290,8 +1295,8 @@ function planKeepingSummaries(shape, messages, reserveChars) {
|
|
|
1290
1295
|
return shape.plan(messages, callerPinned, reserveChars);
|
|
1291
1296
|
}
|
|
1292
1297
|
}
|
|
1293
|
-
function replan(shape,
|
|
1294
|
-
const kept =
|
|
1298
|
+
function replan(shape, plain2, summary) {
|
|
1299
|
+
const kept = plain2.messages;
|
|
1295
1300
|
const insertAt = kept.findIndex((message) => message.role !== "system");
|
|
1296
1301
|
const at = insertAt < 0 ? kept.length : insertAt;
|
|
1297
1302
|
const next = [...kept.slice(0, at), summary, ...kept.slice(at)];
|
|
@@ -1307,9 +1312,9 @@ function replan(shape, plain, summary) {
|
|
|
1307
1312
|
if (!kept.every((message) => survived.has(message))) return void 0;
|
|
1308
1313
|
return plan;
|
|
1309
1314
|
}
|
|
1310
|
-
function compactionWarning(plan,
|
|
1311
|
-
const turns = plural2(
|
|
1312
|
-
const messages = plural2(
|
|
1315
|
+
function compactionWarning(plan, plain2) {
|
|
1316
|
+
const turns = plural2(plain2.droppedTurns, "earlier turn");
|
|
1317
|
+
const messages = plural2(plain2.dropped.length, "message");
|
|
1313
1318
|
return `Context budget: compacted ${turns} (${messages}) into a summary to stay under the ${count2(plan.usage.promptLimitChars)}-character prompt limit. The summary is in the transcript in their place; the recent turns are untouched.`;
|
|
1314
1319
|
}
|
|
1315
1320
|
function truncateToLine(text, maxChars) {
|
|
@@ -2043,6 +2048,10 @@ var UltracProvider = class {
|
|
|
2043
2048
|
if (chunk.choices[0]?.finish_reason) finishReason = chunk.choices[0].finish_reason;
|
|
2044
2049
|
const delta = chunk.choices[0]?.delta;
|
|
2045
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?.();
|
|
2046
2055
|
if (delta.content) content += delta.content;
|
|
2047
2056
|
for (const part of delta.tool_calls ?? []) {
|
|
2048
2057
|
const call = calls.get(part.index) ?? { id: "", name: "", arguments: "" };
|
|
@@ -2198,8 +2207,8 @@ async function validateSnapshotWithin(snapshot, timeoutMs) {
|
|
|
2198
2207
|
try {
|
|
2199
2208
|
return await Promise.race([
|
|
2200
2209
|
snapshot.validate(),
|
|
2201
|
-
new Promise((
|
|
2202
|
-
timer = setTimeout(() =>
|
|
2210
|
+
new Promise((resolve17) => {
|
|
2211
|
+
timer = setTimeout(() => resolve17(void 0), timeoutMs);
|
|
2203
2212
|
})
|
|
2204
2213
|
]);
|
|
2205
2214
|
} finally {
|
|
@@ -2263,8 +2272,8 @@ async function settleWithin(promise, timeoutMs) {
|
|
|
2263
2272
|
try {
|
|
2264
2273
|
await Promise.race([
|
|
2265
2274
|
Promise.resolve(promise).catch(() => void 0),
|
|
2266
|
-
new Promise((
|
|
2267
|
-
timer = setTimeout(
|
|
2275
|
+
new Promise((resolve17) => {
|
|
2276
|
+
timer = setTimeout(resolve17, timeoutMs);
|
|
2268
2277
|
})
|
|
2269
2278
|
]);
|
|
2270
2279
|
} finally {
|
|
@@ -2308,10 +2317,11 @@ function toChatMessages(request) {
|
|
|
2308
2317
|
];
|
|
2309
2318
|
}
|
|
2310
2319
|
function toChatMessage(message) {
|
|
2311
|
-
return {
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
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 };
|
|
2315
2325
|
}
|
|
2316
2326
|
function toNativeChatMessage(message) {
|
|
2317
2327
|
if (message.role === "tool") {
|
|
@@ -2331,6 +2341,10 @@ function toNativeChatMessage(message) {
|
|
|
2331
2341
|
}))
|
|
2332
2342
|
};
|
|
2333
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
|
+
] };
|
|
2334
2348
|
return { role: message.role, content: message.content };
|
|
2335
2349
|
}
|
|
2336
2350
|
function toOpenAiTool(tool) {
|
|
@@ -2481,7 +2495,7 @@ var FallbackConfigSchema = z3.object({
|
|
|
2481
2495
|
backoffFactor: z3.number().min(1).max(10).default(2),
|
|
2482
2496
|
/** Randomize backoff to avoid synchronized retries. */
|
|
2483
2497
|
jitter: z3.boolean().default(true),
|
|
2484
|
-
/** Per-attempt timeout;
|
|
2498
|
+
/** Per-attempt timeout; text streams bound first-token time, native streams bound inactivity. 0 disables it. */
|
|
2485
2499
|
requestTimeoutMs: z3.number().int().min(0).max(6e5).default(12e4)
|
|
2486
2500
|
});
|
|
2487
2501
|
var ProviderTimeoutError = class extends Error {
|
|
@@ -2722,17 +2736,17 @@ function computeBackoffDelay(attempt, options) {
|
|
|
2722
2736
|
return Math.round(capped * (0.5 + random() * 0.5));
|
|
2723
2737
|
}
|
|
2724
2738
|
function defaultSleep(ms, cancel) {
|
|
2725
|
-
return new Promise((
|
|
2739
|
+
return new Promise((resolve17) => {
|
|
2726
2740
|
if (cancel?.aborted) {
|
|
2727
|
-
|
|
2741
|
+
resolve17();
|
|
2728
2742
|
return;
|
|
2729
2743
|
}
|
|
2730
|
-
const timer = setTimeout(
|
|
2744
|
+
const timer = setTimeout(resolve17, ms);
|
|
2731
2745
|
cancel?.addEventListener(
|
|
2732
2746
|
"abort",
|
|
2733
2747
|
() => {
|
|
2734
2748
|
clearTimeout(timer);
|
|
2735
|
-
|
|
2749
|
+
resolve17();
|
|
2736
2750
|
},
|
|
2737
2751
|
{ once: true }
|
|
2738
2752
|
);
|
|
@@ -2807,10 +2821,19 @@ var FallbackProvider = class {
|
|
|
2807
2821
|
);
|
|
2808
2822
|
}
|
|
2809
2823
|
async chatWithTools(request) {
|
|
2810
|
-
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal) => {
|
|
2824
|
+
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal, touch) => {
|
|
2811
2825
|
if (entry.provider.supportsToolCalling && entry.provider.chatWithTools) {
|
|
2812
|
-
return entry.provider.chatWithTools({
|
|
2826
|
+
return entry.provider.chatWithTools({
|
|
2827
|
+
...request,
|
|
2828
|
+
model,
|
|
2829
|
+
signal,
|
|
2830
|
+
onProgress: () => {
|
|
2831
|
+
touch();
|
|
2832
|
+
request.onProgress?.();
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2813
2835
|
}
|
|
2836
|
+
if (request.messages.some((message) => "image" in message && message.image)) throw Object.assign(new Error("This fallback provider cannot inspect image attachments."), { status: 400 });
|
|
2814
2837
|
return this.chatThroughToolProtocol(entry, model, request, signal);
|
|
2815
2838
|
});
|
|
2816
2839
|
}
|
|
@@ -2895,7 +2918,8 @@ var FallbackProvider = class {
|
|
|
2895
2918
|
const result = await this.withDeadline(
|
|
2896
2919
|
entry.name,
|
|
2897
2920
|
signal,
|
|
2898
|
-
(attemptSignal) => invoke(entry, model, attemptSignal)
|
|
2921
|
+
(attemptSignal, touch) => invoke(entry, model, attemptSignal, touch),
|
|
2922
|
+
operation === "chatWithTools"
|
|
2899
2923
|
);
|
|
2900
2924
|
this.active = target;
|
|
2901
2925
|
trace.end({ outcome: "ok" });
|
|
@@ -3115,10 +3139,10 @@ var FallbackProvider = class {
|
|
|
3115
3139
|
error.name = "AbortError";
|
|
3116
3140
|
throw error;
|
|
3117
3141
|
}
|
|
3118
|
-
async withDeadline(provider, signal, run) {
|
|
3119
|
-
const deadline = this.createDeadline(provider, signal);
|
|
3142
|
+
async withDeadline(provider, signal, run, inactivity = false) {
|
|
3143
|
+
const deadline = this.createDeadline(provider, signal, inactivity);
|
|
3120
3144
|
try {
|
|
3121
|
-
return await deadline.race(run(deadline.signal));
|
|
3145
|
+
return await deadline.race(run(deadline.signal, deadline.touch));
|
|
3122
3146
|
} finally {
|
|
3123
3147
|
deadline.clear();
|
|
3124
3148
|
}
|
|
@@ -3128,31 +3152,55 @@ var FallbackProvider = class {
|
|
|
3128
3152
|
* only ever rejects while the deadline is live, so clearing it can never leave
|
|
3129
3153
|
* an unhandled rejection behind.
|
|
3130
3154
|
*/
|
|
3131
|
-
createDeadline(provider, signal) {
|
|
3155
|
+
createDeadline(provider, signal, inactivity = false) {
|
|
3132
3156
|
if (this.requestTimeoutMs <= 0) {
|
|
3133
|
-
return { signal, race: (promise) => promise, clear: () => void 0 };
|
|
3157
|
+
return { signal, race: (promise) => promise, clear: () => void 0, touch: () => void 0 };
|
|
3134
3158
|
}
|
|
3135
3159
|
const controller = new AbortController();
|
|
3136
|
-
|
|
3160
|
+
let cleared = false;
|
|
3161
|
+
let idleTimer;
|
|
3162
|
+
const hardTimer = new AbortController();
|
|
3163
|
+
let rejectExpired;
|
|
3164
|
+
const expired = new Promise((_, reject) => {
|
|
3165
|
+
rejectExpired = reject;
|
|
3166
|
+
});
|
|
3167
|
+
const forwardAbort = () => {
|
|
3168
|
+
const reason = signal?.reason ?? Object.assign(new Error("Request was aborted."), { name: "AbortError" });
|
|
3169
|
+
rejectExpired(reason);
|
|
3170
|
+
controller.abort(reason);
|
|
3171
|
+
};
|
|
3172
|
+
const expire = (timeoutMs) => {
|
|
3173
|
+
const error = new ProviderTimeoutError(timeoutMs, provider);
|
|
3174
|
+
rejectExpired(error);
|
|
3175
|
+
controller.abort(error);
|
|
3176
|
+
};
|
|
3177
|
+
const touch = () => {
|
|
3178
|
+
if (cleared || controller.signal.aborted) return;
|
|
3179
|
+
idleTimer?.abort();
|
|
3180
|
+
const timer = new AbortController();
|
|
3181
|
+
idleTimer = timer;
|
|
3182
|
+
void this.sleep(this.requestTimeoutMs, timer.signal).then(() => {
|
|
3183
|
+
if (!cleared && !timer.signal.aborted) expire(this.requestTimeoutMs);
|
|
3184
|
+
});
|
|
3185
|
+
};
|
|
3186
|
+
touch();
|
|
3187
|
+
if (inactivity) {
|
|
3188
|
+
void this.sleep(3e5, hardTimer.signal).then(() => {
|
|
3189
|
+
if (!cleared && !hardTimer.signal.aborted) expire(3e5);
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3137
3192
|
if (signal) {
|
|
3138
3193
|
if (signal.aborted) forwardAbort();
|
|
3139
3194
|
else signal.addEventListener("abort", forwardAbort, { once: true });
|
|
3140
3195
|
}
|
|
3141
|
-
let cleared = false;
|
|
3142
|
-
const cancelDeadline = new AbortController();
|
|
3143
|
-
const expired = new Promise((_, reject) => {
|
|
3144
|
-
void this.sleep(this.requestTimeoutMs, cancelDeadline.signal).then(() => {
|
|
3145
|
-
if (cleared) return;
|
|
3146
|
-
controller.abort();
|
|
3147
|
-
reject(new ProviderTimeoutError(this.requestTimeoutMs, provider));
|
|
3148
|
-
});
|
|
3149
|
-
});
|
|
3150
3196
|
return {
|
|
3151
3197
|
signal: controller.signal,
|
|
3152
3198
|
race: (promise) => Promise.race([promise, expired]),
|
|
3199
|
+
touch: inactivity ? touch : () => void 0,
|
|
3153
3200
|
clear: () => {
|
|
3154
3201
|
cleared = true;
|
|
3155
|
-
|
|
3202
|
+
idleTimer?.abort();
|
|
3203
|
+
hardTimer.abort();
|
|
3156
3204
|
signal?.removeEventListener("abort", forwardAbort);
|
|
3157
3205
|
}
|
|
3158
3206
|
};
|
|
@@ -4065,7 +4113,7 @@ function runProcess(command, args, options) {
|
|
|
4065
4113
|
});
|
|
4066
4114
|
}
|
|
4067
4115
|
const { env: env2, redactedEnvKeys } = createSanitizedProcessEnv();
|
|
4068
|
-
return new Promise((
|
|
4116
|
+
return new Promise((resolve17, reject) => {
|
|
4069
4117
|
const child = spawn(command, args, {
|
|
4070
4118
|
cwd: options.cwd,
|
|
4071
4119
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -4083,7 +4131,7 @@ function runProcess(command, args, options) {
|
|
|
4083
4131
|
});
|
|
4084
4132
|
const untrack = trackProcessGroup(child.pid);
|
|
4085
4133
|
const stdout6 = createCapture(maxBytes);
|
|
4086
|
-
const
|
|
4134
|
+
const stderr2 = createCapture(maxBytes);
|
|
4087
4135
|
let settled = false;
|
|
4088
4136
|
let started = false;
|
|
4089
4137
|
let termination;
|
|
@@ -4139,7 +4187,7 @@ function runProcess(command, args, options) {
|
|
|
4139
4187
|
};
|
|
4140
4188
|
const onAbort = () => beginTermination("aborted");
|
|
4141
4189
|
child.stdout.on("data", (chunk) => stdout6.push(chunk));
|
|
4142
|
-
child.stderr.on("data", (chunk) =>
|
|
4190
|
+
child.stderr.on("data", (chunk) => stderr2.push(chunk));
|
|
4143
4191
|
child.stdout.on("error", () => {
|
|
4144
4192
|
});
|
|
4145
4193
|
child.stderr.on("error", () => {
|
|
@@ -4150,9 +4198,9 @@ function runProcess(command, args, options) {
|
|
|
4150
4198
|
child.on("error", (error) => {
|
|
4151
4199
|
finish(() => {
|
|
4152
4200
|
if (termination === "aborted") {
|
|
4153
|
-
|
|
4201
|
+
resolve17({
|
|
4154
4202
|
stdout: stdout6.text(),
|
|
4155
|
-
stderr:
|
|
4203
|
+
stderr: stderr2.text(),
|
|
4156
4204
|
started,
|
|
4157
4205
|
aborted: true,
|
|
4158
4206
|
exitCode: null,
|
|
@@ -4160,7 +4208,7 @@ function runProcess(command, args, options) {
|
|
|
4160
4208
|
timedOut: false,
|
|
4161
4209
|
timeoutMs,
|
|
4162
4210
|
...stdout6.truncation() ? { stdoutTruncation: stdout6.truncation() } : {},
|
|
4163
|
-
...
|
|
4211
|
+
...stderr2.truncation() ? { stderrTruncation: stderr2.truncation() } : {},
|
|
4164
4212
|
redactedEnvKeys
|
|
4165
4213
|
});
|
|
4166
4214
|
return;
|
|
@@ -4170,11 +4218,11 @@ function runProcess(command, args, options) {
|
|
|
4170
4218
|
});
|
|
4171
4219
|
child.on("close", (exitCode, signal) => {
|
|
4172
4220
|
const stdoutTruncation = stdout6.truncation();
|
|
4173
|
-
const stderrTruncation =
|
|
4221
|
+
const stderrTruncation = stderr2.truncation();
|
|
4174
4222
|
finish(
|
|
4175
|
-
() =>
|
|
4223
|
+
() => resolve17({
|
|
4176
4224
|
stdout: stdout6.text(),
|
|
4177
|
-
stderr:
|
|
4225
|
+
stderr: stderr2.text(),
|
|
4178
4226
|
started,
|
|
4179
4227
|
aborted: termination === "aborted",
|
|
4180
4228
|
exitCode,
|
|
@@ -5376,7 +5424,7 @@ var diagnosticsTool = {
|
|
|
5376
5424
|
const byFile = summarizeByFile(matched);
|
|
5377
5425
|
const records = matched.slice(0, input.maxRecords);
|
|
5378
5426
|
const checkerExit = runs[0].exitCode;
|
|
5379
|
-
const
|
|
5427
|
+
const clean2 = matched.length === 0;
|
|
5380
5428
|
const unparsed = matched.length === 0 && runs.some((run) => run.exitCode !== 0);
|
|
5381
5429
|
const rawTail = unparsed ? tail(runs.find((run) => run.exitCode !== 0)?.output ?? "", 2e3) : void 0;
|
|
5382
5430
|
const lines = [
|
|
@@ -5393,9 +5441,9 @@ var diagnosticsTool = {
|
|
|
5393
5441
|
...byFile.length ? byFile.slice(0, input.maxFiles).map((entry) => `- ${entry.file}: ${entry.errors} error(s), ${entry.warnings} warning(s)`) : ["- none"],
|
|
5394
5442
|
byFile.length > input.maxFiles ? `- ...${byFile.length - input.maxFiles} more file(s)` : void 0,
|
|
5395
5443
|
"",
|
|
5396
|
-
|
|
5397
|
-
...
|
|
5398
|
-
!
|
|
5444
|
+
clean2 ? unparsed ? "No diagnostics parsed, but the checker exited non-zero. Raw output tail:" : "No diagnostics." : `Diagnostics (showing ${records.length} of ${matched.length}):`,
|
|
5445
|
+
...clean2 ? rawTail ? [rawTail] : [] : records.map(renderDiagnostic),
|
|
5446
|
+
!clean2 && matched.length > records.length ? `...${matched.length - records.length} more suppressed` : void 0
|
|
5399
5447
|
].filter((line) => line !== void 0);
|
|
5400
5448
|
return {
|
|
5401
5449
|
ok: !runs.some((run) => run.aborted) && (matched.length > 0 || runs.every((run) => run.exitCode === 0)),
|
|
@@ -5675,8 +5723,8 @@ function normalizeFilters(workspace, paths) {
|
|
|
5675
5723
|
}
|
|
5676
5724
|
function normalizeDiagnosticPath(workspace, diagnostic) {
|
|
5677
5725
|
try {
|
|
5678
|
-
const
|
|
5679
|
-
return { ...diagnostic, file:
|
|
5726
|
+
const relative3 = toWorkspaceRelative(workspace, resolveInsideWorkspace(workspace, diagnostic.file));
|
|
5727
|
+
return { ...diagnostic, file: relative3.replaceAll("\\", "/") };
|
|
5680
5728
|
} catch {
|
|
5681
5729
|
return { ...diagnostic, file: diagnostic.file.replaceAll("\\", "/") };
|
|
5682
5730
|
}
|
|
@@ -6089,6 +6137,21 @@ var listFilesTool = {
|
|
|
6089
6137
|
}
|
|
6090
6138
|
};
|
|
6091
6139
|
var RANGE_MARKER_RESERVE_CHARS = 300;
|
|
6140
|
+
async function resolveReadableFile(workspace, path) {
|
|
6141
|
+
try {
|
|
6142
|
+
const target = await resolveReadablePath(workspace, path);
|
|
6143
|
+
const fileStat = await stat4(target);
|
|
6144
|
+
if (!fileStat.isFile()) throw new Error(`Not a file: ${path}`);
|
|
6145
|
+
return { target, fileStat };
|
|
6146
|
+
} catch (error) {
|
|
6147
|
+
const code = error.code;
|
|
6148
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
6149
|
+
throw new Error(
|
|
6150
|
+
`File not found: ${JSON.stringify(path)}. Use list_files to discover the exact workspace path or search_files to locate relevant code, then retry with a confirmed path. No alternate file was read.`,
|
|
6151
|
+
{ cause: error }
|
|
6152
|
+
);
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6092
6155
|
async function readFilePrefix(target, maxBytes) {
|
|
6093
6156
|
const handle = await open3(target, "r");
|
|
6094
6157
|
try {
|
|
@@ -6147,11 +6210,7 @@ var readFileTool = {
|
|
|
6147
6210
|
description: "Read a UTF-8 text file from inside the workspace. Reads a line range (startLine, maxLines) when the whole file would not fit the tool-result cap, and says where to continue.",
|
|
6148
6211
|
inputSchema: ReadFileInput,
|
|
6149
6212
|
async execute(input, context) {
|
|
6150
|
-
const target = await
|
|
6151
|
-
const fileStat = await stat4(target);
|
|
6152
|
-
if (!fileStat.isFile()) {
|
|
6153
|
-
throw new Error(`Not a file: ${input.path}`);
|
|
6154
|
-
}
|
|
6213
|
+
const { target } = await resolveReadableFile(context.workspace, input.path);
|
|
6155
6214
|
const { buffer: clipped, totalBytes, stable } = await readFilePrefix(target, input.maxBytes);
|
|
6156
6215
|
const text = clipped.toString("utf8");
|
|
6157
6216
|
const clippedByBytes = totalBytes > clipped.byteLength;
|
|
@@ -6247,11 +6306,7 @@ var readManyFilesTool = {
|
|
|
6247
6306
|
const sections = [];
|
|
6248
6307
|
let remainingBytes = input.maxTotalBytes;
|
|
6249
6308
|
for (const path of paths) {
|
|
6250
|
-
const target = await
|
|
6251
|
-
const fileStat = await stat4(target);
|
|
6252
|
-
if (!fileStat.isFile()) {
|
|
6253
|
-
throw new Error(`Not a file: ${path}`);
|
|
6254
|
-
}
|
|
6309
|
+
const { target, fileStat } = await resolveReadableFile(context.workspace, path);
|
|
6255
6310
|
const readBudget = Math.min(input.maxBytesPerFile, remainingBytes);
|
|
6256
6311
|
if (readBudget <= 0) {
|
|
6257
6312
|
files.push({ path, bytesRead: 0, totalBytes: fileStat.size, truncated: true });
|
|
@@ -6864,12 +6919,12 @@ var builtinSecretRules = [
|
|
|
6864
6919
|
];
|
|
6865
6920
|
async function resolveArtifact(workspace, requestedPath) {
|
|
6866
6921
|
const absolute = await resolveInsideWorkspaceReal(workspace, requestedPath);
|
|
6867
|
-
const
|
|
6922
|
+
const relative3 = toWorkspaceRelative(workspace, absolute);
|
|
6868
6923
|
const info = await stat5(absolute);
|
|
6869
6924
|
if (!info.isFile()) {
|
|
6870
6925
|
throw new Error(`Not a file: ${requestedPath}`);
|
|
6871
6926
|
}
|
|
6872
|
-
return { absolute, relative:
|
|
6927
|
+
return { absolute, relative: relative3 };
|
|
6873
6928
|
}
|
|
6874
6929
|
function formatMode(mode) {
|
|
6875
6930
|
return (mode & 511).toString(8).padStart(3, "0");
|
|
@@ -8082,6 +8137,213 @@ async function loadSkillsContext(workspace) {
|
|
|
8082
8137
|
}).join("\n") + notice;
|
|
8083
8138
|
}
|
|
8084
8139
|
|
|
8140
|
+
// src/skills/commands.ts
|
|
8141
|
+
var BUILTIN_SLASH_COMMANDS = [
|
|
8142
|
+
"doctor",
|
|
8143
|
+
"mcp",
|
|
8144
|
+
"continue",
|
|
8145
|
+
"autonomous",
|
|
8146
|
+
"skill",
|
|
8147
|
+
"update",
|
|
8148
|
+
"logs",
|
|
8149
|
+
"ghost",
|
|
8150
|
+
"output",
|
|
8151
|
+
"usage",
|
|
8152
|
+
"max-steps",
|
|
8153
|
+
"resume",
|
|
8154
|
+
"deny",
|
|
8155
|
+
"plan",
|
|
8156
|
+
"review",
|
|
8157
|
+
"coding",
|
|
8158
|
+
"status",
|
|
8159
|
+
"context",
|
|
8160
|
+
"compact",
|
|
8161
|
+
"diff",
|
|
8162
|
+
"sessions",
|
|
8163
|
+
"save",
|
|
8164
|
+
"test",
|
|
8165
|
+
"init",
|
|
8166
|
+
"new",
|
|
8167
|
+
"allow",
|
|
8168
|
+
"clear",
|
|
8169
|
+
"exit",
|
|
8170
|
+
"help",
|
|
8171
|
+
"memory",
|
|
8172
|
+
"mode",
|
|
8173
|
+
"model",
|
|
8174
|
+
"permissions",
|
|
8175
|
+
"quit",
|
|
8176
|
+
"skills",
|
|
8177
|
+
"tools",
|
|
8178
|
+
"whoami"
|
|
8179
|
+
];
|
|
8180
|
+
var COMMAND_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8181
|
+
var MAX_COMMAND_LENGTH = 64;
|
|
8182
|
+
function parseSlashInput(input) {
|
|
8183
|
+
const trimmed = input.trim();
|
|
8184
|
+
if (!trimmed.startsWith("/")) return null;
|
|
8185
|
+
const rest = trimmed.slice(1);
|
|
8186
|
+
const match = rest.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
8187
|
+
if (!match) return null;
|
|
8188
|
+
return { command: match[1], args: (match[2] ?? "").trim() };
|
|
8189
|
+
}
|
|
8190
|
+
function isBuiltinSlashCommand(command) {
|
|
8191
|
+
return BUILTIN_SLASH_COMMANDS.includes(command.toLowerCase());
|
|
8192
|
+
}
|
|
8193
|
+
function normalizeCommandName(value) {
|
|
8194
|
+
const candidate = value.trim().replace(/^\/+/, "").trim();
|
|
8195
|
+
if (!candidate) {
|
|
8196
|
+
throw new Error("Slash command name is empty; expected kebab-case, for example: open-pr.");
|
|
8197
|
+
}
|
|
8198
|
+
if (candidate.length > MAX_COMMAND_LENGTH) {
|
|
8199
|
+
throw new Error(
|
|
8200
|
+
`Slash command name "${candidate}" is longer than ${MAX_COMMAND_LENGTH} characters.`
|
|
8201
|
+
);
|
|
8202
|
+
}
|
|
8203
|
+
if (!COMMAND_NAME_RE.test(candidate)) {
|
|
8204
|
+
throw new Error(
|
|
8205
|
+
`Invalid slash command name "${candidate}": use kebab-case - lowercase letters, digits, and single hyphens, no spaces or slashes (for example: open-pr).`
|
|
8206
|
+
);
|
|
8207
|
+
}
|
|
8208
|
+
return candidate;
|
|
8209
|
+
}
|
|
8210
|
+
async function collectEntries(workspace) {
|
|
8211
|
+
const skills = await discoverSkills(workspace);
|
|
8212
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
8213
|
+
const conflicts = [];
|
|
8214
|
+
for (const skill of skills) {
|
|
8215
|
+
let declaration;
|
|
8216
|
+
try {
|
|
8217
|
+
declaration = skill.command ?? null;
|
|
8218
|
+
} catch (error) {
|
|
8219
|
+
conflicts.push({
|
|
8220
|
+
command: "",
|
|
8221
|
+
skillName: skill.name,
|
|
8222
|
+
scope: skill.scope,
|
|
8223
|
+
path: skill.path,
|
|
8224
|
+
reason: "invalid",
|
|
8225
|
+
message: `Skill "${skill.name}" (${skill.scope}) was skipped: ${error instanceof Error ? error.message : String(error)}`
|
|
8226
|
+
});
|
|
8227
|
+
continue;
|
|
8228
|
+
}
|
|
8229
|
+
if (!declaration) continue;
|
|
8230
|
+
let command;
|
|
8231
|
+
try {
|
|
8232
|
+
command = normalizeCommandName(declaration.raw);
|
|
8233
|
+
} catch (error) {
|
|
8234
|
+
conflicts.push({
|
|
8235
|
+
command: declaration.raw,
|
|
8236
|
+
skillName: skill.name,
|
|
8237
|
+
scope: skill.scope,
|
|
8238
|
+
path: skill.path,
|
|
8239
|
+
reason: "invalid",
|
|
8240
|
+
message: `Skill "${skill.name}" (${skill.scope}) declares a malformed command: ${error instanceof Error ? error.message : String(error)}`
|
|
8241
|
+
});
|
|
8242
|
+
continue;
|
|
8243
|
+
}
|
|
8244
|
+
if (isBuiltinSlashCommand(command)) {
|
|
8245
|
+
conflicts.push({
|
|
8246
|
+
command,
|
|
8247
|
+
skillName: skill.name,
|
|
8248
|
+
scope: skill.scope,
|
|
8249
|
+
path: skill.path,
|
|
8250
|
+
reason: "builtin",
|
|
8251
|
+
message: `Skill "${skill.name}" (${skill.scope}) declares /${command}, which is a built-in command. Built-ins always win - rename the command: key in ${skill.path}.`
|
|
8252
|
+
});
|
|
8253
|
+
continue;
|
|
8254
|
+
}
|
|
8255
|
+
const entry = {
|
|
8256
|
+
skill,
|
|
8257
|
+
command: {
|
|
8258
|
+
command,
|
|
8259
|
+
skillName: skill.name,
|
|
8260
|
+
description: skill.description,
|
|
8261
|
+
argumentHint: declaration.argumentHint,
|
|
8262
|
+
scope: skill.scope,
|
|
8263
|
+
path: skill.path
|
|
8264
|
+
}
|
|
8265
|
+
};
|
|
8266
|
+
const existing = claimed.get(command);
|
|
8267
|
+
if (!existing) {
|
|
8268
|
+
claimed.set(command, entry);
|
|
8269
|
+
continue;
|
|
8270
|
+
}
|
|
8271
|
+
const winner = existing.skill.scope === "global" && skill.scope === "project" ? entry : existing;
|
|
8272
|
+
const loser = winner === entry ? existing : entry;
|
|
8273
|
+
claimed.set(command, winner);
|
|
8274
|
+
conflicts.push({
|
|
8275
|
+
command,
|
|
8276
|
+
skillName: loser.skill.name,
|
|
8277
|
+
scope: loser.skill.scope,
|
|
8278
|
+
path: loser.skill.path,
|
|
8279
|
+
reason: "duplicate",
|
|
8280
|
+
message: `/${command} is declared by both "${winner.skill.name}" (${winner.skill.scope}) and "${loser.skill.name}" (${loser.skill.scope}). "${winner.skill.name}" wins; rename the command: key in ${loser.skill.path}.`
|
|
8281
|
+
});
|
|
8282
|
+
}
|
|
8283
|
+
const entries = [...claimed.values()].sort((a, b) => a.command.command.localeCompare(b.command.command));
|
|
8284
|
+
return { entries, conflicts };
|
|
8285
|
+
}
|
|
8286
|
+
async function listSkillCommands(workspace) {
|
|
8287
|
+
const { entries, conflicts } = await collectEntries(workspace);
|
|
8288
|
+
return { commands: entries.map((entry) => entry.command), conflicts };
|
|
8289
|
+
}
|
|
8290
|
+
function buildSkillCommandPrompt(skill, command, args) {
|
|
8291
|
+
const argumentLine = command.argumentHint ? `Arguments (${command.argumentHint}): ${args || "(none provided)"}` : `Arguments: ${args || "(none provided)"}`;
|
|
8292
|
+
const header = [
|
|
8293
|
+
`The user ran the /${command.command} slash command, which runs the "${skill.name}" skill (${skill.scope}).`,
|
|
8294
|
+
skill.description ? `Description: ${skill.description}` : void 0,
|
|
8295
|
+
argumentLine,
|
|
8296
|
+
skill.permissions.length ? `Permissions needed: ${skill.permissions.join(", ")}` : void 0,
|
|
8297
|
+
"Follow the instructions below now, applying the arguments where they are relevant."
|
|
8298
|
+
].filter(Boolean).join("\n");
|
|
8299
|
+
return `${header}
|
|
8300
|
+
|
|
8301
|
+
--- instructions ---
|
|
8302
|
+
${skill.body}`;
|
|
8303
|
+
}
|
|
8304
|
+
async function resolveSkillCommand(workspace, input) {
|
|
8305
|
+
const parsed = parseSlashInput(input);
|
|
8306
|
+
if (!parsed) return { status: "not-a-command" };
|
|
8307
|
+
const command = parsed.command.toLowerCase();
|
|
8308
|
+
if (isBuiltinSlashCommand(command)) {
|
|
8309
|
+
return { status: "builtin", command };
|
|
8310
|
+
}
|
|
8311
|
+
const { entries, conflicts } = await collectEntries(workspace);
|
|
8312
|
+
const match = entries.find((entry) => entry.command.command === command);
|
|
8313
|
+
if (!match) {
|
|
8314
|
+
const related = conflicts.filter((conflict) => conflict.command === command);
|
|
8315
|
+
if (related.length) {
|
|
8316
|
+
return {
|
|
8317
|
+
status: "conflict",
|
|
8318
|
+
command,
|
|
8319
|
+
message: related.map((conflict) => conflict.message).join("\n"),
|
|
8320
|
+
conflicts: related
|
|
8321
|
+
};
|
|
8322
|
+
}
|
|
8323
|
+
return {
|
|
8324
|
+
status: "unknown",
|
|
8325
|
+
command,
|
|
8326
|
+
available: entries.map((entry) => entry.command.command)
|
|
8327
|
+
};
|
|
8328
|
+
}
|
|
8329
|
+
return {
|
|
8330
|
+
status: "ok",
|
|
8331
|
+
command: match.command,
|
|
8332
|
+
skill: match.skill,
|
|
8333
|
+
args: parsed.args,
|
|
8334
|
+
prompt: buildSkillCommandPrompt(match.skill, match.command, parsed.args)
|
|
8335
|
+
};
|
|
8336
|
+
}
|
|
8337
|
+
function formatSkillCommandHelp(index) {
|
|
8338
|
+
if (!index.commands.length) return "";
|
|
8339
|
+
const lines = index.commands.map((command) => {
|
|
8340
|
+
const invocation = command.argumentHint ? `/${command.command} ${command.argumentHint}` : `/${command.command}`;
|
|
8341
|
+
const description = command.description || `Run the ${command.skillName} skill`;
|
|
8342
|
+
return ` ${invocation.padEnd(22)}${description} (skill: ${command.skillName})`;
|
|
8343
|
+
});
|
|
8344
|
+
return ["Skill commands:", ...lines].join("\n");
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8085
8347
|
// src/tools/workspace/skill-tools.ts
|
|
8086
8348
|
var SkillInput = z17.object({
|
|
8087
8349
|
name: z17.string().min(1).describe("Name of the skill to load, as shown in the skills list.")
|
|
@@ -8128,6 +8390,26 @@ var listSkillsTool = {
|
|
|
8128
8390
|
};
|
|
8129
8391
|
}
|
|
8130
8392
|
};
|
|
8393
|
+
var CreateSkillInput = z17.object({
|
|
8394
|
+
name: z17.string().min(1).max(80),
|
|
8395
|
+
description: z17.string().trim().min(1).max(512),
|
|
8396
|
+
body: z17.string().trim().min(1).max(6e4),
|
|
8397
|
+
command: z17.string().min(1).max(64).optional(),
|
|
8398
|
+
argumentHint: z17.string().max(256).optional()
|
|
8399
|
+
});
|
|
8400
|
+
var createSkillTool = {
|
|
8401
|
+
name: "create_skill",
|
|
8402
|
+
description: "Create a project SKILL.md with instructions; never overwrites. Trust is required before loading project skills.",
|
|
8403
|
+
permissions: ["write"],
|
|
8404
|
+
inputSchema: CreateSkillInput,
|
|
8405
|
+
async execute(input, context) {
|
|
8406
|
+
const command = input.command ? normalizeCommandName(input.command) : void 0;
|
|
8407
|
+
if (command && isBuiltinSlashCommand(command)) throw new Error(`/${command} is reserved.`);
|
|
8408
|
+
if (input.argumentHint && (!command || /[\r\n]/.test(input.argumentHint))) throw new Error("Argument hint requires a command and a single line.");
|
|
8409
|
+
const skill = await createSkill(context.workspace, { ...input, command, scope: "project" });
|
|
8410
|
+
return { changed: true, content: `Created ${skill.name}: ${skill.path}. Project skills require workspace trust before use.`, data: { name: skill.name, path: skill.path } };
|
|
8411
|
+
}
|
|
8412
|
+
};
|
|
8131
8413
|
|
|
8132
8414
|
// src/tools/workspace/todo-tools.ts
|
|
8133
8415
|
import { z as z18 } from "zod";
|
|
@@ -9121,18 +9403,18 @@ function isConcurrencySafe(tool) {
|
|
|
9121
9403
|
}
|
|
9122
9404
|
function planToolBatches(calls, tools) {
|
|
9123
9405
|
const batches = [];
|
|
9124
|
-
let
|
|
9406
|
+
let open11;
|
|
9125
9407
|
for (const call of calls) {
|
|
9126
9408
|
if (!isConcurrencySafe(tools.get(call.tool))) {
|
|
9127
9409
|
batches.push([call]);
|
|
9128
|
-
|
|
9410
|
+
open11 = void 0;
|
|
9129
9411
|
continue;
|
|
9130
9412
|
}
|
|
9131
|
-
if (!
|
|
9132
|
-
|
|
9133
|
-
batches.push(
|
|
9413
|
+
if (!open11) {
|
|
9414
|
+
open11 = [];
|
|
9415
|
+
batches.push(open11);
|
|
9134
9416
|
}
|
|
9135
|
-
|
|
9417
|
+
open11.push(call);
|
|
9136
9418
|
}
|
|
9137
9419
|
return batches;
|
|
9138
9420
|
}
|
|
@@ -9627,7 +9909,7 @@ async function acquireLock(directory, waitMs) {
|
|
|
9627
9909
|
};
|
|
9628
9910
|
} catch (error) {
|
|
9629
9911
|
if (error.code !== "EEXIST" || Date.now() >= deadline) throw error;
|
|
9630
|
-
await new Promise((
|
|
9912
|
+
await new Promise((resolve17) => setTimeout(resolve17, 10));
|
|
9631
9913
|
}
|
|
9632
9914
|
}
|
|
9633
9915
|
}
|
|
@@ -9675,6 +9957,8 @@ function readSessionContextSnapshot(session) {
|
|
|
9675
9957
|
const snapshot = session.contextSnapshot;
|
|
9676
9958
|
if (!snapshot || typeof snapshot !== "object" || snapshot.version !== 1 || !["native", "text", "run"].includes(snapshot.kind) || !Number.isSafeInteger(snapshot.completedSteps) || snapshot.completedSteps < 0 || !Array.isArray(snapshot.messages) || snapshot.messages.some((message) => {
|
|
9677
9959
|
if (!message || typeof message !== "object" || !["user", "assistant", "system", "tool"].includes(message.role) || typeof message.content !== "string") return true;
|
|
9960
|
+
const image = message.image;
|
|
9961
|
+
if (image !== void 0 && (message.role !== "user" || !image || typeof image.url !== "string" || image.url.length > 7 * 1024 * 1024 || !/^data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(image.url))) return true;
|
|
9678
9962
|
const native = message;
|
|
9679
9963
|
return snapshot.kind !== "native" && (message.role === "system" || native.toolCalls !== void 0 || native.toolCallId !== void 0) || native.toolCalls !== void 0 && message.role !== "assistant" || native.toolCallId !== void 0 && message.role !== "tool" || snapshot.kind === "native" && message.role === "tool" && typeof native.toolCallId !== "string" || native.toolCallId !== void 0 && typeof native.toolCallId !== "string" || native.toolCalls !== void 0 && (!Array.isArray(native.toolCalls) || native.toolCalls.some((call) => !call || typeof call.id !== "string" || typeof call.name !== "string" || typeof call.arguments !== "string"));
|
|
9680
9964
|
}) || !completeContextUnits(snapshot.messages) || (readSessionUserConstraints(session) ?? [{ content: session.task }]).some((constraint) => !snapshot.messages.some((message) => message.role === "user" && message.content === constraint.content))) throw new Error(`Session ${session.id} has an invalid saved context snapshot and cannot be continued safely.`);
|
|
@@ -10916,10 +11200,10 @@ async function withHardTimeout(promise, ms, onTimeout) {
|
|
|
10916
11200
|
(error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })
|
|
10917
11201
|
);
|
|
10918
11202
|
let timer;
|
|
10919
|
-
const expiry = new Promise((
|
|
11203
|
+
const expiry = new Promise((resolve17) => {
|
|
10920
11204
|
timer = setTimeout(() => {
|
|
10921
11205
|
onTimeout?.();
|
|
10922
|
-
|
|
11206
|
+
resolve17(void 0);
|
|
10923
11207
|
}, ms);
|
|
10924
11208
|
});
|
|
10925
11209
|
try {
|
|
@@ -11351,8 +11635,8 @@ function tokenizeCommand(command) {
|
|
|
11351
11635
|
function renderCommandLine(plan) {
|
|
11352
11636
|
return [plan.command, ...plan.args].join(" ");
|
|
11353
11637
|
}
|
|
11354
|
-
function parseTestOutput(runner, stdout6,
|
|
11355
|
-
const combined = [stdout6,
|
|
11638
|
+
function parseTestOutput(runner, stdout6, stderr2, workspace) {
|
|
11639
|
+
const combined = [stdout6, stderr2].filter(Boolean).join("\n");
|
|
11356
11640
|
if (runner === "vitest" || runner === "jest") {
|
|
11357
11641
|
const report = extractJestStyleReport(stdout6) ?? extractJestStyleReport(combined);
|
|
11358
11642
|
if (report) return parseJestStyleReport(report, workspace);
|
|
@@ -12076,8 +12360,8 @@ function findFileInText(text, workspace) {
|
|
|
12076
12360
|
for (const match of text.matchAll(stackFrame)) {
|
|
12077
12361
|
const candidate = match[1];
|
|
12078
12362
|
if (candidate.includes("node_modules") || candidate.includes("/internal/")) continue;
|
|
12079
|
-
const
|
|
12080
|
-
if (
|
|
12363
|
+
const relative3 = relativizeFile(candidate, workspace);
|
|
12364
|
+
if (relative3 !== "unknown") return relative3;
|
|
12081
12365
|
}
|
|
12082
12366
|
return null;
|
|
12083
12367
|
}
|
|
@@ -12565,12 +12849,12 @@ async function sendRequest(url, input, approved, signal) {
|
|
|
12565
12849
|
// The socket goes to a pinned address; TLS still verifies the name.
|
|
12566
12850
|
servername: secure && !isIP(hostname) ? hostname : void 0
|
|
12567
12851
|
};
|
|
12568
|
-
return await new Promise((
|
|
12852
|
+
return await new Promise((resolve17, reject) => {
|
|
12569
12853
|
const request = secure ? httpsRequest(options) : httpRequest(options);
|
|
12570
12854
|
request.on("response", (response) => {
|
|
12571
12855
|
response.on("error", () => {
|
|
12572
12856
|
});
|
|
12573
|
-
|
|
12857
|
+
resolve17({
|
|
12574
12858
|
status: response.statusCode ?? 0,
|
|
12575
12859
|
statusText: response.statusMessage ?? "",
|
|
12576
12860
|
ok: (response.statusCode ?? 0) >= 200 && (response.statusCode ?? 0) < 300,
|
|
@@ -12798,64 +13082,67 @@ function decodeCodePoint(value, fallback) {
|
|
|
12798
13082
|
// src/tools/workspace.ts
|
|
12799
13083
|
function createDefaultToolRegistry() {
|
|
12800
13084
|
const { todoWriteTool, todoReadTool } = createTodoTools();
|
|
12801
|
-
return new ToolRegistry().register(listFilesTool).register(readFileTool).register(readManyFilesTool).register(searchFilesTool).register(astSearchTool).register(inspectProjectTool).register(inspectArtifactTool).register(hexDumpTool).register(extractStringsTool).register(scanSecretsTool).register(fileTimelineTool).register(repoMapTool).register(repoHandbookTool).register(contextBundleTool).register(verificationPlanTool).register(verificationReportTool).register(writeFileTool).register(replaceInFileTool).register(multiEditTool).register(applyPatchTool).register(gitStatusTool).register(gitWorktreeSummaryTool).register(gitDiffTool).register(gitLogTool).register(gitShowTool).register(gitBlameTool).register(gitCommitTool).register(gitPushTool).register(runPackageScriptTool).register(runTestsTool).register(diagnosticsTool).register(listSessionsTool).register(readSessionTool).register(searchSessionsTool).register(fetchUrlTool).register(runShellTool).register(listProcessesTool).register(memoryWriteTool).register(memoryReadTool).register(memoryListTool).register(memorySearchTool).register(memoryDeleteTool).register(skillTool).register(listSkillsTool).register(todoWriteTool).register(todoReadTool).register(taskTool).register(askUserTool);
|
|
13085
|
+
return new ToolRegistry().register(listFilesTool).register(readFileTool).register(readManyFilesTool).register(searchFilesTool).register(astSearchTool).register(inspectProjectTool).register(inspectArtifactTool).register(hexDumpTool).register(extractStringsTool).register(scanSecretsTool).register(fileTimelineTool).register(repoMapTool).register(repoHandbookTool).register(contextBundleTool).register(verificationPlanTool).register(verificationReportTool).register(writeFileTool).register(replaceInFileTool).register(multiEditTool).register(applyPatchTool).register(gitStatusTool).register(gitWorktreeSummaryTool).register(gitDiffTool).register(gitLogTool).register(gitShowTool).register(gitBlameTool).register(gitCommitTool).register(gitPushTool).register(runPackageScriptTool).register(runTestsTool).register(diagnosticsTool).register(listSessionsTool).register(readSessionTool).register(searchSessionsTool).register(fetchUrlTool).register(runShellTool).register(listProcessesTool).register(memoryWriteTool).register(memoryReadTool).register(memoryListTool).register(memorySearchTool).register(memoryDeleteTool).register(skillTool).register(listSkillsTool).register(createSkillTool).register(todoWriteTool).register(todoReadTool).register(taskTool).register(askUserTool);
|
|
12802
13086
|
}
|
|
12803
13087
|
|
|
12804
13088
|
// src/commands/doctor.ts
|
|
12805
13089
|
function registerDoctorCommand(program2) {
|
|
12806
13090
|
program2.command("doctor").description("Check the local ultrac setup").option("-w, --workspace <path>", "Workspace path", ".").option("--json", "Print JSON output", false).action(async (options) => {
|
|
12807
|
-
|
|
12808
|
-
const workspaceReady = await stat10(workspace).then((entry) => entry.isDirectory(), () => false);
|
|
12809
|
-
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split(".").map(Number);
|
|
12810
|
-
const nodeReady = nodeMajor > 22 || nodeMajor === 22 && nodeMinor >= 12;
|
|
12811
|
-
const config = await loadConfig(workspace).catch(() => null);
|
|
12812
|
-
const configExists = await hasConfig(workspace);
|
|
12813
|
-
const isGitRepo = await pathExists(resolve7(workspace, ".git"));
|
|
12814
|
-
const ultracCredentials = await resolveUltracCredentials();
|
|
12815
|
-
const baseUrl = ultracCredentials ? { baseUrl: ultracCredentials.baseUrl, source: ultracCredentials.baseUrlSource } : resolveBaseUrl();
|
|
12816
|
-
const tools = createDefaultToolRegistry().list();
|
|
12817
|
-
const toolSchemasReady = tools.every((tool) => tool.inputSchema && typeof tool.inputSchema === "object");
|
|
12818
|
-
const checks = [
|
|
12819
|
-
{ label: "node", value: `${process.versions.node} (requires >=22.12.0)`, ok: nodeReady },
|
|
12820
|
-
{
|
|
12821
|
-
label: "workspace",
|
|
12822
|
-
value: workspaceReady ? workspace : `${workspace} - must be an accessible directory`,
|
|
12823
|
-
ok: workspaceReady
|
|
12824
|
-
},
|
|
12825
|
-
{ label: "git repository", value: isGitRepo ? "found" : "not found", ok: isGitRepo },
|
|
12826
|
-
{
|
|
12827
|
-
label: "config",
|
|
12828
|
-
value: config ? configExists ? configPath(workspace) : "using defaults" : `unable to load configuration - check ${configPath(workspace)} and local trust settings`,
|
|
12829
|
-
ok: config !== null
|
|
12830
|
-
},
|
|
12831
|
-
{
|
|
12832
|
-
label: "tool schemas",
|
|
12833
|
-
value: `${tools.length} exported`,
|
|
12834
|
-
ok: toolSchemasReady
|
|
12835
|
-
},
|
|
12836
|
-
{ label: "provider", value: config?.provider ?? "unavailable", ok: config !== null },
|
|
12837
|
-
{ label: "model", value: config?.model ?? "unavailable", ok: config !== null },
|
|
12838
|
-
{
|
|
12839
|
-
label: "Ultrac sign-in",
|
|
12840
|
-
value: ultracCredentials ? `signed in via ${describeKeySource(ultracCredentials)}` : "not signed in - run `ultrac login` or set ULTRAC_API_KEY",
|
|
12841
|
-
ok: config?.provider !== "ultrac" || Boolean(ultracCredentials)
|
|
12842
|
-
},
|
|
12843
|
-
{
|
|
12844
|
-
label: "Ultrac API base URL",
|
|
12845
|
-
value: `${baseUrl.baseUrl} via ${describeBaseUrlSource(baseUrl.source)}`,
|
|
12846
|
-
ok: true
|
|
12847
|
-
}
|
|
12848
|
-
];
|
|
12849
|
-
if (options.json) {
|
|
12850
|
-
console.log(JSON.stringify({ ok: checks.every((check) => check.ok), checks }, null, 2));
|
|
12851
|
-
} else {
|
|
12852
|
-
checks.forEach((check) => printCheck(check));
|
|
12853
|
-
}
|
|
12854
|
-
if (checks.some((check) => !check.ok)) {
|
|
12855
|
-
process.exitCode = 1;
|
|
12856
|
-
}
|
|
13091
|
+
await runDoctor(options.workspace, options.json);
|
|
12857
13092
|
});
|
|
12858
13093
|
}
|
|
13094
|
+
async function runDoctor(workspacePath, json = false, setExitCode = true) {
|
|
13095
|
+
const workspace = resolve7(workspacePath);
|
|
13096
|
+
const workspaceReady = await stat10(workspace).then((entry) => entry.isDirectory(), () => false);
|
|
13097
|
+
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split(".").map(Number);
|
|
13098
|
+
const nodeReady = nodeMajor > 22 || nodeMajor === 22 && nodeMinor >= 12;
|
|
13099
|
+
const config = await loadConfig(workspace).catch(() => null);
|
|
13100
|
+
const configExists = await hasConfig(workspace);
|
|
13101
|
+
const isGitRepo = await pathExists(resolve7(workspace, ".git"));
|
|
13102
|
+
const ultracCredentials = await resolveUltracCredentials();
|
|
13103
|
+
const baseUrl = ultracCredentials ? { baseUrl: ultracCredentials.baseUrl, source: ultracCredentials.baseUrlSource } : resolveBaseUrl();
|
|
13104
|
+
const tools = createDefaultToolRegistry().list();
|
|
13105
|
+
const toolSchemasReady = tools.every((tool) => tool.inputSchema && typeof tool.inputSchema === "object");
|
|
13106
|
+
const checks = [
|
|
13107
|
+
{ label: "node", value: `${process.versions.node} (requires >=22.12.0)`, ok: nodeReady },
|
|
13108
|
+
{
|
|
13109
|
+
label: "workspace",
|
|
13110
|
+
value: workspaceReady ? workspace : `${workspace} - must be an accessible directory`,
|
|
13111
|
+
ok: workspaceReady
|
|
13112
|
+
},
|
|
13113
|
+
{ label: "git repository", value: isGitRepo ? "found" : "not found", ok: isGitRepo },
|
|
13114
|
+
{
|
|
13115
|
+
label: "config",
|
|
13116
|
+
value: config ? configExists ? configPath(workspace) : "using defaults" : `unable to load configuration - check ${configPath(workspace)} and local trust settings`,
|
|
13117
|
+
ok: config !== null
|
|
13118
|
+
},
|
|
13119
|
+
{
|
|
13120
|
+
label: "tool schemas",
|
|
13121
|
+
value: `${tools.length} exported`,
|
|
13122
|
+
ok: toolSchemasReady
|
|
13123
|
+
},
|
|
13124
|
+
{ label: "provider", value: config?.provider ?? "unavailable", ok: config !== null },
|
|
13125
|
+
{ label: "model", value: config?.model ?? "unavailable", ok: config !== null },
|
|
13126
|
+
{
|
|
13127
|
+
label: "Ultrac sign-in",
|
|
13128
|
+
value: ultracCredentials ? `signed in via ${describeKeySource(ultracCredentials)}` : "not signed in - run `ultrac login` or set ULTRAC_API_KEY",
|
|
13129
|
+
ok: config?.provider !== "ultrac" || Boolean(ultracCredentials)
|
|
13130
|
+
},
|
|
13131
|
+
{
|
|
13132
|
+
label: "Ultrac API base URL",
|
|
13133
|
+
value: `${baseUrl.baseUrl} via ${describeBaseUrlSource(baseUrl.source)}`,
|
|
13134
|
+
ok: true
|
|
13135
|
+
}
|
|
13136
|
+
];
|
|
13137
|
+
if (json) {
|
|
13138
|
+
console.log(JSON.stringify({ ok: checks.every((check) => check.ok), checks }, null, 2));
|
|
13139
|
+
} else {
|
|
13140
|
+
checks.forEach((check) => printCheck(check));
|
|
13141
|
+
}
|
|
13142
|
+
if (setExitCode && checks.some((check) => !check.ok)) {
|
|
13143
|
+
process.exitCode = 1;
|
|
13144
|
+
}
|
|
13145
|
+
}
|
|
12859
13146
|
async function pathExists(path) {
|
|
12860
13147
|
try {
|
|
12861
13148
|
await access2(path);
|
|
@@ -13046,8 +13333,8 @@ var LOOPBACK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
|
13046
13333
|
async function startLoopbackServer(options) {
|
|
13047
13334
|
const timeoutMs = options.timeoutMs ?? LOOPBACK_TIMEOUT_MS;
|
|
13048
13335
|
let settle;
|
|
13049
|
-
const wait = new Promise((
|
|
13050
|
-
settle =
|
|
13336
|
+
const wait = new Promise((resolve17) => {
|
|
13337
|
+
settle = resolve17;
|
|
13051
13338
|
});
|
|
13052
13339
|
const server = http.createServer((request, response) => {
|
|
13053
13340
|
const host = request.headers.host ?? "127.0.0.1";
|
|
@@ -13094,10 +13381,10 @@ async function startLoopbackServer(options) {
|
|
|
13094
13381
|
settle?.({ ok: false, error: "server_error", description: error.message });
|
|
13095
13382
|
settle = void 0;
|
|
13096
13383
|
});
|
|
13097
|
-
const port = await new Promise((
|
|
13384
|
+
const port = await new Promise((resolve17, reject) => {
|
|
13098
13385
|
server.listen(0, "127.0.0.1", () => {
|
|
13099
13386
|
const address = server.address();
|
|
13100
|
-
if (address && typeof address === "object")
|
|
13387
|
+
if (address && typeof address === "object") resolve17(address.port);
|
|
13101
13388
|
else reject(new Error("loopback server did not bind a port"));
|
|
13102
13389
|
});
|
|
13103
13390
|
server.once("error", reject);
|
|
@@ -13116,9 +13403,9 @@ async function startLoopbackServer(options) {
|
|
|
13116
13403
|
port,
|
|
13117
13404
|
redirectUri: `http://127.0.0.1:${port}${LOOPBACK_PATH}`,
|
|
13118
13405
|
wait,
|
|
13119
|
-
close: () => new Promise((
|
|
13406
|
+
close: () => new Promise((resolve17) => {
|
|
13120
13407
|
clearTimeout(timer);
|
|
13121
|
-
server.close(() =>
|
|
13408
|
+
server.close(() => resolve17());
|
|
13122
13409
|
})
|
|
13123
13410
|
};
|
|
13124
13411
|
}
|
|
@@ -13232,6 +13519,22 @@ function errorMessage(error) {
|
|
|
13232
13519
|
}
|
|
13233
13520
|
return String(error);
|
|
13234
13521
|
}
|
|
13522
|
+
async function readAccountPlan(credentials, fetchImpl = (input, init) => fetch(input, init)) {
|
|
13523
|
+
if (!credentials) return void 0;
|
|
13524
|
+
try {
|
|
13525
|
+
const response = await fetchImpl(modelsUrl(credentials.baseUrl), {
|
|
13526
|
+
headers: { authorization: `Bearer ${credentials.apiKey}`, accept: "application/json" },
|
|
13527
|
+
redirect: "error",
|
|
13528
|
+
signal: AbortSignal.timeout(2e3)
|
|
13529
|
+
});
|
|
13530
|
+
if (!response.ok) return void 0;
|
|
13531
|
+
const body = await response.json();
|
|
13532
|
+
const plans = { free: "Free", pro: "Pro", team: "Team", ultra: "Ultra" };
|
|
13533
|
+
return typeof body.account?.plan === "string" && Object.hasOwn(plans, body.account.plan) ? plans[body.account.plan] : void 0;
|
|
13534
|
+
} catch {
|
|
13535
|
+
return void 0;
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13235
13538
|
|
|
13236
13539
|
// src/auth/browser-login.ts
|
|
13237
13540
|
var BROWSER_LOGIN_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -13429,7 +13732,7 @@ function silenceTimeoutMs() {
|
|
|
13429
13732
|
return Number.isFinite(override) && override > 0 ? override : DEFAULT_NON_TTY_SILENCE_TIMEOUT_MS;
|
|
13430
13733
|
}
|
|
13431
13734
|
function ask(rl, question, onSettle) {
|
|
13432
|
-
return new Promise((
|
|
13735
|
+
return new Promise((resolve17) => {
|
|
13433
13736
|
let settled = false;
|
|
13434
13737
|
let silenceTimer;
|
|
13435
13738
|
const keepAwake = () => {
|
|
@@ -13443,7 +13746,7 @@ function ask(rl, question, onSettle) {
|
|
|
13443
13746
|
stdin2.off("data", keepAwake);
|
|
13444
13747
|
rl.close();
|
|
13445
13748
|
onSettle?.();
|
|
13446
|
-
|
|
13749
|
+
resolve17(value.trim());
|
|
13447
13750
|
};
|
|
13448
13751
|
if (!stdin2.isTTY) {
|
|
13449
13752
|
silenceTimer = setTimeout(() => finish(""), silenceTimeoutMs());
|
|
@@ -13795,9 +14098,9 @@ var JsonRpcEndpoint = class {
|
|
|
13795
14098
|
if (signal?.aborted) {
|
|
13796
14099
|
return Promise.reject(new JsonRpcRequestAbortedError(method, void 0, false));
|
|
13797
14100
|
}
|
|
13798
|
-
return new Promise((
|
|
14101
|
+
return new Promise((resolve17, reject) => {
|
|
13799
14102
|
const limit = timeoutMs ?? this.defaultTimeoutMs;
|
|
13800
|
-
const entry = { method, resolve:
|
|
14103
|
+
const entry = { method, resolve: resolve17, reject, signal, dispatched: false };
|
|
13801
14104
|
if (signal) {
|
|
13802
14105
|
entry.abortHandler = () => {
|
|
13803
14106
|
this.settle(id, () => reject(new JsonRpcRequestAbortedError(method, id, entry.dispatched)));
|
|
@@ -14480,13 +14783,13 @@ function renderMcpStatuses(statuses) {
|
|
|
14480
14783
|
}).join("\n");
|
|
14481
14784
|
}
|
|
14482
14785
|
function withTimeout(promise, ms, message) {
|
|
14483
|
-
return new Promise((
|
|
14786
|
+
return new Promise((resolve17, reject) => {
|
|
14484
14787
|
const timer = setTimeout(() => reject(new Error(`${message} within ${ms}ms.`)), ms);
|
|
14485
14788
|
timer.unref?.();
|
|
14486
14789
|
promise.then(
|
|
14487
14790
|
(value) => {
|
|
14488
14791
|
clearTimeout(timer);
|
|
14489
|
-
|
|
14792
|
+
resolve17(value);
|
|
14490
14793
|
},
|
|
14491
14794
|
(error) => {
|
|
14492
14795
|
clearTimeout(timer);
|
|
@@ -14885,14 +15188,14 @@ function redact(secret) {
|
|
|
14885
15188
|
// src/notify/channel.ts
|
|
14886
15189
|
function sleep(ms, signal) {
|
|
14887
15190
|
if (signal?.aborted) return Promise.resolve(false);
|
|
14888
|
-
return new Promise((
|
|
15191
|
+
return new Promise((resolve17) => {
|
|
14889
15192
|
const timer = setTimeout(() => {
|
|
14890
15193
|
signal?.removeEventListener("abort", onAbort);
|
|
14891
|
-
|
|
15194
|
+
resolve17(true);
|
|
14892
15195
|
}, ms);
|
|
14893
15196
|
const onAbort = () => {
|
|
14894
15197
|
clearTimeout(timer);
|
|
14895
|
-
|
|
15198
|
+
resolve17(false);
|
|
14896
15199
|
};
|
|
14897
15200
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
14898
15201
|
});
|
|
@@ -15140,7 +15443,7 @@ ${question}${optionLines}`, signal);
|
|
|
15140
15443
|
await Promise.allSettled(this.channels.map((channel) => channel.call?.(spoken, wait.signal)));
|
|
15141
15444
|
})();
|
|
15142
15445
|
const timeout = sleep(this.timing.timeoutMs, wait.signal).then(() => null);
|
|
15143
|
-
const cancelled = new Promise((
|
|
15446
|
+
const cancelled = new Promise((resolve17) => wait.signal.addEventListener("abort", () => resolve17(null), { once: true }));
|
|
15144
15447
|
const reply = await Promise.race([...replies, timeout, cancelled]);
|
|
15145
15448
|
return reply === null ? null : pickOption(reply, options);
|
|
15146
15449
|
} finally {
|
|
@@ -15277,7 +15580,7 @@ function registerNotifyCommand(program2) {
|
|
|
15277
15580
|
|
|
15278
15581
|
// src/commands/repl.ts
|
|
15279
15582
|
import { createInterface as createInterface3, emitKeypressEvents } from "readline";
|
|
15280
|
-
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
15583
|
+
import { stdin as stdin5, stdout as stdout5, stderr } from "process";
|
|
15281
15584
|
import chalk11 from "chalk";
|
|
15282
15585
|
|
|
15283
15586
|
// src/config/budget.ts
|
|
@@ -15451,6 +15754,80 @@ function registerTrustCommand(program2) {
|
|
|
15451
15754
|
});
|
|
15452
15755
|
}
|
|
15453
15756
|
|
|
15757
|
+
// src/core/image-attachments.ts
|
|
15758
|
+
import { constants as constants6 } from "fs";
|
|
15759
|
+
import { open as open9 } from "fs/promises";
|
|
15760
|
+
import { homedir as homedir2 } from "os";
|
|
15761
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
15762
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15763
|
+
import sharp from "sharp";
|
|
15764
|
+
var MAX_SOURCE_BYTES = 20 * 1024 * 1024;
|
|
15765
|
+
function imageDrops(text) {
|
|
15766
|
+
const drops = [];
|
|
15767
|
+
const tokens = /"(?:\\.|[^"\\])*"|'[^']*'|(?:\\.|[^\s])+/g;
|
|
15768
|
+
for (const match of text.matchAll(tokens)) {
|
|
15769
|
+
const token = match[0];
|
|
15770
|
+
let path = token.startsWith("'") ? token.slice(1, -1) : token.startsWith('"') ? token.slice(1, -1).replace(/\\([\\"$`])/g, "$1") : token.replace(/\\(.)/g, "$1");
|
|
15771
|
+
if (path.startsWith("file://")) {
|
|
15772
|
+
try {
|
|
15773
|
+
path = fileURLToPath2(path);
|
|
15774
|
+
} catch {
|
|
15775
|
+
continue;
|
|
15776
|
+
}
|
|
15777
|
+
}
|
|
15778
|
+
if (path.startsWith("~/")) path = `${homedir2()}/${path.slice(2)}`;
|
|
15779
|
+
if (!isAbsolute4(path) || !/\.(png|jpe?g|webp)$/i.test(path)) continue;
|
|
15780
|
+
drops.push({ start: match.index, end: match.index + token.length, path, label: `[Image ${drops.length + 1}]` });
|
|
15781
|
+
}
|
|
15782
|
+
return drops;
|
|
15783
|
+
}
|
|
15784
|
+
function formatImageInput(text, cursor = text.length) {
|
|
15785
|
+
let display = "";
|
|
15786
|
+
let previous = 0;
|
|
15787
|
+
let shift = 0;
|
|
15788
|
+
let adjusted = cursor;
|
|
15789
|
+
for (const drop of imageDrops(text)) {
|
|
15790
|
+
display += text.slice(previous, drop.start) + drop.label;
|
|
15791
|
+
if (cursor >= drop.end) adjusted += drop.label.length - (drop.end - drop.start);
|
|
15792
|
+
else if (cursor > drop.start) adjusted = drop.start + shift + Math.min(drop.label.length, cursor - drop.start);
|
|
15793
|
+
shift += drop.label.length - (drop.end - drop.start);
|
|
15794
|
+
previous = drop.end;
|
|
15795
|
+
}
|
|
15796
|
+
return { text: display + text.slice(previous), cursor: Math.max(0, adjusted) };
|
|
15797
|
+
}
|
|
15798
|
+
function hasDroppedImage(text) {
|
|
15799
|
+
return imageDrops(text).length > 0;
|
|
15800
|
+
}
|
|
15801
|
+
async function prepareImageInput(text) {
|
|
15802
|
+
const drops = imageDrops(text);
|
|
15803
|
+
if (!drops.length) return { text };
|
|
15804
|
+
if (drops.length > 1) throw new Error("Attach one image per message. Cosmic currently accepts one image in its active context.");
|
|
15805
|
+
const drop = drops[0];
|
|
15806
|
+
const file = await open9(drop.path, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
15807
|
+
let bytes;
|
|
15808
|
+
try {
|
|
15809
|
+
const stat11 = await file.stat();
|
|
15810
|
+
if (!stat11.isFile() || stat11.size > MAX_SOURCE_BYTES) throw new Error("Image must be a regular file no larger than 20 MiB.");
|
|
15811
|
+
const buffer = Buffer.alloc(Math.min(stat11.size, MAX_SOURCE_BYTES) + 1);
|
|
15812
|
+
let used = 0;
|
|
15813
|
+
while (used < buffer.length) {
|
|
15814
|
+
const read = await file.read(buffer, used, buffer.length - used, null);
|
|
15815
|
+
if (!read.bytesRead) break;
|
|
15816
|
+
used += read.bytesRead;
|
|
15817
|
+
}
|
|
15818
|
+
if (used > MAX_SOURCE_BYTES) throw new Error("Image exceeds 20 MiB.");
|
|
15819
|
+
bytes = buffer.subarray(0, used);
|
|
15820
|
+
} finally {
|
|
15821
|
+
await file.close();
|
|
15822
|
+
}
|
|
15823
|
+
const image = sharp(bytes, { limitInputPixels: 4e7, animated: false });
|
|
15824
|
+
const metadata = await image.metadata();
|
|
15825
|
+
if (!["png", "jpeg", "webp"].includes(metadata.format ?? "") || (metadata.pages ?? 1) > 1) throw new Error("Attach a single PNG, JPEG or WebP image.");
|
|
15826
|
+
const normalized = await image.rotate().resize({ width: 1024, height: 1024, fit: "inside", withoutEnlargement: true }).png().toBuffer();
|
|
15827
|
+
if (normalized.length > 5 * 1024 * 1024) throw new Error("Normalized image exceeds the upload limit.");
|
|
15828
|
+
return { text: formatImageInput(text).text, image: { url: `data:image/png;base64,${normalized.toString("base64")}` } };
|
|
15829
|
+
}
|
|
15830
|
+
|
|
15454
15831
|
// src/core/manual-compaction.ts
|
|
15455
15832
|
async function compactManually(messages, pinned, provider, model, focus = "", signal) {
|
|
15456
15833
|
const retained = new Set(pinned);
|
|
@@ -15728,7 +16105,8 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
15728
16105
|
async ask(input, handlers = {}) {
|
|
15729
16106
|
if (!this.started) await this.start();
|
|
15730
16107
|
if (!this.firstTask) this.firstTask = input;
|
|
15731
|
-
|
|
16108
|
+
if (handlers.image) for (const message of this.messages) delete message.image;
|
|
16109
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
15732
16110
|
this.messages.push(asked);
|
|
15733
16111
|
this.pinnedMessages.push(asked);
|
|
15734
16112
|
this.userConstraints.push({ kind: "message", content: input });
|
|
@@ -16044,7 +16422,7 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
16044
16422
|
constraintVersion: 1,
|
|
16045
16423
|
userConstraints: [...this.userConstraints],
|
|
16046
16424
|
mcpResultArchiveVersion: 1,
|
|
16047
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16425
|
+
...this.messages.some((message) => isCompactionSummary(message) || message.image) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16048
16426
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16049
16427
|
// its final message is an answer or the place it ran out of room - the
|
|
16050
16428
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16259,7 +16637,10 @@ ${context}`
|
|
|
16259
16637
|
async ask(input, handlers = {}) {
|
|
16260
16638
|
if (!this.started) await this.start();
|
|
16261
16639
|
if (!this.firstTask) this.firstTask = input;
|
|
16262
|
-
|
|
16640
|
+
if (handlers.image) {
|
|
16641
|
+
for (const message of this.messages) if (message.role === "user" || message.role === "system") delete message.image;
|
|
16642
|
+
}
|
|
16643
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
16263
16644
|
this.messages.push(asked);
|
|
16264
16645
|
this.pinnedMessages.push(asked);
|
|
16265
16646
|
this.userConstraints.push({ kind: "message", content: input });
|
|
@@ -16609,7 +16990,7 @@ ${guidance.join("\n")}` : ""}`;
|
|
|
16609
16990
|
constraintVersion: 1,
|
|
16610
16991
|
userConstraints: [...this.userConstraints],
|
|
16611
16992
|
mcpResultArchiveVersion: 1,
|
|
16612
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "native", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16993
|
+
...this.messages.some((message) => isCompactionSummary(message) || (message.role === "user" || message.role === "system") && message.image) ? { contextSnapshot: { version: 1, kind: "native", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16613
16994
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16614
16995
|
// its final message is an answer or the place it ran out of room - the
|
|
16615
16996
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16633,6 +17014,152 @@ function noteNativeCompactionFallback(reason) {
|
|
|
16633
17014
|
);
|
|
16634
17015
|
}
|
|
16635
17016
|
|
|
17017
|
+
// src/core/autonomous.ts
|
|
17018
|
+
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
17019
|
+
var AUTONOMOUS_CONTINUE_TASK = "Continue the task from where the previous round stopped. Check what is already done, finish the rest, and verify it.";
|
|
17020
|
+
var DEFAULT_AUTONOMOUS_MAX_ROUNDS = 5;
|
|
17021
|
+
var AUTONOMOUS_MAX_STEPS = 200;
|
|
17022
|
+
var DEFAULT_AUTONOMOUS_MAX_ELAPSED_MS = 6 * 60 * 60 * 1e3;
|
|
17023
|
+
var DEFAULT_AUTONOMOUS_BASE_DELAY_MS = 1e3;
|
|
17024
|
+
var DEFAULT_AUTONOMOUS_MAX_DELAY_MS = 3e4;
|
|
17025
|
+
var DEFAULT_AUTONOMOUS_STALL_ROUNDS = 3;
|
|
17026
|
+
function shouldResume(stopReason) {
|
|
17027
|
+
return stopReason === "max-steps" || stopReason === "error";
|
|
17028
|
+
}
|
|
17029
|
+
function boundedInteger(value, name, min, max) {
|
|
17030
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${name} must be an integer from ${min} to ${max}.`);
|
|
17031
|
+
return value;
|
|
17032
|
+
}
|
|
17033
|
+
function abortableSleep(milliseconds, signal) {
|
|
17034
|
+
signal.throwIfAborted();
|
|
17035
|
+
return new Promise((resolve17, reject) => {
|
|
17036
|
+
const timer = setTimeout(() => {
|
|
17037
|
+
signal.removeEventListener("abort", abort);
|
|
17038
|
+
resolve17();
|
|
17039
|
+
}, milliseconds);
|
|
17040
|
+
const abort = () => {
|
|
17041
|
+
clearTimeout(timer);
|
|
17042
|
+
signal.removeEventListener("abort", abort);
|
|
17043
|
+
reject(signal.reason);
|
|
17044
|
+
};
|
|
17045
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
17046
|
+
if (signal.aborted) abort();
|
|
17047
|
+
});
|
|
17048
|
+
}
|
|
17049
|
+
function progress(session, startStep = 0) {
|
|
17050
|
+
const hash = createHash7("sha256");
|
|
17051
|
+
let completedTools = 0;
|
|
17052
|
+
hash.update(session.finalMessage ?? "");
|
|
17053
|
+
for (const step of (session.steps ?? []).slice(startStep)) {
|
|
17054
|
+
hash.update(step.modelText ?? "");
|
|
17055
|
+
for (const tool of step.toolResults ?? []) {
|
|
17056
|
+
completedTools += 1;
|
|
17057
|
+
hash.update(tool.call.tool);
|
|
17058
|
+
hash.update(tool.result.ok ? "ok" : "failed");
|
|
17059
|
+
hash.update(tool.result.content ?? "");
|
|
17060
|
+
try {
|
|
17061
|
+
hash.update(JSON.stringify(tool.call.input));
|
|
17062
|
+
} catch {
|
|
17063
|
+
hash.update("unserializable-input");
|
|
17064
|
+
}
|
|
17065
|
+
}
|
|
17066
|
+
}
|
|
17067
|
+
return { fingerprint: hash.digest("hex"), completedTools };
|
|
17068
|
+
}
|
|
17069
|
+
async function runAutonomousLoop(options) {
|
|
17070
|
+
boundedInteger(options.maxRounds, "maxRounds", 1, 1e3);
|
|
17071
|
+
const maxElapsedMs = boundedInteger(options.maxElapsedMs ?? DEFAULT_AUTONOMOUS_MAX_ELAPSED_MS, "maxElapsedMs", 1, 7 * 24 * 60 * 60 * 1e3);
|
|
17072
|
+
const baseDelay = boundedInteger(options.baseDelayMs ?? DEFAULT_AUTONOMOUS_BASE_DELAY_MS, "baseDelayMs", 0, 3e5);
|
|
17073
|
+
const maxDelay = boundedInteger(options.maxDelayMs ?? DEFAULT_AUTONOMOUS_MAX_DELAY_MS, "maxDelayMs", baseDelay, 3e5);
|
|
17074
|
+
const stallLimit = boundedInteger(options.stallRounds ?? DEFAULT_AUTONOMOUS_STALL_ROUNDS, "stallRounds", 1, 1e3);
|
|
17075
|
+
const now = options.now ?? (() => performance.now());
|
|
17076
|
+
const started = now();
|
|
17077
|
+
const elapsed = () => Math.max(0, now() - started);
|
|
17078
|
+
const budget = new AbortController();
|
|
17079
|
+
const deadline = setTimeout(() => budget.abort(new DOMException("Autonomous elapsed budget reached.", "TimeoutError")), maxElapsedMs);
|
|
17080
|
+
deadline.unref?.();
|
|
17081
|
+
const signal = options.signal ? AbortSignal.any([options.signal, budget.signal]) : budget.signal;
|
|
17082
|
+
const sleep2 = options.sleep ?? abortableSleep;
|
|
17083
|
+
const id = randomUUID5();
|
|
17084
|
+
let previous;
|
|
17085
|
+
let lastFingerprint;
|
|
17086
|
+
let lastSessionId;
|
|
17087
|
+
let lastStepCount = 0;
|
|
17088
|
+
let stalls = 0;
|
|
17089
|
+
let consecutiveErrors = 0;
|
|
17090
|
+
try {
|
|
17091
|
+
signal.throwIfAborted();
|
|
17092
|
+
for (let round = 1; ; round += 1) {
|
|
17093
|
+
const roundStart = now();
|
|
17094
|
+
diagnosticEvent("round.start", { id, round, maxRounds: options.maxRounds, elapsedMs: elapsed() });
|
|
17095
|
+
let session;
|
|
17096
|
+
try {
|
|
17097
|
+
session = await options.runRound(round, previous, signal);
|
|
17098
|
+
} catch (error) {
|
|
17099
|
+
diagnosticEvent("round.end", { id, round, reason: signal.aborted ? "aborted" : "exception", elapsedMs: elapsed(), durationMs: Math.max(0, now() - roundStart) });
|
|
17100
|
+
throw error;
|
|
17101
|
+
}
|
|
17102
|
+
const stepCount = session.steps?.length ?? 0;
|
|
17103
|
+
const startStep = session.id === lastSessionId && stepCount > lastStepCount ? lastStepCount : 0;
|
|
17104
|
+
const current = progress(session, startStep);
|
|
17105
|
+
lastSessionId = session.id;
|
|
17106
|
+
lastStepCount = stepCount;
|
|
17107
|
+
const unchanged = lastFingerprint !== void 0 && current.fingerprint === lastFingerprint;
|
|
17108
|
+
stalls = unchanged ? stalls + 1 : 0;
|
|
17109
|
+
lastFingerprint = current.fingerprint;
|
|
17110
|
+
const classification = session.stopReason === "error" ? options.classifyFailure?.(session) : void 0;
|
|
17111
|
+
consecutiveErrors = session.stopReason === "error" ? consecutiveErrors + 1 : 0;
|
|
17112
|
+
let haltReason;
|
|
17113
|
+
if (session.stopReason === "completed") haltReason = "completed";
|
|
17114
|
+
else if (budget.signal.aborted || elapsed() >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
17115
|
+
else if (signal.aborted || session.stopReason === "aborted") haltReason = "aborted";
|
|
17116
|
+
else if (classification && !classification.retryable) haltReason = "permanent_error";
|
|
17117
|
+
else if (!shouldResume(session.stopReason)) haltReason = "stopped";
|
|
17118
|
+
else if (stalls >= stallLimit) haltReason = "stalled";
|
|
17119
|
+
else if (round >= options.maxRounds) haltReason = "max_rounds";
|
|
17120
|
+
const retryDelay = Math.min(maxDelay, baseDelay * 2 ** Math.min(30, consecutiveErrors - 1));
|
|
17121
|
+
if (!haltReason && session.stopReason === "error" && elapsed() + retryDelay >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
17122
|
+
diagnosticEvent("round.end", {
|
|
17123
|
+
id,
|
|
17124
|
+
round,
|
|
17125
|
+
sessionId: session.id,
|
|
17126
|
+
reason: haltReason ?? session.stopReason,
|
|
17127
|
+
failureKind: classification?.kind,
|
|
17128
|
+
attempt: consecutiveErrors,
|
|
17129
|
+
completedTools: current.completedTools,
|
|
17130
|
+
elapsedMs: elapsed(),
|
|
17131
|
+
durationMs: Math.max(0, now() - roundStart)
|
|
17132
|
+
});
|
|
17133
|
+
if (haltReason === "stalled") diagnosticEvent("round.stall", { id, round, repeats: stalls, completedTools: current.completedTools, elapsedMs: elapsed() });
|
|
17134
|
+
await options.onRoundEnd?.(round, session, !haltReason);
|
|
17135
|
+
if (haltReason) return { session, rounds: round, haltReason };
|
|
17136
|
+
if (session.stopReason === "error") {
|
|
17137
|
+
const delayMs = retryDelay;
|
|
17138
|
+
if (elapsed() + delayMs >= maxElapsedMs) return { session, rounds: round, haltReason: "elapsed_budget" };
|
|
17139
|
+
diagnosticEvent("round.retry", {
|
|
17140
|
+
id,
|
|
17141
|
+
round,
|
|
17142
|
+
nextRound: round + 1,
|
|
17143
|
+
attempt: consecutiveErrors,
|
|
17144
|
+
failureKind: classification?.kind ?? "unclassified",
|
|
17145
|
+
delayMs,
|
|
17146
|
+
elapsedMs: elapsed()
|
|
17147
|
+
});
|
|
17148
|
+
try {
|
|
17149
|
+
await sleep2(delayMs, signal);
|
|
17150
|
+
} catch (error) {
|
|
17151
|
+
if (!signal.aborted) throw error;
|
|
17152
|
+
return { session, rounds: round, haltReason: budget.signal.aborted ? "elapsed_budget" : "aborted" };
|
|
17153
|
+
}
|
|
17154
|
+
}
|
|
17155
|
+
if (signal.aborted || elapsed() >= maxElapsedMs) return { session, rounds: round, haltReason: budget.signal.aborted || elapsed() >= maxElapsedMs ? "elapsed_budget" : "aborted" };
|
|
17156
|
+
previous = session;
|
|
17157
|
+
}
|
|
17158
|
+
} finally {
|
|
17159
|
+
clearTimeout(deadline);
|
|
17160
|
+
}
|
|
17161
|
+
}
|
|
17162
|
+
|
|
16636
17163
|
// src/commands/session-checkpoint.ts
|
|
16637
17164
|
var SESSION_CHECKPOINT_FAILURE_WARNING = "Warning: the latest session checkpoint could not be saved; progress since the previous checkpoint may be lost.";
|
|
16638
17165
|
function createSessionCheckpointWriter(workspace, onFailure) {
|
|
@@ -16690,7 +17217,7 @@ function printSession(session, sessionPath2, options = {}) {
|
|
|
16690
17217
|
}
|
|
16691
17218
|
for (const toolResult of step.toolResults) {
|
|
16692
17219
|
const status = toolResult.result.ok ? chalk8.green("ok") : chalk8.red("failed");
|
|
16693
|
-
console.log(chalk8.
|
|
17220
|
+
console.log(chalk8.white(toolResult.call.tool), status);
|
|
16694
17221
|
console.log(chalk8.gray(indent2(formatToolOutput(toolResult.result.content, { verbose: options.verboseTools, failed: !toolResult.result.ok, hint: "Use --verbose-tools or inspect the saved session for full output." }))));
|
|
16695
17222
|
}
|
|
16696
17223
|
}
|
|
@@ -16710,13 +17237,13 @@ function printTools(tools) {
|
|
|
16710
17237
|
for (const tool of tools) {
|
|
16711
17238
|
const permissionText = tool.permissions.length ? chalk8.yellow(` [${tool.permissions.join(", ")}]`) : "";
|
|
16712
17239
|
const optionalText = tool.optionalPermissions.length ? chalk8.gray(` [optional: ${tool.optionalPermissions.join(", ")}]`) : "";
|
|
16713
|
-
console.log(`${chalk8.
|
|
17240
|
+
console.log(`${chalk8.white(tool.name)}${permissionText}${optionalText}`);
|
|
16714
17241
|
console.log(chalk8.gray(indent2(tool.description)));
|
|
16715
17242
|
}
|
|
16716
17243
|
}
|
|
16717
17244
|
function printToolResult(result, options = {}) {
|
|
16718
17245
|
const status = result.ok ? chalk8.green("ok") : chalk8.red("failed");
|
|
16719
|
-
console.log(`${chalk8.
|
|
17246
|
+
console.log(`${chalk8.white(result.tool)} ${status}`);
|
|
16720
17247
|
console.log(chalk8.gray(indent2(formatToolOutput(result.content, { verbose: options.verboseTools, failed: !result.ok, hint: "Use --verbose-tools or --result-json for full output." }))));
|
|
16721
17248
|
}
|
|
16722
17249
|
function indent2(value) {
|
|
@@ -16734,222 +17261,21 @@ function formatToolOutput(content, options = {}) {
|
|
|
16734
17261
|
\u2026 ${hidden ? `${hidden} more lines` : "Long lines shortened"}. ${options.hint ?? "Full output remains available in the saved session."}`;
|
|
16735
17262
|
}
|
|
16736
17263
|
|
|
16737
|
-
// src/
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
"
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
"status",
|
|
16750
|
-
"context",
|
|
16751
|
-
"compact",
|
|
16752
|
-
"diff",
|
|
16753
|
-
"sessions",
|
|
16754
|
-
"save",
|
|
16755
|
-
"test",
|
|
16756
|
-
"init",
|
|
16757
|
-
"new",
|
|
16758
|
-
"allow",
|
|
16759
|
-
"clear",
|
|
16760
|
-
"exit",
|
|
16761
|
-
"help",
|
|
16762
|
-
"memory",
|
|
16763
|
-
"mode",
|
|
16764
|
-
"model",
|
|
16765
|
-
"permissions",
|
|
16766
|
-
"quit",
|
|
16767
|
-
"skills",
|
|
16768
|
-
"tools",
|
|
16769
|
-
"whoami"
|
|
16770
|
-
];
|
|
16771
|
-
var COMMAND_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
16772
|
-
var MAX_COMMAND_LENGTH = 64;
|
|
16773
|
-
function parseSlashInput(input) {
|
|
16774
|
-
const trimmed = input.trim();
|
|
16775
|
-
if (!trimmed.startsWith("/")) return null;
|
|
16776
|
-
const rest = trimmed.slice(1);
|
|
16777
|
-
const match = rest.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
16778
|
-
if (!match) return null;
|
|
16779
|
-
return { command: match[1], args: (match[2] ?? "").trim() };
|
|
16780
|
-
}
|
|
16781
|
-
function isBuiltinSlashCommand(command) {
|
|
16782
|
-
return BUILTIN_SLASH_COMMANDS.includes(command.toLowerCase());
|
|
16783
|
-
}
|
|
16784
|
-
function normalizeCommandName(value) {
|
|
16785
|
-
const candidate = value.trim().replace(/^\/+/, "").trim();
|
|
16786
|
-
if (!candidate) {
|
|
16787
|
-
throw new Error("Slash command name is empty; expected kebab-case, for example: open-pr.");
|
|
16788
|
-
}
|
|
16789
|
-
if (candidate.length > MAX_COMMAND_LENGTH) {
|
|
16790
|
-
throw new Error(
|
|
16791
|
-
`Slash command name "${candidate}" is longer than ${MAX_COMMAND_LENGTH} characters.`
|
|
16792
|
-
);
|
|
16793
|
-
}
|
|
16794
|
-
if (!COMMAND_NAME_RE.test(candidate)) {
|
|
16795
|
-
throw new Error(
|
|
16796
|
-
`Invalid slash command name "${candidate}": use kebab-case - lowercase letters, digits, and single hyphens, no spaces or slashes (for example: open-pr).`
|
|
16797
|
-
);
|
|
17264
|
+
// src/ui/progress.ts
|
|
17265
|
+
import { stdout as stdout3 } from "process";
|
|
17266
|
+
import chalk9 from "chalk";
|
|
17267
|
+
var PENDING_GLYPH = "\u25CB";
|
|
17268
|
+
var DONE_GLYPH = "\u25CF";
|
|
17269
|
+
var REPAINT_MS = 100;
|
|
17270
|
+
function formatTokens(tokens) {
|
|
17271
|
+
if (!Number.isFinite(tokens) || tokens <= 0) return "0";
|
|
17272
|
+
if (tokens < 1e3) return String(Math.floor(tokens));
|
|
17273
|
+
if (tokens < 1e6) {
|
|
17274
|
+
const tenths2 = Math.floor(tokens / 100);
|
|
17275
|
+
return `${Math.floor(tenths2 / 10)}.${tenths2 % 10}k`;
|
|
16798
17276
|
}
|
|
16799
|
-
|
|
16800
|
-
}
|
|
16801
|
-
async function collectEntries(workspace) {
|
|
16802
|
-
const skills = await discoverSkills(workspace);
|
|
16803
|
-
const claimed = /* @__PURE__ */ new Map();
|
|
16804
|
-
const conflicts = [];
|
|
16805
|
-
for (const skill of skills) {
|
|
16806
|
-
let declaration;
|
|
16807
|
-
try {
|
|
16808
|
-
declaration = skill.command ?? null;
|
|
16809
|
-
} catch (error) {
|
|
16810
|
-
conflicts.push({
|
|
16811
|
-
command: "",
|
|
16812
|
-
skillName: skill.name,
|
|
16813
|
-
scope: skill.scope,
|
|
16814
|
-
path: skill.path,
|
|
16815
|
-
reason: "invalid",
|
|
16816
|
-
message: `Skill "${skill.name}" (${skill.scope}) was skipped: ${error instanceof Error ? error.message : String(error)}`
|
|
16817
|
-
});
|
|
16818
|
-
continue;
|
|
16819
|
-
}
|
|
16820
|
-
if (!declaration) continue;
|
|
16821
|
-
let command;
|
|
16822
|
-
try {
|
|
16823
|
-
command = normalizeCommandName(declaration.raw);
|
|
16824
|
-
} catch (error) {
|
|
16825
|
-
conflicts.push({
|
|
16826
|
-
command: declaration.raw,
|
|
16827
|
-
skillName: skill.name,
|
|
16828
|
-
scope: skill.scope,
|
|
16829
|
-
path: skill.path,
|
|
16830
|
-
reason: "invalid",
|
|
16831
|
-
message: `Skill "${skill.name}" (${skill.scope}) declares a malformed command: ${error instanceof Error ? error.message : String(error)}`
|
|
16832
|
-
});
|
|
16833
|
-
continue;
|
|
16834
|
-
}
|
|
16835
|
-
if (isBuiltinSlashCommand(command)) {
|
|
16836
|
-
conflicts.push({
|
|
16837
|
-
command,
|
|
16838
|
-
skillName: skill.name,
|
|
16839
|
-
scope: skill.scope,
|
|
16840
|
-
path: skill.path,
|
|
16841
|
-
reason: "builtin",
|
|
16842
|
-
message: `Skill "${skill.name}" (${skill.scope}) declares /${command}, which is a built-in command. Built-ins always win - rename the command: key in ${skill.path}.`
|
|
16843
|
-
});
|
|
16844
|
-
continue;
|
|
16845
|
-
}
|
|
16846
|
-
const entry = {
|
|
16847
|
-
skill,
|
|
16848
|
-
command: {
|
|
16849
|
-
command,
|
|
16850
|
-
skillName: skill.name,
|
|
16851
|
-
description: skill.description,
|
|
16852
|
-
argumentHint: declaration.argumentHint,
|
|
16853
|
-
scope: skill.scope,
|
|
16854
|
-
path: skill.path
|
|
16855
|
-
}
|
|
16856
|
-
};
|
|
16857
|
-
const existing = claimed.get(command);
|
|
16858
|
-
if (!existing) {
|
|
16859
|
-
claimed.set(command, entry);
|
|
16860
|
-
continue;
|
|
16861
|
-
}
|
|
16862
|
-
const winner = existing.skill.scope === "global" && skill.scope === "project" ? entry : existing;
|
|
16863
|
-
const loser = winner === entry ? existing : entry;
|
|
16864
|
-
claimed.set(command, winner);
|
|
16865
|
-
conflicts.push({
|
|
16866
|
-
command,
|
|
16867
|
-
skillName: loser.skill.name,
|
|
16868
|
-
scope: loser.skill.scope,
|
|
16869
|
-
path: loser.skill.path,
|
|
16870
|
-
reason: "duplicate",
|
|
16871
|
-
message: `/${command} is declared by both "${winner.skill.name}" (${winner.skill.scope}) and "${loser.skill.name}" (${loser.skill.scope}). "${winner.skill.name}" wins; rename the command: key in ${loser.skill.path}.`
|
|
16872
|
-
});
|
|
16873
|
-
}
|
|
16874
|
-
const entries = [...claimed.values()].sort((a, b) => a.command.command.localeCompare(b.command.command));
|
|
16875
|
-
return { entries, conflicts };
|
|
16876
|
-
}
|
|
16877
|
-
async function listSkillCommands(workspace) {
|
|
16878
|
-
const { entries, conflicts } = await collectEntries(workspace);
|
|
16879
|
-
return { commands: entries.map((entry) => entry.command), conflicts };
|
|
16880
|
-
}
|
|
16881
|
-
function buildSkillCommandPrompt(skill, command, args) {
|
|
16882
|
-
const argumentLine = command.argumentHint ? `Arguments (${command.argumentHint}): ${args || "(none provided)"}` : `Arguments: ${args || "(none provided)"}`;
|
|
16883
|
-
const header = [
|
|
16884
|
-
`The user ran the /${command.command} slash command, which runs the "${skill.name}" skill (${skill.scope}).`,
|
|
16885
|
-
skill.description ? `Description: ${skill.description}` : void 0,
|
|
16886
|
-
argumentLine,
|
|
16887
|
-
skill.permissions.length ? `Permissions needed: ${skill.permissions.join(", ")}` : void 0,
|
|
16888
|
-
"Follow the instructions below now, applying the arguments where they are relevant."
|
|
16889
|
-
].filter(Boolean).join("\n");
|
|
16890
|
-
return `${header}
|
|
16891
|
-
|
|
16892
|
-
--- instructions ---
|
|
16893
|
-
${skill.body}`;
|
|
16894
|
-
}
|
|
16895
|
-
async function resolveSkillCommand(workspace, input) {
|
|
16896
|
-
const parsed = parseSlashInput(input);
|
|
16897
|
-
if (!parsed) return { status: "not-a-command" };
|
|
16898
|
-
const command = parsed.command.toLowerCase();
|
|
16899
|
-
if (isBuiltinSlashCommand(command)) {
|
|
16900
|
-
return { status: "builtin", command };
|
|
16901
|
-
}
|
|
16902
|
-
const { entries, conflicts } = await collectEntries(workspace);
|
|
16903
|
-
const match = entries.find((entry) => entry.command.command === command);
|
|
16904
|
-
if (!match) {
|
|
16905
|
-
const related = conflicts.filter((conflict) => conflict.command === command);
|
|
16906
|
-
if (related.length) {
|
|
16907
|
-
return {
|
|
16908
|
-
status: "conflict",
|
|
16909
|
-
command,
|
|
16910
|
-
message: related.map((conflict) => conflict.message).join("\n"),
|
|
16911
|
-
conflicts: related
|
|
16912
|
-
};
|
|
16913
|
-
}
|
|
16914
|
-
return {
|
|
16915
|
-
status: "unknown",
|
|
16916
|
-
command,
|
|
16917
|
-
available: entries.map((entry) => entry.command.command)
|
|
16918
|
-
};
|
|
16919
|
-
}
|
|
16920
|
-
return {
|
|
16921
|
-
status: "ok",
|
|
16922
|
-
command: match.command,
|
|
16923
|
-
skill: match.skill,
|
|
16924
|
-
args: parsed.args,
|
|
16925
|
-
prompt: buildSkillCommandPrompt(match.skill, match.command, parsed.args)
|
|
16926
|
-
};
|
|
16927
|
-
}
|
|
16928
|
-
function formatSkillCommandHelp(index) {
|
|
16929
|
-
if (!index.commands.length) return "";
|
|
16930
|
-
const lines = index.commands.map((command) => {
|
|
16931
|
-
const invocation = command.argumentHint ? `/${command.command} ${command.argumentHint}` : `/${command.command}`;
|
|
16932
|
-
const description = command.description || `Run the ${command.skillName} skill`;
|
|
16933
|
-
return ` ${invocation.padEnd(22)}${description} (skill: ${command.skillName})`;
|
|
16934
|
-
});
|
|
16935
|
-
return ["Skill commands:", ...lines].join("\n");
|
|
16936
|
-
}
|
|
16937
|
-
|
|
16938
|
-
// src/ui/progress.ts
|
|
16939
|
-
import { stdout as stdout3 } from "process";
|
|
16940
|
-
import chalk9 from "chalk";
|
|
16941
|
-
var PENDING_GLYPH = "\u25CB";
|
|
16942
|
-
var DONE_GLYPH = "\u25CF";
|
|
16943
|
-
var REPAINT_MS = 100;
|
|
16944
|
-
function formatTokens(tokens) {
|
|
16945
|
-
if (!Number.isFinite(tokens) || tokens <= 0) return "0";
|
|
16946
|
-
if (tokens < 1e3) return String(Math.floor(tokens));
|
|
16947
|
-
if (tokens < 1e6) {
|
|
16948
|
-
const tenths2 = Math.floor(tokens / 100);
|
|
16949
|
-
return `${Math.floor(tenths2 / 10)}.${tenths2 % 10}k`;
|
|
16950
|
-
}
|
|
16951
|
-
const tenths = Math.floor(tokens / 1e5);
|
|
16952
|
-
return `${Math.floor(tenths / 10)}.${tenths % 10}M`;
|
|
17277
|
+
const tenths = Math.floor(tokens / 1e5);
|
|
17278
|
+
return `${Math.floor(tenths / 10)}.${tenths % 10}M`;
|
|
16953
17279
|
}
|
|
16954
17280
|
function formatElapsed(ms) {
|
|
16955
17281
|
if (!Number.isFinite(ms) || ms <= 0) return "0s";
|
|
@@ -16964,12 +17290,12 @@ function formatElapsed(ms) {
|
|
|
16964
17290
|
}
|
|
16965
17291
|
function formatProgressLine(snapshot) {
|
|
16966
17292
|
const finished = snapshot.done >= snapshot.total;
|
|
16967
|
-
const glyph = finished ? chalk9.green(DONE_GLYPH) : chalk9.
|
|
17293
|
+
const glyph = finished ? chalk9.green(DONE_GLYPH) : chalk9.white(PENDING_GLYPH);
|
|
16968
17294
|
const noun = snapshot.total === 1 ? "agent" : "agents";
|
|
16969
17295
|
const stats = [`${snapshot.done}/${snapshot.total} ${noun} done`, formatElapsed(snapshot.elapsedMs)];
|
|
16970
17296
|
if (snapshot.tokens !== void 0) stats.push(`\u2193 ${formatTokens(snapshot.tokens)} tokens`);
|
|
16971
17297
|
const rendered = stats.map((stat11) => chalk9.dim(stat11)).join(chalk9.dim(" \xB7 "));
|
|
16972
|
-
return ` ${glyph} ${chalk9.
|
|
17298
|
+
return ` ${glyph} ${chalk9.white(snapshot.label)} ${snapshot.description} ${rendered}`;
|
|
16973
17299
|
}
|
|
16974
17300
|
var ProgressLine = class {
|
|
16975
17301
|
constructor(label, description, total) {
|
|
@@ -17095,53 +17421,8 @@ function attachProgressUi(options = {}) {
|
|
|
17095
17421
|
};
|
|
17096
17422
|
}
|
|
17097
17423
|
|
|
17098
|
-
// src/ui/
|
|
17099
|
-
import {
|
|
17100
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17101
|
-
var Spinner = class {
|
|
17102
|
-
constructor(label = "thinking") {
|
|
17103
|
-
this.label = label;
|
|
17104
|
-
}
|
|
17105
|
-
label;
|
|
17106
|
-
timer;
|
|
17107
|
-
index = 0;
|
|
17108
|
-
holdWhile;
|
|
17109
|
-
held = false;
|
|
17110
|
-
start(holdWhile) {
|
|
17111
|
-
this.holdWhile = holdWhile;
|
|
17112
|
-
if (!stdout4.isTTY || this.timer) return;
|
|
17113
|
-
this.render();
|
|
17114
|
-
this.timer = setInterval(() => this.render(), 90);
|
|
17115
|
-
}
|
|
17116
|
-
update(label) {
|
|
17117
|
-
this.label = label;
|
|
17118
|
-
}
|
|
17119
|
-
stop() {
|
|
17120
|
-
if (this.timer) {
|
|
17121
|
-
clearInterval(this.timer);
|
|
17122
|
-
this.timer = void 0;
|
|
17123
|
-
}
|
|
17124
|
-
if (stdout4.isTTY) {
|
|
17125
|
-
stdout4.clearLine(0);
|
|
17126
|
-
stdout4.cursorTo(0);
|
|
17127
|
-
}
|
|
17128
|
-
}
|
|
17129
|
-
render() {
|
|
17130
|
-
if (this.holdWhile?.()) {
|
|
17131
|
-
if (!this.held) {
|
|
17132
|
-
stdout4.clearLine(0);
|
|
17133
|
-
stdout4.cursorTo(0);
|
|
17134
|
-
this.held = true;
|
|
17135
|
-
}
|
|
17136
|
-
return;
|
|
17137
|
-
}
|
|
17138
|
-
this.held = false;
|
|
17139
|
-
this.index = (this.index + 1) % FRAMES.length;
|
|
17140
|
-
stdout4.clearLine(0);
|
|
17141
|
-
stdout4.cursorTo(0);
|
|
17142
|
-
stdout4.write(` ${FRAMES[this.index]} ${this.label}\u2026`);
|
|
17143
|
-
}
|
|
17144
|
-
};
|
|
17424
|
+
// src/ui/activity.ts
|
|
17425
|
+
import { stripVTControlCharacters } from "util";
|
|
17145
17426
|
|
|
17146
17427
|
// src/ui/tool-activity.ts
|
|
17147
17428
|
var VERBS = {
|
|
@@ -17203,19 +17484,956 @@ function compact2(value, max = 64) {
|
|
|
17203
17484
|
if (!value) return "";
|
|
17204
17485
|
return truncate2(value.replace(/\s+/g, " "), max);
|
|
17205
17486
|
}
|
|
17206
|
-
function quote(value, max = 48) {
|
|
17207
|
-
return value ? `\u201C${compact2(value, max)}\u201D` : "";
|
|
17487
|
+
function quote(value, max = 48) {
|
|
17488
|
+
return value ? `\u201C${compact2(value, max)}\u201D` : "";
|
|
17489
|
+
}
|
|
17490
|
+
function truncate2(value, max) {
|
|
17491
|
+
return value.length > max ? `${value.slice(0, Math.max(1, max - 1))}\u2026` : value;
|
|
17492
|
+
}
|
|
17493
|
+
function humanize(value) {
|
|
17494
|
+
const words = value.replace(/[_-]+/g, " ").trim();
|
|
17495
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : "Tool";
|
|
17496
|
+
}
|
|
17497
|
+
|
|
17498
|
+
// src/ui/spinner.ts
|
|
17499
|
+
import { stdout as stdout4 } from "process";
|
|
17500
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17501
|
+
var Spinner = class {
|
|
17502
|
+
constructor(label = "thinking") {
|
|
17503
|
+
this.label = label;
|
|
17504
|
+
}
|
|
17505
|
+
label;
|
|
17506
|
+
timer;
|
|
17507
|
+
index = 0;
|
|
17508
|
+
holdWhile;
|
|
17509
|
+
held = false;
|
|
17510
|
+
startedAt;
|
|
17511
|
+
start(holdWhile, startedAt = performance.now()) {
|
|
17512
|
+
this.holdWhile = holdWhile;
|
|
17513
|
+
if (this.startedAt !== void 0) return;
|
|
17514
|
+
this.startedAt = startedAt;
|
|
17515
|
+
this.held = false;
|
|
17516
|
+
if (!stdout4.isTTY) return;
|
|
17517
|
+
this.render();
|
|
17518
|
+
this.timer = setInterval(() => this.render(), 90);
|
|
17519
|
+
}
|
|
17520
|
+
update(label) {
|
|
17521
|
+
this.label = label;
|
|
17522
|
+
}
|
|
17523
|
+
stop(completion) {
|
|
17524
|
+
const elapsed = this.startedAt === void 0 ? void 0 : performance.now() - this.startedAt;
|
|
17525
|
+
this.startedAt = void 0;
|
|
17526
|
+
if (this.timer) {
|
|
17527
|
+
clearInterval(this.timer);
|
|
17528
|
+
this.timer = void 0;
|
|
17529
|
+
}
|
|
17530
|
+
if (stdout4.isTTY && elapsed !== void 0) {
|
|
17531
|
+
stdout4.clearLine(0);
|
|
17532
|
+
stdout4.cursorTo(0);
|
|
17533
|
+
if (completion && elapsed >= 1e3) stdout4.write(` ${completion} ${formatSeconds(elapsed)}.
|
|
17534
|
+
`);
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
render() {
|
|
17538
|
+
if (this.holdWhile?.()) {
|
|
17539
|
+
if (!this.held) {
|
|
17540
|
+
stdout4.clearLine(0);
|
|
17541
|
+
stdout4.cursorTo(0);
|
|
17542
|
+
this.held = true;
|
|
17543
|
+
}
|
|
17544
|
+
return;
|
|
17545
|
+
}
|
|
17546
|
+
this.held = false;
|
|
17547
|
+
this.index = (this.index + 1) % FRAMES.length;
|
|
17548
|
+
stdout4.clearLine(0);
|
|
17549
|
+
stdout4.cursorTo(0);
|
|
17550
|
+
const milliseconds = performance.now() - (this.startedAt ?? performance.now());
|
|
17551
|
+
const elapsed = milliseconds >= 1e3 ? formatElapsed(milliseconds) : "";
|
|
17552
|
+
const limit = Math.max(0, (stdout4.columns ?? 80) - elapsed.length - 10);
|
|
17553
|
+
let label = "";
|
|
17554
|
+
let cells2 = 0;
|
|
17555
|
+
for (const character of this.label.replace(/[\x00-\x1f\x7f]/g, " ")) {
|
|
17556
|
+
const size = character.codePointAt(0) > 127 ? 2 : 1;
|
|
17557
|
+
if (cells2 + size > limit) break;
|
|
17558
|
+
label += character;
|
|
17559
|
+
cells2 += size;
|
|
17560
|
+
}
|
|
17561
|
+
stdout4.write(` ${FRAMES[this.index]} ${label}\u2026${elapsed ? ` \xB7 ${elapsed}` : ""}`);
|
|
17562
|
+
}
|
|
17563
|
+
};
|
|
17564
|
+
function formatSeconds(milliseconds) {
|
|
17565
|
+
const seconds = Math.max(0, milliseconds) / 1e3;
|
|
17566
|
+
const count3 = seconds.toFixed(1).replace(/\.0$/, "");
|
|
17567
|
+
return `${count3} ${count3 === "1" ? "second" : "seconds"}`;
|
|
17568
|
+
}
|
|
17569
|
+
|
|
17570
|
+
// src/ui/activity.ts
|
|
17571
|
+
var sink;
|
|
17572
|
+
function setActivitySink(value) {
|
|
17573
|
+
sink = value;
|
|
17574
|
+
}
|
|
17575
|
+
function getActivitySink() {
|
|
17576
|
+
return sink;
|
|
17577
|
+
}
|
|
17578
|
+
var clean = (text) => stripVTControlCharacters(text).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17579
|
+
function activityEntry(call, result, durationMs) {
|
|
17580
|
+
const fields = ["command", "script", "path", "paths", "query", "pattern", "url", "cwd"];
|
|
17581
|
+
const request = fields.flatMap((key) => {
|
|
17582
|
+
const value = call.input[key];
|
|
17583
|
+
return typeof value === "string" ? [`${key}: ${value}`] : Array.isArray(value) && value.every((item) => typeof item === "string") ? [`${key}: ${value.join(", ")}`] : [];
|
|
17584
|
+
}).join(" \xB7 ");
|
|
17585
|
+
return {
|
|
17586
|
+
label: clean(toolActivityLabel(call)),
|
|
17587
|
+
request: clean(request).slice(0, 4e3),
|
|
17588
|
+
output: clean(result.content).slice(0, 1e3),
|
|
17589
|
+
ok: result.ok,
|
|
17590
|
+
durationMs
|
|
17591
|
+
};
|
|
17592
|
+
}
|
|
17593
|
+
function activityRows(snapshot, expanded, selected, end) {
|
|
17594
|
+
const failures = snapshot.entries.filter((entry) => !entry.ok).length;
|
|
17595
|
+
const elapsed = snapshot.thinkingMs >= 1e3 ? ` \xB7 Thought for ${formatSeconds(snapshot.thinkingMs)}` : "";
|
|
17596
|
+
const summary = `${expanded ? "\u25BE" : "\u25B8"} \u25CF ${snapshot.entries.length} actions${failures ? ` \xB7 ${failures} failed` : ""}${snapshot.finished ? elapsed : ""} \xB7 Ctrl+O details`;
|
|
17597
|
+
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17598
|
+
if (!expanded) return rows;
|
|
17599
|
+
const last = Math.min(snapshot.entries.length, end);
|
|
17600
|
+
for (let index = Math.max(0, last - 5); index < last; index += 1) {
|
|
17601
|
+
const entry = snapshot.entries[index];
|
|
17602
|
+
const duration = entry.durationMs >= 1e3 ? ` \xB7 Worked for ${formatSeconds(entry.durationMs)}` : "";
|
|
17603
|
+
rows.push({ text: ` ${selected === index ? "\u25BE" : "\u25B8"} \u25CF ${entry.label}${duration}`, ok: entry.ok, entry: index });
|
|
17604
|
+
if (selected === index) {
|
|
17605
|
+
if (entry.request) rows.push({ text: ` ${entry.request}`, entry: index });
|
|
17606
|
+
if (entry.output) rows.push({ text: ` ${entry.output}`, ok: entry.ok, entry: index });
|
|
17607
|
+
}
|
|
17608
|
+
}
|
|
17609
|
+
if (snapshot.entries.length > 5) rows.push({ text: " Scroll to browse actions \xB7 /output for full saved results" });
|
|
17610
|
+
return rows;
|
|
17611
|
+
}
|
|
17612
|
+
|
|
17613
|
+
// src/ui/interactive-choice.ts
|
|
17614
|
+
var MOUSE_ON = "\x1B[?1006h\x1B[?1000h";
|
|
17615
|
+
var MOUSE_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
17616
|
+
function displayLabel(value, width2) {
|
|
17617
|
+
const clean2 = value.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|.)/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17618
|
+
let result = "";
|
|
17619
|
+
let used = 0;
|
|
17620
|
+
for (const character of clean2) {
|
|
17621
|
+
const size = /[\p{Mark}\u200d\ufe0e\ufe0f]/u.test(character) ? 0 : character.codePointAt(0) < 128 ? 1 : 2;
|
|
17622
|
+
if (used + size > width2 - 1) return `${result}\u2026`;
|
|
17623
|
+
result += character;
|
|
17624
|
+
used += size;
|
|
17625
|
+
}
|
|
17626
|
+
return result;
|
|
17627
|
+
}
|
|
17628
|
+
function attachInteractiveChoices(readline, options, settings = {}) {
|
|
17629
|
+
const rl = readline;
|
|
17630
|
+
const output = settings.output ?? rl.output;
|
|
17631
|
+
const prompt = settings.prompt ?? " \u203A ";
|
|
17632
|
+
let resolveReady;
|
|
17633
|
+
const ready = new Promise((resolve17) => {
|
|
17634
|
+
resolveReady = resolve17;
|
|
17635
|
+
});
|
|
17636
|
+
const original = rl._ttyWrite;
|
|
17637
|
+
const priorPrompt = rl.getPrompt();
|
|
17638
|
+
const priorLine = rl.line;
|
|
17639
|
+
const priorCursor = rl.cursor;
|
|
17640
|
+
const priorHistory = rl.history?.slice();
|
|
17641
|
+
let injected = false;
|
|
17642
|
+
let selected = 0;
|
|
17643
|
+
let disposed = false;
|
|
17644
|
+
let mouse = false;
|
|
17645
|
+
let optionRow = null;
|
|
17646
|
+
let reportPending = false;
|
|
17647
|
+
let reportTimer;
|
|
17648
|
+
let sequenceTimer;
|
|
17649
|
+
let sequence = "";
|
|
17650
|
+
const maxRow = () => settings.maxRow?.() ?? output?.rows ?? 1e4;
|
|
17651
|
+
const columns = () => Math.max(16, output?.columns ?? 80);
|
|
17652
|
+
const write = (text) => {
|
|
17653
|
+
output?.write(text);
|
|
17654
|
+
};
|
|
17655
|
+
const labels = () => options.map((option, index) => {
|
|
17656
|
+
const prefix = ` ${selected === index ? "\u203A" : " "} ${index + 1}. `;
|
|
17657
|
+
const text = `${prefix}${displayLabel(option, columns() - 9)}`;
|
|
17658
|
+
return selected === index ? `\x1B[7m${text}\x1B[0m` : text;
|
|
17659
|
+
});
|
|
17660
|
+
const restoreLine = () => {
|
|
17661
|
+
if (!original || !rl.terminal || rl.closed) return;
|
|
17662
|
+
if (rl.line) {
|
|
17663
|
+
original.call(rl, void 0, { name: "end" });
|
|
17664
|
+
original.call(rl, void 0, { name: "u", ctrl: true });
|
|
17665
|
+
}
|
|
17666
|
+
if (priorLine) {
|
|
17667
|
+
original.call(rl, priorLine, {});
|
|
17668
|
+
for (const _character of Array.from(priorLine.slice(priorCursor))) original.call(rl, void 0, { name: "left" });
|
|
17669
|
+
}
|
|
17670
|
+
};
|
|
17671
|
+
const cleanup = () => {
|
|
17672
|
+
if (disposed) return;
|
|
17673
|
+
disposed = true;
|
|
17674
|
+
clearTimeout(reportTimer);
|
|
17675
|
+
clearTimeout(sequenceTimer);
|
|
17676
|
+
if (rl._ttyWrite === intercept) rl._ttyWrite = original;
|
|
17677
|
+
rl.off("line", onLine);
|
|
17678
|
+
rl.off("close", cleanup);
|
|
17679
|
+
output?.off("resize", onResize);
|
|
17680
|
+
output?.off("error", cleanup);
|
|
17681
|
+
settings.signal?.removeEventListener("abort", cleanup);
|
|
17682
|
+
try {
|
|
17683
|
+
if (mouse) write(MOUSE_OFF);
|
|
17684
|
+
} catch {
|
|
17685
|
+
}
|
|
17686
|
+
mouse = false;
|
|
17687
|
+
rl.setPrompt(priorPrompt);
|
|
17688
|
+
if (injected && priorHistory && rl.history) rl.history.splice(0, rl.history.length, ...priorHistory);
|
|
17689
|
+
try {
|
|
17690
|
+
restoreLine();
|
|
17691
|
+
} catch {
|
|
17692
|
+
}
|
|
17693
|
+
resolveReady(false);
|
|
17694
|
+
};
|
|
17695
|
+
function onLine() {
|
|
17696
|
+
cleanup();
|
|
17697
|
+
}
|
|
17698
|
+
function redraw() {
|
|
17699
|
+
if (disposed || rl.line || options.length + 2 > maxRow()) return;
|
|
17700
|
+
write("\x1B7");
|
|
17701
|
+
if (optionRow !== null) write(`\x1B[${optionRow};1H`);
|
|
17702
|
+
else write(`\x1B[${options.length + 1}A\r`);
|
|
17703
|
+
for (const label of labels()) write(`\x1B[2K${label}
|
|
17704
|
+
\r`);
|
|
17705
|
+
write("\x1B8");
|
|
17706
|
+
}
|
|
17707
|
+
function choose(index) {
|
|
17708
|
+
if (disposed || !original || index < 0 || index >= options.length) return;
|
|
17709
|
+
selected = index;
|
|
17710
|
+
injected = true;
|
|
17711
|
+
if (rl.line) {
|
|
17712
|
+
original.call(rl, void 0, { name: "end" });
|
|
17713
|
+
original.call(rl, void 0, { name: "u", ctrl: true });
|
|
17714
|
+
}
|
|
17715
|
+
original.call(rl, String(index + 1), {});
|
|
17716
|
+
original.call(rl, "\r", { name: "return", sequence: "\r" });
|
|
17717
|
+
}
|
|
17718
|
+
function requestPosition() {
|
|
17719
|
+
if (disposed) return;
|
|
17720
|
+
optionRow = null;
|
|
17721
|
+
reportPending = true;
|
|
17722
|
+
clearTimeout(reportTimer);
|
|
17723
|
+
write("\x1B[6n");
|
|
17724
|
+
reportTimer = setTimeout(() => {
|
|
17725
|
+
reportPending = false;
|
|
17726
|
+
resolveReady(false);
|
|
17727
|
+
}, Math.max(1, settings.cursorReportTimeoutMs ?? 250));
|
|
17728
|
+
}
|
|
17729
|
+
function onResize() {
|
|
17730
|
+
if (disposed) return;
|
|
17731
|
+
if (mouse) write(MOUSE_OFF);
|
|
17732
|
+
mouse = false;
|
|
17733
|
+
optionRow = null;
|
|
17734
|
+
requestPosition();
|
|
17735
|
+
}
|
|
17736
|
+
function consumeControl(value) {
|
|
17737
|
+
const position = /^\x1b\[(\d+);(\d+)R$/.exec(value);
|
|
17738
|
+
if (position) {
|
|
17739
|
+
const row = Number(position[1]);
|
|
17740
|
+
const column = Number(position[2]);
|
|
17741
|
+
if (reportPending && row > options.length + 1 && row <= maxRow() && column >= 1 && column <= columns()) {
|
|
17742
|
+
optionRow = row - options.length - 1;
|
|
17743
|
+
reportPending = false;
|
|
17744
|
+
clearTimeout(reportTimer);
|
|
17745
|
+
write(MOUSE_ON);
|
|
17746
|
+
mouse = true;
|
|
17747
|
+
resolveReady(true);
|
|
17748
|
+
}
|
|
17749
|
+
return true;
|
|
17750
|
+
}
|
|
17751
|
+
const click = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(value);
|
|
17752
|
+
if (click) {
|
|
17753
|
+
const button = Number(click[1]);
|
|
17754
|
+
const x = Number(click[2]);
|
|
17755
|
+
const y = Number(click[3]);
|
|
17756
|
+
if (mouse && optionRow !== null && click[4] === "M" && button < 32 && (button & 3) === 0 && x >= 1 && x <= columns() && y <= maxRow()) choose(y - optionRow);
|
|
17757
|
+
return true;
|
|
17758
|
+
}
|
|
17759
|
+
return false;
|
|
17760
|
+
}
|
|
17761
|
+
function intercept(text, key) {
|
|
17762
|
+
if (disposed) {
|
|
17763
|
+
original?.call(rl, text, key);
|
|
17764
|
+
return;
|
|
17765
|
+
}
|
|
17766
|
+
const value = key?.sequence ?? text ?? "";
|
|
17767
|
+
if (sequence || value.startsWith("\x1B[<") || /^\x1b\[\d/.test(value)) {
|
|
17768
|
+
sequence += value;
|
|
17769
|
+
clearTimeout(sequenceTimer);
|
|
17770
|
+
if (consumeControl(sequence)) {
|
|
17771
|
+
sequence = "";
|
|
17772
|
+
return;
|
|
17773
|
+
}
|
|
17774
|
+
if (/^\x1b\[(?:<[0-9;]*|[0-9;]*)$/.test(sequence) && sequence.length < 64) {
|
|
17775
|
+
sequenceTimer = setTimeout(() => {
|
|
17776
|
+
sequence = "";
|
|
17777
|
+
}, 250);
|
|
17778
|
+
return;
|
|
17779
|
+
}
|
|
17780
|
+
const unrecognized = sequence;
|
|
17781
|
+
sequence = "";
|
|
17782
|
+
original?.call(rl, unrecognized, key);
|
|
17783
|
+
return;
|
|
17784
|
+
}
|
|
17785
|
+
if (!rl.line && (key?.name === "up" || key?.name === "down")) {
|
|
17786
|
+
selected = (selected + (key.name === "down" ? 1 : options.length - 1)) % options.length;
|
|
17787
|
+
redraw();
|
|
17788
|
+
return;
|
|
17789
|
+
}
|
|
17790
|
+
if (!rl.line && (key?.name === "return" || key?.name === "enter")) {
|
|
17791
|
+
choose(selected);
|
|
17792
|
+
return;
|
|
17793
|
+
}
|
|
17794
|
+
if (key?.ctrl && key.name === "c") cleanup();
|
|
17795
|
+
if (key?.ctrl && key.name === "d" && !rl.line) {
|
|
17796
|
+
original?.call(rl, text, key);
|
|
17797
|
+
cleanup();
|
|
17798
|
+
return;
|
|
17799
|
+
}
|
|
17800
|
+
original?.call(rl, text, key);
|
|
17801
|
+
}
|
|
17802
|
+
if (settings.signal?.aborted) {
|
|
17803
|
+
disposed = true;
|
|
17804
|
+
resolveReady(false);
|
|
17805
|
+
return { cleanup, ready };
|
|
17806
|
+
}
|
|
17807
|
+
if (!output || !rl.terminal || !output.isTTY || process.env.TERM === "dumb" || !original || !options.length) {
|
|
17808
|
+
options.forEach((option, index) => write(` ${index + 1}. ${displayLabel(option, columns() - 6)}
|
|
17809
|
+
`));
|
|
17810
|
+
write(prompt);
|
|
17811
|
+
resolveReady(false);
|
|
17812
|
+
disposed = true;
|
|
17813
|
+
return { cleanup, ready };
|
|
17814
|
+
}
|
|
17815
|
+
try {
|
|
17816
|
+
if (priorLine) {
|
|
17817
|
+
original.call(rl, void 0, { name: "end" });
|
|
17818
|
+
original.call(rl, void 0, { name: "u", ctrl: true });
|
|
17819
|
+
}
|
|
17820
|
+
rl.setPrompt(prompt);
|
|
17821
|
+
rl._ttyWrite = intercept;
|
|
17822
|
+
rl.once("line", onLine);
|
|
17823
|
+
rl.once("close", cleanup);
|
|
17824
|
+
output.on("resize", onResize);
|
|
17825
|
+
output.on("error", cleanup);
|
|
17826
|
+
settings.signal?.addEventListener("abort", cleanup, { once: true });
|
|
17827
|
+
for (const label of labels()) write(`${label}\r
|
|
17828
|
+
`);
|
|
17829
|
+
write(" \u2191/\u2193 and Enter, a number, or your own answer.\r\n");
|
|
17830
|
+
rl.prompt();
|
|
17831
|
+
requestPosition();
|
|
17832
|
+
} catch (error) {
|
|
17833
|
+
cleanup();
|
|
17834
|
+
throw error;
|
|
17835
|
+
}
|
|
17836
|
+
return { cleanup, ready };
|
|
17837
|
+
}
|
|
17838
|
+
|
|
17839
|
+
// src/ui/slash-completion.ts
|
|
17840
|
+
function builtinSlashEntries(commands, help) {
|
|
17841
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
17842
|
+
for (const line of help.split("\n")) {
|
|
17843
|
+
const match = /^(\/[^\n]+?)\s{2,}(.+)$/.exec(line.trim());
|
|
17844
|
+
if (!match) continue;
|
|
17845
|
+
for (const command of match[1].matchAll(/\/([a-z-]+)/g)) descriptions.set(command[1], match[2]);
|
|
17846
|
+
}
|
|
17847
|
+
return commands.map((command) => ({ text: `/${command}`, label: `/${command}`, description: descriptions.get(command) }));
|
|
17848
|
+
}
|
|
17849
|
+
function attachSlashCompletion(readline, loadEntries, render, initialEntries = []) {
|
|
17850
|
+
const rl = readline;
|
|
17851
|
+
const original = rl._ttyWrite;
|
|
17852
|
+
let entries = [...initialEntries];
|
|
17853
|
+
let matches = [];
|
|
17854
|
+
let selected = 0;
|
|
17855
|
+
let disposed = false;
|
|
17856
|
+
let suppressed = false;
|
|
17857
|
+
let opened = false;
|
|
17858
|
+
let epoch = 0;
|
|
17859
|
+
let handledEscape = false;
|
|
17860
|
+
const clear = () => {
|
|
17861
|
+
matches = [];
|
|
17862
|
+
selected = 0;
|
|
17863
|
+
render([], 0);
|
|
17864
|
+
};
|
|
17865
|
+
const refresh = () => {
|
|
17866
|
+
if (disposed || suppressed) return;
|
|
17867
|
+
const line = rl.line;
|
|
17868
|
+
const skillPrefix = /^\/skill\s+([a-z0-9-]*)$/i.exec(line);
|
|
17869
|
+
if (!/^\/[a-z0-9-]*$/i.test(line) && !skillPrefix) {
|
|
17870
|
+
opened = false;
|
|
17871
|
+
epoch += 1;
|
|
17872
|
+
clear();
|
|
17873
|
+
return;
|
|
17874
|
+
}
|
|
17875
|
+
if (!opened) {
|
|
17876
|
+
opened = true;
|
|
17877
|
+
const request = ++epoch;
|
|
17878
|
+
void loadEntries().then((items) => {
|
|
17879
|
+
if (disposed || request !== epoch || !opened) return;
|
|
17880
|
+
entries = items;
|
|
17881
|
+
refresh();
|
|
17882
|
+
}).catch(() => {
|
|
17883
|
+
if (!disposed && request === epoch) clear();
|
|
17884
|
+
});
|
|
17885
|
+
}
|
|
17886
|
+
const query = (skillPrefix?.[1] ?? line.slice(1)).toLowerCase();
|
|
17887
|
+
matches = entries.filter((entry) => (!skillPrefix || entry.skill) && (entry.text.toLowerCase().includes(query) || entry.label.toLowerCase().includes(query)));
|
|
17888
|
+
selected = Math.min(selected, Math.max(0, matches.length - 1));
|
|
17889
|
+
render(matches.map((entry) => `${entry.label}${entry.description ? ` ${entry.description}` : ""}`.replace(/[\x00-\x1f\x7f]/g, " ")), selected);
|
|
17890
|
+
};
|
|
17891
|
+
const wrapper = function(text, key) {
|
|
17892
|
+
if (!suppressed && matches.length) {
|
|
17893
|
+
if (key?.name === "escape") {
|
|
17894
|
+
handledEscape = true;
|
|
17895
|
+
setTimeout(() => {
|
|
17896
|
+
handledEscape = false;
|
|
17897
|
+
}, 0);
|
|
17898
|
+
opened = false;
|
|
17899
|
+
epoch += 1;
|
|
17900
|
+
clear();
|
|
17901
|
+
return;
|
|
17902
|
+
}
|
|
17903
|
+
if (key?.name === "up" || key?.name === "down") {
|
|
17904
|
+
selected = (selected + (key.name === "up" ? -1 : 1) + matches.length) % matches.length;
|
|
17905
|
+
render(matches.map((entry) => `${entry.label}${entry.description ? ` ${entry.description}` : ""}`.replace(/[\x00-\x1f\x7f]/g, " ")), selected);
|
|
17906
|
+
return;
|
|
17907
|
+
}
|
|
17908
|
+
if (key?.name === "tab" || key?.name === "return" || key?.name === "enter") {
|
|
17909
|
+
const choice = matches[selected];
|
|
17910
|
+
suppressed = true;
|
|
17911
|
+
try {
|
|
17912
|
+
rl.write(null, { ctrl: true, name: "u" });
|
|
17913
|
+
rl.write(`${choice.text} `);
|
|
17914
|
+
} finally {
|
|
17915
|
+
suppressed = false;
|
|
17916
|
+
opened = false;
|
|
17917
|
+
epoch += 1;
|
|
17918
|
+
clear();
|
|
17919
|
+
}
|
|
17920
|
+
return;
|
|
17921
|
+
}
|
|
17922
|
+
}
|
|
17923
|
+
original.call(this, text, key);
|
|
17924
|
+
refresh();
|
|
17925
|
+
};
|
|
17926
|
+
rl._ttyWrite = wrapper;
|
|
17927
|
+
return {
|
|
17928
|
+
handledEscape: () => handledEscape,
|
|
17929
|
+
suspend: () => {
|
|
17930
|
+
suppressed = true;
|
|
17931
|
+
opened = false;
|
|
17932
|
+
epoch += 1;
|
|
17933
|
+
clear();
|
|
17934
|
+
},
|
|
17935
|
+
resume: () => {
|
|
17936
|
+
suppressed = false;
|
|
17937
|
+
refresh();
|
|
17938
|
+
},
|
|
17939
|
+
cleanup: () => {
|
|
17940
|
+
disposed = true;
|
|
17941
|
+
epoch += 1;
|
|
17942
|
+
clear();
|
|
17943
|
+
if (rl._ttyWrite === wrapper) rl._ttyWrite = original;
|
|
17944
|
+
}
|
|
17945
|
+
};
|
|
17946
|
+
}
|
|
17947
|
+
|
|
17948
|
+
// src/ui/terminal-layout.ts
|
|
17949
|
+
import { Writable } from "stream";
|
|
17950
|
+
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
17951
|
+
var CSI = "\x1B[";
|
|
17952
|
+
var RESET_REGION = `${CSI}r`;
|
|
17953
|
+
var MOUSE_OFF2 = `${CSI}?1000l${CSI}?1002l${CSI}?1003l${CSI}?1006l`;
|
|
17954
|
+
var BASE_FOOTER_ROWS = 4;
|
|
17955
|
+
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
17956
|
+
var cells = (value) => [...segmenter.segment(value)].map(({ segment }) => {
|
|
17957
|
+
const code = segment.codePointAt(0) ?? 0;
|
|
17958
|
+
const wide = /\p{Extended_Pictographic}/u.test(segment) || code >= 4352 && (code <= 4447 || code === 9001 || code === 9002 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072);
|
|
17959
|
+
return { text: segment, width: /^\p{Mark}+$/u.test(segment) ? 0 : wide ? 2 : 1 };
|
|
17960
|
+
});
|
|
17961
|
+
var width = (text) => cells(text).reduce((sum, cell) => sum + cell.width, 0);
|
|
17962
|
+
function plain(value) {
|
|
17963
|
+
return stripVTControlCharacters2(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
17964
|
+
}
|
|
17965
|
+
function fit(text, columns) {
|
|
17966
|
+
if (columns <= 0) return "";
|
|
17967
|
+
let result = "";
|
|
17968
|
+
let used = 0;
|
|
17969
|
+
for (const cell of cells(plain(text))) {
|
|
17970
|
+
if (used + cell.width > columns) break;
|
|
17971
|
+
result += cell.text;
|
|
17972
|
+
used += cell.width;
|
|
17973
|
+
}
|
|
17974
|
+
return result;
|
|
17975
|
+
}
|
|
17976
|
+
var cup = (row, column = 1) => `${CSI}${row};${column}H`;
|
|
17977
|
+
var ComposerOutput = class extends Writable {
|
|
17978
|
+
constructor(layout) {
|
|
17979
|
+
super();
|
|
17980
|
+
this.layout = layout;
|
|
17981
|
+
}
|
|
17982
|
+
layout;
|
|
17983
|
+
get isTTY() {
|
|
17984
|
+
return this.layout.enabled;
|
|
17985
|
+
}
|
|
17986
|
+
get columns() {
|
|
17987
|
+
return this.layout.columns;
|
|
17988
|
+
}
|
|
17989
|
+
get rows() {
|
|
17990
|
+
return this.layout.rows;
|
|
17991
|
+
}
|
|
17992
|
+
_write(chunk, _encoding, callback) {
|
|
17993
|
+
try {
|
|
17994
|
+
this.layout.writeComposer(chunk);
|
|
17995
|
+
callback();
|
|
17996
|
+
} catch (error) {
|
|
17997
|
+
callback(error instanceof Error ? error : new Error("Terminal output failed"));
|
|
17998
|
+
}
|
|
17999
|
+
}
|
|
18000
|
+
};
|
|
18001
|
+
var TerminalLayout = class {
|
|
18002
|
+
outputProxy;
|
|
18003
|
+
enabled;
|
|
18004
|
+
output;
|
|
18005
|
+
errorOutput;
|
|
18006
|
+
originalWrite;
|
|
18007
|
+
originalErrorWrite;
|
|
18008
|
+
rawWrite;
|
|
18009
|
+
details;
|
|
18010
|
+
rl;
|
|
18011
|
+
prompt = "\u203A ";
|
|
18012
|
+
inputFormatter;
|
|
18013
|
+
transcriptRow = 1;
|
|
18014
|
+
transcriptColumn = 1;
|
|
18015
|
+
wrapPending = false;
|
|
18016
|
+
savedTranscript;
|
|
18017
|
+
lastFooterTop = 0;
|
|
18018
|
+
suggestions = [];
|
|
18019
|
+
selected = 0;
|
|
18020
|
+
activity;
|
|
18021
|
+
activityExpanded = false;
|
|
18022
|
+
activitySelected = -1;
|
|
18023
|
+
activityEnd = 0;
|
|
18024
|
+
restoreActivityInput;
|
|
18025
|
+
installed = false;
|
|
18026
|
+
closed = false;
|
|
18027
|
+
suspended = 0;
|
|
18028
|
+
paintQueued = false;
|
|
18029
|
+
bindSignals;
|
|
18030
|
+
resize = () => {
|
|
18031
|
+
if (this.closed || !this.enabled) return;
|
|
18032
|
+
this.clearPreviousFooter();
|
|
18033
|
+
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
18034
|
+
this.transcriptColumn = Math.min(this.transcriptColumn, this.columns);
|
|
18035
|
+
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
18036
|
+
this.outputProxy.emit("resize");
|
|
18037
|
+
this.paintFooter();
|
|
18038
|
+
};
|
|
18039
|
+
exit = () => {
|
|
18040
|
+
this.close();
|
|
18041
|
+
};
|
|
18042
|
+
constructor(options) {
|
|
18043
|
+
this.output = options.output;
|
|
18044
|
+
this.errorOutput = options.errorOutput;
|
|
18045
|
+
this.details = { ...options.details };
|
|
18046
|
+
this.bindSignals = options.bindSignals !== false;
|
|
18047
|
+
this.originalWrite = options.output.write;
|
|
18048
|
+
this.rawWrite = options.output.write.bind(options.output);
|
|
18049
|
+
this.originalErrorWrite = options.errorOutput?.write;
|
|
18050
|
+
this.enabled = Boolean(options.output.isTTY && (options.output.columns ?? 80) >= 12 && (options.output.rows ?? 24) >= 7);
|
|
18051
|
+
this.outputProxy = new ComposerOutput(this);
|
|
18052
|
+
}
|
|
18053
|
+
get columns() {
|
|
18054
|
+
return Math.max(12, this.output.columns ?? 80);
|
|
18055
|
+
}
|
|
18056
|
+
get rows() {
|
|
18057
|
+
return Math.max(7, this.output.rows ?? 24);
|
|
18058
|
+
}
|
|
18059
|
+
get suggestionRows() {
|
|
18060
|
+
return Math.min(8, this.suggestions.length, Math.max(0, this.rows - BASE_FOOTER_ROWS - 2));
|
|
18061
|
+
}
|
|
18062
|
+
get activityLines() {
|
|
18063
|
+
if (!this.activity) return [];
|
|
18064
|
+
return activityRows(this.activity, this.activityExpanded, this.activitySelected, this.activityEnd).slice(0, Math.max(1, Math.min(10, this.rows - BASE_FOOTER_ROWS - this.suggestionRows - 2)));
|
|
18065
|
+
}
|
|
18066
|
+
get contentRows() {
|
|
18067
|
+
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows - this.activityLines.length;
|
|
18068
|
+
}
|
|
18069
|
+
get inputRow() {
|
|
18070
|
+
return this.rows - 2;
|
|
18071
|
+
}
|
|
18072
|
+
install() {
|
|
18073
|
+
if (this.installed || this.closed || !this.enabled) return this;
|
|
18074
|
+
this.installed = true;
|
|
18075
|
+
this.output.write = ((chunk, encoding, callback) => this.writeTranscript(chunk, encoding, callback));
|
|
18076
|
+
if (this.errorOutput && this.errorOutput !== this.output) this.errorOutput.write = this.output.write;
|
|
18077
|
+
this.output.on("resize", this.resize);
|
|
18078
|
+
this.output.on("error", this.exit);
|
|
18079
|
+
if (this.errorOutput !== this.output) this.errorOutput?.on("error", this.exit);
|
|
18080
|
+
process.once("exit", this.exit);
|
|
18081
|
+
if (this.bindSignals) {
|
|
18082
|
+
process.once("SIGINT", this.exit);
|
|
18083
|
+
process.once("SIGTERM", this.exit);
|
|
18084
|
+
}
|
|
18085
|
+
this.rawWrite(`${CSI}2J${cup(1)}${CSI}1;${this.contentRows}r`);
|
|
18086
|
+
this.paintFooter();
|
|
18087
|
+
return this;
|
|
18088
|
+
}
|
|
18089
|
+
attachReadline(rl) {
|
|
18090
|
+
this.rl = rl;
|
|
18091
|
+
rl.setPrompt(this.prompt);
|
|
18092
|
+
const reader = rl;
|
|
18093
|
+
const original = reader._ttyWrite;
|
|
18094
|
+
let sequence = "";
|
|
18095
|
+
let sequenceTimer;
|
|
18096
|
+
const wrapper = (text, key) => {
|
|
18097
|
+
if (this.activity && !this.suspended && key?.ctrl && key.name === "o") {
|
|
18098
|
+
this.toggleActivity();
|
|
18099
|
+
return;
|
|
18100
|
+
}
|
|
18101
|
+
const value = key?.sequence ?? text ?? "";
|
|
18102
|
+
if (sequence || value.startsWith("\x1B[<")) {
|
|
18103
|
+
sequence += value;
|
|
18104
|
+
clearTimeout(sequenceTimer);
|
|
18105
|
+
const mouse = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(sequence);
|
|
18106
|
+
if (mouse) {
|
|
18107
|
+
sequence = "";
|
|
18108
|
+
if (!this.suspended) this.activityMouse(Number(mouse[1]), Number(mouse[3]), mouse[4] === "M");
|
|
18109
|
+
return;
|
|
18110
|
+
}
|
|
18111
|
+
if (/^\x1b\[<[0-9;]*$/.test(sequence) && sequence.length < 64) {
|
|
18112
|
+
sequenceTimer = setTimeout(() => {
|
|
18113
|
+
sequence = "";
|
|
18114
|
+
}, 250);
|
|
18115
|
+
return;
|
|
18116
|
+
}
|
|
18117
|
+
const pending = sequence;
|
|
18118
|
+
sequence = "";
|
|
18119
|
+
original.call(reader, pending, key);
|
|
18120
|
+
return;
|
|
18121
|
+
}
|
|
18122
|
+
original.call(reader, text, key);
|
|
18123
|
+
};
|
|
18124
|
+
reader._ttyWrite = wrapper;
|
|
18125
|
+
this.restoreActivityInput = () => {
|
|
18126
|
+
clearTimeout(sequenceTimer);
|
|
18127
|
+
if (reader._ttyWrite === wrapper) reader._ttyWrite = original;
|
|
18128
|
+
};
|
|
18129
|
+
this.paintFooter();
|
|
18130
|
+
}
|
|
18131
|
+
setActivity(snapshot) {
|
|
18132
|
+
this.clearPreviousFooter();
|
|
18133
|
+
const previous = this.activity?.entries.length ?? 0;
|
|
18134
|
+
if (!snapshot.entries.length && !snapshot.finished) {
|
|
18135
|
+
this.activityExpanded = false;
|
|
18136
|
+
this.activitySelected = -1;
|
|
18137
|
+
}
|
|
18138
|
+
this.activity = snapshot;
|
|
18139
|
+
if (this.activityEnd >= previous || !previous) this.activityEnd = snapshot.entries.length;
|
|
18140
|
+
this.reflowActivity();
|
|
18141
|
+
}
|
|
18142
|
+
toggleActivity() {
|
|
18143
|
+
if (!this.activity) return;
|
|
18144
|
+
this.clearPreviousFooter();
|
|
18145
|
+
this.activityExpanded = !this.activityExpanded;
|
|
18146
|
+
this.reflowActivity();
|
|
18147
|
+
}
|
|
18148
|
+
reflowActivity() {
|
|
18149
|
+
if (!this.enabled || !this.installed || this.closed) return;
|
|
18150
|
+
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
18151
|
+
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
18152
|
+
this.paintFooter();
|
|
18153
|
+
}
|
|
18154
|
+
activityMouse(button, row, press) {
|
|
18155
|
+
if (!this.activity || !press) return;
|
|
18156
|
+
if (button === 64 || button === 65) {
|
|
18157
|
+
if (!this.activityExpanded) return;
|
|
18158
|
+
this.clearPreviousFooter();
|
|
18159
|
+
this.activityEnd = Math.max(Math.min(5, this.activity.entries.length), Math.min(this.activity.entries.length, this.activityEnd + (button === 64 ? -1 : 1)));
|
|
18160
|
+
this.activitySelected = -1;
|
|
18161
|
+
this.reflowActivity();
|
|
18162
|
+
return;
|
|
18163
|
+
}
|
|
18164
|
+
if (button !== 0) return;
|
|
18165
|
+
const index = row - this.contentRows - 1;
|
|
18166
|
+
if (index === 0) {
|
|
18167
|
+
this.toggleActivity();
|
|
18168
|
+
return;
|
|
18169
|
+
}
|
|
18170
|
+
const entry = this.activityLines[index]?.entry;
|
|
18171
|
+
if (entry !== void 0) {
|
|
18172
|
+
this.clearPreviousFooter();
|
|
18173
|
+
this.activitySelected = this.activitySelected === entry ? -1 : entry;
|
|
18174
|
+
this.reflowActivity();
|
|
18175
|
+
}
|
|
18176
|
+
}
|
|
18177
|
+
setInputFormatter(formatter) {
|
|
18178
|
+
this.inputFormatter = formatter;
|
|
18179
|
+
this.paintFooter();
|
|
18180
|
+
}
|
|
18181
|
+
setPrompt(prompt) {
|
|
18182
|
+
this.prompt = prompt;
|
|
18183
|
+
this.rl?.setPrompt(prompt);
|
|
18184
|
+
this.paintFooter();
|
|
18185
|
+
}
|
|
18186
|
+
updateDetails(details) {
|
|
18187
|
+
this.details = { ...this.details, ...details };
|
|
18188
|
+
this.paintFooter();
|
|
18189
|
+
}
|
|
18190
|
+
setSuggestions(items, selectedIndex = 0) {
|
|
18191
|
+
if (this.enabled && this.installed && !this.closed) this.clearPreviousFooter();
|
|
18192
|
+
this.suggestions = [...items];
|
|
18193
|
+
this.selected = Math.max(0, Math.min(items.length - 1, selectedIndex));
|
|
18194
|
+
if (this.enabled && this.installed && !this.closed) {
|
|
18195
|
+
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
18196
|
+
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
18197
|
+
this.paintFooter();
|
|
18198
|
+
}
|
|
18199
|
+
}
|
|
18200
|
+
/** Freeze the footer and focus choices in the transcript; its reserved rows stay visible. */
|
|
18201
|
+
suspend() {
|
|
18202
|
+
if (this.closed || !this.enabled) return;
|
|
18203
|
+
this.suspended += 1;
|
|
18204
|
+
this.rawWrite(cup(this.transcriptRow, this.transcriptColumn));
|
|
18205
|
+
}
|
|
18206
|
+
resume() {
|
|
18207
|
+
if (this.suspended > 0) this.suspended -= 1;
|
|
18208
|
+
if (!this.suspended) this.paintFooter();
|
|
18209
|
+
}
|
|
18210
|
+
writeComposer(chunk) {
|
|
18211
|
+
if (this.closed || !this.enabled || !this.installed) {
|
|
18212
|
+
this.rawWrite(chunk);
|
|
18213
|
+
return;
|
|
18214
|
+
}
|
|
18215
|
+
if (this.suspended) {
|
|
18216
|
+
this.writeTranscript(chunk);
|
|
18217
|
+
return;
|
|
18218
|
+
}
|
|
18219
|
+
if (!this.paintQueued) {
|
|
18220
|
+
this.paintQueued = true;
|
|
18221
|
+
queueMicrotask(() => {
|
|
18222
|
+
this.paintQueued = false;
|
|
18223
|
+
if (!this.closed && !this.suspended) this.paintFooter();
|
|
18224
|
+
});
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
writeTranscript(chunk, encoding, callback) {
|
|
18228
|
+
if (this.closed || !this.enabled || !this.installed) return this.rawWrite(chunk, encoding, callback);
|
|
18229
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(typeof encoding === "string" ? encoding : "utf8");
|
|
18230
|
+
const formatted = text.replace(/(?<!\r)\n/g, "\r\n").replace(/\x1b\[([012]?)J/g, (_escape, mode) => {
|
|
18231
|
+
const saved = cup(this.transcriptRow, this.transcriptColumn);
|
|
18232
|
+
const first = mode === "2" || mode === "1" ? 1 : this.transcriptRow;
|
|
18233
|
+
const last = mode === "1" ? this.transcriptRow : this.contentRows;
|
|
18234
|
+
let erased = "";
|
|
18235
|
+
for (let row = first; row <= last; row += 1) erased += `${cup(row)}${CSI}2K`;
|
|
18236
|
+
return erased + saved;
|
|
18237
|
+
});
|
|
18238
|
+
if (this.wrapPending && !/^[\r\n]/.test(stripVTControlCharacters2(formatted))) {
|
|
18239
|
+
this.rawWrite(`${cup(this.transcriptRow, this.columns)}\r
|
|
18240
|
+
`);
|
|
18241
|
+
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow + 1);
|
|
18242
|
+
this.transcriptColumn = 1;
|
|
18243
|
+
this.wrapPending = false;
|
|
18244
|
+
}
|
|
18245
|
+
this.rawWrite(cup(this.transcriptRow, this.transcriptColumn));
|
|
18246
|
+
const accepted = this.rawWrite(
|
|
18247
|
+
formatted,
|
|
18248
|
+
typeof encoding === "string" ? encoding : void 0,
|
|
18249
|
+
typeof encoding === "function" ? encoding : callback
|
|
18250
|
+
);
|
|
18251
|
+
this.trackTranscript(formatted);
|
|
18252
|
+
if (!this.suspended) this.placeInputCursor();
|
|
18253
|
+
return accepted;
|
|
18254
|
+
}
|
|
18255
|
+
trackTranscript(text) {
|
|
18256
|
+
const parts = text.split(/(\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07]*(?:\x07|\x1b\\)|\x1b.)/g);
|
|
18257
|
+
for (const part of parts) {
|
|
18258
|
+
if (part === "\x1B7") {
|
|
18259
|
+
this.savedTranscript = { row: this.transcriptRow, column: this.transcriptColumn, wrapPending: this.wrapPending };
|
|
18260
|
+
} else if (part === "\x1B8") {
|
|
18261
|
+
if (this.savedTranscript) {
|
|
18262
|
+
this.transcriptRow = this.savedTranscript.row;
|
|
18263
|
+
this.transcriptColumn = this.savedTranscript.column;
|
|
18264
|
+
this.wrapPending = this.savedTranscript.wrapPending;
|
|
18265
|
+
}
|
|
18266
|
+
} else if (part.startsWith("\x1B[")) {
|
|
18267
|
+
const match = /^\x1b\[([\d;]*)([A-GHf])$/.exec(part);
|
|
18268
|
+
if (!match) continue;
|
|
18269
|
+
const values = match[1].split(";").map((value) => Number(value) || 1);
|
|
18270
|
+
const n = values[0];
|
|
18271
|
+
this.wrapPending = false;
|
|
18272
|
+
if (match[2] === "A") this.transcriptRow -= n;
|
|
18273
|
+
else if (match[2] === "B") this.transcriptRow += n;
|
|
18274
|
+
else if (match[2] === "C") this.transcriptColumn += n;
|
|
18275
|
+
else if (match[2] === "D") this.transcriptColumn -= n;
|
|
18276
|
+
else if (match[2] === "G") this.transcriptColumn = n;
|
|
18277
|
+
else if (match[2] === "H" || match[2] === "f") {
|
|
18278
|
+
this.transcriptRow = n;
|
|
18279
|
+
this.transcriptColumn = values[1] ?? 1;
|
|
18280
|
+
}
|
|
18281
|
+
} else if (!part.startsWith("\x1B")) {
|
|
18282
|
+
for (const cell of cells(part)) {
|
|
18283
|
+
if (cell.text === "\r" || cell.text === "\r\n") {
|
|
18284
|
+
this.wrapPending = false;
|
|
18285
|
+
this.transcriptColumn = 1;
|
|
18286
|
+
if (cell.text === "\r\n") this.transcriptRow += 1;
|
|
18287
|
+
} else if (cell.text === "\n") {
|
|
18288
|
+
this.wrapPending = false;
|
|
18289
|
+
this.transcriptColumn = 1;
|
|
18290
|
+
this.transcriptRow += 1;
|
|
18291
|
+
} else if (cell.text === "\b") this.transcriptColumn -= 1;
|
|
18292
|
+
else if (cell.text === " ") this.transcriptColumn += 8 - (this.transcriptColumn - 1) % 8;
|
|
18293
|
+
else {
|
|
18294
|
+
if (this.wrapPending || this.transcriptColumn + cell.width - 1 > this.columns) {
|
|
18295
|
+
this.transcriptColumn = 1;
|
|
18296
|
+
this.transcriptRow += 1;
|
|
18297
|
+
this.wrapPending = false;
|
|
18298
|
+
}
|
|
18299
|
+
this.transcriptColumn += cell.width;
|
|
18300
|
+
if (this.transcriptColumn > this.columns) {
|
|
18301
|
+
this.transcriptColumn = this.columns;
|
|
18302
|
+
this.wrapPending = true;
|
|
18303
|
+
}
|
|
18304
|
+
}
|
|
18305
|
+
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow);
|
|
18306
|
+
}
|
|
18307
|
+
}
|
|
18308
|
+
this.transcriptRow = Math.max(1, Math.min(this.contentRows, this.transcriptRow));
|
|
18309
|
+
this.transcriptColumn = Math.max(1, Math.min(this.columns, this.transcriptColumn));
|
|
18310
|
+
}
|
|
18311
|
+
}
|
|
18312
|
+
composer() {
|
|
18313
|
+
const available = this.columns - 4;
|
|
18314
|
+
const prompt = fit(this.rl?.getPrompt() ?? this.prompt, Math.min(24, Math.max(2, Math.floor(available / 3))));
|
|
18315
|
+
const raw = this.rl?.line ?? "";
|
|
18316
|
+
const formatted = this.inputFormatter?.(raw, this.rl?.cursor ?? 0) ?? { text: raw, cursor: this.rl?.cursor ?? 0 };
|
|
18317
|
+
const input = plain(formatted.text);
|
|
18318
|
+
const cursor = width(plain(formatted.text.slice(0, formatted.cursor)));
|
|
18319
|
+
const capacity = Math.max(1, available - width(prompt));
|
|
18320
|
+
const offset = Math.max(0, cursor - capacity + 1);
|
|
18321
|
+
let visible = "";
|
|
18322
|
+
let used = 0;
|
|
18323
|
+
let skipped = 0;
|
|
18324
|
+
for (const cell of cells(input)) {
|
|
18325
|
+
if (skipped + cell.width <= offset) {
|
|
18326
|
+
skipped += cell.width;
|
|
18327
|
+
continue;
|
|
18328
|
+
}
|
|
18329
|
+
if (used + cell.width > capacity) break;
|
|
18330
|
+
visible += cell.text;
|
|
18331
|
+
used += cell.width;
|
|
18332
|
+
}
|
|
18333
|
+
return { text: `${prompt}${visible}`, column: Math.min(this.columns - 1, 3 + width(prompt) + cursor - skipped) };
|
|
18334
|
+
}
|
|
18335
|
+
placeInputCursor() {
|
|
18336
|
+
const composer = this.composer();
|
|
18337
|
+
this.rawWrite(`${cup(this.inputRow, composer.column)}${CSI}?25h`);
|
|
18338
|
+
}
|
|
18339
|
+
clearPreviousFooter() {
|
|
18340
|
+
if (!this.lastFooterTop) return;
|
|
18341
|
+
let clear = "";
|
|
18342
|
+
for (let row = this.lastFooterTop; row <= this.rows; row += 1) clear += `${cup(row)}${CSI}2K`;
|
|
18343
|
+
this.rawWrite(clear);
|
|
18344
|
+
}
|
|
18345
|
+
paintFooter() {
|
|
18346
|
+
if (!this.enabled || !this.installed || this.closed || this.suspended) return;
|
|
18347
|
+
this.lastFooterTop = this.contentRows + 1;
|
|
18348
|
+
const inner = this.columns - 2;
|
|
18349
|
+
const border = "\u2500".repeat(inner);
|
|
18350
|
+
const composer = this.composer();
|
|
18351
|
+
const model = /^cosmic[- ]1$/i.test(this.details.model) ? "Cosmic 1" : this.details.model;
|
|
18352
|
+
const metadata = fit(model, this.columns);
|
|
18353
|
+
let frame = "";
|
|
18354
|
+
const activity = this.activityLines;
|
|
18355
|
+
if (activity.length && !this.suspended) frame += `${CSI}?1006h${CSI}?1000h`;
|
|
18356
|
+
activity.forEach((row, index) => {
|
|
18357
|
+
const text = fit(row.text, this.columns);
|
|
18358
|
+
const color = process.env.NO_COLOR || row.ok === void 0 ? "" : `${CSI}${row.ok ? 32 : 31}m`;
|
|
18359
|
+
const dot = text.indexOf("\u25CF");
|
|
18360
|
+
const rendered = dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18361
|
+
frame += `${cup(this.contentRows + 1 + index)}${CSI}0m${CSI}2K${rendered}`;
|
|
18362
|
+
});
|
|
18363
|
+
const suggestions = this.suggestionRows;
|
|
18364
|
+
const first = Math.max(0, Math.min(this.selected - Math.floor(suggestions / 2), this.suggestions.length - suggestions));
|
|
18365
|
+
const commandWidth = Math.min(
|
|
18366
|
+
Math.max(18, ...this.suggestions.map((row) => width(plain(row).split(/ {2,}/)[0] ?? ""))),
|
|
18367
|
+
Math.max(8, Math.floor(this.columns / 3))
|
|
18368
|
+
);
|
|
18369
|
+
for (let i = 0; i < suggestions; i += 1) {
|
|
18370
|
+
const index = first + i;
|
|
18371
|
+
const selected = index === this.selected;
|
|
18372
|
+
const entry = plain(this.suggestions[index] ?? "");
|
|
18373
|
+
const match = /^(.+?) {2,}(.+)$/.exec(entry);
|
|
18374
|
+
const command = fit(match?.[1] ?? entry, commandWidth);
|
|
18375
|
+
const description = match?.[2] ?? "";
|
|
18376
|
+
const row = fit(`${selected ? "\u203A" : " "} ${command}${description ? `${" ".repeat(Math.max(0, commandWidth - width(command)) + 6)}${description}` : ""}`, this.columns);
|
|
18377
|
+
const style = process.env.NO_COLOR ? "" : selected ? `${CSI}1m${CSI}39m${CSI}49m` : `${CSI}22m${CSI}39m${CSI}49m`;
|
|
18378
|
+
frame += `${cup(this.contentRows + activity.length + 1 + i)}${CSI}0m${CSI}2K${style}${row}${" ".repeat(this.columns - width(row))}${CSI}0m`;
|
|
18379
|
+
}
|
|
18380
|
+
const inputText = fit(composer.text, this.columns - 4);
|
|
18381
|
+
const paddedInput = inputText + " ".repeat(Math.max(0, this.columns - 4 - width(inputText)));
|
|
18382
|
+
const dimBorder = (value) => process.env.NO_COLOR ? value : `${CSI}90m${value}${CSI}39m`;
|
|
18383
|
+
const lines = [dimBorder(`\u250C${border}\u2510`), `${dimBorder("\u2502")} ${paddedInput} ${dimBorder("\u2502")}`, dimBorder(`\u2514${border}\u2518`), metadata];
|
|
18384
|
+
lines.forEach((line, index) => {
|
|
18385
|
+
frame += `${cup(this.rows - 3 + index)}${CSI}2K${line}`;
|
|
18386
|
+
});
|
|
18387
|
+
this.rawWrite(`${frame}${cup(this.inputRow, composer.column)}${CSI}?25h`);
|
|
18388
|
+
}
|
|
18389
|
+
close() {
|
|
18390
|
+
if (this.closed) return;
|
|
18391
|
+
this.closed = true;
|
|
18392
|
+
this.restoreActivityInput?.();
|
|
18393
|
+
if (!this.installed) return;
|
|
18394
|
+
this.output.write = this.originalWrite;
|
|
18395
|
+
if (this.errorOutput && this.errorOutput !== this.output && this.originalErrorWrite) this.errorOutput.write = this.originalErrorWrite;
|
|
18396
|
+
this.output.off("resize", this.resize);
|
|
18397
|
+
this.output.off("error", this.exit);
|
|
18398
|
+
if (this.errorOutput !== this.output) this.errorOutput?.off("error", this.exit);
|
|
18399
|
+
process.off("exit", this.exit);
|
|
18400
|
+
process.off("SIGINT", this.exit);
|
|
18401
|
+
process.off("SIGTERM", this.exit);
|
|
18402
|
+
try {
|
|
18403
|
+
this.rawWrite(`${RESET_REGION}${MOUSE_OFF2}${CSI}?25h`);
|
|
18404
|
+
this.clearPreviousFooter();
|
|
18405
|
+
this.rawWrite(`${cup(this.transcriptRow, this.transcriptColumn)}\r
|
|
18406
|
+
`);
|
|
18407
|
+
} catch {
|
|
18408
|
+
}
|
|
18409
|
+
}
|
|
18410
|
+
};
|
|
18411
|
+
function createTerminalLayout(options) {
|
|
18412
|
+
return new TerminalLayout(options).install();
|
|
18413
|
+
}
|
|
18414
|
+
|
|
18415
|
+
// src/ui/welcome.ts
|
|
18416
|
+
import chalk10, { Chalk } from "chalk";
|
|
18417
|
+
import { homedir as homedir3 } from "os";
|
|
18418
|
+
import { sep } from "path";
|
|
18419
|
+
|
|
18420
|
+
// src/ui/provider-brand.ts
|
|
18421
|
+
import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
|
|
18422
|
+
var GOLD = "\x1B[38;2;202;168;103m";
|
|
18423
|
+
var DEFAULT_FOREGROUND = "\x1B[39m";
|
|
18424
|
+
function safeDisplayText(value) {
|
|
18425
|
+
return stripVTControlCharacters3(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
17208
18426
|
}
|
|
17209
|
-
function
|
|
17210
|
-
|
|
18427
|
+
function providerDisplayName(provider) {
|
|
18428
|
+
const name = safeDisplayText(provider);
|
|
18429
|
+
return name.toLowerCase() === "ultrac" ? "spinalfluid" : name;
|
|
17211
18430
|
}
|
|
17212
|
-
function
|
|
17213
|
-
|
|
17214
|
-
return
|
|
18431
|
+
function paintProviderSpan(line, offset, provider, color) {
|
|
18432
|
+
if (!color || provider !== "spinalfluid" || offset < 0 || line.slice(offset, offset + provider.length) !== provider) return line;
|
|
18433
|
+
return `${line.slice(0, offset)}${GOLD}${provider}${DEFAULT_FOREGROUND}${line.slice(offset + provider.length)}`;
|
|
17215
18434
|
}
|
|
17216
18435
|
|
|
17217
18436
|
// src/ui/welcome.ts
|
|
17218
|
-
import chalk10, { Chalk } from "chalk";
|
|
17219
18437
|
var LOGO = [
|
|
17220
18438
|
"\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
17221
18439
|
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
@@ -17227,29 +18445,25 @@ var LOGO = [
|
|
|
17227
18445
|
function renderWelcome(info, options = {}) {
|
|
17228
18446
|
const columns = normalizeColumns(options.columns);
|
|
17229
18447
|
const paint = new Chalk({ level: options.color === false ? 0 : chalk10.level });
|
|
17230
|
-
const
|
|
17231
|
-
const innerWidth = cardWidth - 4;
|
|
18448
|
+
const innerWidth = columns - 2;
|
|
17232
18449
|
const wide = columns >= 64;
|
|
17233
18450
|
const lines = [];
|
|
17234
18451
|
if (wide) {
|
|
17235
18452
|
lines.push("");
|
|
17236
18453
|
for (let index = 0; index < LOGO.length; index += 1) {
|
|
17237
|
-
|
|
17238
|
-
lines.push(` ${color(LOGO[index])}`);
|
|
18454
|
+
lines.push(` ${paint.bold.white(LOGO[index])}`);
|
|
17239
18455
|
}
|
|
17240
18456
|
} else {
|
|
17241
|
-
lines.push("", ` ${paint.bold.
|
|
17242
|
-
}
|
|
17243
|
-
const
|
|
17244
|
-
const
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
lines.push(
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
lines.push(
|
|
17251
|
-
lines.push(cardLine(`logs ${shortenMiddle(info.diagnosticPath, Math.max(8, innerWidth - 5))}`, innerWidth, paint.gray));
|
|
17252
|
-
lines.push(paint.cyan(`\u2570${"\u2500".repeat(cardWidth - 2)}\u256F`));
|
|
18457
|
+
lines.push("", ` ${paint.bold.white("ULTRAC")} ${paint.gray(`v${info.version}`)}`);
|
|
18458
|
+
}
|
|
18459
|
+
const provider = providerDisplayName(info.providerName);
|
|
18460
|
+
const prefix = `${safeDisplayText(info.model)} \xB7 `;
|
|
18461
|
+
const account = `${provider}${info.planName ? ` ${safeDisplayText(info.planName)}` : ""}`;
|
|
18462
|
+
const modelLine = ` ${truncate3(`${prefix}${account}`, innerWidth)}`;
|
|
18463
|
+
lines.push("", paintProviderSpan(modelLine, 2 + prefix.length, provider, paint.level > 0));
|
|
18464
|
+
const home = homedir3();
|
|
18465
|
+
const workspace = info.workspace === home ? "~" : info.workspace.startsWith(`${home}${sep}`) ? `~${info.workspace.slice(home.length)}` : info.workspace;
|
|
18466
|
+
lines.push(paint.gray(` ${shortenMiddle(safeDisplayText(workspace), innerWidth)}`), "");
|
|
17253
18467
|
lines.push(
|
|
17254
18468
|
wide ? `${paint.gray(" /help commands")} ${paint.dim("\xB7")} ${paint.gray("Esc stop")} ${paint.dim("\xB7")} ${paint.gray("Ctrl-C exit")}` : paint.gray(" /help \xB7 Esc \xB7 Ctrl-C")
|
|
17255
18469
|
);
|
|
@@ -17257,40 +18471,43 @@ function renderWelcome(info, options = {}) {
|
|
|
17257
18471
|
}
|
|
17258
18472
|
function renderComposer(columns = 80, color = true) {
|
|
17259
18473
|
const paint = new Chalk({ level: color ? chalk10.level : 0 });
|
|
17260
|
-
const
|
|
18474
|
+
const width2 = normalizeColumns(columns);
|
|
17261
18475
|
const label = " message ";
|
|
17262
|
-
const fill = Math.max(1,
|
|
18476
|
+
const fill = Math.max(1, width2 - label.length - 2);
|
|
17263
18477
|
return {
|
|
17264
18478
|
frame: `
|
|
17265
|
-
${paint.
|
|
17266
|
-
${paint.
|
|
17267
|
-
prompt: paint.bold.
|
|
18479
|
+
${paint.gray(`\u256D\u2500${label}${"\u2500".repeat(fill)}`)}
|
|
18480
|
+
${paint.gray("\u2502")}`,
|
|
18481
|
+
prompt: paint.bold.white("\u2570\u2500\u276F ")
|
|
17268
18482
|
};
|
|
17269
18483
|
}
|
|
17270
|
-
function cardLine(value, width, style) {
|
|
17271
|
-
const clipped = truncate3(value, width);
|
|
17272
|
-
return `\u2502 ${style(clipped)}${" ".repeat(Math.max(0, width - clipped.length))} \u2502`;
|
|
17273
|
-
}
|
|
17274
18484
|
function normalizeColumns(columns) {
|
|
17275
|
-
return Number.isFinite(columns) ? Math.max(
|
|
18485
|
+
return Number.isFinite(columns) ? Math.max(16, Math.floor(columns)) : 80;
|
|
17276
18486
|
}
|
|
17277
|
-
function truncate3(value,
|
|
17278
|
-
if (value.length <=
|
|
17279
|
-
if (
|
|
17280
|
-
return `${value.slice(0,
|
|
18487
|
+
function truncate3(value, width2) {
|
|
18488
|
+
if (value.length <= width2) return value;
|
|
18489
|
+
if (width2 <= 1) return "\u2026";
|
|
18490
|
+
return `${value.slice(0, width2 - 1)}\u2026`;
|
|
17281
18491
|
}
|
|
17282
|
-
function shortenMiddle(value,
|
|
17283
|
-
if (value.length <=
|
|
17284
|
-
if (
|
|
17285
|
-
const side = Math.floor((
|
|
17286
|
-
return `${value.slice(0, side)}\u2026${value.slice(value.length - (
|
|
18492
|
+
function shortenMiddle(value, width2) {
|
|
18493
|
+
if (value.length <= width2) return value;
|
|
18494
|
+
if (width2 < 9) return truncate3(value, width2);
|
|
18495
|
+
const side = Math.floor((width2 - 1) / 2);
|
|
18496
|
+
return `${value.slice(0, side)}\u2026${value.slice(value.length - (width2 - side - 1))}`;
|
|
17287
18497
|
}
|
|
17288
18498
|
|
|
18499
|
+
// src/ui/update-prompt.ts
|
|
18500
|
+
import { constants as constants7 } from "fs";
|
|
18501
|
+
import { lstat as lstat4, mkdir as mkdir15, open as open10, rename as rename4, unlink as unlink5 } from "fs/promises";
|
|
18502
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
18503
|
+
import { homedir as homedir4 } from "os";
|
|
18504
|
+
import { join as join15, parse, resolve as resolve13 } from "path";
|
|
18505
|
+
|
|
17289
18506
|
// src/update/check.ts
|
|
17290
18507
|
import { spawn as spawn4 } from "child_process";
|
|
17291
18508
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
17292
18509
|
import { readFile as readFile13, rename as rename3, unlink as unlink4, writeFile as writeFile12 } from "fs/promises";
|
|
17293
|
-
import { join as join14 } from "path";
|
|
18510
|
+
import { isAbsolute as isAbsolute5, join as join14, relative as relative2, resolve as resolve12 } from "path";
|
|
17294
18511
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
17295
18512
|
var QUERY_TIMEOUT_MS = 5e3;
|
|
17296
18513
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -17323,17 +18540,67 @@ async function queryLatestVersion(packageName) {
|
|
|
17323
18540
|
return void 0;
|
|
17324
18541
|
}
|
|
17325
18542
|
}
|
|
17326
|
-
async function installLatestVersion(packageName) {
|
|
18543
|
+
async function installLatestVersion(packageName, version = "latest") {
|
|
18544
|
+
if (version !== "latest" && !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
18545
|
+
throw new Error("Invalid npm release version.");
|
|
18546
|
+
}
|
|
17327
18547
|
const result = await runNpm(
|
|
17328
|
-
["install", "--global", `${packageName}
|
|
18548
|
+
["install", "--global", `${packageName}@${version}`, "--no-audit", "--no-fund"],
|
|
17329
18549
|
{ timeoutMs: INSTALL_TIMEOUT_MS, capture: false }
|
|
17330
18550
|
);
|
|
17331
18551
|
if (result.code !== 0) {
|
|
17332
18552
|
throw new Error(
|
|
17333
|
-
`npm could not install ${packageName}
|
|
18553
|
+
`npm could not install ${packageName}@${version}. Confirm npm can reach the registry and install global packages.`
|
|
17334
18554
|
);
|
|
17335
18555
|
}
|
|
17336
18556
|
}
|
|
18557
|
+
async function installVerifiedVersion(packageName, version) {
|
|
18558
|
+
await installLatestVersion(packageName, version);
|
|
18559
|
+
const result = await runNpm(["list", "--global", packageName, "--depth=0", "--json"], {
|
|
18560
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18561
|
+
capture: true,
|
|
18562
|
+
maxOutputBytes: 4096
|
|
18563
|
+
});
|
|
18564
|
+
let installedVersion;
|
|
18565
|
+
try {
|
|
18566
|
+
const parsed = JSON.parse(result.stdout);
|
|
18567
|
+
if (typeof parsed === "object" && parsed !== null && "dependencies" in parsed && typeof parsed.dependencies === "object" && parsed.dependencies !== null) {
|
|
18568
|
+
const dependency = parsed.dependencies[packageName];
|
|
18569
|
+
if (typeof dependency === "object" && dependency !== null && "version" in dependency) {
|
|
18570
|
+
installedVersion = dependency.version;
|
|
18571
|
+
}
|
|
18572
|
+
}
|
|
18573
|
+
} catch {
|
|
18574
|
+
}
|
|
18575
|
+
if (result.code !== 0 || installedVersion !== version) {
|
|
18576
|
+
throw new Error("The update could not be verified. Run ultrac update and restart Ultrac.");
|
|
18577
|
+
}
|
|
18578
|
+
try {
|
|
18579
|
+
const globalRoot = await runNpm(["root", "--global"], {
|
|
18580
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18581
|
+
capture: true,
|
|
18582
|
+
maxOutputBytes: 4096
|
|
18583
|
+
});
|
|
18584
|
+
const root = globalRoot.stdout.trim();
|
|
18585
|
+
if (globalRoot.code !== 0 || !isAbsolute5(root) || root.includes("\n")) throw new Error("Invalid npm root");
|
|
18586
|
+
const packageRoot = join14(root, packageName);
|
|
18587
|
+
const manifest = JSON.parse(await readFile13(join14(packageRoot, "package.json"), "utf8"));
|
|
18588
|
+
if (typeof manifest !== "object" || manifest === null || !("version" in manifest) || manifest.version !== version || !("bin" in manifest)) throw new Error("Invalid package");
|
|
18589
|
+
const bin = manifest.bin;
|
|
18590
|
+
const entry = typeof bin === "string" ? bin : typeof bin === "object" && bin !== null && "ultrac" in bin ? bin.ultrac : void 0;
|
|
18591
|
+
if (typeof entry !== "string") throw new Error("Missing executable");
|
|
18592
|
+
const executable = resolve12(packageRoot, entry);
|
|
18593
|
+
const withinPackage = relative2(packageRoot, executable);
|
|
18594
|
+
if (withinPackage.startsWith("..") || isAbsolute5(withinPackage)) throw new Error("Invalid executable");
|
|
18595
|
+
const probe = await runExecutable(process.execPath, [executable, "--version"], {
|
|
18596
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18597
|
+
capture: true
|
|
18598
|
+
});
|
|
18599
|
+
if (probe.code !== 0 || probe.stdout.trim() !== version) throw new Error("Invalid executable version");
|
|
18600
|
+
} catch {
|
|
18601
|
+
throw new Error("The installed Ultrac executable could not be verified. Restart and run ultrac --version.");
|
|
18602
|
+
}
|
|
18603
|
+
}
|
|
17337
18604
|
function isNewerVersion(candidate, current) {
|
|
17338
18605
|
const left = parseVersion(candidate);
|
|
17339
18606
|
const right = parseVersion(current);
|
|
@@ -17386,12 +18653,15 @@ async function writeCache(path, cache) {
|
|
|
17386
18653
|
}
|
|
17387
18654
|
}
|
|
17388
18655
|
async function runNpm(args, options) {
|
|
18656
|
+
return runExecutable(process.platform === "win32" ? "npm.cmd" : "npm", args, options);
|
|
18657
|
+
}
|
|
18658
|
+
async function runExecutable(executable, args, options) {
|
|
17389
18659
|
const env2 = { ...process.env };
|
|
17390
18660
|
for (const key of Object.keys(env2)) {
|
|
17391
18661
|
if (key.startsWith("ULTRAC_")) delete env2[key];
|
|
17392
18662
|
}
|
|
17393
|
-
return new Promise((
|
|
17394
|
-
const child = spawn4(
|
|
18663
|
+
return new Promise((resolve17) => {
|
|
18664
|
+
const child = spawn4(executable, args, {
|
|
17395
18665
|
env: env2,
|
|
17396
18666
|
stdio: options.capture ? ["ignore", "pipe", "ignore"] : "inherit",
|
|
17397
18667
|
windowsHide: true
|
|
@@ -17400,24 +18670,97 @@ async function runNpm(args, options) {
|
|
|
17400
18670
|
if (options.capture) {
|
|
17401
18671
|
child.stdout?.setEncoding("utf8");
|
|
17402
18672
|
child.stdout?.on("data", (chunk) => {
|
|
17403
|
-
|
|
18673
|
+
const limit = options.maxOutputBytes ?? 256;
|
|
18674
|
+
if (stdout6.length <= limit) stdout6 += chunk.slice(0, limit + 1 - stdout6.length);
|
|
17404
18675
|
});
|
|
17405
18676
|
}
|
|
17406
18677
|
const timer = setTimeout(() => child.kill(), options.timeoutMs);
|
|
17407
18678
|
child.once("error", () => {
|
|
17408
18679
|
clearTimeout(timer);
|
|
17409
|
-
|
|
18680
|
+
resolve17({ code: 1, stdout: "" });
|
|
17410
18681
|
});
|
|
17411
18682
|
child.once("close", (code) => {
|
|
17412
18683
|
clearTimeout(timer);
|
|
17413
|
-
|
|
18684
|
+
resolve17({ code: code ?? 1, stdout: stdout6 });
|
|
17414
18685
|
});
|
|
17415
18686
|
});
|
|
17416
18687
|
}
|
|
17417
18688
|
|
|
18689
|
+
// src/ui/update-prompt.ts
|
|
18690
|
+
var UPDATE_CHOICES = ["Update now", "Skip", "Skip until next version"];
|
|
18691
|
+
async function offerUpdatePrompt(status, options) {
|
|
18692
|
+
const version = status.latestVersion;
|
|
18693
|
+
if (!options.interactive || options.signal?.aborted || !status.updateAvailable || !version || !validVersion(version) || !validVersion(status.currentVersion) || !isNewerVersion(version, status.currentVersion) || await skippedVersion() === version) return "not_offered";
|
|
18694
|
+
const answer = await options.ask(
|
|
18695
|
+
`Ultrac ${version} is available. You are running ${status.currentVersion}.`,
|
|
18696
|
+
UPDATE_CHOICES,
|
|
18697
|
+
options.signal
|
|
18698
|
+
);
|
|
18699
|
+
if (options.signal?.aborted) return "skip";
|
|
18700
|
+
if (answer === UPDATE_CHOICES[0]) return "update_now";
|
|
18701
|
+
if (answer === UPDATE_CHOICES[2]) {
|
|
18702
|
+
await rememberSkippedVersion(version);
|
|
18703
|
+
return "skip_version";
|
|
18704
|
+
}
|
|
18705
|
+
return "skip";
|
|
18706
|
+
}
|
|
18707
|
+
function validVersion(version) {
|
|
18708
|
+
return version.length <= 128 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
18709
|
+
}
|
|
18710
|
+
async function statePath(create) {
|
|
18711
|
+
const home = resolve13(ultracHomeDir());
|
|
18712
|
+
if (home === parse(home).root || home === resolve13(homedir4())) {
|
|
18713
|
+
throw new Error("Ultrac update preferences require a dedicated ULTRAC_HOME directory.");
|
|
18714
|
+
}
|
|
18715
|
+
if (create) await mkdir15(home, { recursive: true, mode: 448 });
|
|
18716
|
+
const info = await lstat4(home);
|
|
18717
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
18718
|
+
throw new Error("Ultrac update preferences require a real home directory.");
|
|
18719
|
+
}
|
|
18720
|
+
return join15(home, "update-preferences.json");
|
|
18721
|
+
}
|
|
18722
|
+
async function skippedVersion() {
|
|
18723
|
+
try {
|
|
18724
|
+
const path = await statePath(false);
|
|
18725
|
+
const file = await open10(path, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
18726
|
+
try {
|
|
18727
|
+
const info = await file.stat();
|
|
18728
|
+
if (!info.isFile() || info.size > 1024) return void 0;
|
|
18729
|
+
const parsed = JSON.parse(await file.readFile("utf8"));
|
|
18730
|
+
if (typeof parsed !== "object" || parsed === null || !("skippedVersion" in parsed)) return void 0;
|
|
18731
|
+
return typeof parsed.skippedVersion === "string" && validVersion(parsed.skippedVersion) ? parsed.skippedVersion : void 0;
|
|
18732
|
+
} finally {
|
|
18733
|
+
await file.close();
|
|
18734
|
+
}
|
|
18735
|
+
} catch {
|
|
18736
|
+
return void 0;
|
|
18737
|
+
}
|
|
18738
|
+
}
|
|
18739
|
+
async function rememberSkippedVersion(version) {
|
|
18740
|
+
const path = await statePath(true);
|
|
18741
|
+
const temporary = `${path}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
18742
|
+
try {
|
|
18743
|
+
const file = await open10(temporary, constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL, 384);
|
|
18744
|
+
try {
|
|
18745
|
+
await file.writeFile(`${JSON.stringify({ skippedVersion: version })}
|
|
18746
|
+
`, "utf8");
|
|
18747
|
+
await file.sync();
|
|
18748
|
+
} finally {
|
|
18749
|
+
await file.close();
|
|
18750
|
+
}
|
|
18751
|
+
await rename4(temporary, path);
|
|
18752
|
+
} finally {
|
|
18753
|
+
await unlink5(temporary).catch(() => void 0);
|
|
18754
|
+
}
|
|
18755
|
+
}
|
|
18756
|
+
|
|
17418
18757
|
// src/commands/repl.ts
|
|
17419
18758
|
var HELP = `Commands:
|
|
17420
18759
|
/help Show this help
|
|
18760
|
+
/doctor Check local setup
|
|
18761
|
+
/mcp Show connected MCP servers
|
|
18762
|
+
/continue [focus] Continue unfinished work
|
|
18763
|
+
/autonomous [on|off|status] Continue step-limited turns (up to five rounds)
|
|
17421
18764
|
/model [name] Show or set the model
|
|
17422
18765
|
/mode [name] Show or set the agent mode (${agentModeNames.join(", ")})
|
|
17423
18766
|
/permissions [readonly|workspace|full] Show or set permissions
|
|
@@ -17441,9 +18784,10 @@ var HELP = `Commands:
|
|
|
17441
18784
|
/whoami Show sign-in state
|
|
17442
18785
|
/tools List available tools
|
|
17443
18786
|
/logs Show the private diagnostic log path
|
|
17444
|
-
/ghost [on|off|status]
|
|
18787
|
+
/ghost [on|off|status] Quiet activity output; diagnostics stay enabled
|
|
17445
18788
|
/output [number|list] Inspect saved tool output (latest by default)
|
|
17446
18789
|
/memory List and inspect stored memories
|
|
18790
|
+
/skill <name> [args] Run an installed skill
|
|
17447
18791
|
/skills List installed skills
|
|
17448
18792
|
/update Show how to install an available CLI update
|
|
17449
18793
|
/clear Start a fresh conversation
|
|
@@ -17543,7 +18887,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
17543
18887
|
const hasFallbackChain = Boolean(config.fallback?.chain?.length);
|
|
17544
18888
|
if (providerName === "ultrac" && !hasFallbackChain && !await resolveUltracCredentials()) {
|
|
17545
18889
|
console.log(chalk11.yellow("You are not signed in to Ultrac."));
|
|
17546
|
-
console.log(`Run ${chalk11.
|
|
18890
|
+
console.log(`Run ${chalk11.white("ultrac login")} first, or start with ${chalk11.white("--provider echo")}.`);
|
|
17547
18891
|
process.exitCode = EXIT_REPL_NOT_STARTED;
|
|
17548
18892
|
return;
|
|
17549
18893
|
}
|
|
@@ -17605,10 +18949,38 @@ async function runRepl(options, initialTask = "") {
|
|
|
17605
18949
|
mode,
|
|
17606
18950
|
permissions: basePermissions
|
|
17607
18951
|
});
|
|
18952
|
+
const layout = stdin5.isTTY && stdout5.isTTY && process.env.TERM !== "dumb" ? createTerminalLayout({
|
|
18953
|
+
output: stdout5,
|
|
18954
|
+
errorOutput: stderr,
|
|
18955
|
+
bindSignals: false,
|
|
18956
|
+
details: { provider: provider.name, model: session.model, mode: session.mode, status: "Idle" }
|
|
18957
|
+
}) : void 0;
|
|
18958
|
+
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
18959
|
+
const refreshDetails = () => {
|
|
18960
|
+
if (!layout?.enabled) return;
|
|
18961
|
+
const steps = session.toSession().steps;
|
|
18962
|
+
const sum = (key) => {
|
|
18963
|
+
const values = steps.flatMap((step) => typeof step.usage?.[key] === "number" ? [step.usage[key]] : []);
|
|
18964
|
+
return values.length ? values.reduce((a, b) => a + b, 0) : void 0;
|
|
18965
|
+
};
|
|
18966
|
+
const context = session.contextStatus();
|
|
18967
|
+
layout.updateDetails({
|
|
18968
|
+
contextUsed: context.estimatedPromptTokens,
|
|
18969
|
+
contextMax: context.contextTokens,
|
|
18970
|
+
provider: provider.name,
|
|
18971
|
+
model: session.model,
|
|
18972
|
+
mode: session.mode,
|
|
18973
|
+
tokens: sum("totalTokens"),
|
|
18974
|
+
inputTokens: sum("inputTokens"),
|
|
18975
|
+
outputTokens: sum("outputTokens"),
|
|
18976
|
+
steps: steps.length
|
|
18977
|
+
});
|
|
18978
|
+
};
|
|
17608
18979
|
const writeCheckpoint = createSessionCheckpointWriter(workspace, (message) => {
|
|
17609
18980
|
console.error(chalk11.yellow(message));
|
|
17610
18981
|
});
|
|
17611
18982
|
const persistSession = async (target = session) => {
|
|
18983
|
+
refreshDetails();
|
|
17612
18984
|
const snapshot = target.toSession();
|
|
17613
18985
|
if (!hasSavedSessionContent(snapshot)) return null;
|
|
17614
18986
|
return writeCheckpoint(snapshot);
|
|
@@ -17621,7 +18993,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
17621
18993
|
workspace,
|
|
17622
18994
|
useNativeTools ? "native tools" : "json protocol",
|
|
17623
18995
|
describeBudget(mode, toolSelection, registry),
|
|
17624
|
-
diagnosticPath2
|
|
18996
|
+
diagnosticPath2,
|
|
18997
|
+
provider.name.toLowerCase() === "ultrac" ? await readAccountPlan(await resolveUltracCredentials()) : void 0
|
|
17625
18998
|
);
|
|
17626
18999
|
if (mcpRuntime && mcpRuntime.statuses.length > 0) {
|
|
17627
19000
|
console.log(chalk11.bold("\nMCP servers"));
|
|
@@ -17630,29 +19003,66 @@ async function runRepl(options, initialTask = "") {
|
|
|
17630
19003
|
try {
|
|
17631
19004
|
await session.start();
|
|
17632
19005
|
} catch (error) {
|
|
19006
|
+
layout?.close();
|
|
17633
19007
|
throw new Error(`Could not start the session: ${errorMessage2(error)}`, { cause: error });
|
|
17634
19008
|
}
|
|
17635
19009
|
const update = await updateCheck;
|
|
17636
|
-
if (update?.updateAvailable && update.latestVersion) {
|
|
17637
|
-
console.log(chalk11.yellow(`
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
19010
|
+
if (!stdin5.isTTY && update?.updateAvailable && update.latestVersion) {
|
|
19011
|
+
console.log(chalk11.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}. Run ultrac update.`));
|
|
19012
|
+
}
|
|
19013
|
+
const rl = createInterface3({ input: stdin5, output: layout?.enabled ? layout.outputProxy : stdout5 });
|
|
19014
|
+
layout?.attachReadline(rl);
|
|
19015
|
+
layout?.setInputFormatter(formatImageInput);
|
|
19016
|
+
refreshDetails();
|
|
19017
|
+
const builtinEntries = builtinSlashEntries(BUILTIN_SLASH_COMMANDS, HELP);
|
|
19018
|
+
const slashCompletion = layout?.enabled ? attachSlashCompletion(rl, async () => {
|
|
19019
|
+
const [commands, skills] = await Promise.all([listSkillCommands(workspace), listSkills(workspace)]);
|
|
19020
|
+
const declared = new Set(commands.commands.map((command) => command.skillName));
|
|
19021
|
+
return [
|
|
19022
|
+
...builtinEntries,
|
|
19023
|
+
...commands.commands.map((command) => ({ text: `/${command.command}`, label: `/${command.command}`, description: command.description, skill: true })),
|
|
19024
|
+
...skills.filter((skill) => !declared.has(skill.name)).map((skill) => ({ text: `/skill ${skill.name}`, label: `/skill ${skill.name}`, description: skill.description, skill: true }))
|
|
19025
|
+
];
|
|
19026
|
+
}, (rows, index) => layout.setSuggestions(rows, index), builtinEntries) : void 0;
|
|
17642
19027
|
const reader = new LineReader(rl);
|
|
17643
19028
|
const question = async (prompt, signal) => {
|
|
17644
|
-
|
|
17645
|
-
|
|
19029
|
+
slashCompletion?.suspend();
|
|
19030
|
+
try {
|
|
19031
|
+
if (layout?.enabled) {
|
|
19032
|
+
console.log(prompt);
|
|
19033
|
+
layout.setPrompt("\u203A ");
|
|
19034
|
+
} else stdout5.write(prompt);
|
|
19035
|
+
return await reader.nextFresh(signal);
|
|
19036
|
+
} finally {
|
|
19037
|
+
layout?.setPrompt("\u203A ");
|
|
19038
|
+
slashCompletion?.resume();
|
|
19039
|
+
}
|
|
17646
19040
|
};
|
|
17647
19041
|
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
19042
|
+
let autonomousRounds = 1;
|
|
17648
19043
|
setAskUserRuntime(async (text, options2, signal) => {
|
|
17649
|
-
console.log(chalk11.
|
|
19044
|
+
console.log(chalk11.white(`
|
|
17650
19045
|
? ${text}`));
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
19046
|
+
let rawAnswer;
|
|
19047
|
+
if (options2?.length && layout?.enabled) {
|
|
19048
|
+
slashCompletion?.suspend();
|
|
19049
|
+
layout.setPrompt("\u203A ");
|
|
19050
|
+
let choices;
|
|
19051
|
+
try {
|
|
19052
|
+
choices = attachInteractiveChoices(rl, options2, { signal, output: stdout5, prompt: "\u203A ", maxRow: () => layout.contentRows });
|
|
19053
|
+
rawAnswer = await reader.nextFresh(signal);
|
|
19054
|
+
} finally {
|
|
19055
|
+
choices?.cleanup();
|
|
19056
|
+
layout.setPrompt("\u203A ");
|
|
19057
|
+
slashCompletion?.resume();
|
|
19058
|
+
}
|
|
19059
|
+
} else {
|
|
19060
|
+
if (options2?.length) {
|
|
19061
|
+
options2.forEach((option, index) => console.log(chalk11.gray(` ${index + 1}. ${option}`)));
|
|
19062
|
+
console.log(chalk11.gray(" (type a number, or answer in your own words)"));
|
|
19063
|
+
}
|
|
19064
|
+
rawAnswer = await question(chalk11.white(" \u203A "), signal);
|
|
17654
19065
|
}
|
|
17655
|
-
const rawAnswer = await question(chalk11.cyan(" \u203A "), signal);
|
|
17656
19066
|
if (signal?.aborted) return null;
|
|
17657
19067
|
const answer = (rawAnswer ?? "").trim();
|
|
17658
19068
|
const picked = Number.parseInt(answer, 10);
|
|
@@ -17672,7 +19082,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
17672
19082
|
rl.on("SIGINT", exitSession);
|
|
17673
19083
|
const onKeypress = (_input, key) => {
|
|
17674
19084
|
if (!key || key.name !== "escape" || key.ctrl || key.meta) return;
|
|
17675
|
-
handleReplEscape(turnAbort);
|
|
19085
|
+
if (!slashCompletion?.handledEscape()) handleReplEscape(turnAbort);
|
|
17676
19086
|
};
|
|
17677
19087
|
if (stdin5.isTTY) {
|
|
17678
19088
|
emitKeypressEvents(stdin5, rl);
|
|
@@ -17686,31 +19096,51 @@ async function runRepl(options, initialTask = "") {
|
|
|
17686
19096
|
process.on("SIGTERM", terminate);
|
|
17687
19097
|
const withInterrupt = async (run) => {
|
|
17688
19098
|
turnAbort = new AbortController();
|
|
19099
|
+
layout?.updateDetails({ status: "Running" });
|
|
17689
19100
|
try {
|
|
17690
19101
|
return await run(turnAbort.signal);
|
|
17691
19102
|
} finally {
|
|
17692
19103
|
turnAbort = void 0;
|
|
19104
|
+
refreshDetails();
|
|
19105
|
+
layout?.updateDetails({ status: "Idle" });
|
|
17693
19106
|
}
|
|
17694
19107
|
};
|
|
17695
19108
|
try {
|
|
19109
|
+
if (update) {
|
|
19110
|
+
const decision = await withInterrupt((signal) => offerUpdatePrompt(update, {
|
|
19111
|
+
interactive: Boolean(stdin5.isTTY && stdout5.isTTY),
|
|
19112
|
+
signal,
|
|
19113
|
+
ask: (text, choices, askSignal) => getAskUserRuntime()(text, [...choices], askSignal)
|
|
19114
|
+
}));
|
|
19115
|
+
if (decision === "update_now") {
|
|
19116
|
+
layout?.close();
|
|
19117
|
+
rl.close();
|
|
19118
|
+
await installVerifiedVersion(packageInfo.name, update.latestVersion);
|
|
19119
|
+
console.log(`Updated to ${update.latestVersion}. Run ultrac again to use the new version.`);
|
|
19120
|
+
return;
|
|
19121
|
+
}
|
|
19122
|
+
}
|
|
17696
19123
|
if (initialTask) {
|
|
17697
|
-
console.log(chalk11.
|
|
17698
|
-
\u203A ${initialTask}`));
|
|
17699
|
-
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages, () => reader.composing));
|
|
19124
|
+
console.log(chalk11.white(`
|
|
19125
|
+
\u203A ${formatImageInput(initialTask).text}`));
|
|
19126
|
+
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
17700
19127
|
}
|
|
17701
19128
|
while (running) {
|
|
17702
|
-
if (
|
|
19129
|
+
if (layout?.enabled) {
|
|
19130
|
+
layout.setPrompt("\u203A ");
|
|
19131
|
+
} else if (stdout5.isTTY) {
|
|
17703
19132
|
const composer = renderComposer(stdout5.columns, true);
|
|
17704
19133
|
console.log(composer.frame);
|
|
17705
19134
|
stdout5.write(composer.prompt);
|
|
17706
19135
|
} else {
|
|
17707
|
-
stdout5.write(chalk11.
|
|
19136
|
+
stdout5.write(chalk11.white("\n\u203A "));
|
|
17708
19137
|
}
|
|
17709
19138
|
const line = await reader.next();
|
|
17710
19139
|
if (line === null) break;
|
|
19140
|
+
if (layout?.enabled && line.trim()) console.log(`\u203A ${formatImageInput(line).text.replace(/[\x00-\x1f\x7f]/g, " ")}`);
|
|
17711
19141
|
const raw = line.trim();
|
|
17712
19142
|
if (!raw) continue;
|
|
17713
|
-
if (raw.startsWith("/")) {
|
|
19143
|
+
if (raw.startsWith("/") && !hasDroppedImage(raw)) {
|
|
17714
19144
|
const previousSession = session;
|
|
17715
19145
|
const slashCommand = raw.slice(1).trim().split(/\s+/, 1)[0]?.toLowerCase();
|
|
17716
19146
|
const handled = await withInterrupt(async (signal) => {
|
|
@@ -17723,7 +19153,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
17723
19153
|
resetContextBudgetNotices();
|
|
17724
19154
|
console.log(chalk11.gray("Started a fresh conversation."));
|
|
17725
19155
|
}, async (prompt) => {
|
|
17726
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing);
|
|
19156
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
17727
19157
|
}, signal, async (id) => {
|
|
17728
19158
|
const lineage = await loadSessionLineage(workspace, id);
|
|
17729
19159
|
const restored = createSession({ model: session.model, mode: session.mode, permissions: session.currentPermissions });
|
|
@@ -17732,7 +19162,11 @@ async function runRepl(options, initialTask = "") {
|
|
|
17732
19162
|
session = restored;
|
|
17733
19163
|
resetContextBudgetNotices();
|
|
17734
19164
|
console.log(`Restored ${lineage.sessions.length} saved session(s). Permissions were kept from the current session.`);
|
|
17735
|
-
})
|
|
19165
|
+
}, { mcpStatus: () => mcpRuntime?.statuses ?? [], autonomous: (setting) => {
|
|
19166
|
+
if (setting === "on") autonomousRounds = 5;
|
|
19167
|
+
if (setting === "off") autonomousRounds = 1;
|
|
19168
|
+
return autonomousRounds;
|
|
19169
|
+
} });
|
|
17736
19170
|
});
|
|
17737
19171
|
if (handled === "exit") {
|
|
17738
19172
|
running = false;
|
|
@@ -17741,7 +19175,9 @@ async function runRepl(options, initialTask = "") {
|
|
|
17741
19175
|
setSubagentRuntime({ provider, model: session.model, mode: session.mode, registry });
|
|
17742
19176
|
if (handled === "unhandled") {
|
|
17743
19177
|
try {
|
|
17744
|
-
await runSkillCommand(session, raw, workspace, question)
|
|
19178
|
+
await withInterrupt((signal) => runSkillCommand(session, raw, workspace, question, async (prompt) => {
|
|
19179
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
19180
|
+
}));
|
|
17745
19181
|
} finally {
|
|
17746
19182
|
await persistSession();
|
|
17747
19183
|
}
|
|
@@ -17752,26 +19188,51 @@ async function runRepl(options, initialTask = "") {
|
|
|
17752
19188
|
}
|
|
17753
19189
|
continue;
|
|
17754
19190
|
}
|
|
17755
|
-
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages, () => reader.composing));
|
|
19191
|
+
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
17756
19192
|
}
|
|
17757
19193
|
} finally {
|
|
19194
|
+
slashCompletion?.cleanup();
|
|
17758
19195
|
process.off("SIGTERM", terminate);
|
|
17759
19196
|
if (stdin5.isTTY) stdin5.off("keypress", onKeypress);
|
|
17760
19197
|
setAskUserRuntime(void 0);
|
|
19198
|
+
setActivitySink(void 0);
|
|
17761
19199
|
rl.close();
|
|
17762
|
-
|
|
17763
|
-
|
|
19200
|
+
try {
|
|
19201
|
+
await saveTranscript(session, writeCheckpoint);
|
|
19202
|
+
} finally {
|
|
19203
|
+
mcpRuntime?.close();
|
|
19204
|
+
layout?.close();
|
|
19205
|
+
}
|
|
17764
19206
|
}
|
|
17765
19207
|
console.log(chalk11.gray("Goodbye."));
|
|
17766
19208
|
}
|
|
17767
|
-
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing) {
|
|
19209
|
+
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop = true) {
|
|
19210
|
+
const activity = isGhostEnabled(session) ? void 0 : getActivitySink();
|
|
19211
|
+
const entries = [];
|
|
19212
|
+
let thinkingMs = 0;
|
|
19213
|
+
let thinkingStarted;
|
|
19214
|
+
let turnFailed = false;
|
|
19215
|
+
const refreshActivity = (finished = false) => activity?.({ entries: [...entries], thinkingMs, finished, failed: turnFailed });
|
|
19216
|
+
refreshActivity();
|
|
17768
19217
|
const spinner = new Spinner();
|
|
19218
|
+
const workSpinner = new Spinner("working");
|
|
19219
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
19220
|
+
let permissionPrompts = 0;
|
|
19221
|
+
const renderWorking = () => {
|
|
19222
|
+
workSpinner.stop();
|
|
19223
|
+
const active = [...activeTools.entries()].at(-1);
|
|
19224
|
+
if (!active || quiet || permissionPrompts || [...activeTools.keys()].some((call) => call.tool === "ask_user")) return;
|
|
19225
|
+
workSpinner.update(`working \xB7 ${toolActivityLabel(active[0])}${activeTools.size > 1 ? ` (+${activeTools.size - 1} active)` : ""}`);
|
|
19226
|
+
workSpinner.start(isComposing, active[1]);
|
|
19227
|
+
};
|
|
17769
19228
|
const quiet = isGhostEnabled(session);
|
|
17770
19229
|
const detachProgress = quiet ? () => {
|
|
17771
19230
|
} : attachProgressUi();
|
|
17772
19231
|
let deltaChars = 0;
|
|
17773
19232
|
try {
|
|
17774
|
-
await
|
|
19233
|
+
const prepared = allowImageDrop ? await prepareImageInput(input) : { text: input };
|
|
19234
|
+
await session.ask(prepared.text, {
|
|
19235
|
+
...prepared.image ? { image: prepared.image } : {},
|
|
17775
19236
|
...signal ? { signal } : {},
|
|
17776
19237
|
...persist ? {
|
|
17777
19238
|
onCheckpoint: async () => {
|
|
@@ -17784,13 +19245,22 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
17784
19245
|
},
|
|
17785
19246
|
onModelStart: () => {
|
|
17786
19247
|
deltaChars = 0;
|
|
17787
|
-
|
|
19248
|
+
thinkingStarted = performance.now();
|
|
19249
|
+
if (!quiet) {
|
|
19250
|
+
spinner.update("thinking");
|
|
19251
|
+
spinner.start(isComposing);
|
|
19252
|
+
}
|
|
17788
19253
|
},
|
|
17789
19254
|
onModelDelta: (text) => {
|
|
17790
19255
|
deltaChars += text.length;
|
|
17791
19256
|
if (!quiet) spinner.update(`thinking (${deltaChars} chars)`);
|
|
17792
19257
|
},
|
|
17793
|
-
onModelSettled: () =>
|
|
19258
|
+
onModelSettled: () => {
|
|
19259
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19260
|
+
thinkingStarted = void 0;
|
|
19261
|
+
spinner.stop(activity ? void 0 : signal?.aborted ? "Thinking stopped after" : "Thought for");
|
|
19262
|
+
refreshActivity();
|
|
19263
|
+
},
|
|
17794
19264
|
onAssistantMessage: (message) => {
|
|
17795
19265
|
if (message.trim()) {
|
|
17796
19266
|
console.log(`
|
|
@@ -17798,9 +19268,24 @@ ${message.trim()}`);
|
|
|
17798
19268
|
}
|
|
17799
19269
|
},
|
|
17800
19270
|
onToolStart: (call) => {
|
|
17801
|
-
|
|
19271
|
+
activeTools.set(call, performance.now());
|
|
19272
|
+
renderWorking();
|
|
19273
|
+
if (!stdout5.isTTY && !quiet) console.log(chalk11.dim(` ${toolActivityLabel(call)}`));
|
|
19274
|
+
},
|
|
19275
|
+
onToolResult: (call, result) => {
|
|
19276
|
+
workSpinner.stop();
|
|
19277
|
+
const started = activeTools.get(call);
|
|
19278
|
+
activeTools.delete(call);
|
|
19279
|
+
const duration = started === void 0 ? 0 : performance.now() - started;
|
|
19280
|
+
entries.push(activityEntry(call, result, duration));
|
|
19281
|
+
refreshActivity();
|
|
19282
|
+
if (!activity && !quiet) {
|
|
19283
|
+
const dot = result.ok ? chalk11.green("\u25CF") : chalk11.red("\u25CF");
|
|
19284
|
+
console.log(` ${dot} ${toolActivityLabel(call)}${duration >= 1e3 ? ` \xB7 Worked for ${formatSeconds(duration)}` : ""}`);
|
|
19285
|
+
}
|
|
19286
|
+
printToolResult2(result, Boolean(activity));
|
|
19287
|
+
renderWorking();
|
|
17802
19288
|
},
|
|
17803
|
-
onToolResult: (_call, result) => printToolResult2(result),
|
|
17804
19289
|
...drainQueuedMessages ? {
|
|
17805
19290
|
drainQueuedMessages,
|
|
17806
19291
|
onQueuedMessage: (message) => {
|
|
@@ -17824,35 +19309,65 @@ ${message.trim()}`);
|
|
|
17824
19309
|
console.log(chalk11.gray(` ${hint}`));
|
|
17825
19310
|
},
|
|
17826
19311
|
requestPermission: async (call, permission) => {
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
|
|
17835
|
-
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
19312
|
+
permissionPrompts += 1;
|
|
19313
|
+
workSpinner.stop();
|
|
19314
|
+
try {
|
|
19315
|
+
const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access. [o]nce (this call only) / [a]lways (all shell tools for this session) / [n]o (this call) / n[e]ver (this session) \u203A ` : ` ${call.tool} wants ${permission} access. [o]nce / [a]lways / [n]o / n[e]ver \u203A `;
|
|
19316
|
+
const rawAnswer = await question(chalk11.yellow(prompt), signal);
|
|
19317
|
+
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19318
|
+
const answer = rawAnswer.trim().toLowerCase();
|
|
19319
|
+
if (answer === "a" || answer === "always") return "always";
|
|
19320
|
+
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes") return "once";
|
|
19321
|
+
if (answer === "e" || answer === "never") {
|
|
19322
|
+
console.log(
|
|
19323
|
+
chalk11.gray(
|
|
19324
|
+
` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
|
|
19325
|
+
)
|
|
19326
|
+
);
|
|
19327
|
+
return "never";
|
|
19328
|
+
}
|
|
19329
|
+
return "deny";
|
|
19330
|
+
} finally {
|
|
19331
|
+
permissionPrompts -= 1;
|
|
19332
|
+
renderWorking();
|
|
17840
19333
|
}
|
|
17841
|
-
return "deny";
|
|
17842
19334
|
}
|
|
17843
19335
|
});
|
|
17844
19336
|
return true;
|
|
17845
19337
|
} catch (error) {
|
|
19338
|
+
turnFailed = true;
|
|
17846
19339
|
spinner.stop();
|
|
17847
19340
|
printTurnError(error);
|
|
17848
19341
|
return false;
|
|
17849
19342
|
} finally {
|
|
19343
|
+
spinner.stop();
|
|
19344
|
+
workSpinner.stop();
|
|
19345
|
+
activeTools.clear();
|
|
19346
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19347
|
+
refreshActivity(true);
|
|
17850
19348
|
detachProgress();
|
|
17851
19349
|
}
|
|
17852
19350
|
}
|
|
17853
|
-
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing) {
|
|
19351
|
+
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1, allowImageDrop = true) {
|
|
17854
19352
|
try {
|
|
17855
|
-
return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing);
|
|
19353
|
+
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
19354
|
+
let successful = true;
|
|
19355
|
+
const result = await runAutonomousLoop({
|
|
19356
|
+
maxRounds,
|
|
19357
|
+
signal,
|
|
19358
|
+
classifyFailure: () => ({ kind: "unknown", retryable: false, failoverable: false, reason: "Interactive provider retries already exhausted." }),
|
|
19359
|
+
runRound: async (round, _previous, roundSignal) => {
|
|
19360
|
+
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
19361
|
+
await persist(session);
|
|
19362
|
+
const snapshot = session.toSession();
|
|
19363
|
+
return successful ? snapshot : { ...snapshot, stopReason: roundSignal.aborted ? "aborted" : "error" };
|
|
19364
|
+
},
|
|
19365
|
+
onRoundEnd: (round, _snapshot, willResume) => {
|
|
19366
|
+
if (willResume) console.log(chalk11.gray(`Continuing unfinished work: round ${round + 1}/${maxRounds}.`));
|
|
19367
|
+
}
|
|
19368
|
+
});
|
|
19369
|
+
if (result.haltReason !== "completed" && result.haltReason !== "aborted") console.log(chalk11.gray(`Automatic continuation stopped: ${result.haltReason}. Session preserved.`));
|
|
19370
|
+
return successful;
|
|
17856
19371
|
} finally {
|
|
17857
19372
|
await persist(session);
|
|
17858
19373
|
}
|
|
@@ -17919,11 +19434,41 @@ function slashCommandChangesSession(raw) {
|
|
|
17919
19434
|
function errorMessage2(error) {
|
|
17920
19435
|
return error instanceof Error ? error.message : String(error);
|
|
17921
19436
|
}
|
|
17922
|
-
async function handleSlashCommand(raw, session, providerName, workspace, onClear, runPrompt, signal, resume) {
|
|
19437
|
+
async function handleSlashCommand(raw, session, providerName, workspace, onClear, runPrompt, signal, resume, services) {
|
|
17923
19438
|
const [name, ...rest] = raw.slice(1).split(/\s+/);
|
|
17924
19439
|
const command = (name ?? "").toLowerCase();
|
|
17925
19440
|
const argument = rest.join(" ").trim();
|
|
17926
19441
|
switch (command) {
|
|
19442
|
+
case "doctor":
|
|
19443
|
+
await runDoctor(workspace, false, false);
|
|
19444
|
+
return "ok";
|
|
19445
|
+
case "mcp": {
|
|
19446
|
+
const statuses = services?.mcpStatus?.() ?? [];
|
|
19447
|
+
console.log(statuses.length ? renderMcpStatuses(statuses) : "No connected MCP servers. Configure with ultrac mcp add, approve with ultrac trust, then restart the session.");
|
|
19448
|
+
return "ok";
|
|
19449
|
+
}
|
|
19450
|
+
case "autonomous": {
|
|
19451
|
+
const setting = argument || "status";
|
|
19452
|
+
if (setting !== "on" && setting !== "off" && setting !== "status") {
|
|
19453
|
+
console.log("Usage: /autonomous on|off|status");
|
|
19454
|
+
return "ok";
|
|
19455
|
+
}
|
|
19456
|
+
if (!services?.autonomous) {
|
|
19457
|
+
console.log("Automatic continuation is available in the interactive session.");
|
|
19458
|
+
return "ok";
|
|
19459
|
+
}
|
|
19460
|
+
const rounds = services.autonomous(setting);
|
|
19461
|
+
console.log(`Automatic continuation ${rounds > 1 ? "on" : "off"}: up to ${rounds} round(s), with checkpoint, elapsed and stall limits.`);
|
|
19462
|
+
return "ok";
|
|
19463
|
+
}
|
|
19464
|
+
case "continue": {
|
|
19465
|
+
if (!runPrompt || !hasSavedSessionContent(session.toSession())) {
|
|
19466
|
+
console.log("No current task to continue. Start a task or use /resume <id>.");
|
|
19467
|
+
return "ok";
|
|
19468
|
+
}
|
|
19469
|
+
await runPrompt(`${AUTONOMOUS_CONTINUE_TASK}${argument ? ` Focus: ${argument}` : ""}`);
|
|
19470
|
+
return "ok";
|
|
19471
|
+
}
|
|
17927
19472
|
case "logs": {
|
|
17928
19473
|
console.log(currentDiagnosticLogPath() ?? "Diagnostic logging has not been configured for this process.");
|
|
17929
19474
|
return "ok";
|
|
@@ -18133,6 +19678,8 @@ ${extra}`);
|
|
|
18133
19678
|
}
|
|
18134
19679
|
const entry = results[selected - 1];
|
|
18135
19680
|
console.log(`${selected}. ${toolActivityLabel(entry.call)} (${entry.result.ok ? "ok" : "failed"})`);
|
|
19681
|
+
const detail = activityEntry(entry.call, entry.result, 0);
|
|
19682
|
+
if (detail.request) console.log(detail.request);
|
|
18136
19683
|
console.log(entry.result.content);
|
|
18137
19684
|
for (const warning of getMcpArchiveWarnings(entry.result)) console.log(chalk11.yellow(`Warning: ${warning}`));
|
|
18138
19685
|
return "ok";
|
|
@@ -18158,6 +19705,20 @@ ${extra}`);
|
|
|
18158
19705
|
}
|
|
18159
19706
|
return "ok";
|
|
18160
19707
|
}
|
|
19708
|
+
case "skill": {
|
|
19709
|
+
const [skillName, ...args] = argument.split(/\s+/);
|
|
19710
|
+
if (!skillName || !runPrompt) {
|
|
19711
|
+
console.log("Usage: /skill <name> [args]");
|
|
19712
|
+
return "ok";
|
|
19713
|
+
}
|
|
19714
|
+
const skill = await readSkill(workspace, skillName);
|
|
19715
|
+
if (!skill) {
|
|
19716
|
+
console.log(`No available skill named ${skillName}. Use /skills to list them.`);
|
|
19717
|
+
return "ok";
|
|
19718
|
+
}
|
|
19719
|
+
await runPrompt(buildSkillCommandPrompt(skill, { command: "skill", skillName: skill.name, description: skill.description, scope: skill.scope, path: skill.path }, args.join(" ")));
|
|
19720
|
+
return "ok";
|
|
19721
|
+
}
|
|
18161
19722
|
case "skills": {
|
|
18162
19723
|
const skills = await listSkills(workspace);
|
|
18163
19724
|
if (!skills.length) {
|
|
@@ -18190,13 +19751,14 @@ ${extra}`);
|
|
|
18190
19751
|
return "unhandled";
|
|
18191
19752
|
}
|
|
18192
19753
|
}
|
|
18193
|
-
async function runSkillCommand(session, raw, workspace, question) {
|
|
19754
|
+
async function runSkillCommand(session, raw, workspace, question, runPrompt) {
|
|
18194
19755
|
const resolved = await resolveSkillCommand(workspace, raw);
|
|
18195
19756
|
if (resolved.status === "ok") {
|
|
18196
19757
|
console.log(
|
|
18197
19758
|
chalk11.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
|
|
18198
19759
|
);
|
|
18199
|
-
await
|
|
19760
|
+
if (runPrompt) await runPrompt(resolved.prompt);
|
|
19761
|
+
else await runTurn(session, resolved.prompt, question, void 0, void 0, void 0, void 0, false);
|
|
18200
19762
|
return;
|
|
18201
19763
|
}
|
|
18202
19764
|
if (resolved.status === "conflict") {
|
|
@@ -18261,7 +19823,7 @@ var LineReader = class {
|
|
|
18261
19823
|
const messages = [];
|
|
18262
19824
|
while (this.queue.length) {
|
|
18263
19825
|
const next = this.queue[0].trim();
|
|
18264
|
-
if (next.startsWith("/")) break;
|
|
19826
|
+
if (next.startsWith("/") || hasDroppedImage(next)) break;
|
|
18265
19827
|
this.queue.shift();
|
|
18266
19828
|
if (next) messages.push(next);
|
|
18267
19829
|
}
|
|
@@ -18274,8 +19836,8 @@ var LineReader = class {
|
|
|
18274
19836
|
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
18275
19837
|
}
|
|
18276
19838
|
if (this.closed) return Promise.resolve(null);
|
|
18277
|
-
return new Promise((
|
|
18278
|
-
const waiter = { resolve:
|
|
19839
|
+
return new Promise((resolve17) => {
|
|
19840
|
+
const waiter = { resolve: resolve17, ...signal ? { signal } : {} };
|
|
18279
19841
|
if (signal) {
|
|
18280
19842
|
waiter.onAbort = () => {
|
|
18281
19843
|
const index = this.waiters.indexOf(waiter);
|
|
@@ -18308,11 +19870,12 @@ function handleReplExit(activeTurn) {
|
|
|
18308
19870
|
activeTurn?.abort();
|
|
18309
19871
|
return "exit-session";
|
|
18310
19872
|
}
|
|
18311
|
-
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2) {
|
|
19873
|
+
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2, planName) {
|
|
18312
19874
|
console.log(renderWelcome(
|
|
18313
19875
|
{
|
|
18314
19876
|
version,
|
|
18315
19877
|
providerName,
|
|
19878
|
+
planName,
|
|
18316
19879
|
model,
|
|
18317
19880
|
mode,
|
|
18318
19881
|
workspace,
|
|
@@ -18337,9 +19900,9 @@ function describeAdvertisedTools(mode, selection, registry = createDefaultToolRe
|
|
|
18337
19900
|
names: advertised.map((tool) => tool.name)
|
|
18338
19901
|
};
|
|
18339
19902
|
}
|
|
18340
|
-
function printToolResult2(result) {
|
|
19903
|
+
function printToolResult2(result, collapsed = false) {
|
|
18341
19904
|
const failure = toolFailurePreview(result);
|
|
18342
|
-
if (failure) console.log(chalk11.red(` Failed: ${failure}`));
|
|
19905
|
+
if (failure && !collapsed) console.log(chalk11.red(` Failed: ${failure}`));
|
|
18343
19906
|
for (const warning of getMcpArchiveWarnings(result)) {
|
|
18344
19907
|
console.log(chalk11.yellow(indent3(`Warning: ${warning}`)));
|
|
18345
19908
|
}
|
|
@@ -18370,148 +19933,6 @@ function indent3(value) {
|
|
|
18370
19933
|
// src/commands/run.ts
|
|
18371
19934
|
import { basename as basename3 } from "path";
|
|
18372
19935
|
import chalk12 from "chalk";
|
|
18373
|
-
|
|
18374
|
-
// src/core/autonomous.ts
|
|
18375
|
-
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
18376
|
-
var AUTONOMOUS_CONTINUE_TASK = "Continue the task from where the previous round stopped. Check what is already done, finish the rest, and verify it.";
|
|
18377
|
-
var DEFAULT_AUTONOMOUS_MAX_ROUNDS = 5;
|
|
18378
|
-
var AUTONOMOUS_MAX_STEPS = 200;
|
|
18379
|
-
var DEFAULT_AUTONOMOUS_MAX_ELAPSED_MS = 6 * 60 * 60 * 1e3;
|
|
18380
|
-
var DEFAULT_AUTONOMOUS_BASE_DELAY_MS = 1e3;
|
|
18381
|
-
var DEFAULT_AUTONOMOUS_MAX_DELAY_MS = 3e4;
|
|
18382
|
-
var DEFAULT_AUTONOMOUS_STALL_ROUNDS = 3;
|
|
18383
|
-
function shouldResume(stopReason) {
|
|
18384
|
-
return stopReason === "max-steps" || stopReason === "error";
|
|
18385
|
-
}
|
|
18386
|
-
function boundedInteger(value, name, min, max) {
|
|
18387
|
-
if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${name} must be an integer from ${min} to ${max}.`);
|
|
18388
|
-
return value;
|
|
18389
|
-
}
|
|
18390
|
-
function abortableSleep(milliseconds, signal) {
|
|
18391
|
-
signal.throwIfAborted();
|
|
18392
|
-
return new Promise((resolve15, reject) => {
|
|
18393
|
-
const timer = setTimeout(() => {
|
|
18394
|
-
signal.removeEventListener("abort", abort);
|
|
18395
|
-
resolve15();
|
|
18396
|
-
}, milliseconds);
|
|
18397
|
-
const abort = () => {
|
|
18398
|
-
clearTimeout(timer);
|
|
18399
|
-
signal.removeEventListener("abort", abort);
|
|
18400
|
-
reject(signal.reason);
|
|
18401
|
-
};
|
|
18402
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
18403
|
-
if (signal.aborted) abort();
|
|
18404
|
-
});
|
|
18405
|
-
}
|
|
18406
|
-
function progress(session) {
|
|
18407
|
-
const hash = createHash7("sha256");
|
|
18408
|
-
let completedTools = 0;
|
|
18409
|
-
hash.update(session.finalMessage ?? "");
|
|
18410
|
-
for (const step of session.steps ?? []) {
|
|
18411
|
-
hash.update(step.modelText ?? "");
|
|
18412
|
-
for (const tool of step.toolResults ?? []) {
|
|
18413
|
-
completedTools += 1;
|
|
18414
|
-
hash.update(tool.call.tool);
|
|
18415
|
-
hash.update(tool.result.ok ? "ok" : "failed");
|
|
18416
|
-
hash.update(tool.result.content ?? "");
|
|
18417
|
-
try {
|
|
18418
|
-
hash.update(JSON.stringify(tool.call.input));
|
|
18419
|
-
} catch {
|
|
18420
|
-
hash.update("unserializable-input");
|
|
18421
|
-
}
|
|
18422
|
-
}
|
|
18423
|
-
}
|
|
18424
|
-
return { fingerprint: hash.digest("hex"), completedTools };
|
|
18425
|
-
}
|
|
18426
|
-
async function runAutonomousLoop(options) {
|
|
18427
|
-
boundedInteger(options.maxRounds, "maxRounds", 1, 1e3);
|
|
18428
|
-
const maxElapsedMs = boundedInteger(options.maxElapsedMs ?? DEFAULT_AUTONOMOUS_MAX_ELAPSED_MS, "maxElapsedMs", 1, 7 * 24 * 60 * 60 * 1e3);
|
|
18429
|
-
const baseDelay = boundedInteger(options.baseDelayMs ?? DEFAULT_AUTONOMOUS_BASE_DELAY_MS, "baseDelayMs", 0, 3e5);
|
|
18430
|
-
const maxDelay = boundedInteger(options.maxDelayMs ?? DEFAULT_AUTONOMOUS_MAX_DELAY_MS, "maxDelayMs", baseDelay, 3e5);
|
|
18431
|
-
const stallLimit = boundedInteger(options.stallRounds ?? DEFAULT_AUTONOMOUS_STALL_ROUNDS, "stallRounds", 1, 1e3);
|
|
18432
|
-
const now = options.now ?? (() => performance.now());
|
|
18433
|
-
const started = now();
|
|
18434
|
-
const elapsed = () => Math.max(0, now() - started);
|
|
18435
|
-
const budget = new AbortController();
|
|
18436
|
-
const deadline = setTimeout(() => budget.abort(new DOMException("Autonomous elapsed budget reached.", "TimeoutError")), maxElapsedMs);
|
|
18437
|
-
deadline.unref?.();
|
|
18438
|
-
const signal = options.signal ? AbortSignal.any([options.signal, budget.signal]) : budget.signal;
|
|
18439
|
-
const sleep2 = options.sleep ?? abortableSleep;
|
|
18440
|
-
const id = randomUUID5();
|
|
18441
|
-
let previous;
|
|
18442
|
-
let lastFingerprint;
|
|
18443
|
-
let stalls = 0;
|
|
18444
|
-
let consecutiveErrors = 0;
|
|
18445
|
-
try {
|
|
18446
|
-
signal.throwIfAborted();
|
|
18447
|
-
for (let round = 1; ; round += 1) {
|
|
18448
|
-
const roundStart = now();
|
|
18449
|
-
diagnosticEvent("round.start", { id, round, maxRounds: options.maxRounds, elapsedMs: elapsed() });
|
|
18450
|
-
let session;
|
|
18451
|
-
try {
|
|
18452
|
-
session = await options.runRound(round, previous, signal);
|
|
18453
|
-
} catch (error) {
|
|
18454
|
-
diagnosticEvent("round.end", { id, round, reason: signal.aborted ? "aborted" : "exception", elapsedMs: elapsed(), durationMs: Math.max(0, now() - roundStart) });
|
|
18455
|
-
throw error;
|
|
18456
|
-
}
|
|
18457
|
-
const current = progress(session);
|
|
18458
|
-
const unchanged = lastFingerprint !== void 0 && current.fingerprint === lastFingerprint;
|
|
18459
|
-
stalls = unchanged ? stalls + 1 : 0;
|
|
18460
|
-
lastFingerprint = current.fingerprint;
|
|
18461
|
-
const classification = session.stopReason === "error" ? options.classifyFailure?.(session) : void 0;
|
|
18462
|
-
consecutiveErrors = session.stopReason === "error" ? consecutiveErrors + 1 : 0;
|
|
18463
|
-
let haltReason;
|
|
18464
|
-
if (session.stopReason === "completed") haltReason = "completed";
|
|
18465
|
-
else if (budget.signal.aborted || elapsed() >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
18466
|
-
else if (signal.aborted || session.stopReason === "aborted") haltReason = "aborted";
|
|
18467
|
-
else if (classification && !classification.retryable) haltReason = "permanent_error";
|
|
18468
|
-
else if (!shouldResume(session.stopReason)) haltReason = "stopped";
|
|
18469
|
-
else if (stalls >= stallLimit) haltReason = "stalled";
|
|
18470
|
-
else if (round >= options.maxRounds) haltReason = "max_rounds";
|
|
18471
|
-
const retryDelay = Math.min(maxDelay, baseDelay * 2 ** Math.min(30, consecutiveErrors - 1));
|
|
18472
|
-
if (!haltReason && session.stopReason === "error" && elapsed() + retryDelay >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
18473
|
-
diagnosticEvent("round.end", {
|
|
18474
|
-
id,
|
|
18475
|
-
round,
|
|
18476
|
-
sessionId: session.id,
|
|
18477
|
-
reason: haltReason ?? session.stopReason,
|
|
18478
|
-
failureKind: classification?.kind,
|
|
18479
|
-
attempt: consecutiveErrors,
|
|
18480
|
-
completedTools: current.completedTools,
|
|
18481
|
-
elapsedMs: elapsed(),
|
|
18482
|
-
durationMs: Math.max(0, now() - roundStart)
|
|
18483
|
-
});
|
|
18484
|
-
if (haltReason === "stalled") diagnosticEvent("round.stall", { id, round, repeats: stalls, completedTools: current.completedTools, elapsedMs: elapsed() });
|
|
18485
|
-
await options.onRoundEnd?.(round, session, !haltReason);
|
|
18486
|
-
if (haltReason) return { session, rounds: round, haltReason };
|
|
18487
|
-
if (session.stopReason === "error") {
|
|
18488
|
-
const delayMs = retryDelay;
|
|
18489
|
-
if (elapsed() + delayMs >= maxElapsedMs) return { session, rounds: round, haltReason: "elapsed_budget" };
|
|
18490
|
-
diagnosticEvent("round.retry", {
|
|
18491
|
-
id,
|
|
18492
|
-
round,
|
|
18493
|
-
nextRound: round + 1,
|
|
18494
|
-
attempt: consecutiveErrors,
|
|
18495
|
-
failureKind: classification?.kind ?? "unclassified",
|
|
18496
|
-
delayMs,
|
|
18497
|
-
elapsedMs: elapsed()
|
|
18498
|
-
});
|
|
18499
|
-
try {
|
|
18500
|
-
await sleep2(delayMs, signal);
|
|
18501
|
-
} catch (error) {
|
|
18502
|
-
if (!signal.aborted) throw error;
|
|
18503
|
-
return { session, rounds: round, haltReason: budget.signal.aborted ? "elapsed_budget" : "aborted" };
|
|
18504
|
-
}
|
|
18505
|
-
}
|
|
18506
|
-
if (signal.aborted || elapsed() >= maxElapsedMs) return { session, rounds: round, haltReason: budget.signal.aborted || elapsed() >= maxElapsedMs ? "elapsed_budget" : "aborted" };
|
|
18507
|
-
previous = session;
|
|
18508
|
-
}
|
|
18509
|
-
} finally {
|
|
18510
|
-
clearTimeout(deadline);
|
|
18511
|
-
}
|
|
18512
|
-
}
|
|
18513
|
-
|
|
18514
|
-
// src/commands/run.ts
|
|
18515
19936
|
function parsePositiveInt(value, flag) {
|
|
18516
19937
|
const parsed = Number.parseInt(value, 10);
|
|
18517
19938
|
if (!Number.isInteger(parsed) || parsed < 1 || String(parsed) !== value.trim()) {
|
|
@@ -18757,7 +20178,7 @@ ${summary}` : ""}`).catch((error) => console.error(chalk12.yellow(`Could not sen
|
|
|
18757
20178
|
}
|
|
18758
20179
|
|
|
18759
20180
|
// src/commands/sessions.ts
|
|
18760
|
-
import { resolve as
|
|
20181
|
+
import { resolve as resolve14 } from "path";
|
|
18761
20182
|
function registerSessionsCommand(program2) {
|
|
18762
20183
|
const sessions = program2.command("sessions").description("Inspect saved Ultrac session transcripts");
|
|
18763
20184
|
sessions.command("list").description("List saved sessions").option("-w, --workspace <path>", "Workspace path", ".").option("--json", "Print JSON output", false).option("--max-results <number>", "Maximum sessions to show", "20").action(async (options) => {
|
|
@@ -18774,7 +20195,7 @@ function registerSessionsCommand(program2) {
|
|
|
18774
20195
|
);
|
|
18775
20196
|
});
|
|
18776
20197
|
sessions.command("export").description("Export a saved session as an auditable episode package").argument("<id>", "Session id").option("-w, --workspace <path>", "Workspace path", ".").option("--format <format>", "Output format: markdown or json", "markdown").option("--max-chars <number>", "Maximum characters per model/tool text block in markdown", "4000").option("--outcome <outcome>", "Outcome status: pass, fail, blocked, or unknown", "unknown").option("--notes <text>", "Outcome notes to include in the episode package").option("--out <path>", "Write export to a path inside the workspace instead of stdout").action(async (id, options) => {
|
|
18777
|
-
const workspace =
|
|
20198
|
+
const workspace = resolve14(options.workspace);
|
|
18778
20199
|
const format3 = parseExportFormat(options.format);
|
|
18779
20200
|
const maxChars = parsePositiveInt2(options.maxChars);
|
|
18780
20201
|
const outcome = parseOutcomeStatus(options.outcome);
|
|
@@ -18791,7 +20212,7 @@ function registerSessionsCommand(program2) {
|
|
|
18791
20212
|
});
|
|
18792
20213
|
}
|
|
18793
20214
|
async function runSessionTool(tool, input, options) {
|
|
18794
|
-
const workspace =
|
|
20215
|
+
const workspace = resolve14(options.workspace);
|
|
18795
20216
|
const result = await createDefaultToolRegistry().execute(
|
|
18796
20217
|
{ tool, input },
|
|
18797
20218
|
{ workspace, permissions: { write: false, shell: false, network: false } }
|
|
@@ -18967,11 +20388,11 @@ function collectToolArtifacts(session, toolName) {
|
|
|
18967
20388
|
}
|
|
18968
20389
|
|
|
18969
20390
|
// src/commands/skills.ts
|
|
18970
|
-
import { resolve as
|
|
20391
|
+
import { resolve as resolve15 } from "path";
|
|
18971
20392
|
function registerSkillsCommand(program2) {
|
|
18972
20393
|
const skills = program2.command("skills").description("Manage Ultrac skills (SKILL.md playbooks)");
|
|
18973
20394
|
skills.command("list").description("List installed skills").option("-w, --workspace <path>", "Workspace path", ".").action(async (options) => {
|
|
18974
|
-
const installed = await listSkills(
|
|
20395
|
+
const installed = await listSkills(resolve15(options.workspace));
|
|
18975
20396
|
if (!installed.length) {
|
|
18976
20397
|
console.log("No skills installed. Create one with: ultrac skills create <name> --description <text>");
|
|
18977
20398
|
return;
|
|
@@ -18982,7 +20403,7 @@ function registerSkillsCommand(program2) {
|
|
|
18982
20403
|
}
|
|
18983
20404
|
});
|
|
18984
20405
|
skills.command("show").description("Show a skill's full instructions").argument("<name>", "Skill name").option("-w, --workspace <path>", "Workspace path", ".").action(async (name, options) => {
|
|
18985
|
-
const skill = await readSkill(
|
|
20406
|
+
const skill = await readSkill(resolve15(options.workspace), name);
|
|
18986
20407
|
if (!skill) throw new Error(`No skill named "${name}".`);
|
|
18987
20408
|
console.log(`# ${skill.name} (${skill.scope})`);
|
|
18988
20409
|
console.log(`Path: ${skill.path}`);
|
|
@@ -18997,7 +20418,7 @@ function registerSkillsCommand(program2) {
|
|
|
18997
20418
|
if (options.argumentHint && !command) throw new Error("--argument-hint requires --command.");
|
|
18998
20419
|
const argumentHint = options.argumentHint?.trim();
|
|
18999
20420
|
if (argumentHint && (argumentHint.length > 256 || /[\r\n]/.test(argumentHint))) throw new Error("Argument hints must be one line, at most 256 characters.");
|
|
19000
|
-
const skill = await createSkill(
|
|
20421
|
+
const skill = await createSkill(resolve15(options.workspace), {
|
|
19001
20422
|
name,
|
|
19002
20423
|
description: options.description,
|
|
19003
20424
|
whenToUse: options.when,
|
|
@@ -19012,7 +20433,7 @@ function registerSkillsCommand(program2) {
|
|
|
19012
20433
|
}
|
|
19013
20434
|
);
|
|
19014
20435
|
skills.command("path").description("Print the skills directory path for a scope").option("-w, --workspace <path>", "Workspace path", ".").option("--scope <scope>", "global or project", "project").action((options) => {
|
|
19015
|
-
console.log(skillsDir(parseScope2(options.scope),
|
|
20436
|
+
console.log(skillsDir(parseScope2(options.scope), resolve15(options.workspace)));
|
|
19016
20437
|
});
|
|
19017
20438
|
}
|
|
19018
20439
|
function parseScope2(scope) {
|
|
@@ -19314,13 +20735,13 @@ var FollowAbortedError = class extends Error {
|
|
|
19314
20735
|
}
|
|
19315
20736
|
};
|
|
19316
20737
|
function defaultSleep2(ms, signal) {
|
|
19317
|
-
return new Promise((
|
|
19318
|
-
if (signal?.aborted) return
|
|
20738
|
+
return new Promise((resolve17) => {
|
|
20739
|
+
if (signal?.aborted) return resolve17();
|
|
19319
20740
|
const timer = setTimeout(done, ms);
|
|
19320
20741
|
function done() {
|
|
19321
20742
|
signal?.removeEventListener("abort", done);
|
|
19322
20743
|
clearTimeout(timer);
|
|
19323
|
-
|
|
20744
|
+
resolve17();
|
|
19324
20745
|
}
|
|
19325
20746
|
signal?.addEventListener("abort", done, { once: true });
|
|
19326
20747
|
});
|
|
@@ -19816,11 +21237,11 @@ async function interrupted(r, runId, options, printed) {
|
|
|
19816
21237
|
}
|
|
19817
21238
|
async function resolveDeps(options, deps) {
|
|
19818
21239
|
if (options.baseUrl) setBaseUrlFlag(options.baseUrl);
|
|
19819
|
-
const
|
|
21240
|
+
const resolve17 = deps.credentials ?? (async () => {
|
|
19820
21241
|
const credentials2 = await resolveUltracCredentials();
|
|
19821
21242
|
return credentials2 ? { apiKey: credentials2.apiKey, baseUrl: credentials2.baseUrl } : null;
|
|
19822
21243
|
});
|
|
19823
|
-
const credentials = await
|
|
21244
|
+
const credentials = await resolve17();
|
|
19824
21245
|
if (!credentials) {
|
|
19825
21246
|
throw new Error("Not signed in to Ultrac. Run `ultrac login` (or set ULTRAC_API_KEY).");
|
|
19826
21247
|
}
|
|
@@ -19919,7 +21340,7 @@ ${run.output}${run.output.endsWith("\n") ? "" : "\n"}`;
|
|
|
19919
21340
|
}
|
|
19920
21341
|
|
|
19921
21342
|
// src/commands/tools.ts
|
|
19922
|
-
import { resolve as
|
|
21343
|
+
import { resolve as resolve16 } from "path";
|
|
19923
21344
|
import chalk14 from "chalk";
|
|
19924
21345
|
function registerToolsCommand(program2) {
|
|
19925
21346
|
const toolsCommand = program2.command("tools");
|
|
@@ -19954,7 +21375,7 @@ ${tools.length} of ${all.length} tools advertised in ${options.mode} mode, ${ser
|
|
|
19954
21375
|
const result = await createDefaultToolRegistry().execute(
|
|
19955
21376
|
{ tool, input },
|
|
19956
21377
|
{
|
|
19957
|
-
workspace:
|
|
21378
|
+
workspace: resolve16(options.workspace),
|
|
19958
21379
|
permissions: {
|
|
19959
21380
|
write: options.allowWrite,
|
|
19960
21381
|
shell: options.allowShell,
|