@standardagents/code 0.1.2 → 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 +212 -60
- 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
|
+
};
|
|
55
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
|
+
};
|
|
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
|
|
@@ -478,10 +595,6 @@ function gradientText(text, phase = 0) {
|
|
|
478
595
|
}
|
|
479
596
|
return out + "\x1B[0m";
|
|
480
597
|
}
|
|
481
|
-
function gradientArt(lines) {
|
|
482
|
-
const n = Math.max(1, lines.length - 1);
|
|
483
|
-
return lines.map((line, i) => gradientText(line, i / n * 0.55));
|
|
484
|
-
}
|
|
485
598
|
|
|
486
599
|
// src/bridge.ts
|
|
487
600
|
var PATH_ARG_TOOLS = /* @__PURE__ */ new Set(["read_file", "grep", "glob", "write_file", "edit_file"]);
|
|
@@ -668,7 +781,7 @@ var Bridge = class {
|
|
|
668
781
|
this.hooks.onStatus?.(callKey, null);
|
|
669
782
|
}
|
|
670
783
|
if (result.ok) {
|
|
671
|
-
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;
|
|
672
785
|
let detail;
|
|
673
786
|
if (req.tool === "edit_file") {
|
|
674
787
|
detail = diffLines(String(req.args.old_string ?? ""), String(req.args.new_string ?? ""));
|
|
@@ -717,7 +830,7 @@ function describe(req) {
|
|
|
717
830
|
case "run_skill_script":
|
|
718
831
|
return `skill ${a.skill}: run ${a.entry}`;
|
|
719
832
|
case "bash":
|
|
720
|
-
return `bash: ${String(a.command)
|
|
833
|
+
return `bash: ${oneLineCommand(String(a.command ?? ""), 80)}`;
|
|
721
834
|
default:
|
|
722
835
|
return `${req.tool} ${JSON.stringify(a).slice(0, 80)}`;
|
|
723
836
|
}
|
|
@@ -1199,6 +1312,9 @@ ${truncated}`
|
|
|
1199
1312
|
const command = String(args.command || "");
|
|
1200
1313
|
if (!command.trim()) return { ok: false, error: "command is required" };
|
|
1201
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
|
+
}
|
|
1202
1318
|
const id = crypto.randomUUID().slice(0, 8);
|
|
1203
1319
|
const logPath = path3.join(LOG_DIR, `${id}.log`);
|
|
1204
1320
|
let out;
|
|
@@ -1215,15 +1331,22 @@ ${truncated}`
|
|
|
1215
1331
|
fs4.closeSync(out);
|
|
1216
1332
|
return { ok: false, error: `Failed to start: ${err instanceof Error ? err.message : String(err)}` };
|
|
1217
1333
|
}
|
|
1334
|
+
let spawnError = null;
|
|
1335
|
+
child.on("error", (err) => {
|
|
1336
|
+
spawnError = err;
|
|
1337
|
+
});
|
|
1218
1338
|
fs4.closeSync(out);
|
|
1219
1339
|
const pid = child.pid;
|
|
1220
|
-
if (!pid) return { ok: false, error: "Process failed to start (no pid)." };
|
|
1221
1340
|
let earlyExit;
|
|
1222
1341
|
const onEarlyExit = (code) => {
|
|
1223
1342
|
earlyExit = code;
|
|
1224
1343
|
};
|
|
1225
1344
|
child.on("exit", onEarlyExit);
|
|
1226
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)." };
|
|
1227
1350
|
if (earlyExit !== void 0 || !isAlive2(pid)) {
|
|
1228
1351
|
const tail = await readLogTail(logPath, 15);
|
|
1229
1352
|
const code = earlyExit ?? "unknown";
|
|
@@ -2350,24 +2473,22 @@ var Tui = class _Tui {
|
|
|
2350
2473
|
spinnerFrame() {
|
|
2351
2474
|
return `${C.bold}${C.cyan}${FRAMES[Math.floor(Date.now() / 100) % FRAMES.length]}${C.reset}`;
|
|
2352
2475
|
}
|
|
2353
|
-
/**
|
|
2354
|
-
* "↑X ↓Y" cumulative token totals (greyed — low-priority), plus a context
|
|
2355
|
-
* window gauge "ctx N%" when known. The gauge colour ramps with fill (green →
|
|
2356
|
-
* yellow → red) so the user can see compaction approaching at a glance.
|
|
2357
|
-
*/
|
|
2476
|
+
/** "↑X ↓Y" cumulative token totals (greyed — low-priority). */
|
|
2358
2477
|
tokensText() {
|
|
2359
2478
|
const parts = [];
|
|
2360
2479
|
if (this.tokensIn > 0) parts.push(`\u2191${this.fmtTokens(this.tokensIn)}`);
|
|
2361
2480
|
if (this.tokensOut > 0) parts.push(`\u2193${this.fmtTokens(this.tokensOut)}`);
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
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}`;
|
|
2371
2492
|
}
|
|
2372
2493
|
/**
|
|
2373
2494
|
* Set the context-window fill percentage (0–100), or null to hide it.
|
|
@@ -2380,9 +2501,9 @@ var Tui = class _Tui {
|
|
|
2380
2501
|
this.contextPct = next;
|
|
2381
2502
|
this.renderBottom();
|
|
2382
2503
|
}
|
|
2383
|
-
/** Plain
|
|
2504
|
+
/** Plain context label (no ANSI) for menu hints, or "" when unknown. */
|
|
2384
2505
|
contextPctLabel() {
|
|
2385
|
-
return this.contextPct == null ? "" : `
|
|
2506
|
+
return this.contextPct == null ? "" : `context ${this.contextPct}%`;
|
|
2386
2507
|
}
|
|
2387
2508
|
/** Register the slash commands shown in the inline `/` palette. */
|
|
2388
2509
|
setCommands(commands) {
|
|
@@ -2417,24 +2538,34 @@ var Tui = class _Tui {
|
|
|
2417
2538
|
*/
|
|
2418
2539
|
statusLineText(cols2) {
|
|
2419
2540
|
const tk = this.tokensText();
|
|
2541
|
+
const gauge = this.contextGaugeText();
|
|
2542
|
+
const gaugeReserve = gauge ? this.visibleWidth(gauge) + 1 : 0;
|
|
2543
|
+
let line;
|
|
2420
2544
|
if (this.working) {
|
|
2421
2545
|
const el = this.formatElapsed(Date.now() - this.workingStart);
|
|
2422
2546
|
const right = `${C.dim}${el}${C.reset}${tk ? " " + tk : ""}`;
|
|
2423
2547
|
const head = `${this.spinnerFrame()} ${C.bold}Working${C.reset}`;
|
|
2424
|
-
const avail = Math.max(
|
|
2548
|
+
const avail = Math.max(
|
|
2549
|
+
0,
|
|
2550
|
+
cols2 - this.visibleWidth(head) - this.visibleWidth(right) - 2 - gaugeReserve
|
|
2551
|
+
);
|
|
2425
2552
|
let stepPart = "";
|
|
2426
2553
|
if (this.step && avail > 1) {
|
|
2427
2554
|
let s = this.step;
|
|
2428
2555
|
if (s.length > avail) s = s.slice(0, avail - 1) + "\u2026";
|
|
2429
2556
|
stepPart = ` ${C.dim}${s}${C.reset}`;
|
|
2430
2557
|
}
|
|
2431
|
-
|
|
2432
|
-
}
|
|
2433
|
-
if (this.goalComplete) {
|
|
2558
|
+
line = `${head}${stepPart} ${right}`;
|
|
2559
|
+
} else if (this.goalComplete) {
|
|
2434
2560
|
const done = `${C.bold}${gradientText("\u2713 Goal complete.")}${C.reset}`;
|
|
2435
|
-
|
|
2561
|
+
line = tk ? `${done} ${tk}` : done;
|
|
2562
|
+
} else {
|
|
2563
|
+
line = tk ? tk : null;
|
|
2436
2564
|
}
|
|
2437
|
-
|
|
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}`;
|
|
2438
2569
|
}
|
|
2439
2570
|
/** The prompt line prefix (with ANSI colour) that precedes the typed text. */
|
|
2440
2571
|
promptPrefix() {
|
|
@@ -3724,6 +3855,7 @@ function saveDefaultEndpoint(endpoint) {
|
|
|
3724
3855
|
|
|
3725
3856
|
// src/index.ts
|
|
3726
3857
|
var AGENT_ID = "standard_code_agent";
|
|
3858
|
+
var AGENT_ID_VARIANTS = [AGENT_ID, "standard_code_high_agent"];
|
|
3727
3859
|
var c = {
|
|
3728
3860
|
reset: "\x1B[0m",
|
|
3729
3861
|
dim: "\x1B[2m",
|
|
@@ -3874,11 +4006,10 @@ function printWelcome(endpoint, projectDir) {
|
|
|
3874
4006
|
`${c.dim}${dir}${c.reset}`
|
|
3875
4007
|
];
|
|
3876
4008
|
const markWidth = Math.max(...LOGO_MARK.map((l) => [...l].length));
|
|
3877
|
-
const gradMark = gradientArt(LOGO_MARK);
|
|
3878
4009
|
const metaTop = Math.floor((LOGO_MARK.length - meta.length) / 2);
|
|
3879
4010
|
stdout.write("\n");
|
|
3880
4011
|
for (let i = 0; i < LOGO_MARK.length; i++) {
|
|
3881
|
-
const glyph =
|
|
4012
|
+
const glyph = LOGO_MARK[i].padEnd(markWidth);
|
|
3882
4013
|
const line = meta[i - metaTop];
|
|
3883
4014
|
stdout.write(`${pad}${glyph}${line ? ` ${line}` : ""}
|
|
3884
4015
|
`);
|
|
@@ -3987,8 +4118,22 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
3987
4118
|
}
|
|
3988
4119
|
const stored = getCredential(endpoint);
|
|
3989
4120
|
let api = stored ? new ApiClient(endpoint, stored.access_token) : null;
|
|
3990
|
-
|
|
4121
|
+
const storedCheck = api ? await api.verifyDetailed() : null;
|
|
4122
|
+
if (!api || !storedCheck?.ok) {
|
|
3991
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
|
+
};
|
|
3992
4137
|
stdout.write(
|
|
3993
4138
|
`${c.bold}${c.white}Sign in${c.reset} ${c.dim}\u2014 connect to${c.reset} ${c.teal}${host}${c.reset}
|
|
3994
4139
|
`
|
|
@@ -4008,7 +4153,8 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4008
4153
|
});
|
|
4009
4154
|
if (!got) continue;
|
|
4010
4155
|
api = new ApiClient(endpoint, got);
|
|
4011
|
-
|
|
4156
|
+
const check2 = await api.verifyDetailed();
|
|
4157
|
+
if (check2.ok) {
|
|
4012
4158
|
saveCredential(
|
|
4013
4159
|
{ endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
|
|
4014
4160
|
{ updateDefault: !endpointOverride }
|
|
@@ -4017,12 +4163,12 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4017
4163
|
`);
|
|
4018
4164
|
break;
|
|
4019
4165
|
}
|
|
4020
|
-
|
|
4021
|
-
`);
|
|
4166
|
+
explainFailure(check2, "Browser sign-in didn't verify: ");
|
|
4022
4167
|
continue;
|
|
4023
4168
|
}
|
|
4024
4169
|
api = new ApiClient(endpoint, token);
|
|
4025
|
-
|
|
4170
|
+
const check = await api.verifyDetailed();
|
|
4171
|
+
if (check.ok) {
|
|
4026
4172
|
saveCredential(
|
|
4027
4173
|
{ endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
4028
4174
|
{ updateDefault: !endpointOverride }
|
|
@@ -4031,8 +4177,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4031
4177
|
`);
|
|
4032
4178
|
break;
|
|
4033
4179
|
}
|
|
4034
|
-
|
|
4035
|
-
`);
|
|
4180
|
+
explainFailure(check, "");
|
|
4036
4181
|
}
|
|
4037
4182
|
} else if (endpointPrompted) {
|
|
4038
4183
|
saveDefaultEndpoint(endpoint);
|
|
@@ -4043,7 +4188,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4043
4188
|
const tags = [`path:${projectDir}`, `machine:${machine}`];
|
|
4044
4189
|
let existing = [];
|
|
4045
4190
|
try {
|
|
4046
|
-
existing = await api.listThreads(
|
|
4191
|
+
existing = await api.listThreads(AGENT_ID_VARIANTS, tags);
|
|
4047
4192
|
} catch {
|
|
4048
4193
|
existing = [];
|
|
4049
4194
|
}
|
|
@@ -4298,9 +4443,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4298
4443
|
const subs = await api.listSubagents(threadId);
|
|
4299
4444
|
activeSubagents.clear();
|
|
4300
4445
|
for (const s of subs) {
|
|
4301
|
-
|
|
4446
|
+
const status = (s.status || "").trim();
|
|
4447
|
+
if (status === "idle" || status === "terminated") continue;
|
|
4448
|
+
const detail = status && status !== "running" ? ` \u2014 ${status.slice(0, 80)}` : "";
|
|
4302
4449
|
activeSubagents.set(s.id, {
|
|
4303
|
-
label: subagentLabel(s, agentTitles)
|
|
4450
|
+
label: `${subagentLabel(s, agentTitles)}${detail}`,
|
|
4304
4451
|
agentName: s.agent_name ?? void 0
|
|
4305
4452
|
});
|
|
4306
4453
|
}
|
|
@@ -4336,6 +4483,10 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4336
4483
|
if (activeSubagents.has(id)) scheduleReconcile();
|
|
4337
4484
|
}
|
|
4338
4485
|
});
|
|
4486
|
+
const heartbeatPoll = setInterval(() => {
|
|
4487
|
+
if (activeSubagents.size > 0) scheduleReconcile();
|
|
4488
|
+
}, 5e3);
|
|
4489
|
+
heartbeatPoll.unref();
|
|
4339
4490
|
const quit = async () => {
|
|
4340
4491
|
tui.end();
|
|
4341
4492
|
const stopped = api.stop(threadId).catch(() => {
|
|
@@ -4616,10 +4767,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4616
4767
|
refreshStatus();
|
|
4617
4768
|
} catch {
|
|
4618
4769
|
}
|
|
4770
|
+
const COMPACTION_TRIGGER_FRACTION = 0.5;
|
|
4619
4771
|
try {
|
|
4620
4772
|
const cu = await api.kvGet(threadId, "context_usage");
|
|
4621
4773
|
const used = Number(cu?.inputTokens) || 0;
|
|
4622
|
-
const max = Number(cu?.maxContextTokens) || 0;
|
|
4774
|
+
const max = (Number(cu?.maxContextTokens) || 0) * COMPACTION_TRIGGER_FRACTION;
|
|
4623
4775
|
tui.setContextPct(max > 0 && used > 0 ? used / max * 100 : null);
|
|
4624
4776
|
} catch {
|
|
4625
4777
|
}
|