@spinalfluid/ultrac 0.1.0 → 0.1.5
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 +20 -4
- package/dist/cli.js +424 -160
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ For local testing before a registry release, build and install the verified tarb
|
|
|
35
35
|
pnpm check
|
|
36
36
|
pnpm verify:install
|
|
37
37
|
npm pack
|
|
38
|
-
npm install -g ./spinalfluid-ultrac-0.1.
|
|
38
|
+
npm install -g ./spinalfluid-ultrac-0.1.5.tgz
|
|
39
39
|
ultrac login
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -53,7 +53,17 @@ ultrac logout # drop local credentials and revoke the minted key
|
|
|
53
53
|
|
|
54
54
|
For interactive use, `ultrac login` opens `https://ultrac.io/cli/login` (or the same path on a local
|
|
55
55
|
site). Approve the CLI using your existing website account; the site mints a key and the CLI stores
|
|
56
|
-
it in `~/.ultrac/auth.json
|
|
56
|
+
it in `~/.ultrac/auth.json`. The browser return is bound to the initiating CLI with loopback,
|
|
57
|
+
state, and PKCE checks. Confirm that the browser and terminal show the same reference code; you do
|
|
58
|
+
not enter that code during the normal flow. If the browser cannot reach the loopback callback, the
|
|
59
|
+
CLI accepts the short-lived, one-time completion code from the failed
|
|
60
|
+
localhost callback URL and exchanges it with the same PKCE verifier. This recovery path never asks
|
|
61
|
+
for a permanent API key. `--no-browser` prints the authorization URL instead of opening it.
|
|
62
|
+
|
|
63
|
+
Credentials use an owner-only `0600` file because the package intentionally avoids native keychain
|
|
64
|
+
dependencies. On filesystems that do not enforce Unix permission bits, protect the account and home
|
|
65
|
+
directory with the operating system's access controls. You do not need to create or paste a key for
|
|
66
|
+
normal browser sign-in.
|
|
57
67
|
|
|
58
68
|
For scripts and CI, do not run the browser sign-in flow. Create a dedicated API key in the account's
|
|
59
69
|
`/keys` page, store it in your CI secret manager, and expose it to the command as `ULTRAC_API_KEY`.
|
|
@@ -62,8 +72,10 @@ for an existing key, and `ultrac login --key <key>` verifies and stores one; use
|
|
|
62
72
|
intentionally need a local saved credential, not as the default CI setup. Avoid putting secrets in
|
|
63
73
|
shell history, command arguments, project config, or source control. An approved Ultrac account is required for model access.
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
Every key is checked with `GET <base>/models` before it is saved. A browser-issued key is stored only
|
|
76
|
+
after the API accepts it; a 401/403 or unavailable verification writes nothing. For compatibility,
|
|
77
|
+
an explicitly supplied `--key` or `--manual` key may still be saved with a warning when verification
|
|
78
|
+
is unavailable, but a rejected key is never saved.
|
|
67
79
|
|
|
68
80
|
### Cosmic 1, in a repo
|
|
69
81
|
|
|
@@ -129,6 +141,10 @@ ultrac "Find the main entry points and summarize the architecture"
|
|
|
129
141
|
Inside a session:
|
|
130
142
|
|
|
131
143
|
- Type to talk to Ultrac; it gathers repository context first and can call tools.
|
|
144
|
+
- You can type another message while Ultrac is working. It is queued and inserted after the current
|
|
145
|
+
tool batch or completed model response, before the next model request. Slash commands wait for
|
|
146
|
+
the main prompt instead of entering model context.
|
|
147
|
+
- Ctrl-C stops active work at a safe boundary. At an idle prompt, Ctrl-C exits and saves the session.
|
|
132
148
|
- Gated tools (file writes, shell, network) prompt for approval: `once` / `always` / `no`.
|
|
133
149
|
- Coding commands: `/plan [task]`, `/coding`, `/review [focus]`, `/diff [--staged]`,
|
|
134
150
|
`/test [instructions]`, `/init`, `/max-steps [number]`.
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import chalk15 from "chalk";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/ask.ts
|
|
@@ -11186,16 +11186,16 @@ function normalizeFilter(filter, workspace) {
|
|
|
11186
11186
|
function tokenizeCommand(command) {
|
|
11187
11187
|
const tokens = [];
|
|
11188
11188
|
let current = "";
|
|
11189
|
-
let
|
|
11189
|
+
let quote2 = null;
|
|
11190
11190
|
let started = false;
|
|
11191
11191
|
for (const character of command) {
|
|
11192
|
-
if (
|
|
11193
|
-
if (character ===
|
|
11192
|
+
if (quote2) {
|
|
11193
|
+
if (character === quote2) quote2 = null;
|
|
11194
11194
|
else current += character;
|
|
11195
11195
|
continue;
|
|
11196
11196
|
}
|
|
11197
11197
|
if (character === '"' || character === "'") {
|
|
11198
|
-
|
|
11198
|
+
quote2 = character;
|
|
11199
11199
|
started = true;
|
|
11200
11200
|
continue;
|
|
11201
11201
|
}
|
|
@@ -12900,6 +12900,7 @@ import { stdin as stdin3 } from "process";
|
|
|
12900
12900
|
|
|
12901
12901
|
// src/auth/browser-login.ts
|
|
12902
12902
|
import { hostname as osHostname } from "os";
|
|
12903
|
+
import { randomInt } from "crypto";
|
|
12903
12904
|
|
|
12904
12905
|
// src/auth/loopback.ts
|
|
12905
12906
|
import http from "http";
|
|
@@ -13097,6 +13098,7 @@ function errorMessage(error) {
|
|
|
13097
13098
|
|
|
13098
13099
|
// src/auth/browser-login.ts
|
|
13099
13100
|
var BROWSER_LOGIN_REQUEST_TIMEOUT_MS = 15e3;
|
|
13101
|
+
var BROWSER_LOGIN_CALLBACK_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
13100
13102
|
var BrowserLoginError = class extends Error {
|
|
13101
13103
|
code;
|
|
13102
13104
|
constructor(code, message) {
|
|
@@ -13110,16 +13112,20 @@ async function loginWithBrowser(input) {
|
|
|
13110
13112
|
const hostname = (input.hostname ?? osHostname()).trim();
|
|
13111
13113
|
const fetchImpl = input.fetchImpl ?? ((url, init) => fetch(url, init));
|
|
13112
13114
|
const requestTimeoutMs = input.requestTimeoutMs ?? BROWSER_LOGIN_REQUEST_TIMEOUT_MS;
|
|
13115
|
+
const callbackTimeoutMs = input.timeoutMs ?? BROWSER_LOGIN_CALLBACK_TIMEOUT_MS;
|
|
13113
13116
|
const pkce = generatePkce();
|
|
13114
13117
|
const state = generateOAuthState();
|
|
13115
13118
|
const log = input.onInfo ?? (() => void 0);
|
|
13119
|
+
const startLoopback = input.startLoopback ?? startLoopbackServer;
|
|
13116
13120
|
let loopback;
|
|
13121
|
+
let redirectUri;
|
|
13117
13122
|
try {
|
|
13118
|
-
loopback = await
|
|
13123
|
+
loopback = await startLoopback({ expectedState: state, timeoutMs: callbackTimeoutMs });
|
|
13124
|
+
redirectUri = loopback.redirectUri;
|
|
13119
13125
|
} catch (error) {
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
`Could not bind a local callback server (${error instanceof Error ? error.message : String(error)})
|
|
13126
|
+
redirectUri = `http://127.0.0.1:${randomInt(49152, 65536)}/callback`;
|
|
13127
|
+
log(
|
|
13128
|
+
`Could not bind a local callback server (${error instanceof Error ? error.message : String(error)}).`
|
|
13123
13129
|
);
|
|
13124
13130
|
}
|
|
13125
13131
|
try {
|
|
@@ -13129,7 +13135,7 @@ async function loginWithBrowser(input) {
|
|
|
13129
13135
|
{
|
|
13130
13136
|
client: "ultrac",
|
|
13131
13137
|
hostname,
|
|
13132
|
-
redirect_uri:
|
|
13138
|
+
redirect_uri: redirectUri,
|
|
13133
13139
|
state,
|
|
13134
13140
|
code_challenge: pkce.challenge,
|
|
13135
13141
|
code_challenge_method: pkce.method
|
|
@@ -13153,26 +13159,27 @@ async function loginWithBrowser(input) {
|
|
|
13153
13159
|
if (input.openBrowser(verificationUrl) === false) {
|
|
13154
13160
|
log("If a browser did not open, paste the URL above.");
|
|
13155
13161
|
}
|
|
13156
|
-
|
|
13157
|
-
if (
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13162
|
+
let code;
|
|
13163
|
+
if (loopback) {
|
|
13164
|
+
const callback = await loopback.wait;
|
|
13165
|
+
if (callback.ok) {
|
|
13166
|
+
code = callback.code;
|
|
13167
|
+
} else {
|
|
13168
|
+
if (callback.error === "access_denied") {
|
|
13169
|
+
throw new BrowserLoginError("access_denied", "Sign-in was denied in the browser.");
|
|
13170
|
+
}
|
|
13171
|
+
code = await requestRecoveryCode(input, log, callback.description);
|
|
13163
13172
|
}
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
callback.description ?? `Browser login failed (${callback.error}).`
|
|
13167
|
-
);
|
|
13173
|
+
} else {
|
|
13174
|
+
code = await requestRecoveryCode(input, log);
|
|
13168
13175
|
}
|
|
13169
13176
|
const exchanged = await postJson(
|
|
13170
13177
|
fetchImpl,
|
|
13171
13178
|
`${origin}/api/cli/login/exchange`,
|
|
13172
13179
|
{
|
|
13173
|
-
code
|
|
13180
|
+
code,
|
|
13174
13181
|
code_verifier: pkce.verifier,
|
|
13175
|
-
redirect_uri:
|
|
13182
|
+
redirect_uri: redirectUri
|
|
13176
13183
|
},
|
|
13177
13184
|
requestTimeoutMs
|
|
13178
13185
|
);
|
|
@@ -13193,7 +13200,32 @@ async function loginWithBrowser(input) {
|
|
|
13193
13200
|
label: exchanged.body.label
|
|
13194
13201
|
};
|
|
13195
13202
|
} finally {
|
|
13196
|
-
await loopback
|
|
13203
|
+
await loopback?.close().catch(() => void 0);
|
|
13204
|
+
}
|
|
13205
|
+
}
|
|
13206
|
+
async function requestRecoveryCode(input, log, reason) {
|
|
13207
|
+
if (!input.requestCompletionCode) {
|
|
13208
|
+
throw new BrowserLoginError(
|
|
13209
|
+
"callback_failed",
|
|
13210
|
+
`${reason ? `${reason} ` : ""}Browser completion did not reach this terminal. Run ultrac login again.`
|
|
13211
|
+
);
|
|
13212
|
+
}
|
|
13213
|
+
log("Browser completion did not reach this terminal.");
|
|
13214
|
+
log("Copy the short-lived completion code (or the failed localhost callback URL) from the browser.");
|
|
13215
|
+
const supplied = (await input.requestCompletionCode()).trim();
|
|
13216
|
+
const code = completionCodeFromInput(supplied);
|
|
13217
|
+
if (!code) {
|
|
13218
|
+
throw new BrowserLoginError("missing_code", "No completion code was provided. Nothing was saved.");
|
|
13219
|
+
}
|
|
13220
|
+
return code;
|
|
13221
|
+
}
|
|
13222
|
+
function completionCodeFromInput(value) {
|
|
13223
|
+
if (!value) return "";
|
|
13224
|
+
try {
|
|
13225
|
+
const url = new URL(value);
|
|
13226
|
+
return url.searchParams.get("code")?.trim() ?? "";
|
|
13227
|
+
} catch {
|
|
13228
|
+
return value;
|
|
13197
13229
|
}
|
|
13198
13230
|
}
|
|
13199
13231
|
async function postJson(fetchImpl, url, body, timeoutMs) {
|
|
@@ -13376,7 +13408,8 @@ async function browserLogin(baseUrl, shouldOpen) {
|
|
|
13376
13408
|
baseUrl,
|
|
13377
13409
|
hostname: osHostname2(),
|
|
13378
13410
|
openBrowser: (url) => shouldOpen ? openBrowser(url) : false,
|
|
13379
|
-
onInfo: (line) => console.log(line)
|
|
13411
|
+
onInfo: (line) => console.log(line),
|
|
13412
|
+
requestCompletionCode: () => promptSecret("Paste the one-time completion code: ")
|
|
13380
13413
|
});
|
|
13381
13414
|
} catch (error) {
|
|
13382
13415
|
if (error instanceof BrowserLoginError) throw new Error(error.message);
|
|
@@ -13400,7 +13433,10 @@ async function verifyAndStore(input) {
|
|
|
13400
13433
|
} else {
|
|
13401
13434
|
console.log(chalk4.yellow("unverified"));
|
|
13402
13435
|
console.log(chalk4.yellow(` Could not verify the key (${check.message}).`));
|
|
13403
|
-
|
|
13436
|
+
if (!input.pasted) {
|
|
13437
|
+
throw new Error("Nothing was saved. Browser sign-in keys are stored only after the API accepts them.");
|
|
13438
|
+
}
|
|
13439
|
+
console.log(chalk4.yellow(" Saving the explicitly supplied key; run `ultrac doctor` once the API is reachable."));
|
|
13404
13440
|
}
|
|
13405
13441
|
const path = await writeCredentials({
|
|
13406
13442
|
apiKey: input.apiKey,
|
|
@@ -14529,7 +14565,7 @@ async function readStdin() {
|
|
|
14529
14565
|
// src/commands/repl.ts
|
|
14530
14566
|
import { createInterface as createInterface3 } from "readline";
|
|
14531
14567
|
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
14532
|
-
import
|
|
14568
|
+
import chalk10 from "chalk";
|
|
14533
14569
|
|
|
14534
14570
|
// src/config/budget.ts
|
|
14535
14571
|
function applyBudgetFlags(flags, config) {
|
|
@@ -14929,6 +14965,21 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
14929
14965
|
this.fileProvenance.set(contextMessage, unknownContextProvenance());
|
|
14930
14966
|
this.started = true;
|
|
14931
14967
|
}
|
|
14968
|
+
appendQueuedMessages(handlers) {
|
|
14969
|
+
const queued = handlers.drainQueuedMessages?.() ?? [];
|
|
14970
|
+
let appended = 0;
|
|
14971
|
+
for (const raw of queued) {
|
|
14972
|
+
const content = raw.trim();
|
|
14973
|
+
if (!content) continue;
|
|
14974
|
+
const message = { role: "user", content };
|
|
14975
|
+
this.messages.push(message);
|
|
14976
|
+
this.pinnedMessages.push(message);
|
|
14977
|
+
this.userConstraints.push({ kind: "message", content });
|
|
14978
|
+
handlers.onQueuedMessage?.(content);
|
|
14979
|
+
appended += 1;
|
|
14980
|
+
}
|
|
14981
|
+
return appended;
|
|
14982
|
+
}
|
|
14932
14983
|
async ask(input, handlers = {}) {
|
|
14933
14984
|
if (!this.started) await this.start();
|
|
14934
14985
|
if (!this.firstTask) this.firstTask = input;
|
|
@@ -14947,6 +14998,7 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
14947
14998
|
handlers.onInterrupted?.();
|
|
14948
14999
|
break;
|
|
14949
15000
|
}
|
|
15001
|
+
this.appendQueuedMessages(handlers);
|
|
14950
15002
|
const system = buildSystemPrompt(
|
|
14951
15003
|
this.advertisedTools(),
|
|
14952
15004
|
getEffectiveModePermissions(this.activeMode, this.permissions),
|
|
@@ -15029,7 +15081,8 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
15029
15081
|
handlers.onInterrupted?.();
|
|
15030
15082
|
break;
|
|
15031
15083
|
}
|
|
15032
|
-
|
|
15084
|
+
const queuedMessages = this.appendQueuedMessages(handlers);
|
|
15085
|
+
if (parsed.done && parsed.toolCalls.length === 0 && queuedMessages === 0) {
|
|
15033
15086
|
stopReason = "completed";
|
|
15034
15087
|
break;
|
|
15035
15088
|
}
|
|
@@ -15395,6 +15448,21 @@ ${context}`
|
|
|
15395
15448
|
this.fileProvenance.set(contextMessage, unknownContextProvenance());
|
|
15396
15449
|
this.started = true;
|
|
15397
15450
|
}
|
|
15451
|
+
appendQueuedMessages(handlers) {
|
|
15452
|
+
const queued = handlers.drainQueuedMessages?.() ?? [];
|
|
15453
|
+
let appended = 0;
|
|
15454
|
+
for (const raw of queued) {
|
|
15455
|
+
const content = raw.trim();
|
|
15456
|
+
if (!content) continue;
|
|
15457
|
+
const message = { role: "user", content };
|
|
15458
|
+
this.messages.push(message);
|
|
15459
|
+
this.pinnedMessages.push(message);
|
|
15460
|
+
this.userConstraints.push({ kind: "message", content });
|
|
15461
|
+
handlers.onQueuedMessage?.(content);
|
|
15462
|
+
appended += 1;
|
|
15463
|
+
}
|
|
15464
|
+
return appended;
|
|
15465
|
+
}
|
|
15398
15466
|
async ask(input, handlers = {}) {
|
|
15399
15467
|
if (!this.started) await this.start();
|
|
15400
15468
|
if (!this.firstTask) this.firstTask = input;
|
|
@@ -15412,6 +15480,7 @@ ${context}`
|
|
|
15412
15480
|
handlers.onInterrupted?.();
|
|
15413
15481
|
break;
|
|
15414
15482
|
}
|
|
15483
|
+
this.appendQueuedMessages(handlers);
|
|
15415
15484
|
this.refreshSystemPrompt();
|
|
15416
15485
|
const tools = toToolSchemas(this.advertisedTools());
|
|
15417
15486
|
const turn = this.steps.length + 1;
|
|
@@ -15462,9 +15531,17 @@ ${context}`
|
|
|
15462
15531
|
});
|
|
15463
15532
|
}
|
|
15464
15533
|
if (!response.toolCalls.length) {
|
|
15465
|
-
stopReason = "completed";
|
|
15466
15534
|
this.steps.push(step);
|
|
15467
|
-
|
|
15535
|
+
if (handlers.signal?.aborted) {
|
|
15536
|
+
stopReason = "aborted";
|
|
15537
|
+
handlers.onInterrupted?.();
|
|
15538
|
+
break;
|
|
15539
|
+
}
|
|
15540
|
+
if (this.appendQueuedMessages(handlers) === 0) {
|
|
15541
|
+
stopReason = "completed";
|
|
15542
|
+
break;
|
|
15543
|
+
}
|
|
15544
|
+
continue;
|
|
15468
15545
|
}
|
|
15469
15546
|
for (const call of response.toolCalls) {
|
|
15470
15547
|
if (handlers.signal?.aborted) break;
|
|
@@ -15508,6 +15585,7 @@ ${context}`
|
|
|
15508
15585
|
handlers.onInterrupted?.();
|
|
15509
15586
|
break;
|
|
15510
15587
|
}
|
|
15588
|
+
this.appendQueuedMessages(handlers);
|
|
15511
15589
|
}
|
|
15512
15590
|
} catch (error) {
|
|
15513
15591
|
stopReason = "error";
|
|
@@ -16212,6 +16290,149 @@ var Spinner = class {
|
|
|
16212
16290
|
}
|
|
16213
16291
|
};
|
|
16214
16292
|
|
|
16293
|
+
// src/ui/tool-activity.ts
|
|
16294
|
+
var VERBS = {
|
|
16295
|
+
read_file: "Read",
|
|
16296
|
+
read_many_files: "Read",
|
|
16297
|
+
write_file: "Edit",
|
|
16298
|
+
replace_in_file: "Edit",
|
|
16299
|
+
multi_edit: "Edit",
|
|
16300
|
+
apply_patch: "Apply patch",
|
|
16301
|
+
search_files: "Search",
|
|
16302
|
+
list_files: "List files",
|
|
16303
|
+
inspect_project: "Inspect project",
|
|
16304
|
+
repo_map: "Map repository",
|
|
16305
|
+
context_bundle: "Gather context",
|
|
16306
|
+
run_shell: "Run",
|
|
16307
|
+
run_package_script: "Run package script",
|
|
16308
|
+
run_tests: "Run tests",
|
|
16309
|
+
diagnostics: "Check diagnostics",
|
|
16310
|
+
task: "Delegate",
|
|
16311
|
+
fetch_url: "Fetch",
|
|
16312
|
+
git_status: "Check Git status",
|
|
16313
|
+
git_diff: "Read Git diff",
|
|
16314
|
+
git_log: "Read Git log",
|
|
16315
|
+
git_show: "Read Git object",
|
|
16316
|
+
git_blame: "Read Git blame",
|
|
16317
|
+
git_commit: "Commit changes",
|
|
16318
|
+
git_push: "Push changes"
|
|
16319
|
+
};
|
|
16320
|
+
function toolActivityLabel(call) {
|
|
16321
|
+
const verb = VERBS[call.tool] ?? humanize(call.tool);
|
|
16322
|
+
const target = toolTarget(call);
|
|
16323
|
+
return target ? `${verb} ${target}` : verb;
|
|
16324
|
+
}
|
|
16325
|
+
function toolFailurePreview(result) {
|
|
16326
|
+
if (result.ok) return void 0;
|
|
16327
|
+
const detail = result.content.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 2).join(" \xB7 ");
|
|
16328
|
+
return detail ? truncate2(detail, 240) : `${humanize(result.tool)} failed`;
|
|
16329
|
+
}
|
|
16330
|
+
function toolTarget(call) {
|
|
16331
|
+
const input = call.input;
|
|
16332
|
+
const path = stringValue(input.path);
|
|
16333
|
+
if (path) return compact2(path);
|
|
16334
|
+
const paths = Array.isArray(input.paths) ? input.paths.filter((value) => typeof value === "string") : [];
|
|
16335
|
+
if (paths.length === 1) return compact2(paths[0]);
|
|
16336
|
+
if (paths.length > 1) return `${paths.length} files`;
|
|
16337
|
+
if (call.tool === "run_shell") return compact2(stringValue(input.command), 72);
|
|
16338
|
+
if (call.tool === "run_package_script") return compact2(stringValue(input.script), 50);
|
|
16339
|
+
if (call.tool === "search_files") return quote(stringValue(input.query) || stringValue(input.pattern));
|
|
16340
|
+
if (call.tool === "fetch_url") return compact2(stringValue(input.url), 72);
|
|
16341
|
+
if (call.tool === "task") return compact2(stringValue(input.description) || stringValue(input.task), 64);
|
|
16342
|
+
if (call.tool === "git_show") return compact2(stringValue(input.ref), 40);
|
|
16343
|
+
if (call.tool === "git_commit") return quote(stringValue(input.message), 56);
|
|
16344
|
+
return "";
|
|
16345
|
+
}
|
|
16346
|
+
function stringValue(value) {
|
|
16347
|
+
return typeof value === "string" ? value.trim() : "";
|
|
16348
|
+
}
|
|
16349
|
+
function compact2(value, max = 64) {
|
|
16350
|
+
if (!value) return "";
|
|
16351
|
+
return truncate2(value.replace(/\s+/g, " "), max);
|
|
16352
|
+
}
|
|
16353
|
+
function quote(value, max = 48) {
|
|
16354
|
+
return value ? `\u201C${compact2(value, max)}\u201D` : "";
|
|
16355
|
+
}
|
|
16356
|
+
function truncate2(value, max) {
|
|
16357
|
+
return value.length > max ? `${value.slice(0, Math.max(1, max - 1))}\u2026` : value;
|
|
16358
|
+
}
|
|
16359
|
+
function humanize(value) {
|
|
16360
|
+
const words = value.replace(/[_-]+/g, " ").trim();
|
|
16361
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : "Tool";
|
|
16362
|
+
}
|
|
16363
|
+
|
|
16364
|
+
// src/ui/welcome.ts
|
|
16365
|
+
import chalk9, { Chalk } from "chalk";
|
|
16366
|
+
var LOGO = [
|
|
16367
|
+
"\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",
|
|
16368
|
+
"\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",
|
|
16369
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ",
|
|
16370
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 ",
|
|
16371
|
+
"\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
16372
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
16373
|
+
];
|
|
16374
|
+
function renderWelcome(info, options = {}) {
|
|
16375
|
+
const columns = normalizeColumns(options.columns);
|
|
16376
|
+
const paint = new Chalk({ level: options.color === false ? 0 : chalk9.level });
|
|
16377
|
+
const cardWidth = Math.min(88, Math.max(42, columns));
|
|
16378
|
+
const innerWidth = cardWidth - 4;
|
|
16379
|
+
const wide = columns >= 64;
|
|
16380
|
+
const lines = [];
|
|
16381
|
+
if (wide) {
|
|
16382
|
+
lines.push("");
|
|
16383
|
+
for (let index = 0; index < LOGO.length; index += 1) {
|
|
16384
|
+
const color = index < 2 ? paint.hex("#38BDF8") : index < 4 ? paint.hex("#22D3EE") : paint.hex("#A78BFA");
|
|
16385
|
+
lines.push(` ${color(LOGO[index])}`);
|
|
16386
|
+
}
|
|
16387
|
+
} else {
|
|
16388
|
+
lines.push("", ` ${paint.bold.hex("#22D3EE")("ULTRAC")} ${paint.gray(`v${info.version}`)}`);
|
|
16389
|
+
}
|
|
16390
|
+
const topLabel = wide ? ` ultrac v${info.version} ` : " session ";
|
|
16391
|
+
const topFill = Math.max(0, cardWidth - topLabel.length - 2);
|
|
16392
|
+
lines.push(paint.cyan(`\u256D${topLabel}${"\u2500".repeat(topFill)}\u256E`));
|
|
16393
|
+
lines.push(cardLine(`${info.model} via ${info.providerName}`, innerWidth, paint.bold));
|
|
16394
|
+
lines.push(cardLine(`${info.mode} \xB7 ${info.protocol}`, innerWidth, paint.white));
|
|
16395
|
+
lines.push(cardLine(shortenMiddle(info.workspace, innerWidth), innerWidth, paint.gray));
|
|
16396
|
+
lines.push(paint.cyan(`\u251C${"\u2500".repeat(cardWidth - 2)}\u2524`));
|
|
16397
|
+
lines.push(cardLine(info.budget, innerWidth, paint.gray));
|
|
16398
|
+
lines.push(cardLine(`logs ${shortenMiddle(info.diagnosticPath, Math.max(8, innerWidth - 5))}`, innerWidth, paint.gray));
|
|
16399
|
+
lines.push(paint.cyan(`\u2570${"\u2500".repeat(cardWidth - 2)}\u256F`));
|
|
16400
|
+
lines.push(
|
|
16401
|
+
wide ? `${paint.gray(" /help commands")} ${paint.dim("\xB7")} ${paint.gray("/exit quit")} ${paint.dim("\xB7")} ${paint.gray("Ctrl-C interrupt")}` : paint.gray(" /help \xB7 /exit \xB7 Ctrl-C")
|
|
16402
|
+
);
|
|
16403
|
+
return lines.join("\n");
|
|
16404
|
+
}
|
|
16405
|
+
function renderComposer(columns = 80, color = true) {
|
|
16406
|
+
const paint = new Chalk({ level: color ? chalk9.level : 0 });
|
|
16407
|
+
const width = Math.min(88, Math.max(28, normalizeColumns(columns)));
|
|
16408
|
+
const label = " message ";
|
|
16409
|
+
const fill = Math.max(1, width - label.length - 2);
|
|
16410
|
+
return {
|
|
16411
|
+
frame: `
|
|
16412
|
+
${paint.cyan(`\u256D\u2500${label}${"\u2500".repeat(fill)}`)}
|
|
16413
|
+
${paint.cyan("\u2502")}`,
|
|
16414
|
+
prompt: paint.bold.hex("#22D3EE")("\u2570\u2500\u276F ")
|
|
16415
|
+
};
|
|
16416
|
+
}
|
|
16417
|
+
function cardLine(value, width, style) {
|
|
16418
|
+
const clipped = truncate3(value, width);
|
|
16419
|
+
return `\u2502 ${style(clipped)}${" ".repeat(Math.max(0, width - clipped.length))} \u2502`;
|
|
16420
|
+
}
|
|
16421
|
+
function normalizeColumns(columns) {
|
|
16422
|
+
return Number.isFinite(columns) ? Math.max(28, Math.floor(columns)) : 80;
|
|
16423
|
+
}
|
|
16424
|
+
function truncate3(value, width) {
|
|
16425
|
+
if (value.length <= width) return value;
|
|
16426
|
+
if (width <= 1) return "\u2026";
|
|
16427
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
16428
|
+
}
|
|
16429
|
+
function shortenMiddle(value, width) {
|
|
16430
|
+
if (value.length <= width) return value;
|
|
16431
|
+
if (width < 9) return truncate3(value, width);
|
|
16432
|
+
const side = Math.floor((width - 1) / 2);
|
|
16433
|
+
return `${value.slice(0, side)}\u2026${value.slice(value.length - (width - side - 1))}`;
|
|
16434
|
+
}
|
|
16435
|
+
|
|
16215
16436
|
// src/update/check.ts
|
|
16216
16437
|
import { spawn as spawn4 } from "child_process";
|
|
16217
16438
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
@@ -16438,14 +16659,14 @@ async function runRepl(options, initialTask = "") {
|
|
|
16438
16659
|
if (loaded.needsApproval && await offerWorkspaceTrust(workspace, loaded.withheld, loaded.trustSnapshot, loaded.trustStatus)) {
|
|
16439
16660
|
loaded = await loadConfigWithTrust(workspace);
|
|
16440
16661
|
if (loaded.needsApproval) {
|
|
16441
|
-
console.log(
|
|
16662
|
+
console.log(chalk10.yellow(`
|
|
16442
16663
|
${withheldNotice(workspace, loaded.withheld, loaded.trustStatus)}
|
|
16443
16664
|
`));
|
|
16444
16665
|
}
|
|
16445
16666
|
}
|
|
16446
16667
|
const config = loaded.config;
|
|
16447
16668
|
const toolSelection = applyBudgetFlags(options, config);
|
|
16448
|
-
setContextBudgetNotice((message) => console.log(
|
|
16669
|
+
setContextBudgetNotice((message) => console.log(chalk10.yellow(`
|
|
16449
16670
|
! ${message}`)));
|
|
16450
16671
|
const providerName = options.provider ?? config.provider;
|
|
16451
16672
|
if (!isProviderName(providerName)) {
|
|
@@ -16461,8 +16682,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
16461
16682
|
}
|
|
16462
16683
|
const hasFallbackChain = Boolean(config.fallback?.chain?.length);
|
|
16463
16684
|
if (providerName === "ultrac" && !hasFallbackChain && !await resolveUltracCredentials()) {
|
|
16464
|
-
console.log(
|
|
16465
|
-
console.log(`Run ${
|
|
16685
|
+
console.log(chalk10.yellow("You are not signed in to Ultrac."));
|
|
16686
|
+
console.log(`Run ${chalk10.cyan("ultrac login")} first, or start with ${chalk10.cyan("--provider echo")}.`);
|
|
16466
16687
|
process.exitCode = EXIT_REPL_NOT_STARTED;
|
|
16467
16688
|
return;
|
|
16468
16689
|
}
|
|
@@ -16474,26 +16695,26 @@ async function runRepl(options, initialTask = "") {
|
|
|
16474
16695
|
model,
|
|
16475
16696
|
fallback: config.fallback,
|
|
16476
16697
|
onFailover: (event) => {
|
|
16477
|
-
console.log(
|
|
16698
|
+
console.log(chalk10.yellow(formatFailoverNotice(event)));
|
|
16478
16699
|
if (event.to) {
|
|
16479
|
-
console.log(
|
|
16700
|
+
console.log(chalk10.gray(` now answering: provider ${event.to.provider} \xB7 model ${event.to.model}`));
|
|
16480
16701
|
}
|
|
16481
16702
|
},
|
|
16482
16703
|
// A tool-calling turn served by an entry without native tool calling still
|
|
16483
16704
|
// answers, but over the JSON tool protocol. Say so rather than letting the
|
|
16484
16705
|
// banner's "native tools" claim go stale.
|
|
16485
|
-
onDowngrade: (event) => console.log(
|
|
16706
|
+
onDowngrade: (event) => console.log(chalk10.yellow(formatDowngradeNotice(event))),
|
|
16486
16707
|
onRetry: (event) => {
|
|
16487
16708
|
diagnosticEvent("provider.retry", event);
|
|
16488
16709
|
console.log(
|
|
16489
|
-
|
|
16710
|
+
chalk10.yellow(
|
|
16490
16711
|
` retrying ${event.target.provider} (${event.target.model}) in ${event.delayMs}ms after ${event.classification.reason}`
|
|
16491
16712
|
)
|
|
16492
16713
|
);
|
|
16493
16714
|
}
|
|
16494
16715
|
});
|
|
16495
16716
|
} catch (error) {
|
|
16496
|
-
console.log(
|
|
16717
|
+
console.log(chalk10.red(error instanceof Error ? error.message : String(error)));
|
|
16497
16718
|
process.exitCode = EXIT_REPL_NOT_STARTED;
|
|
16498
16719
|
return;
|
|
16499
16720
|
}
|
|
@@ -16525,7 +16746,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
16525
16746
|
permissions: basePermissions
|
|
16526
16747
|
});
|
|
16527
16748
|
const writeCheckpoint = createSessionCheckpointWriter(workspace, (message) => {
|
|
16528
|
-
console.error(
|
|
16749
|
+
console.error(chalk10.yellow(message));
|
|
16529
16750
|
});
|
|
16530
16751
|
const persistSession = async (target = session) => {
|
|
16531
16752
|
const snapshot = target.toSession();
|
|
@@ -16543,7 +16764,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
16543
16764
|
diagnosticPath2
|
|
16544
16765
|
);
|
|
16545
16766
|
if (mcpRuntime && mcpRuntime.statuses.length > 0) {
|
|
16546
|
-
console.log(
|
|
16767
|
+
console.log(chalk10.bold("\nMCP servers"));
|
|
16547
16768
|
console.log(renderMcpStatuses(mcpRuntime.statuses));
|
|
16548
16769
|
}
|
|
16549
16770
|
try {
|
|
@@ -16553,24 +16774,25 @@ async function runRepl(options, initialTask = "") {
|
|
|
16553
16774
|
}
|
|
16554
16775
|
const update = await updateCheck;
|
|
16555
16776
|
if (update?.updateAvailable && update.latestVersion) {
|
|
16556
|
-
console.log(
|
|
16777
|
+
console.log(chalk10.yellow(`
|
|
16557
16778
|
\u2191 Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
|
|
16558
|
-
console.log(
|
|
16779
|
+
console.log(chalk10.gray(" Run `ultrac update` after this session."));
|
|
16559
16780
|
}
|
|
16560
16781
|
const rl = createInterface3({ input: stdin5, output: stdout5 });
|
|
16561
16782
|
const reader = new LineReader(rl);
|
|
16562
16783
|
const question = async (prompt, signal) => {
|
|
16563
16784
|
stdout5.write(prompt);
|
|
16564
|
-
return reader.
|
|
16785
|
+
return reader.nextFresh(signal);
|
|
16565
16786
|
};
|
|
16787
|
+
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
16566
16788
|
setAskUserRuntime(async (text, options2, signal) => {
|
|
16567
|
-
console.log(
|
|
16789
|
+
console.log(chalk10.cyan(`
|
|
16568
16790
|
? ${text}`));
|
|
16569
16791
|
if (options2?.length) {
|
|
16570
|
-
options2.forEach((option, index) => console.log(
|
|
16571
|
-
console.log(
|
|
16792
|
+
options2.forEach((option, index) => console.log(chalk10.gray(` ${index + 1}. ${option}`)));
|
|
16793
|
+
console.log(chalk10.gray(" (type a number, or answer in your own words)"));
|
|
16572
16794
|
}
|
|
16573
|
-
const rawAnswer = await question(
|
|
16795
|
+
const rawAnswer = await question(chalk10.cyan(" \u203A "), signal);
|
|
16574
16796
|
if (signal?.aborted) return null;
|
|
16575
16797
|
const answer = (rawAnswer ?? "").trim();
|
|
16576
16798
|
const picked = Number.parseInt(answer, 10);
|
|
@@ -16582,11 +16804,10 @@ async function runRepl(options, initialTask = "") {
|
|
|
16582
16804
|
let running = true;
|
|
16583
16805
|
let turnAbort;
|
|
16584
16806
|
rl.on("SIGINT", () => {
|
|
16585
|
-
if (turnAbort
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
console.log("\nUse /exit to end the session.");
|
|
16807
|
+
if (handleReplInterrupt(turnAbort) === "abort-turn") return;
|
|
16808
|
+
running = false;
|
|
16809
|
+
console.log(chalk10.gray("\nExiting ultrac\u2026"));
|
|
16810
|
+
rl.close();
|
|
16590
16811
|
});
|
|
16591
16812
|
const terminate = () => {
|
|
16592
16813
|
running = false;
|
|
@@ -16604,12 +16825,18 @@ async function runRepl(options, initialTask = "") {
|
|
|
16604
16825
|
};
|
|
16605
16826
|
try {
|
|
16606
16827
|
if (initialTask) {
|
|
16607
|
-
console.log(
|
|
16828
|
+
console.log(chalk10.cyan(`
|
|
16608
16829
|
\u203A ${initialTask}`));
|
|
16609
|
-
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession));
|
|
16830
|
+
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages));
|
|
16610
16831
|
}
|
|
16611
16832
|
while (running) {
|
|
16612
|
-
stdout5.
|
|
16833
|
+
if (stdout5.isTTY) {
|
|
16834
|
+
const composer = renderComposer(stdout5.columns, true);
|
|
16835
|
+
console.log(composer.frame);
|
|
16836
|
+
stdout5.write(composer.prompt);
|
|
16837
|
+
} else {
|
|
16838
|
+
stdout5.write(chalk10.cyan("\n\u203A "));
|
|
16839
|
+
}
|
|
16613
16840
|
const line = await reader.next();
|
|
16614
16841
|
if (line === null) break;
|
|
16615
16842
|
const raw = line.trim();
|
|
@@ -16625,9 +16852,9 @@ async function runRepl(options, initialTask = "") {
|
|
|
16625
16852
|
permissions: session.currentPermissions
|
|
16626
16853
|
});
|
|
16627
16854
|
resetContextBudgetNotices();
|
|
16628
|
-
console.log(
|
|
16855
|
+
console.log(chalk10.gray("Started a fresh conversation."));
|
|
16629
16856
|
}, async (prompt) => {
|
|
16630
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession);
|
|
16857
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages);
|
|
16631
16858
|
}, signal, async (id) => {
|
|
16632
16859
|
const lineage = await loadSessionLineage(workspace, id);
|
|
16633
16860
|
const restored = createSession({ model: session.model, mode: session.mode, permissions: session.currentPermissions });
|
|
@@ -16656,7 +16883,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
16656
16883
|
}
|
|
16657
16884
|
continue;
|
|
16658
16885
|
}
|
|
16659
|
-
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession));
|
|
16886
|
+
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages));
|
|
16660
16887
|
}
|
|
16661
16888
|
} finally {
|
|
16662
16889
|
process.off("SIGTERM", terminate);
|
|
@@ -16665,9 +16892,9 @@ async function runRepl(options, initialTask = "") {
|
|
|
16665
16892
|
await saveTranscript(session, writeCheckpoint);
|
|
16666
16893
|
mcpRuntime?.close();
|
|
16667
16894
|
}
|
|
16668
|
-
console.log(
|
|
16895
|
+
console.log(chalk10.gray("Goodbye."));
|
|
16669
16896
|
}
|
|
16670
|
-
async function runTurn(session, input, question, signal) {
|
|
16897
|
+
async function runTurn(session, input, question, signal, drainQueuedMessages) {
|
|
16671
16898
|
const spinner = new Spinner();
|
|
16672
16899
|
const detachProgress = attachProgressUi();
|
|
16673
16900
|
let deltaChars = 0;
|
|
@@ -16676,7 +16903,7 @@ async function runTurn(session, input, question, signal) {
|
|
|
16676
16903
|
...signal ? { signal } : {},
|
|
16677
16904
|
onInterrupted: () => {
|
|
16678
16905
|
spinner.stop();
|
|
16679
|
-
console.log(
|
|
16906
|
+
console.log(chalk10.dim("\n Interrupted \u2014 current work stopped. Session preserved."));
|
|
16680
16907
|
},
|
|
16681
16908
|
onModelStart: () => {
|
|
16682
16909
|
deltaChars = 0;
|
|
@@ -16694,34 +16921,41 @@ ${message.trim()}`);
|
|
|
16694
16921
|
}
|
|
16695
16922
|
},
|
|
16696
16923
|
onToolStart: (call) => {
|
|
16697
|
-
console.log(
|
|
16924
|
+
console.log(chalk10.dim(` ${toolActivityLabel(call)}`));
|
|
16698
16925
|
},
|
|
16699
|
-
onToolResult: (
|
|
16926
|
+
onToolResult: (_call, result) => printToolResult2(result),
|
|
16927
|
+
...drainQueuedMessages ? {
|
|
16928
|
+
drainQueuedMessages,
|
|
16929
|
+
onQueuedMessage: () => {
|
|
16930
|
+
spinner.stop();
|
|
16931
|
+
console.log(chalk10.dim(" Queued message added to the current turn."));
|
|
16932
|
+
}
|
|
16933
|
+
} : {},
|
|
16700
16934
|
onToolDenied: (call, permission) => {
|
|
16701
|
-
console.log(
|
|
16935
|
+
console.log(chalk10.yellow(` \u2717 denied ${permission} for ${call.tool}`));
|
|
16702
16936
|
},
|
|
16703
16937
|
onProviderNotice: (notice) => {
|
|
16704
|
-
console.log(
|
|
16938
|
+
console.log(chalk10.yellow(` ! ${notice}`));
|
|
16705
16939
|
},
|
|
16706
16940
|
onStopped: (reason) => {
|
|
16707
16941
|
if (reason === "aborted") return;
|
|
16708
16942
|
const line = stopReasonLine(reason);
|
|
16709
16943
|
const hint = turnStopHint(reason, session.maxSteps);
|
|
16710
16944
|
if (!line || !hint) return;
|
|
16711
|
-
console.log(
|
|
16945
|
+
console.log(chalk10.yellow(`
|
|
16712
16946
|
\u26A0 ${line}`));
|
|
16713
|
-
console.log(
|
|
16947
|
+
console.log(chalk10.gray(` ${hint}`));
|
|
16714
16948
|
},
|
|
16715
16949
|
requestPermission: async (call, permission) => {
|
|
16716
16950
|
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 `;
|
|
16717
|
-
const rawAnswer = await question(
|
|
16951
|
+
const rawAnswer = await question(chalk10.yellow(prompt), signal);
|
|
16718
16952
|
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
16719
16953
|
const answer = rawAnswer.trim().toLowerCase();
|
|
16720
16954
|
if (answer === "a" || answer === "always") return "always";
|
|
16721
16955
|
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes") return "once";
|
|
16722
16956
|
if (answer === "e" || answer === "never") {
|
|
16723
16957
|
console.log(
|
|
16724
|
-
|
|
16958
|
+
chalk10.gray(
|
|
16725
16959
|
` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
|
|
16726
16960
|
)
|
|
16727
16961
|
);
|
|
@@ -16739,9 +16973,9 @@ ${message.trim()}`);
|
|
|
16739
16973
|
detachProgress();
|
|
16740
16974
|
}
|
|
16741
16975
|
}
|
|
16742
|
-
async function runAndCheckpointTurn(session, input, question, signal, persist) {
|
|
16976
|
+
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages) {
|
|
16743
16977
|
try {
|
|
16744
|
-
return await runTurn(session, input, question, signal);
|
|
16978
|
+
return await runTurn(session, input, question, signal, drainQueuedMessages);
|
|
16745
16979
|
} finally {
|
|
16746
16980
|
await persist(session);
|
|
16747
16981
|
}
|
|
@@ -16758,27 +16992,27 @@ function turnStopHint(reason, maxSteps) {
|
|
|
16758
16992
|
}
|
|
16759
16993
|
function printTurnError(error) {
|
|
16760
16994
|
if (error instanceof ProviderChainError) {
|
|
16761
|
-
console.log(
|
|
16995
|
+
console.log(chalk10.red("\n \u2717 Turn failed: no provider in the chain could answer."));
|
|
16762
16996
|
for (const failure of error.failures) {
|
|
16763
16997
|
const attempts = failure.attempts === 1 ? "1 attempt" : `${failure.attempts} attempts`;
|
|
16764
16998
|
console.log(
|
|
16765
|
-
|
|
16999
|
+
chalk10.red(` - ${failure.provider} (${failure.model}), ${attempts}: ${errorMessage2(failure.error)}`)
|
|
16766
17000
|
);
|
|
16767
17001
|
}
|
|
16768
17002
|
console.log(
|
|
16769
|
-
|
|
17003
|
+
chalk10.gray(" Fix the provider (ultrac login, or --provider echo) and try again; the session is still open.")
|
|
16770
17004
|
);
|
|
16771
17005
|
return;
|
|
16772
17006
|
}
|
|
16773
|
-
console.log(
|
|
17007
|
+
console.log(chalk10.red(`
|
|
16774
17008
|
\u2717 Turn failed: ${errorMessage2(error)}`));
|
|
16775
|
-
console.log(
|
|
17009
|
+
console.log(chalk10.gray(" The session is still open. Try again, or /exit to end it."));
|
|
16776
17010
|
}
|
|
16777
17011
|
async function saveTranscript(session, writeCheckpoint) {
|
|
16778
17012
|
const snapshot = session.toSession();
|
|
16779
17013
|
if (!hasSavedSessionContent(snapshot)) return;
|
|
16780
17014
|
const stored = await writeCheckpoint(snapshot);
|
|
16781
|
-
if (stored) console.log(
|
|
17015
|
+
if (stored) console.log(chalk10.gray(`
|
|
16782
17016
|
Session saved: ${stored}`));
|
|
16783
17017
|
}
|
|
16784
17018
|
function hasSavedSessionContent(session) {
|
|
@@ -16820,7 +17054,7 @@ async function handleSlashCommand(raw, session, providerName, workspace, onClear
|
|
|
16820
17054
|
if (extra) console.log(`
|
|
16821
17055
|
${extra}`);
|
|
16822
17056
|
for (const conflict of index.conflicts) {
|
|
16823
|
-
console.log(
|
|
17057
|
+
console.log(chalk10.yellow(` ! ${conflict.message}`));
|
|
16824
17058
|
}
|
|
16825
17059
|
return "ok";
|
|
16826
17060
|
}
|
|
@@ -16834,7 +17068,7 @@ ${extra}`);
|
|
|
16834
17068
|
case "model":
|
|
16835
17069
|
if (argument) {
|
|
16836
17070
|
session.setModel(argument);
|
|
16837
|
-
console.log(
|
|
17071
|
+
console.log(chalk10.gray(`Model set to ${argument}.`));
|
|
16838
17072
|
} else {
|
|
16839
17073
|
console.log(`Model: ${session.model}`);
|
|
16840
17074
|
}
|
|
@@ -16842,10 +17076,10 @@ ${extra}`);
|
|
|
16842
17076
|
case "mode":
|
|
16843
17077
|
if (argument) {
|
|
16844
17078
|
if (!isAgentModeName(argument)) {
|
|
16845
|
-
console.log(
|
|
17079
|
+
console.log(chalk10.red(`Unknown mode. Expected one of: ${agentModeNames.join(", ")}.`));
|
|
16846
17080
|
} else {
|
|
16847
17081
|
session.setMode(argument);
|
|
16848
|
-
console.log(
|
|
17082
|
+
console.log(chalk10.gray(`Mode set to ${argument}.`));
|
|
16849
17083
|
}
|
|
16850
17084
|
} else {
|
|
16851
17085
|
console.log(`Mode: ${session.mode}`);
|
|
@@ -16894,7 +17128,7 @@ ${extra}`);
|
|
|
16894
17128
|
console.log(
|
|
16895
17129
|
`Selected ${info.selectedMessages.toLocaleString("en-US")} of ${info.messages.toLocaleString("en-US")} messages; would omit ${info.omittedMessages.toLocaleString("en-US")} messages in ${info.omittedTurns.toLocaleString("en-US")} earlier turns. Prompt headroom ${info.promptRemainingTokens.toLocaleString("en-US")} tokens; window headroom ${info.windowRemainingTokens.toLocaleString("en-US")} of ${info.contextTokens.toLocaleString("en-US")} tokens.`
|
|
16896
17130
|
);
|
|
16897
|
-
if (info.error) console.log(
|
|
17131
|
+
if (info.error) console.log(chalk10.yellow(`Context error: ${info.error}`));
|
|
16898
17132
|
return "ok";
|
|
16899
17133
|
}
|
|
16900
17134
|
case "compact":
|
|
@@ -16902,7 +17136,7 @@ ${extra}`);
|
|
|
16902
17136
|
await session.compactContext(argument, signal);
|
|
16903
17137
|
console.log("Context compacted. User instructions were retained; earlier activity was replaced by a bounded continuation record.");
|
|
16904
17138
|
} catch (error) {
|
|
16905
|
-
console.log(
|
|
17139
|
+
console.log(chalk10.red(errorMessage2(error)));
|
|
16906
17140
|
}
|
|
16907
17141
|
return "ok";
|
|
16908
17142
|
case "resume":
|
|
@@ -16913,7 +17147,7 @@ ${extra}`);
|
|
|
16913
17147
|
try {
|
|
16914
17148
|
await resume(argument);
|
|
16915
17149
|
} catch (error) {
|
|
16916
|
-
console.log(
|
|
17150
|
+
console.log(chalk10.red(errorMessage2(error)));
|
|
16917
17151
|
}
|
|
16918
17152
|
return "ok";
|
|
16919
17153
|
case "save": {
|
|
@@ -16964,7 +17198,7 @@ ${extra}`);
|
|
|
16964
17198
|
const refused = session.refusedPermissions;
|
|
16965
17199
|
if (refused.length) {
|
|
16966
17200
|
console.log(
|
|
16967
|
-
|
|
17201
|
+
chalk10.gray(`refused for this session: ${refused.join(", ")} (undo with /allow <perm>)`)
|
|
16968
17202
|
);
|
|
16969
17203
|
}
|
|
16970
17204
|
return "ok";
|
|
@@ -16973,9 +17207,9 @@ ${extra}`);
|
|
|
16973
17207
|
case "allow": {
|
|
16974
17208
|
if (argument === "write" || argument === "shell" || argument === "network") {
|
|
16975
17209
|
session.setPermission(argument, command === "allow");
|
|
16976
|
-
console.log(
|
|
17210
|
+
console.log(chalk10.gray(`${command === "allow" ? "Granted" : "Revoked"} ${argument} for this session.`));
|
|
16977
17211
|
} else {
|
|
16978
|
-
console.log(
|
|
17212
|
+
console.log(chalk10.red(`Usage: /${command} <write|shell|network>`));
|
|
16979
17213
|
}
|
|
16980
17214
|
return "ok";
|
|
16981
17215
|
}
|
|
@@ -16994,7 +17228,7 @@ ${extra}`);
|
|
|
16994
17228
|
const names = session.advertisedToolNames;
|
|
16995
17229
|
console.log(`Advertised to the model this turn (${names.length}): ${names.join(", ")}`);
|
|
16996
17230
|
console.log(
|
|
16997
|
-
|
|
17231
|
+
chalk10.gray(
|
|
16998
17232
|
"Registered tools remain subject to permission and mode checks. `ultrac tools` lists them all with schemas; --tools all advertises them all."
|
|
16999
17233
|
)
|
|
17000
17234
|
);
|
|
@@ -17003,7 +17237,7 @@ ${extra}`);
|
|
|
17003
17237
|
case "memory": {
|
|
17004
17238
|
const memories = await listMemories(workspace);
|
|
17005
17239
|
if (!memories.length) {
|
|
17006
|
-
console.log(
|
|
17240
|
+
console.log(chalk10.gray("No stored memories. Add one with `ultrac memory add`."));
|
|
17007
17241
|
} else {
|
|
17008
17242
|
for (const memory of memories) {
|
|
17009
17243
|
console.log(`- [${memory.scope}/${memory.name}] (${memory.type}) ${memory.description}`);
|
|
@@ -17014,7 +17248,7 @@ ${extra}`);
|
|
|
17014
17248
|
case "skills": {
|
|
17015
17249
|
const skills = await listSkills(workspace);
|
|
17016
17250
|
if (!skills.length) {
|
|
17017
|
-
console.log(
|
|
17251
|
+
console.log(chalk10.gray("No skills installed. Create one with `ultrac skills create`."));
|
|
17018
17252
|
} else {
|
|
17019
17253
|
for (const skill of skills) {
|
|
17020
17254
|
console.log(`- ${skill.name} (${skill.scope}): ${skill.description}`);
|
|
@@ -17030,12 +17264,12 @@ ${extra}`);
|
|
|
17030
17264
|
force: true
|
|
17031
17265
|
});
|
|
17032
17266
|
if (!update.latestVersion) {
|
|
17033
|
-
console.log(
|
|
17267
|
+
console.log(chalk10.yellow("Could not check npm. Confirm npm access, then run `ultrac update --check` outside this session."));
|
|
17034
17268
|
} else if (update.updateAvailable) {
|
|
17035
|
-
console.log(
|
|
17036
|
-
console.log(
|
|
17269
|
+
console.log(chalk10.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
|
|
17270
|
+
console.log(chalk10.gray("Exit this session, then run `ultrac update`."));
|
|
17037
17271
|
} else {
|
|
17038
|
-
console.log(
|
|
17272
|
+
console.log(chalk10.green(`ultrac ${update.currentVersion} is up to date.`));
|
|
17039
17273
|
}
|
|
17040
17274
|
return "ok";
|
|
17041
17275
|
}
|
|
@@ -17047,27 +17281,27 @@ async function runSkillCommand(session, raw, workspace, question) {
|
|
|
17047
17281
|
const resolved = await resolveSkillCommand(workspace, raw);
|
|
17048
17282
|
if (resolved.status === "ok") {
|
|
17049
17283
|
console.log(
|
|
17050
|
-
|
|
17284
|
+
chalk10.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
|
|
17051
17285
|
);
|
|
17052
17286
|
await runTurn(session, resolved.prompt, question);
|
|
17053
17287
|
return;
|
|
17054
17288
|
}
|
|
17055
17289
|
if (resolved.status === "conflict") {
|
|
17056
|
-
console.log(
|
|
17290
|
+
console.log(chalk10.red(resolved.message));
|
|
17057
17291
|
return;
|
|
17058
17292
|
}
|
|
17059
17293
|
if (resolved.status === "builtin") {
|
|
17060
|
-
console.log(
|
|
17294
|
+
console.log(chalk10.red(`/${resolved.command} is a built-in command and was not handled. Try /help.`));
|
|
17061
17295
|
return;
|
|
17062
17296
|
}
|
|
17063
17297
|
if (resolved.status === "unknown") {
|
|
17064
|
-
console.log(
|
|
17298
|
+
console.log(chalk10.red(`Unknown command: /${resolved.command}. Try /help.`));
|
|
17065
17299
|
if (resolved.available.length) {
|
|
17066
|
-
console.log(
|
|
17300
|
+
console.log(chalk10.gray(`Skill commands: ${resolved.available.map((name) => `/${name}`).join(", ")}`));
|
|
17067
17301
|
}
|
|
17068
17302
|
return;
|
|
17069
17303
|
}
|
|
17070
|
-
console.log(
|
|
17304
|
+
console.log(chalk10.red("Unknown command. Try /help."));
|
|
17071
17305
|
}
|
|
17072
17306
|
var LineReader = class {
|
|
17073
17307
|
queue = [];
|
|
@@ -17090,9 +17324,36 @@ var LineReader = class {
|
|
|
17090
17324
|
});
|
|
17091
17325
|
}
|
|
17092
17326
|
next(signal) {
|
|
17327
|
+
return this.waitForLine(signal, true);
|
|
17328
|
+
}
|
|
17329
|
+
/**
|
|
17330
|
+
* Nested prompts must not consume steering messages entered before the
|
|
17331
|
+
* question appeared. They wait for the next line while the queue stays put.
|
|
17332
|
+
*/
|
|
17333
|
+
nextFresh(signal) {
|
|
17334
|
+
return this.waitForLine(signal, false);
|
|
17335
|
+
}
|
|
17336
|
+
/**
|
|
17337
|
+
* Drain ordinary messages in arrival order. A slash command is a control
|
|
17338
|
+
* boundary and remains queued for the main REPL loop along with everything
|
|
17339
|
+
* after it.
|
|
17340
|
+
*/
|
|
17341
|
+
drainQueuedMessages() {
|
|
17342
|
+
const messages = [];
|
|
17343
|
+
while (this.queue.length) {
|
|
17344
|
+
const next = this.queue[0].trim();
|
|
17345
|
+
if (next.startsWith("/")) break;
|
|
17346
|
+
this.queue.shift();
|
|
17347
|
+
if (next) messages.push(next);
|
|
17348
|
+
}
|
|
17349
|
+
return messages;
|
|
17350
|
+
}
|
|
17351
|
+
waitForLine(signal, includeBuffered) {
|
|
17093
17352
|
if (signal?.aborted) return Promise.resolve(null);
|
|
17094
|
-
|
|
17095
|
-
|
|
17353
|
+
if (includeBuffered) {
|
|
17354
|
+
const buffered = this.queue.shift();
|
|
17355
|
+
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
17356
|
+
}
|
|
17096
17357
|
if (this.closed) return Promise.resolve(null);
|
|
17097
17358
|
return new Promise((resolve15) => {
|
|
17098
17359
|
const waiter = { resolve: resolve15, ...signal ? { signal } : {} };
|
|
@@ -17117,13 +17378,27 @@ var LineReader = class {
|
|
|
17117
17378
|
waiter.resolve(line);
|
|
17118
17379
|
}
|
|
17119
17380
|
};
|
|
17381
|
+
function handleReplInterrupt(activeTurn) {
|
|
17382
|
+
if (activeTurn && !activeTurn.signal.aborted) {
|
|
17383
|
+
activeTurn.abort();
|
|
17384
|
+
return "abort-turn";
|
|
17385
|
+
}
|
|
17386
|
+
return "exit-session";
|
|
17387
|
+
}
|
|
17120
17388
|
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2) {
|
|
17121
|
-
console.log(
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17389
|
+
console.log(renderWelcome(
|
|
17390
|
+
{
|
|
17391
|
+
version,
|
|
17392
|
+
providerName,
|
|
17393
|
+
model,
|
|
17394
|
+
mode,
|
|
17395
|
+
workspace,
|
|
17396
|
+
protocol,
|
|
17397
|
+
budget,
|
|
17398
|
+
diagnosticPath: diagnosticPath2
|
|
17399
|
+
},
|
|
17400
|
+
{ columns: stdout5.columns, color: stdout5.isTTY }
|
|
17401
|
+
));
|
|
17127
17402
|
}
|
|
17128
17403
|
function describeBudget(mode, selection, registry) {
|
|
17129
17404
|
const window = resolveContextWindow();
|
|
@@ -17140,20 +17415,12 @@ function describeAdvertisedTools(mode, selection, registry = createDefaultToolRe
|
|
|
17140
17415
|
};
|
|
17141
17416
|
}
|
|
17142
17417
|
function printToolResult2(result) {
|
|
17143
|
-
const
|
|
17144
|
-
console.log(
|
|
17145
|
-
const preview = result.content.split("\n").slice(0, 6).join("\n");
|
|
17146
|
-
if (preview.trim()) {
|
|
17147
|
-
console.log(chalk9.gray(indent3(truncate2(preview, 800))));
|
|
17148
|
-
}
|
|
17418
|
+
const failure = toolFailurePreview(result);
|
|
17419
|
+
if (failure) console.log(chalk10.red(` Failed: ${failure}`));
|
|
17149
17420
|
for (const warning of getMcpArchiveWarnings(result)) {
|
|
17150
|
-
console.log(
|
|
17421
|
+
console.log(chalk10.yellow(indent3(`Warning: ${warning}`)));
|
|
17151
17422
|
}
|
|
17152
17423
|
}
|
|
17153
|
-
function compactInput(input) {
|
|
17154
|
-
const text = JSON.stringify(input);
|
|
17155
|
-
return text.length > 80 ? `${text.slice(0, 77)}...` : text;
|
|
17156
|
-
}
|
|
17157
17424
|
function shellApprovalPrompt(call, workspace) {
|
|
17158
17425
|
const command = typeof call.input.command === "string" ? call.input.command : "<invalid command>";
|
|
17159
17426
|
const requestedCwd = typeof call.input.cwd === "string" ? call.input.cwd : ".";
|
|
@@ -17173,15 +17440,12 @@ function shellApprovalPrompt(call, workspace) {
|
|
|
17173
17440
|
" [o]nce runs only this command / [a]lways grants shell access for this whole session, including other shell-using tools / [n]o (this call) / n[e]ver (this session) \u203A "
|
|
17174
17441
|
].join("\n");
|
|
17175
17442
|
}
|
|
17176
|
-
function truncate2(value, max) {
|
|
17177
|
-
return value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
17178
|
-
}
|
|
17179
17443
|
function indent3(value) {
|
|
17180
17444
|
return value.split("\n").map((line) => ` ${line}`).join("\n");
|
|
17181
17445
|
}
|
|
17182
17446
|
|
|
17183
17447
|
// src/commands/run.ts
|
|
17184
|
-
import
|
|
17448
|
+
import chalk11 from "chalk";
|
|
17185
17449
|
var EXIT_INCOMPLETE_RUN = 2;
|
|
17186
17450
|
function registerRunCommand(program2) {
|
|
17187
17451
|
program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
|
|
@@ -17195,17 +17459,17 @@ function registerRunCommand(program2) {
|
|
|
17195
17459
|
}
|
|
17196
17460
|
const workspace = await assertWorkspaceExists(options.workspace);
|
|
17197
17461
|
const diagnosticPath2 = configureDiagnosticLog(workspace, "run");
|
|
17198
|
-
console.error(
|
|
17462
|
+
console.error(chalk11.gray(`Diagnostic log: ${diagnosticPath2}`));
|
|
17199
17463
|
if (options.baseUrl) {
|
|
17200
17464
|
setBaseUrlFlag(options.baseUrl);
|
|
17201
17465
|
}
|
|
17202
17466
|
const loaded = await loadConfigWithTrust(workspace);
|
|
17203
17467
|
const config = loaded.config;
|
|
17204
17468
|
if (loaded.needsApproval) {
|
|
17205
|
-
console.error(
|
|
17469
|
+
console.error(chalk11.yellow(withheldNotice(workspace, loaded.withheld, loaded.trustStatus)));
|
|
17206
17470
|
}
|
|
17207
17471
|
const toolSelection = applyBudgetFlags(options, config);
|
|
17208
|
-
setContextBudgetNotice((message) => console.error(
|
|
17472
|
+
setContextBudgetNotice((message) => console.error(chalk11.yellow(message)));
|
|
17209
17473
|
const providerName = options.provider ?? config.provider;
|
|
17210
17474
|
const mode = options.mode ?? config.mode;
|
|
17211
17475
|
const previousSessionLineage = options.continue ? await loadSessionLineage(workspace, options.continue) : void 0;
|
|
@@ -17226,14 +17490,14 @@ function registerRunCommand(program2) {
|
|
|
17226
17490
|
model,
|
|
17227
17491
|
fallback: config.fallback,
|
|
17228
17492
|
// stderr, so --json keeps stdout parseable.
|
|
17229
|
-
onFailover: (event) => console.error(
|
|
17493
|
+
onFailover: (event) => console.error(chalk11.yellow(formatFailoverNotice(event))),
|
|
17230
17494
|
// AgentRunner uses native tool calls when supported; wired
|
|
17231
17495
|
// anyway so a non-interactive run stays honest if that ever changes.
|
|
17232
|
-
onDowngrade: (event) => console.error(
|
|
17496
|
+
onDowngrade: (event) => console.error(chalk11.yellow(formatDowngradeNotice(event))),
|
|
17233
17497
|
onRetry: (event) => {
|
|
17234
17498
|
diagnosticEvent("provider.retry", event);
|
|
17235
17499
|
console.error(
|
|
17236
|
-
|
|
17500
|
+
chalk11.yellow(
|
|
17237
17501
|
`Retrying ${event.target.provider} (${event.target.model}) in ${event.delayMs}ms after ${event.classification.reason}`
|
|
17238
17502
|
)
|
|
17239
17503
|
);
|
|
@@ -17244,13 +17508,13 @@ function registerRunCommand(program2) {
|
|
|
17244
17508
|
setSubagentRuntime({ provider, model, mode, registry });
|
|
17245
17509
|
try {
|
|
17246
17510
|
if (mcpRuntime && mcpRuntime.statuses.length > 0) {
|
|
17247
|
-
console.error(
|
|
17511
|
+
console.error(chalk11.bold("MCP servers"));
|
|
17248
17512
|
console.error(renderMcpStatuses(mcpRuntime.statuses));
|
|
17249
17513
|
}
|
|
17250
17514
|
const detachProgress = attachProgressUi();
|
|
17251
17515
|
const runner = new AgentRunner(registry);
|
|
17252
17516
|
const writeCheckpoint = createSessionCheckpointWriter(workspace, (message) => {
|
|
17253
|
-
console.error(
|
|
17517
|
+
console.error(chalk11.yellow(message));
|
|
17254
17518
|
});
|
|
17255
17519
|
const controller = new AbortController();
|
|
17256
17520
|
const abortRun = () => controller.abort();
|
|
@@ -17300,7 +17564,7 @@ function registerRunCommand(program2) {
|
|
|
17300
17564
|
}
|
|
17301
17565
|
const stopReason = result.session.stopReason;
|
|
17302
17566
|
for (const warning of getSessionMcpArchiveWarnings(result.session)) {
|
|
17303
|
-
console.error(
|
|
17567
|
+
console.error(chalk11.yellow(`Warning: ${warning}`));
|
|
17304
17568
|
}
|
|
17305
17569
|
if (options.json) {
|
|
17306
17570
|
console.log(
|
|
@@ -17585,7 +17849,7 @@ function parsePermissions(list) {
|
|
|
17585
17849
|
}
|
|
17586
17850
|
|
|
17587
17851
|
// src/commands/tasks.ts
|
|
17588
|
-
import
|
|
17852
|
+
import chalk12 from "chalk";
|
|
17589
17853
|
|
|
17590
17854
|
// src/runs/api.ts
|
|
17591
17855
|
var RUN_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled"];
|
|
@@ -18121,7 +18385,7 @@ function registerTasksCommand(program2, deps = {}) {
|
|
|
18121
18385
|
}
|
|
18122
18386
|
r.out(formatRunTable(page.data, r.now()));
|
|
18123
18387
|
if (page.next_cursor) {
|
|
18124
|
-
r.err(
|
|
18388
|
+
r.err(chalk12.gray(`More: ultrac tasks --cursor ${page.next_cursor}
|
|
18125
18389
|
`));
|
|
18126
18390
|
}
|
|
18127
18391
|
});
|
|
@@ -18180,7 +18444,7 @@ async function runTask(promptParts, options, deps) {
|
|
|
18180
18444
|
`);
|
|
18181
18445
|
} else {
|
|
18182
18446
|
r.err(
|
|
18183
|
-
|
|
18447
|
+
chalk12.gray(
|
|
18184
18448
|
`Run ${run.id} queued. Following; Ctrl-C ${options.detach ? "leaves it running" : "cancels it"}.
|
|
18185
18449
|
`
|
|
18186
18450
|
)
|
|
@@ -18198,7 +18462,7 @@ async function follow(r, runId, options) {
|
|
|
18198
18462
|
controller.abort();
|
|
18199
18463
|
return;
|
|
18200
18464
|
}
|
|
18201
|
-
r.err(
|
|
18465
|
+
r.err(chalk12.yellow(`
|
|
18202
18466
|
Leaving run ${runId} as it is.
|
|
18203
18467
|
`));
|
|
18204
18468
|
r.exit(EXIT_CANCELLED);
|
|
@@ -18214,7 +18478,7 @@ Leaving run ${runId} as it is.
|
|
|
18214
18478
|
if (status === "running") {
|
|
18215
18479
|
const attempt = typeof data?.attempt === "number" ? data.attempt : null;
|
|
18216
18480
|
if (data?.restarted === true || attempt !== null && attempt > 1) {
|
|
18217
|
-
r.err(
|
|
18481
|
+
r.err(chalk12.yellow(`${lead()}[attempt ${attempt ?? "?"} restarted; streaming from the start]
|
|
18218
18482
|
`));
|
|
18219
18483
|
}
|
|
18220
18484
|
printed = "";
|
|
@@ -18229,7 +18493,7 @@ Leaving run ${runId} as it is.
|
|
|
18229
18493
|
return;
|
|
18230
18494
|
}
|
|
18231
18495
|
case "tool_call": {
|
|
18232
|
-
r.err(
|
|
18496
|
+
r.err(chalk12.cyan(`${lead()}[tool_call ${String(data?.name ?? "")} ${String(data?.arguments ?? "")}]
|
|
18233
18497
|
`));
|
|
18234
18498
|
return;
|
|
18235
18499
|
}
|
|
@@ -18269,7 +18533,7 @@ Leaving run ${runId} as it is.
|
|
|
18269
18533
|
`);
|
|
18270
18534
|
} else {
|
|
18271
18535
|
const seconds = Math.round(info.delayMs / 100) / 10;
|
|
18272
|
-
r.err(
|
|
18536
|
+
r.err(chalk12.gray(`${lead()}[reconnecting in ${seconds}s${info.message ? `: ${info.message}` : ""}]
|
|
18273
18537
|
`));
|
|
18274
18538
|
}
|
|
18275
18539
|
}
|
|
@@ -18301,27 +18565,27 @@ Leaving run ${runId} as it is.
|
|
|
18301
18565
|
return result.outputSource === "incomplete" ? EXIT_FAILED : exitCodeFor(result.status);
|
|
18302
18566
|
}
|
|
18303
18567
|
if (result.outputSource === "incomplete") {
|
|
18304
|
-
r.err(
|
|
18568
|
+
r.err(chalk12.yellow(lead() + "[stored task output could not be confirmed; showing text received from the stream]\n"));
|
|
18305
18569
|
}
|
|
18306
18570
|
switch (result.status) {
|
|
18307
18571
|
case "succeeded":
|
|
18308
18572
|
finishHuman(result.text);
|
|
18309
18573
|
if (result.toolCalls.length && !result.text) {
|
|
18310
18574
|
const count3 = result.toolCalls.length;
|
|
18311
|
-
r.err(
|
|
18575
|
+
r.err(chalk12.gray(`Done with ${count3} tool call${count3 === 1 ? "" : "s"} and no text.
|
|
18312
18576
|
`));
|
|
18313
18577
|
}
|
|
18314
18578
|
return result.outputSource === "incomplete" ? EXIT_FAILED : EXIT_DONE;
|
|
18315
18579
|
case "failed": {
|
|
18316
18580
|
finishHuman(result.text);
|
|
18317
18581
|
const code = result.error?.code ? ` (${result.error.code})` : "";
|
|
18318
|
-
r.err(
|
|
18582
|
+
r.err(chalk12.red(`Run ${runId} failed${code}: ${result.error?.message ?? "The task failed."}
|
|
18319
18583
|
`));
|
|
18320
18584
|
return EXIT_FAILED;
|
|
18321
18585
|
}
|
|
18322
18586
|
case "cancelled":
|
|
18323
18587
|
finishHuman(result.text);
|
|
18324
|
-
r.err(
|
|
18588
|
+
r.err(chalk12.yellow(`Run ${runId} was cancelled.
|
|
18325
18589
|
`));
|
|
18326
18590
|
return EXIT_CANCELLED;
|
|
18327
18591
|
}
|
|
@@ -18334,13 +18598,13 @@ async function interrupted(r, runId, options, printed) {
|
|
|
18334
18598
|
`);
|
|
18335
18599
|
} else {
|
|
18336
18600
|
r.err(
|
|
18337
|
-
|
|
18601
|
+
chalk12.yellow(`${lead}Detached. Run ${runId} continues; follow it again with \`ultrac tasks follow ${runId}\`.
|
|
18338
18602
|
`)
|
|
18339
18603
|
);
|
|
18340
18604
|
}
|
|
18341
18605
|
return EXIT_CANCELLED;
|
|
18342
18606
|
}
|
|
18343
|
-
if (!options.json) r.err(
|
|
18607
|
+
if (!options.json) r.err(chalk12.yellow(`${lead}Cancelling run ${runId}...
|
|
18344
18608
|
`));
|
|
18345
18609
|
try {
|
|
18346
18610
|
const cancelled = await r.client.cancelRun(runId);
|
|
@@ -18348,7 +18612,7 @@ async function interrupted(r, runId, options, printed) {
|
|
|
18348
18612
|
r.out(`${JSON.stringify({ id: null, event: "cancel", data: cancelled })}
|
|
18349
18613
|
`);
|
|
18350
18614
|
} else {
|
|
18351
|
-
r.err(
|
|
18615
|
+
r.err(chalk12.yellow(`${describeCancel(cancelled)}
|
|
18352
18616
|
`));
|
|
18353
18617
|
}
|
|
18354
18618
|
} catch (error) {
|
|
@@ -18357,12 +18621,12 @@ async function interrupted(r, runId, options, printed) {
|
|
|
18357
18621
|
r.out(`${JSON.stringify({ id: null, event: "cancel_failed", data: { runId, message } })}
|
|
18358
18622
|
`);
|
|
18359
18623
|
} else if (error instanceof UltracApiError && error.code === "run_already_finished") {
|
|
18360
|
-
r.err(
|
|
18624
|
+
r.err(chalk12.yellow(`${error.message} Nothing to cancel.
|
|
18361
18625
|
`));
|
|
18362
18626
|
} else {
|
|
18363
|
-
r.err(
|
|
18627
|
+
r.err(chalk12.red(`Could not cancel run ${runId}: ${message}
|
|
18364
18628
|
`));
|
|
18365
|
-
r.err(
|
|
18629
|
+
r.err(chalk12.gray(`Try again with \`ultrac tasks cancel ${runId}\`.
|
|
18366
18630
|
`));
|
|
18367
18631
|
}
|
|
18368
18632
|
}
|
|
@@ -18474,7 +18738,7 @@ ${run.output}${run.output.endsWith("\n") ? "" : "\n"}`;
|
|
|
18474
18738
|
|
|
18475
18739
|
// src/commands/tools.ts
|
|
18476
18740
|
import { resolve as resolve14 } from "path";
|
|
18477
|
-
import
|
|
18741
|
+
import chalk13 from "chalk";
|
|
18478
18742
|
function registerToolsCommand(program2) {
|
|
18479
18743
|
const toolsCommand = program2.command("tools");
|
|
18480
18744
|
toolsCommand.description("List built-in agent tools").option("--json", "Print tool metadata as JSON", false).option("--mode <name>", `Only the tools a mode advertises: ${agentModeNames.join(", ")}`).option("--budget", "Print what each mode's tool set costs in characters", false).action((options) => {
|
|
@@ -18494,7 +18758,7 @@ function registerToolsCommand(program2) {
|
|
|
18494
18758
|
printTools(tools);
|
|
18495
18759
|
if (options.mode) {
|
|
18496
18760
|
console.log(
|
|
18497
|
-
|
|
18761
|
+
chalk13.gray(
|
|
18498
18762
|
`
|
|
18499
18763
|
${tools.length} of ${all.length} tools advertised in ${options.mode} mode, ${serializedToolsSize(
|
|
18500
18764
|
toToolSchemas(tools)
|
|
@@ -18543,11 +18807,11 @@ function printToolBudget(all, json) {
|
|
|
18543
18807
|
}
|
|
18544
18808
|
for (const row of [...rows, { ...everything, names: all.map((tool) => tool.name) }]) {
|
|
18545
18809
|
console.log(
|
|
18546
|
-
`${
|
|
18810
|
+
`${chalk13.cyan(row.mode.padEnd(9))} ${String(row.tools).padStart(2)} tools ${String(
|
|
18547
18811
|
row.chars.toLocaleString("en-US")
|
|
18548
18812
|
).padStart(7)} chars`
|
|
18549
18813
|
);
|
|
18550
|
-
console.log(
|
|
18814
|
+
console.log(chalk13.gray(` ${row.names.join(", ")}`));
|
|
18551
18815
|
}
|
|
18552
18816
|
}
|
|
18553
18817
|
function parseToolInput(inputText) {
|
|
@@ -18559,7 +18823,7 @@ function parseToolInput(inputText) {
|
|
|
18559
18823
|
}
|
|
18560
18824
|
|
|
18561
18825
|
// src/commands/update.ts
|
|
18562
|
-
import
|
|
18826
|
+
import chalk14 from "chalk";
|
|
18563
18827
|
function registerUpdateCommand(program2) {
|
|
18564
18828
|
const packageInfo = readPackageInfo();
|
|
18565
18829
|
program2.command("update").alias("upgrade").description(`Check for and install the latest ${packageInfo.name} release`).option("--check", "Only check; do not install an available update", false).action(async (options) => {
|
|
@@ -18574,7 +18838,7 @@ function registerUpdateCommand(program2) {
|
|
|
18574
18838
|
);
|
|
18575
18839
|
}
|
|
18576
18840
|
if (!status.updateAvailable) {
|
|
18577
|
-
console.log(
|
|
18841
|
+
console.log(chalk14.green(`ultrac ${status.currentVersion} is up to date.`));
|
|
18578
18842
|
return;
|
|
18579
18843
|
}
|
|
18580
18844
|
if (options.check) {
|
|
@@ -18583,12 +18847,12 @@ function registerUpdateCommand(program2) {
|
|
|
18583
18847
|
}
|
|
18584
18848
|
console.log(`Updating ultrac ${status.currentVersion} \u2192 ${status.latestVersion}\u2026`);
|
|
18585
18849
|
await installLatestVersion(packageInfo.name);
|
|
18586
|
-
console.log(
|
|
18850
|
+
console.log(chalk14.green(`Updated to ultrac ${status.latestVersion}. Restart ultrac to use the new version.`));
|
|
18587
18851
|
});
|
|
18588
18852
|
}
|
|
18589
18853
|
function printAvailable(currentVersion, latestVersion) {
|
|
18590
|
-
console.log(
|
|
18591
|
-
console.log(
|
|
18854
|
+
console.log(chalk14.yellow(`Update available: ultrac ${currentVersion} \u2192 ${latestVersion}`));
|
|
18855
|
+
console.log(chalk14.gray("Run `ultrac update` to install it."));
|
|
18592
18856
|
}
|
|
18593
18857
|
|
|
18594
18858
|
// src/cli.ts
|
|
@@ -18618,6 +18882,6 @@ registerSkillsCommand(program);
|
|
|
18618
18882
|
registerReplCommand(program);
|
|
18619
18883
|
program.parseAsync(process.argv).catch((error) => {
|
|
18620
18884
|
const message = error instanceof Error ? error.message : String(error);
|
|
18621
|
-
console.error(
|
|
18885
|
+
console.error(chalk15.red(message));
|
|
18622
18886
|
process.exitCode = 1;
|
|
18623
18887
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spinalfluid/ultrac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Ultrac terminal agent harness for Cosmic 1, with local tools, browser sign-in, streamed answers, and resumable sessions.",
|
|
6
6
|
"author": "Aditya Pundir",
|