@standardagents/code 0.1.3 → 0.2.0
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/dist/index.js +211 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,69 @@ import crypto from 'crypto';
|
|
|
10
10
|
import readline from 'readline';
|
|
11
11
|
|
|
12
12
|
// src/api.ts
|
|
13
|
+
function classifyConnectError(err, endpoint) {
|
|
14
|
+
const host = endpoint.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
15
|
+
const codes = /* @__PURE__ */ new Set();
|
|
16
|
+
const messages = [];
|
|
17
|
+
for (let e = err; e; e = e.cause) {
|
|
18
|
+
if (typeof e.code === "string") codes.add(e.code);
|
|
19
|
+
if (typeof e.message === "string") messages.push(e.message);
|
|
20
|
+
if (Array.isArray(e.errors)) {
|
|
21
|
+
for (const sub of e.errors) {
|
|
22
|
+
if (typeof sub?.code === "string") codes.add(sub.code);
|
|
23
|
+
if (typeof sub?.message === "string") messages.push(sub.message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const text = messages.join(" | ");
|
|
28
|
+
const has = (...wanted) => wanted.some((w) => codes.has(w));
|
|
29
|
+
if (has("ENOTFOUND", "EAI_AGAIN")) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
reason: `Can't resolve ${host} \u2014 no such hostname from this machine.`,
|
|
33
|
+
hint: "The endpoint URL is probably wrong (typo, or that server isn't running). Check the URL, your VPN, and local DNS."
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (has("ECONNREFUSED")) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
reason: `${host} refused the connection \u2014 nothing is listening there.`,
|
|
40
|
+
hint: "The instance looks stopped. Start it (or check the port in the endpoint URL)."
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (has("EHOSTUNREACH", "ENETUNREACH", "ENETDOWN")) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: `No route to ${host} \u2014 the network looks down or unreachable.`,
|
|
47
|
+
hint: "Check your internet connection / VPN."
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (has("ETIMEDOUT", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_HEADERS_TIMEOUT") || err?.name === "TimeoutError") {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
reason: `${host} didn't answer in time.`,
|
|
54
|
+
hint: "The server may be overloaded or a firewall is eating the traffic. Try again."
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (has(
|
|
58
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
59
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
60
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
61
|
+
"CERT_HAS_EXPIRED",
|
|
62
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
63
|
+
) || /certificate|TLS|SSL/i.test(text)) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
reason: `TLS problem talking to ${host}: ${text.slice(0, 120)}`,
|
|
67
|
+
hint: "For local/self-signed endpoints, use the http:// URL or trust the local CA."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
reason: `Couldn't reach ${host}${text ? ` (${text.slice(0, 120)})` : ""}.`,
|
|
73
|
+
hint: "This is a connection problem, not a token problem \u2014 check the endpoint URL and your network."
|
|
74
|
+
};
|
|
75
|
+
}
|
|
13
76
|
var ApiClient = class {
|
|
14
77
|
constructor(endpoint, token) {
|
|
15
78
|
this.endpoint = endpoint;
|
|
@@ -47,12 +110,54 @@ var ApiClient = class {
|
|
|
47
110
|
}
|
|
48
111
|
}
|
|
49
112
|
async verify() {
|
|
113
|
+
return (await this.verifyDetailed()).ok;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check the endpoint + token and say PRECISELY what's wrong when they
|
|
117
|
+
* fail. "That token didn't work" is a lie when the real problem is a typo'd
|
|
118
|
+
* hostname, a stopped server, or a dead network — sign-in errors must let
|
|
119
|
+
* the user tell those apart from an actually-rejected key.
|
|
120
|
+
*/
|
|
121
|
+
async verifyDetailed() {
|
|
122
|
+
let res;
|
|
50
123
|
try {
|
|
51
|
-
await this.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
res = await fetch(`${this.endpoint}/api/auth/me`, {
|
|
125
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
126
|
+
signal: AbortSignal.timeout(15e3)
|
|
127
|
+
});
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return classifyConnectError(err, this.endpoint);
|
|
130
|
+
}
|
|
131
|
+
if (res.ok) return { ok: true };
|
|
132
|
+
if (res.status === 401) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
reason: "The instance rejected this token \u2014 it's invalid, expired, or was revoked.",
|
|
136
|
+
hint: "Create a fresh API key in the instance settings (or press Enter to sign in with your browser). Keys only work on the instance that minted them."
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (res.status === 403) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
reason: "The token was recognized, but this account isn't allowed to use the API here.",
|
|
143
|
+
hint: "Ask an instance admin to grant access, or use a different account's key."
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (res.status === 404 || res.status === 405) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
reason: `${this.endpoint} answered, but it doesn't look like a Standard Agents instance (no /api/auth/me).`,
|
|
150
|
+
hint: "Double-check the endpoint URL \u2014 this may be a different service."
|
|
151
|
+
};
|
|
55
152
|
}
|
|
153
|
+
if (res.status >= 500) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
reason: `The instance hit an error while checking the token (HTTP ${res.status}).`,
|
|
157
|
+
hint: "That's a server-side problem \u2014 check the instance's logs and try again."
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return { ok: false, reason: `The instance answered HTTP ${res.status} while checking the token.` };
|
|
56
161
|
}
|
|
57
162
|
/** Agent name → display title map (best effort; for labeling subagents). */
|
|
58
163
|
async listAgents() {
|
|
@@ -69,12 +174,22 @@ var ApiClient = class {
|
|
|
69
174
|
if (!id) throw new Error("Thread create returned no id");
|
|
70
175
|
return id;
|
|
71
176
|
}
|
|
72
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* List threads for an agent (or several variants of it), optionally
|
|
179
|
+
* filtering to those carrying all given tags. Multiple ids exist because
|
|
180
|
+
* the instance's model gearbox hands a session between agent variants — a
|
|
181
|
+
* thread that last ran in another gear must still show up for resume.
|
|
182
|
+
*/
|
|
73
183
|
async listThreads(agentId, requireTags) {
|
|
74
|
-
const
|
|
75
|
-
|
|
184
|
+
const ids = Array.isArray(agentId) ? agentId : [agentId];
|
|
185
|
+
const pages = await Promise.all(
|
|
186
|
+
ids.map(
|
|
187
|
+
(id) => this.json(
|
|
188
|
+
`/api/threads?agent_id=${encodeURIComponent(id)}&limit=100`
|
|
189
|
+
).catch(() => [])
|
|
190
|
+
)
|
|
76
191
|
);
|
|
77
|
-
const arr = Array.isArray(res) ? res : res.threads || [];
|
|
192
|
+
const arr = pages.flatMap((res) => Array.isArray(res) ? res : res.threads || []);
|
|
78
193
|
return arr.map((t) => ({
|
|
79
194
|
id: t.id,
|
|
80
195
|
tags: Array.isArray(t.tags) ? t.tags : [],
|
|
@@ -384,9 +499,6 @@ function newFileLines(content) {
|
|
|
384
499
|
return out;
|
|
385
500
|
}
|
|
386
501
|
function highlightBash(cmd) {
|
|
387
|
-
const CYAN2 = "\x1B[36m";
|
|
388
|
-
const GREEN = "\x1B[32m";
|
|
389
|
-
const MAGENTA = "\x1B[35m";
|
|
390
502
|
let out = "";
|
|
391
503
|
let i = 0;
|
|
392
504
|
let expectProgram = true;
|
|
@@ -398,7 +510,7 @@ function highlightBash(cmd) {
|
|
|
398
510
|
if (ch === '"' && cmd[j] === "\\") j++;
|
|
399
511
|
j++;
|
|
400
512
|
}
|
|
401
|
-
out += `${
|
|
513
|
+
out += `${DIM}${cmd.slice(i, Math.min(j + 1, cmd.length))}${RESET}`;
|
|
402
514
|
i = j + 1;
|
|
403
515
|
continue;
|
|
404
516
|
}
|
|
@@ -408,7 +520,7 @@ function highlightBash(cmd) {
|
|
|
408
520
|
}
|
|
409
521
|
const op = cmd.slice(i).match(/^(\|\||&&|\||;|>>|>|<)/);
|
|
410
522
|
if (op) {
|
|
411
|
-
out += `${
|
|
523
|
+
out += `${DIM}${op[1]}${RESET}`;
|
|
412
524
|
i += op[1].length;
|
|
413
525
|
expectProgram = true;
|
|
414
526
|
continue;
|
|
@@ -416,19 +528,24 @@ function highlightBash(cmd) {
|
|
|
416
528
|
const word = cmd.slice(i).match(/^[^\s'"#|;&<>]+/);
|
|
417
529
|
if (word) {
|
|
418
530
|
const w = word[0];
|
|
419
|
-
if (w.startsWith("-"))
|
|
420
|
-
|
|
421
|
-
out += `${CYAN2}${w}${RESET}`;
|
|
531
|
+
if (expectProgram && !w.startsWith("-")) {
|
|
532
|
+
out += w;
|
|
422
533
|
expectProgram = false;
|
|
423
|
-
} else
|
|
534
|
+
} else {
|
|
535
|
+
out += `${DIM}${w}${RESET}`;
|
|
536
|
+
}
|
|
424
537
|
i += w.length;
|
|
425
538
|
continue;
|
|
426
539
|
}
|
|
427
|
-
out += ch
|
|
540
|
+
out += `${DIM}${ch}${RESET}`;
|
|
428
541
|
i++;
|
|
429
542
|
}
|
|
430
543
|
return out;
|
|
431
544
|
}
|
|
545
|
+
function oneLineCommand(cmd, max) {
|
|
546
|
+
const collapsed = cmd.replace(/\s*\n+\s*/g, " \u23CE ").replace(/[ \t]{2,}/g, " ").trim();
|
|
547
|
+
return clamp(collapsed, max);
|
|
548
|
+
}
|
|
432
549
|
var GRAD_STOPS = [
|
|
433
550
|
[255, 179, 92],
|
|
434
551
|
// amber
|
|
@@ -664,7 +781,7 @@ var Bridge = class {
|
|
|
664
781
|
this.hooks.onStatus?.(callKey, null);
|
|
665
782
|
}
|
|
666
783
|
if (result.ok) {
|
|
667
|
-
const display = req.tool === "bash" ? `bash: ${highlightBash(String(req.args.command ?? "").
|
|
784
|
+
const display = req.tool === "bash" ? `bash: ${highlightBash(oneLineCommand(String(req.args.command ?? ""), Math.max(40, (process.stdout.columns || 80) - 20)))}` : summary;
|
|
668
785
|
let detail;
|
|
669
786
|
if (req.tool === "edit_file") {
|
|
670
787
|
detail = diffLines(String(req.args.old_string ?? ""), String(req.args.new_string ?? ""));
|
|
@@ -713,7 +830,7 @@ function describe(req) {
|
|
|
713
830
|
case "run_skill_script":
|
|
714
831
|
return `skill ${a.skill}: run ${a.entry}`;
|
|
715
832
|
case "bash":
|
|
716
|
-
return `bash: ${String(a.command)
|
|
833
|
+
return `bash: ${oneLineCommand(String(a.command ?? ""), 80)}`;
|
|
717
834
|
default:
|
|
718
835
|
return `${req.tool} ${JSON.stringify(a).slice(0, 80)}`;
|
|
719
836
|
}
|
|
@@ -1195,6 +1312,9 @@ ${truncated}`
|
|
|
1195
1312
|
const command = String(args.command || "");
|
|
1196
1313
|
if (!command.trim()) return { ok: false, error: "command is required" };
|
|
1197
1314
|
const cwd = args.cwd ? this.resolve(String(args.cwd)) : this.projectDir;
|
|
1315
|
+
if (!fs4.existsSync(cwd)) {
|
|
1316
|
+
return { ok: false, error: `cwd does not exist: ${cwd}` };
|
|
1317
|
+
}
|
|
1198
1318
|
const id = crypto.randomUUID().slice(0, 8);
|
|
1199
1319
|
const logPath = path3.join(LOG_DIR, `${id}.log`);
|
|
1200
1320
|
let out;
|
|
@@ -1211,15 +1331,22 @@ ${truncated}`
|
|
|
1211
1331
|
fs4.closeSync(out);
|
|
1212
1332
|
return { ok: false, error: `Failed to start: ${err instanceof Error ? err.message : String(err)}` };
|
|
1213
1333
|
}
|
|
1334
|
+
let spawnError = null;
|
|
1335
|
+
child.on("error", (err) => {
|
|
1336
|
+
spawnError = err;
|
|
1337
|
+
});
|
|
1214
1338
|
fs4.closeSync(out);
|
|
1215
1339
|
const pid = child.pid;
|
|
1216
|
-
if (!pid) return { ok: false, error: "Process failed to start (no pid)." };
|
|
1217
1340
|
let earlyExit;
|
|
1218
1341
|
const onEarlyExit = (code) => {
|
|
1219
1342
|
earlyExit = code;
|
|
1220
1343
|
};
|
|
1221
1344
|
child.on("exit", onEarlyExit);
|
|
1222
1345
|
await new Promise((r) => setTimeout(r, STARTUP_GRACE_MS));
|
|
1346
|
+
if (spawnError) {
|
|
1347
|
+
return { ok: false, error: `Failed to start: ${spawnError.message}` };
|
|
1348
|
+
}
|
|
1349
|
+
if (!pid) return { ok: false, error: "Process failed to start (no pid)." };
|
|
1223
1350
|
if (earlyExit !== void 0 || !isAlive2(pid)) {
|
|
1224
1351
|
const tail = await readLogTail(logPath, 15);
|
|
1225
1352
|
const code = earlyExit ?? "unknown";
|
|
@@ -2346,24 +2473,22 @@ var Tui = class _Tui {
|
|
|
2346
2473
|
spinnerFrame() {
|
|
2347
2474
|
return `${C.bold}${C.cyan}${FRAMES[Math.floor(Date.now() / 100) % FRAMES.length]}${C.reset}`;
|
|
2348
2475
|
}
|
|
2349
|
-
/**
|
|
2350
|
-
* "↑X ↓Y" cumulative token totals (greyed — low-priority), plus a context
|
|
2351
|
-
* window gauge "ctx N%" when known. The gauge colour ramps with fill (green →
|
|
2352
|
-
* yellow → red) so the user can see compaction approaching at a glance.
|
|
2353
|
-
*/
|
|
2476
|
+
/** "↑X ↓Y" cumulative token totals (greyed — low-priority). */
|
|
2354
2477
|
tokensText() {
|
|
2355
2478
|
const parts = [];
|
|
2356
2479
|
if (this.tokensIn > 0) parts.push(`\u2191${this.fmtTokens(this.tokensIn)}`);
|
|
2357
2480
|
if (this.tokensOut > 0) parts.push(`\u2193${this.fmtTokens(this.tokensOut)}`);
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2481
|
+
return parts.length ? `${C.gray}${parts.join(" ")}${C.reset}` : "";
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Understated context gauge, right-aligned on the summary line: just "N%",
|
|
2485
|
+
* dim, and INVISIBLE until the conversation is more than 85% of the way to
|
|
2486
|
+
* the compaction point (the percentage is scaled so 100% = where background
|
|
2487
|
+
* compaction triggers, not the raw model window). Most sessions never see it.
|
|
2488
|
+
*/
|
|
2489
|
+
contextGaugeText() {
|
|
2490
|
+
if (this.contextPct == null || this.contextPct <= 85) return "";
|
|
2491
|
+
return `${C.dim}${C.gray}${this.contextPct}%${C.reset}`;
|
|
2367
2492
|
}
|
|
2368
2493
|
/**
|
|
2369
2494
|
* Set the context-window fill percentage (0–100), or null to hide it.
|
|
@@ -2376,9 +2501,9 @@ var Tui = class _Tui {
|
|
|
2376
2501
|
this.contextPct = next;
|
|
2377
2502
|
this.renderBottom();
|
|
2378
2503
|
}
|
|
2379
|
-
/** Plain
|
|
2504
|
+
/** Plain context label (no ANSI) for menu hints, or "" when unknown. */
|
|
2380
2505
|
contextPctLabel() {
|
|
2381
|
-
return this.contextPct == null ? "" : `
|
|
2506
|
+
return this.contextPct == null ? "" : `context ${this.contextPct}%`;
|
|
2382
2507
|
}
|
|
2383
2508
|
/** Register the slash commands shown in the inline `/` palette. */
|
|
2384
2509
|
setCommands(commands) {
|
|
@@ -2413,24 +2538,34 @@ var Tui = class _Tui {
|
|
|
2413
2538
|
*/
|
|
2414
2539
|
statusLineText(cols2) {
|
|
2415
2540
|
const tk = this.tokensText();
|
|
2541
|
+
const gauge = this.contextGaugeText();
|
|
2542
|
+
const gaugeReserve = gauge ? this.visibleWidth(gauge) + 1 : 0;
|
|
2543
|
+
let line;
|
|
2416
2544
|
if (this.working) {
|
|
2417
2545
|
const el = this.formatElapsed(Date.now() - this.workingStart);
|
|
2418
2546
|
const right = `${C.dim}${el}${C.reset}${tk ? " " + tk : ""}`;
|
|
2419
2547
|
const head = `${this.spinnerFrame()} ${C.bold}Working${C.reset}`;
|
|
2420
|
-
const avail = Math.max(
|
|
2548
|
+
const avail = Math.max(
|
|
2549
|
+
0,
|
|
2550
|
+
cols2 - this.visibleWidth(head) - this.visibleWidth(right) - 2 - gaugeReserve
|
|
2551
|
+
);
|
|
2421
2552
|
let stepPart = "";
|
|
2422
2553
|
if (this.step && avail > 1) {
|
|
2423
2554
|
let s = this.step;
|
|
2424
2555
|
if (s.length > avail) s = s.slice(0, avail - 1) + "\u2026";
|
|
2425
2556
|
stepPart = ` ${C.dim}${s}${C.reset}`;
|
|
2426
2557
|
}
|
|
2427
|
-
|
|
2428
|
-
}
|
|
2429
|
-
if (this.goalComplete) {
|
|
2558
|
+
line = `${head}${stepPart} ${right}`;
|
|
2559
|
+
} else if (this.goalComplete) {
|
|
2430
2560
|
const done = `${C.bold}${gradientText("\u2713 Goal complete.")}${C.reset}`;
|
|
2431
|
-
|
|
2561
|
+
line = tk ? `${done} ${tk}` : done;
|
|
2562
|
+
} else {
|
|
2563
|
+
line = tk ? tk : null;
|
|
2432
2564
|
}
|
|
2433
|
-
|
|
2565
|
+
if (!gauge) return line;
|
|
2566
|
+
const base = line ?? "";
|
|
2567
|
+
const pad = Math.max(1, cols2 - 1 - this.visibleWidth(base) - this.visibleWidth(gauge));
|
|
2568
|
+
return `${base}${" ".repeat(pad)}${gauge}`;
|
|
2434
2569
|
}
|
|
2435
2570
|
/** The prompt line prefix (with ANSI colour) that precedes the typed text. */
|
|
2436
2571
|
promptPrefix() {
|
|
@@ -3720,6 +3855,7 @@ function saveDefaultEndpoint(endpoint) {
|
|
|
3720
3855
|
|
|
3721
3856
|
// src/index.ts
|
|
3722
3857
|
var AGENT_ID = "standard_code_agent";
|
|
3858
|
+
var AGENT_ID_VARIANTS = [AGENT_ID, "standard_code_high_agent"];
|
|
3723
3859
|
var c = {
|
|
3724
3860
|
reset: "\x1B[0m",
|
|
3725
3861
|
dim: "\x1B[2m",
|
|
@@ -3982,8 +4118,22 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
3982
4118
|
}
|
|
3983
4119
|
const stored = getCredential(endpoint);
|
|
3984
4120
|
let api = stored ? new ApiClient(endpoint, stored.access_token) : null;
|
|
3985
|
-
|
|
4121
|
+
const storedCheck = api ? await api.verifyDetailed() : null;
|
|
4122
|
+
if (!api || !storedCheck?.ok) {
|
|
3986
4123
|
const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
4124
|
+
if (storedCheck && !storedCheck.ok) {
|
|
4125
|
+
stdout.write(`${c.red}\u2717${c.reset} ${c.dim}Saved sign-in for this endpoint failed:${c.reset} ${storedCheck.reason}
|
|
4126
|
+
`);
|
|
4127
|
+
if (storedCheck.hint) stdout.write(` ${c.dim}${storedCheck.hint}${c.reset}
|
|
4128
|
+
`);
|
|
4129
|
+
stdout.write("\n");
|
|
4130
|
+
}
|
|
4131
|
+
const explainFailure = (result, prefix) => {
|
|
4132
|
+
stdout.write(`${c.red}\u2717${c.reset} ${prefix}${result.reason}
|
|
4133
|
+
`);
|
|
4134
|
+
if (result.hint) stdout.write(` ${c.dim}${result.hint}${c.reset}
|
|
4135
|
+
`);
|
|
4136
|
+
};
|
|
3987
4137
|
stdout.write(
|
|
3988
4138
|
`${c.bold}${c.white}Sign in${c.reset} ${c.dim}\u2014 connect to${c.reset} ${c.teal}${host}${c.reset}
|
|
3989
4139
|
`
|
|
@@ -4003,7 +4153,8 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4003
4153
|
});
|
|
4004
4154
|
if (!got) continue;
|
|
4005
4155
|
api = new ApiClient(endpoint, got);
|
|
4006
|
-
|
|
4156
|
+
const check2 = await api.verifyDetailed();
|
|
4157
|
+
if (check2.ok) {
|
|
4007
4158
|
saveCredential(
|
|
4008
4159
|
{ endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
|
|
4009
4160
|
{ updateDefault: !endpointOverride }
|
|
@@ -4012,12 +4163,12 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4012
4163
|
`);
|
|
4013
4164
|
break;
|
|
4014
4165
|
}
|
|
4015
|
-
|
|
4016
|
-
`);
|
|
4166
|
+
explainFailure(check2, "Browser sign-in didn't verify: ");
|
|
4017
4167
|
continue;
|
|
4018
4168
|
}
|
|
4019
4169
|
api = new ApiClient(endpoint, token);
|
|
4020
|
-
|
|
4170
|
+
const check = await api.verifyDetailed();
|
|
4171
|
+
if (check.ok) {
|
|
4021
4172
|
saveCredential(
|
|
4022
4173
|
{ endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
4023
4174
|
{ updateDefault: !endpointOverride }
|
|
@@ -4026,8 +4177,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4026
4177
|
`);
|
|
4027
4178
|
break;
|
|
4028
4179
|
}
|
|
4029
|
-
|
|
4030
|
-
`);
|
|
4180
|
+
explainFailure(check, "");
|
|
4031
4181
|
}
|
|
4032
4182
|
} else if (endpointPrompted) {
|
|
4033
4183
|
saveDefaultEndpoint(endpoint);
|
|
@@ -4038,7 +4188,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4038
4188
|
const tags = [`path:${projectDir}`, `machine:${machine}`];
|
|
4039
4189
|
let existing = [];
|
|
4040
4190
|
try {
|
|
4041
|
-
existing = await api.listThreads(
|
|
4191
|
+
existing = await api.listThreads(AGENT_ID_VARIANTS, tags);
|
|
4042
4192
|
} catch {
|
|
4043
4193
|
existing = [];
|
|
4044
4194
|
}
|
|
@@ -4293,9 +4443,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4293
4443
|
const subs = await api.listSubagents(threadId);
|
|
4294
4444
|
activeSubagents.clear();
|
|
4295
4445
|
for (const s of subs) {
|
|
4296
|
-
|
|
4446
|
+
const status = (s.status || "").trim();
|
|
4447
|
+
if (status === "idle" || status === "terminated") continue;
|
|
4448
|
+
const detail = status && status !== "running" ? ` \u2014 ${status.slice(0, 80)}` : "";
|
|
4297
4449
|
activeSubagents.set(s.id, {
|
|
4298
|
-
label: subagentLabel(s, agentTitles)
|
|
4450
|
+
label: `${subagentLabel(s, agentTitles)}${detail}`,
|
|
4299
4451
|
agentName: s.agent_name ?? void 0
|
|
4300
4452
|
});
|
|
4301
4453
|
}
|
|
@@ -4331,6 +4483,10 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4331
4483
|
if (activeSubagents.has(id)) scheduleReconcile();
|
|
4332
4484
|
}
|
|
4333
4485
|
});
|
|
4486
|
+
const heartbeatPoll = setInterval(() => {
|
|
4487
|
+
if (activeSubagents.size > 0) scheduleReconcile();
|
|
4488
|
+
}, 5e3);
|
|
4489
|
+
heartbeatPoll.unref();
|
|
4334
4490
|
const quit = async () => {
|
|
4335
4491
|
tui.end();
|
|
4336
4492
|
const stopped = api.stop(threadId).catch(() => {
|
|
@@ -4611,10 +4767,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4611
4767
|
refreshStatus();
|
|
4612
4768
|
} catch {
|
|
4613
4769
|
}
|
|
4770
|
+
const COMPACTION_TRIGGER_FRACTION = 0.5;
|
|
4614
4771
|
try {
|
|
4615
4772
|
const cu = await api.kvGet(threadId, "context_usage");
|
|
4616
4773
|
const used = Number(cu?.inputTokens) || 0;
|
|
4617
|
-
const max = Number(cu?.maxContextTokens) || 0;
|
|
4774
|
+
const max = (Number(cu?.maxContextTokens) || 0) * COMPACTION_TRIGGER_FRACTION;
|
|
4618
4775
|
tui.setContextPct(max > 0 && used > 0 ? used / max * 100 : null);
|
|
4619
4776
|
} catch {
|
|
4620
4777
|
}
|