mini-coder 0.2.0 → 0.2.1
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/.prettierignore +7 -0
- package/dist/mc.js +788 -797
- package/docs/KNOWN_ISSUES.md +13 -0
- package/docs/mini-coder.1.md +15 -15
- package/lefthook.yml +2 -13
- package/package.json +49 -47
- package/CLAUDE.md +0 -1
package/dist/mc.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import * as
|
|
5
|
+
import * as c22 from "yoctocolors";
|
|
6
6
|
|
|
7
7
|
// src/agent/agent.ts
|
|
8
8
|
import * as c12 from "yoctocolors";
|
|
@@ -12,8 +12,12 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
12
12
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
13
13
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
14
14
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
15
|
+
// src/internal/version.ts
|
|
16
|
+
var PACKAGE_VERSION = "0.2.1";
|
|
17
|
+
|
|
18
|
+
// src/mcp/client.ts
|
|
15
19
|
async function connectMcpServer(config) {
|
|
16
|
-
const client = new Client({ name: "mini-coder", version:
|
|
20
|
+
const client = new Client({ name: "mini-coder", version: PACKAGE_VERSION });
|
|
17
21
|
if (config.transport === "http") {
|
|
18
22
|
if (!config.url) {
|
|
19
23
|
throw new Error(`MCP server "${config.name}" requires a url`);
|
|
@@ -78,7 +82,7 @@ function getDbPath() {
|
|
|
78
82
|
mkdirSync(dir, { recursive: true });
|
|
79
83
|
return join(dir, "sessions.db");
|
|
80
84
|
}
|
|
81
|
-
var DB_VERSION =
|
|
85
|
+
var DB_VERSION = 6;
|
|
82
86
|
var SCHEMA = `
|
|
83
87
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
84
88
|
id TEXT PRIMARY KEY,
|
|
@@ -129,6 +133,7 @@ var SCHEMA = `
|
|
|
129
133
|
CREATE TABLE IF NOT EXISTS model_capabilities (
|
|
130
134
|
canonical_model_id TEXT PRIMARY KEY,
|
|
131
135
|
context_window INTEGER,
|
|
136
|
+
max_output_tokens INTEGER,
|
|
132
137
|
reasoning INTEGER NOT NULL,
|
|
133
138
|
source_provider TEXT,
|
|
134
139
|
raw_json TEXT,
|
|
@@ -164,6 +169,17 @@ var SCHEMA = `
|
|
|
164
169
|
expires_at INTEGER NOT NULL,
|
|
165
170
|
updated_at INTEGER NOT NULL
|
|
166
171
|
);
|
|
172
|
+
|
|
173
|
+
CREATE TABLE IF NOT EXISTS logs (
|
|
174
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
176
|
+
level TEXT NOT NULL,
|
|
177
|
+
timestamp INTEGER NOT NULL,
|
|
178
|
+
data TEXT NOT NULL
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_logs_session
|
|
182
|
+
ON logs(session_id, timestamp DESC);
|
|
167
183
|
`;
|
|
168
184
|
var _db = null;
|
|
169
185
|
function isSqliteBusyError(err) {
|
|
@@ -232,9 +248,70 @@ function pruneOldData() {
|
|
|
232
248
|
});
|
|
233
249
|
}
|
|
234
250
|
runBestEffortMaintenance(() => {
|
|
235
|
-
db.exec("PRAGMA wal_checkpoint(
|
|
251
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
236
252
|
});
|
|
237
253
|
}
|
|
254
|
+
// src/session/db/logs-repo.ts
|
|
255
|
+
class LogsRepo {
|
|
256
|
+
db;
|
|
257
|
+
constructor(db) {
|
|
258
|
+
this.db = db;
|
|
259
|
+
}
|
|
260
|
+
write(sessionId, level, data) {
|
|
261
|
+
const timestamp = Date.now();
|
|
262
|
+
const serialized = data === undefined ? "{}" : JSON.stringify(this.sanitize(data));
|
|
263
|
+
this.db.run(`INSERT INTO logs (session_id, level, timestamp, data)
|
|
264
|
+
VALUES (?, ?, ?, ?)`, [sessionId, level, timestamp, serialized]);
|
|
265
|
+
}
|
|
266
|
+
getLogs(sessionId, level) {
|
|
267
|
+
let sql = `SELECT id, session_id, level, timestamp, data
|
|
268
|
+
FROM logs
|
|
269
|
+
WHERE session_id = ?`;
|
|
270
|
+
if (level) {
|
|
271
|
+
sql += ` AND level = ? ORDER BY timestamp ASC`;
|
|
272
|
+
const params2 = [sessionId, level];
|
|
273
|
+
return this.db.query(sql).all(...params2);
|
|
274
|
+
}
|
|
275
|
+
sql += ` ORDER BY timestamp ASC`;
|
|
276
|
+
const params = [sessionId];
|
|
277
|
+
return this.db.query(sql).all(...params);
|
|
278
|
+
}
|
|
279
|
+
deleteOldLogs(beforeTimestamp) {
|
|
280
|
+
return this.db.run(`DELETE FROM logs WHERE timestamp < ?`, [
|
|
281
|
+
beforeTimestamp
|
|
282
|
+
]).changes;
|
|
283
|
+
}
|
|
284
|
+
deleteSessionLogs(sessionId) {
|
|
285
|
+
return this.db.run(`DELETE FROM logs WHERE session_id = ?`, [sessionId]).changes;
|
|
286
|
+
}
|
|
287
|
+
sanitize(data) {
|
|
288
|
+
if (!this.isObject(data))
|
|
289
|
+
return data;
|
|
290
|
+
const dropKeys = new Set([
|
|
291
|
+
"requestBodyValues",
|
|
292
|
+
"responseBody",
|
|
293
|
+
"responseHeaders",
|
|
294
|
+
"stack"
|
|
295
|
+
]);
|
|
296
|
+
const result = {};
|
|
297
|
+
for (const key in data) {
|
|
298
|
+
if (dropKeys.has(key))
|
|
299
|
+
continue;
|
|
300
|
+
const value = data[key];
|
|
301
|
+
if (key === "errors" && Array.isArray(value)) {
|
|
302
|
+
result[key] = value.map((e) => this.sanitize(e));
|
|
303
|
+
} else if (key === "lastError" && this.isObject(value)) {
|
|
304
|
+
result[key] = this.sanitize(value);
|
|
305
|
+
} else {
|
|
306
|
+
result[key] = value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
isObject(v) {
|
|
312
|
+
return typeof v === "object" && v !== null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
238
315
|
// src/session/db/mcp-repo.ts
|
|
239
316
|
function listMcpServers() {
|
|
240
317
|
return getDb().query("SELECT name, transport, url, command, args, env FROM mcp_servers ORDER BY name").all();
|
|
@@ -261,113 +338,117 @@ function deleteMcpServer(name) {
|
|
|
261
338
|
getDb().run("DELETE FROM mcp_servers WHERE name = ?", [name]);
|
|
262
339
|
}
|
|
263
340
|
// src/cli/output.ts
|
|
264
|
-
import { existsSync as existsSync3 } from "fs";
|
|
265
341
|
import { homedir as homedir4 } from "os";
|
|
266
|
-
import { join as join4 } from "path";
|
|
267
342
|
import * as c8 from "yoctocolors";
|
|
268
343
|
|
|
269
|
-
// src/
|
|
270
|
-
import {
|
|
271
|
-
|
|
272
|
-
// src/cli/log-paths.ts
|
|
273
|
-
import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2 } from "fs";
|
|
344
|
+
// src/agent/context-files.ts
|
|
345
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
274
346
|
import { homedir as homedir2 } from "os";
|
|
275
|
-
import { join as join2 } from "path";
|
|
276
|
-
var
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
347
|
+
import { dirname, join as join2, resolve } from "path";
|
|
348
|
+
var HOME = homedir2();
|
|
349
|
+
function tilde(p) {
|
|
350
|
+
return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
|
|
351
|
+
}
|
|
352
|
+
function globalContextCandidates(homeDir = HOME) {
|
|
353
|
+
return [
|
|
354
|
+
{
|
|
355
|
+
abs: join2(homeDir, ".agents", "AGENTS.md"),
|
|
356
|
+
label: "~/.agents/AGENTS.md"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
abs: join2(homeDir, ".agents", "CLAUDE.md"),
|
|
360
|
+
label: "~/.agents/CLAUDE.md"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
abs: join2(homeDir, ".claude", "CLAUDE.md"),
|
|
364
|
+
label: "~/.claude/CLAUDE.md"
|
|
365
|
+
}
|
|
366
|
+
];
|
|
367
|
+
}
|
|
368
|
+
function dirContextCandidates(dir) {
|
|
369
|
+
const rel = (p) => tilde(resolve(dir, p));
|
|
370
|
+
return [
|
|
371
|
+
{
|
|
372
|
+
abs: join2(dir, ".agents", "AGENTS.md"),
|
|
373
|
+
label: `${rel(".agents/AGENTS.md")}`
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
abs: join2(dir, ".agents", "CLAUDE.md"),
|
|
377
|
+
label: `${rel(".agents/CLAUDE.md")}`
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
abs: join2(dir, ".claude", "CLAUDE.md"),
|
|
381
|
+
label: `${rel(".claude/CLAUDE.md")}`
|
|
382
|
+
},
|
|
383
|
+
{ abs: join2(dir, "CLAUDE.md"), label: `${rel("CLAUDE.md")}` },
|
|
384
|
+
{ abs: join2(dir, "AGENTS.md"), label: `${rel("AGENTS.md")}` }
|
|
385
|
+
];
|
|
280
386
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
387
|
+
function discoverContextFiles(cwd, homeDir) {
|
|
388
|
+
const candidates = [
|
|
389
|
+
...globalContextCandidates(homeDir),
|
|
390
|
+
...dirContextCandidates(cwd)
|
|
391
|
+
];
|
|
392
|
+
return candidates.filter((c) => existsSync2(c.abs)).map((c) => c.label);
|
|
283
393
|
}
|
|
284
|
-
function
|
|
394
|
+
function tryReadFile(p) {
|
|
395
|
+
if (!existsSync2(p))
|
|
396
|
+
return null;
|
|
285
397
|
try {
|
|
286
|
-
|
|
287
|
-
return true;
|
|
398
|
+
return readFileSync(p, "utf-8");
|
|
288
399
|
} catch {
|
|
289
|
-
return
|
|
400
|
+
return null;
|
|
290
401
|
}
|
|
291
402
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return;
|
|
403
|
+
function readCandidates(candidates) {
|
|
404
|
+
const parts = [];
|
|
405
|
+
for (const c of candidates) {
|
|
406
|
+
const content = tryReadFile(c.abs);
|
|
407
|
+
if (content)
|
|
408
|
+
parts.push(content);
|
|
299
409
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
410
|
+
return parts.length > 0 ? parts.join(`
|
|
411
|
+
|
|
412
|
+
`) : null;
|
|
413
|
+
}
|
|
414
|
+
function loadGlobalContextFile(homeDir) {
|
|
415
|
+
return readCandidates(globalContextCandidates(homeDir));
|
|
416
|
+
}
|
|
417
|
+
function loadLocalContextFile(cwd) {
|
|
418
|
+
let current = resolve(cwd);
|
|
419
|
+
while (true) {
|
|
420
|
+
const content = readCandidates(dirContextCandidates(current));
|
|
421
|
+
if (content)
|
|
422
|
+
return content;
|
|
423
|
+
if (existsSync2(join2(current, ".git")))
|
|
424
|
+
break;
|
|
425
|
+
const parent = dirname(current);
|
|
426
|
+
if (parent === current)
|
|
427
|
+
break;
|
|
428
|
+
current = parent;
|
|
313
429
|
}
|
|
430
|
+
return null;
|
|
314
431
|
}
|
|
315
432
|
|
|
316
|
-
// src/
|
|
317
|
-
var
|
|
318
|
-
function
|
|
319
|
-
|
|
320
|
-
return;
|
|
321
|
-
const logPath = pidLogPath("errors");
|
|
322
|
-
writeFileSync(logPath, "");
|
|
323
|
-
writer = Bun.file(logPath).writer();
|
|
324
|
-
process.on("uncaughtException", (err) => {
|
|
325
|
-
logError(err, "uncaught");
|
|
326
|
-
process.exit(1);
|
|
327
|
-
});
|
|
433
|
+
// src/logging/context.ts
|
|
434
|
+
var _currentContext = null;
|
|
435
|
+
function setLogContext(context) {
|
|
436
|
+
_currentContext = context;
|
|
328
437
|
}
|
|
329
|
-
function
|
|
330
|
-
return
|
|
438
|
+
function getLogContext() {
|
|
439
|
+
return _currentContext;
|
|
331
440
|
}
|
|
332
441
|
function logError(err, context) {
|
|
333
|
-
|
|
442
|
+
const logCtx = _currentContext;
|
|
443
|
+
if (!logCtx)
|
|
334
444
|
return;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
entry += ` name: ${err.name}
|
|
343
|
-
`;
|
|
344
|
-
if (typeof err.message === "string")
|
|
345
|
-
entry += ` message: ${err.message}
|
|
346
|
-
`;
|
|
347
|
-
if ("statusCode" in err)
|
|
348
|
-
entry += ` statusCode: ${err.statusCode}
|
|
349
|
-
`;
|
|
350
|
-
if ("url" in err)
|
|
351
|
-
entry += ` url: ${err.url}
|
|
352
|
-
`;
|
|
353
|
-
if ("isRetryable" in err)
|
|
354
|
-
entry += ` isRetryable: ${err.isRetryable}
|
|
355
|
-
`;
|
|
356
|
-
if (typeof err.stack === "string") {
|
|
357
|
-
const indentedStack = err.stack.split(`
|
|
358
|
-
`).map((line, i) => i === 0 ? line : ` ${line}`).join(`
|
|
359
|
-
`);
|
|
360
|
-
entry += ` stack: ${indentedStack}
|
|
361
|
-
`;
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
entry += ` value: ${String(err)}
|
|
365
|
-
`;
|
|
366
|
-
}
|
|
367
|
-
entry += `---
|
|
368
|
-
`;
|
|
369
|
-
writer.write(entry);
|
|
370
|
-
writer.flush();
|
|
445
|
+
logCtx.logsRepo.write(logCtx.sessionId, "error", { error: err, context });
|
|
446
|
+
}
|
|
447
|
+
function logApiEvent(event, data) {
|
|
448
|
+
const logCtx = _currentContext;
|
|
449
|
+
if (!logCtx)
|
|
450
|
+
return;
|
|
451
|
+
logCtx.logsRepo.write(logCtx.sessionId, "api", { event, data });
|
|
371
452
|
}
|
|
372
453
|
|
|
373
454
|
// src/cli/error-parse.ts
|
|
@@ -537,31 +618,61 @@ function parseAppError(err) {
|
|
|
537
618
|
// src/cli/skills.ts
|
|
538
619
|
import {
|
|
539
620
|
closeSync,
|
|
540
|
-
existsSync as
|
|
621
|
+
existsSync as existsSync3,
|
|
541
622
|
openSync,
|
|
542
|
-
readdirSync
|
|
543
|
-
readFileSync,
|
|
623
|
+
readdirSync,
|
|
624
|
+
readFileSync as readFileSync2,
|
|
544
625
|
readSync,
|
|
545
626
|
statSync
|
|
546
627
|
} from "fs";
|
|
547
628
|
import { homedir as homedir3 } from "os";
|
|
548
|
-
import { dirname, join as join3, resolve } from "path";
|
|
549
|
-
|
|
550
|
-
// src/cli/config-conflicts.ts
|
|
629
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
551
630
|
import * as c from "yoctocolors";
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
631
|
+
|
|
632
|
+
// src/cli/frontmatter.ts
|
|
633
|
+
var FM_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
634
|
+
function parseFrontmatter(raw) {
|
|
635
|
+
const m = raw.match(FM_RE);
|
|
636
|
+
if (!m)
|
|
637
|
+
return { meta: {}, body: raw };
|
|
638
|
+
const meta = {};
|
|
639
|
+
const lines = (m[1] ?? "").split(`
|
|
640
|
+
`);
|
|
641
|
+
let parentKey = "";
|
|
642
|
+
for (const line of lines) {
|
|
643
|
+
const stripped = line.replace(/\r$/, "");
|
|
644
|
+
const indent = stripped.length - stripped.trimStart().length;
|
|
645
|
+
if (indent > 0 && parentKey) {
|
|
646
|
+
const colon2 = stripped.indexOf(":");
|
|
647
|
+
if (colon2 === -1)
|
|
648
|
+
continue;
|
|
649
|
+
const key2 = stripped.slice(0, colon2).trim();
|
|
650
|
+
const val2 = stripped.slice(colon2 + 1).trim().replace(/^["']|["']$/g, "");
|
|
651
|
+
if (!key2)
|
|
652
|
+
continue;
|
|
653
|
+
const parent = meta[parentKey];
|
|
654
|
+
if (typeof parent === "object")
|
|
655
|
+
parent[key2] = val2;
|
|
656
|
+
else
|
|
657
|
+
meta[parentKey] = { [key2]: val2 };
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const colon = stripped.indexOf(":");
|
|
661
|
+
if (colon === -1)
|
|
662
|
+
continue;
|
|
663
|
+
const key = stripped.slice(0, colon).trim();
|
|
664
|
+
const val = stripped.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
665
|
+
if (!key)
|
|
666
|
+
continue;
|
|
667
|
+
if (val === "") {
|
|
668
|
+
parentKey = key;
|
|
669
|
+
meta[key] = {};
|
|
670
|
+
} else {
|
|
671
|
+
parentKey = "";
|
|
672
|
+
meta[key] = val;
|
|
673
|
+
}
|
|
559
674
|
}
|
|
560
|
-
|
|
561
|
-
return;
|
|
562
|
-
conflicts.sort((a, b) => a.localeCompare(b));
|
|
563
|
-
const list = conflicts.map((n) => c.cyan(n)).join(c.dim(", "));
|
|
564
|
-
writeln(`${G.warn} conflicting ${kind} in ${scope} .agents and .claude: ${list} ${c.dim("\u2014 using .agents version")}`);
|
|
675
|
+
return { meta, body: (m[2] ?? "").trim() };
|
|
565
676
|
}
|
|
566
677
|
|
|
567
678
|
// src/cli/skills.ts
|
|
@@ -577,28 +688,17 @@ function parseSkillFrontmatter(filePath) {
|
|
|
577
688
|
const chunk = Buffer.allocUnsafe(MAX_FRONTMATTER_BYTES);
|
|
578
689
|
const bytesRead = readSync(fd, chunk, 0, MAX_FRONTMATTER_BYTES, 0);
|
|
579
690
|
const text = chunk.toString("utf8", 0, bytesRead);
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
continue;
|
|
592
|
-
const key = line.slice(0, colon).trim();
|
|
593
|
-
const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
594
|
-
if (key === "name")
|
|
595
|
-
meta.name = value;
|
|
596
|
-
if (key === "description")
|
|
597
|
-
meta.description = value;
|
|
598
|
-
if (key === "context")
|
|
599
|
-
meta.context = value;
|
|
600
|
-
}
|
|
601
|
-
return meta;
|
|
691
|
+
const { meta } = parseFrontmatter(text);
|
|
692
|
+
const result = {};
|
|
693
|
+
if (typeof meta.name === "string" && meta.name)
|
|
694
|
+
result.name = meta.name;
|
|
695
|
+
if (typeof meta.description === "string" && meta.description)
|
|
696
|
+
result.description = meta.description;
|
|
697
|
+
if (typeof meta.context === "string" && meta.context)
|
|
698
|
+
result.context = meta.context;
|
|
699
|
+
if (typeof meta.compatibility === "string" && meta.compatibility)
|
|
700
|
+
result.compatibility = meta.compatibility;
|
|
701
|
+
return result;
|
|
602
702
|
} catch {
|
|
603
703
|
return {};
|
|
604
704
|
} finally {
|
|
@@ -616,18 +716,18 @@ function candidateConflictName(candidate) {
|
|
|
616
716
|
return getCandidateFrontmatter(candidate).name?.trim() || candidate.folderName;
|
|
617
717
|
}
|
|
618
718
|
function findGitBoundary(cwd) {
|
|
619
|
-
let current =
|
|
719
|
+
let current = resolve2(cwd);
|
|
620
720
|
while (true) {
|
|
621
|
-
if (
|
|
721
|
+
if (existsSync3(join3(current, ".git")))
|
|
622
722
|
return current;
|
|
623
|
-
const parent =
|
|
723
|
+
const parent = dirname2(current);
|
|
624
724
|
if (parent === current)
|
|
625
725
|
return null;
|
|
626
726
|
current = parent;
|
|
627
727
|
}
|
|
628
728
|
}
|
|
629
729
|
function localSearchRoots(cwd) {
|
|
630
|
-
const start =
|
|
730
|
+
const start = resolve2(cwd);
|
|
631
731
|
const stop = findGitBoundary(start);
|
|
632
732
|
if (!stop)
|
|
633
733
|
return [start];
|
|
@@ -637,41 +737,54 @@ function localSearchRoots(cwd) {
|
|
|
637
737
|
roots.push(current);
|
|
638
738
|
if (current === stop)
|
|
639
739
|
break;
|
|
640
|
-
const parent =
|
|
740
|
+
const parent = dirname2(current);
|
|
641
741
|
if (parent === current)
|
|
642
742
|
break;
|
|
643
743
|
current = parent;
|
|
644
744
|
}
|
|
645
745
|
return roots;
|
|
646
746
|
}
|
|
747
|
+
var MAX_SKILL_SCAN_DEPTH = 5;
|
|
748
|
+
var MAX_SKILL_DIRS_SCANNED = 2000;
|
|
647
749
|
function listSkillCandidates(skillsDir, source, rootPath) {
|
|
648
|
-
if (!
|
|
649
|
-
return [];
|
|
650
|
-
let entries;
|
|
651
|
-
try {
|
|
652
|
-
entries = readdirSync2(skillsDir).sort((a, b) => a.localeCompare(b));
|
|
653
|
-
} catch {
|
|
750
|
+
if (!existsSync3(skillsDir))
|
|
654
751
|
return [];
|
|
655
|
-
}
|
|
656
752
|
const candidates = [];
|
|
657
|
-
|
|
658
|
-
|
|
753
|
+
let dirsScanned = 0;
|
|
754
|
+
function walk(dir, depth) {
|
|
755
|
+
if (depth > MAX_SKILL_SCAN_DEPTH || dirsScanned >= MAX_SKILL_DIRS_SCANNED)
|
|
756
|
+
return;
|
|
757
|
+
let entries;
|
|
659
758
|
try {
|
|
660
|
-
|
|
661
|
-
continue;
|
|
759
|
+
entries = readdirSync(dir).sort((a, b) => a.localeCompare(b));
|
|
662
760
|
} catch {
|
|
663
|
-
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
for (const entry of entries) {
|
|
764
|
+
if (dirsScanned >= MAX_SKILL_DIRS_SCANNED)
|
|
765
|
+
return;
|
|
766
|
+
const entryPath = join3(dir, entry);
|
|
767
|
+
try {
|
|
768
|
+
if (!statSync(entryPath).isDirectory())
|
|
769
|
+
continue;
|
|
770
|
+
} catch {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
dirsScanned++;
|
|
774
|
+
const filePath = join3(entryPath, "SKILL.md");
|
|
775
|
+
if (existsSync3(filePath)) {
|
|
776
|
+
candidates.push({
|
|
777
|
+
folderName: entry,
|
|
778
|
+
filePath,
|
|
779
|
+
rootPath,
|
|
780
|
+
source
|
|
781
|
+
});
|
|
782
|
+
} else {
|
|
783
|
+
walk(entryPath, depth + 1);
|
|
784
|
+
}
|
|
664
785
|
}
|
|
665
|
-
const filePath = join3(skillDir, "SKILL.md");
|
|
666
|
-
if (!existsSync2(filePath))
|
|
667
|
-
continue;
|
|
668
|
-
candidates.push({
|
|
669
|
-
folderName: entry,
|
|
670
|
-
filePath,
|
|
671
|
-
rootPath,
|
|
672
|
-
source
|
|
673
|
-
});
|
|
674
786
|
}
|
|
787
|
+
walk(skillsDir, 1);
|
|
675
788
|
return candidates;
|
|
676
789
|
}
|
|
677
790
|
function warnInvalidSkill(filePath, reason) {
|
|
@@ -688,6 +801,20 @@ function warnSkillIssue(filePath, reason) {
|
|
|
688
801
|
warnedSkillIssues.add(key);
|
|
689
802
|
writeln(`${G.warn} skill ${filePath}: ${reason}`);
|
|
690
803
|
}
|
|
804
|
+
function warnConventionConflicts(kind, scope, agentsNames, claudeNames) {
|
|
805
|
+
const agents = new Set(agentsNames);
|
|
806
|
+
const claude = new Set(claudeNames);
|
|
807
|
+
const conflicts = [];
|
|
808
|
+
for (const name of agents) {
|
|
809
|
+
if (claude.has(name))
|
|
810
|
+
conflicts.push(name);
|
|
811
|
+
}
|
|
812
|
+
if (conflicts.length === 0)
|
|
813
|
+
return;
|
|
814
|
+
conflicts.sort((a, b) => a.localeCompare(b));
|
|
815
|
+
const list = conflicts.map((n) => c.cyan(n)).join(c.dim(", "));
|
|
816
|
+
writeln(`${G.warn} conflicting ${kind} in ${scope} .agents and .claude: ${list} ${c.dim("\u2014 using .agents version")}`);
|
|
817
|
+
}
|
|
691
818
|
function validateSkill(candidate) {
|
|
692
819
|
const meta = getCandidateFrontmatter(candidate);
|
|
693
820
|
const name = meta.name?.trim();
|
|
@@ -712,7 +839,8 @@ function validateSkill(candidate) {
|
|
|
712
839
|
source: candidate.source,
|
|
713
840
|
rootPath: candidate.rootPath,
|
|
714
841
|
filePath: candidate.filePath,
|
|
715
|
-
...meta.context === "fork" && { context: "fork" }
|
|
842
|
+
...meta.context === "fork" && { context: "fork" },
|
|
843
|
+
...meta.compatibility && { compatibility: meta.compatibility }
|
|
716
844
|
};
|
|
717
845
|
}
|
|
718
846
|
function allSkillCandidates(cwd, homeDir) {
|
|
@@ -747,7 +875,7 @@ function listSkillResources(skillDir) {
|
|
|
747
875
|
function walk(dir, prefix) {
|
|
748
876
|
let entries;
|
|
749
877
|
try {
|
|
750
|
-
entries =
|
|
878
|
+
entries = readdirSync(dir);
|
|
751
879
|
} catch {
|
|
752
880
|
return;
|
|
753
881
|
}
|
|
@@ -772,8 +900,8 @@ function listSkillResources(skillDir) {
|
|
|
772
900
|
}
|
|
773
901
|
function loadSkillContentFromMeta(skill) {
|
|
774
902
|
try {
|
|
775
|
-
const content =
|
|
776
|
-
const skillDir =
|
|
903
|
+
const content = readFileSync2(skill.filePath, "utf-8");
|
|
904
|
+
const skillDir = dirname2(skill.filePath);
|
|
777
905
|
return {
|
|
778
906
|
name: skill.name,
|
|
779
907
|
content,
|
|
@@ -944,15 +1072,17 @@ function stripAnsi(text) {
|
|
|
944
1072
|
return text.replace(ANSI_REGEX, "");
|
|
945
1073
|
}
|
|
946
1074
|
|
|
947
|
-
// src/
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if (value.length <= maxLen)
|
|
1075
|
+
// src/internal/text.ts
|
|
1076
|
+
function truncateText(value, max) {
|
|
1077
|
+
if (value.length <= max)
|
|
951
1078
|
return value;
|
|
952
|
-
if (
|
|
1079
|
+
if (max <= 1)
|
|
953
1080
|
return "\u2026";
|
|
954
|
-
return `${value.slice(0,
|
|
1081
|
+
return `${value.slice(0, max - 1)}\u2026`;
|
|
955
1082
|
}
|
|
1083
|
+
|
|
1084
|
+
// src/cli/status-bar.ts
|
|
1085
|
+
var STATUS_SEP = c3.dim(" \xB7 ");
|
|
956
1086
|
function fmtTokens(n) {
|
|
957
1087
|
if (n >= 1000)
|
|
958
1088
|
return `${(n / 1000).toFixed(1)}k`;
|
|
@@ -986,8 +1116,7 @@ function buildStatusBarSignature(opts) {
|
|
|
986
1116
|
outputTokens: opts.outputTokens,
|
|
987
1117
|
contextTokens: opts.contextTokens,
|
|
988
1118
|
contextWindow: opts.contextWindow,
|
|
989
|
-
thinkingEffort: opts.thinkingEffort ?? null
|
|
990
|
-
showReasoning: opts.showReasoning ?? false
|
|
1119
|
+
thinkingEffort: opts.thinkingEffort ?? null
|
|
991
1120
|
});
|
|
992
1121
|
}
|
|
993
1122
|
function fitStatusSegments(required, optional, cols) {
|
|
@@ -1003,17 +1132,16 @@ function fitStatusSegments(required, optional, cols) {
|
|
|
1003
1132
|
const sepLen = stripAnsi(STATUS_SEP).length;
|
|
1004
1133
|
const fixedPrefix = plainRequired[0] ?? "";
|
|
1005
1134
|
if (plainRequired.length <= 1)
|
|
1006
|
-
return
|
|
1135
|
+
return truncateText(fixedPrefix, cols);
|
|
1007
1136
|
const maxTailLen = Math.max(8, cols - fixedPrefix.length - sepLen);
|
|
1008
|
-
const truncatedTail =
|
|
1137
|
+
const truncatedTail = truncateText(plainRequired[1] ?? "", maxTailLen);
|
|
1009
1138
|
return `${required[0]}${STATUS_SEP}${c3.dim(truncatedTail)}`;
|
|
1010
1139
|
}
|
|
1011
1140
|
function renderStatusBar(opts) {
|
|
1012
1141
|
const cols = Math.max(20, terminal.stdoutColumns || 80);
|
|
1013
|
-
const
|
|
1142
|
+
const modelSegment = opts.thinkingEffort ? `${c3.cyan(opts.model)} ${c3.magenta(c3.italic(`\u2726 ${opts.thinkingEffort}`))}` : c3.cyan(opts.model);
|
|
1143
|
+
const required = [modelSegment, c3.dim(`#${opts.sessionId}`)];
|
|
1014
1144
|
const optional = [];
|
|
1015
|
-
if (opts.thinkingEffort)
|
|
1016
|
-
optional.push(c3.dim(`\u2726 ${opts.thinkingEffort}`));
|
|
1017
1145
|
if (opts.gitBranch)
|
|
1018
1146
|
optional.push(c3.dim(`\u2387 ${opts.gitBranch}`));
|
|
1019
1147
|
if (opts.inputTokens > 0 || opts.outputTokens > 0) {
|
|
@@ -1117,8 +1245,7 @@ function isToolCallPart(part) {
|
|
|
1117
1245
|
function hasObjectToolCallInput(part) {
|
|
1118
1246
|
return isToolCallPart(part) && "input" in part && isRecord(part.input) && !Array.isArray(part.input);
|
|
1119
1247
|
}
|
|
1120
|
-
|
|
1121
|
-
function stripToolRuntimeInputFields(messages) {
|
|
1248
|
+
function mapAssistantParts(messages, transform) {
|
|
1122
1249
|
let mutated = false;
|
|
1123
1250
|
const result = messages.map((message) => {
|
|
1124
1251
|
if (message.role !== "assistant" || !Array.isArray(message.content)) {
|
|
@@ -1126,24 +1253,10 @@ function stripToolRuntimeInputFields(messages) {
|
|
|
1126
1253
|
}
|
|
1127
1254
|
let contentMutated = false;
|
|
1128
1255
|
const nextContent = message.content.map((part) => {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const nextInput = { ...part.input };
|
|
1134
|
-
for (const key of TOOL_RUNTIME_INPUT_KEYS) {
|
|
1135
|
-
if (!(key in nextInput))
|
|
1136
|
-
continue;
|
|
1137
|
-
delete nextInput[key];
|
|
1138
|
-
inputMutated = true;
|
|
1139
|
-
}
|
|
1140
|
-
if (!inputMutated)
|
|
1141
|
-
return part;
|
|
1142
|
-
contentMutated = true;
|
|
1143
|
-
return {
|
|
1144
|
-
...part,
|
|
1145
|
-
input: nextInput
|
|
1146
|
-
};
|
|
1256
|
+
const next = transform(part);
|
|
1257
|
+
if (next !== part)
|
|
1258
|
+
contentMutated = true;
|
|
1259
|
+
return next;
|
|
1147
1260
|
});
|
|
1148
1261
|
if (!contentMutated)
|
|
1149
1262
|
return message;
|
|
@@ -1155,6 +1268,22 @@ function stripToolRuntimeInputFields(messages) {
|
|
|
1155
1268
|
});
|
|
1156
1269
|
return mutated ? result : messages;
|
|
1157
1270
|
}
|
|
1271
|
+
var TOOL_RUNTIME_INPUT_KEYS = new Set(["cwd"]);
|
|
1272
|
+
function stripToolRuntimeInputFields(messages) {
|
|
1273
|
+
return mapAssistantParts(messages, (part) => {
|
|
1274
|
+
if (!hasObjectToolCallInput(part))
|
|
1275
|
+
return part;
|
|
1276
|
+
let inputMutated = false;
|
|
1277
|
+
const nextInput = { ...part.input };
|
|
1278
|
+
for (const key of TOOL_RUNTIME_INPUT_KEYS) {
|
|
1279
|
+
if (!(key in nextInput))
|
|
1280
|
+
continue;
|
|
1281
|
+
delete nextInput[key];
|
|
1282
|
+
inputMutated = true;
|
|
1283
|
+
}
|
|
1284
|
+
return inputMutated ? { ...part, input: nextInput } : part;
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1158
1287
|
|
|
1159
1288
|
// src/llm-api/history/gemini.ts
|
|
1160
1289
|
function getGeminiThoughtSignature(part) {
|
|
@@ -1294,38 +1423,19 @@ function isOpenAIGPT(modelString) {
|
|
|
1294
1423
|
function normalizeOpenAICompatibleToolCallInputs(messages, modelString) {
|
|
1295
1424
|
if (!isZenOpenAICompatibleChatModel(modelString))
|
|
1296
1425
|
return messages;
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return message;
|
|
1426
|
+
return mapAssistantParts(messages, (part) => {
|
|
1427
|
+
if (!isToolCallPart(part) || !("input" in part) || typeof part.input !== "string") {
|
|
1428
|
+
return part;
|
|
1301
1429
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
if (!
|
|
1305
|
-
return part;
|
|
1306
|
-
}
|
|
1307
|
-
try {
|
|
1308
|
-
const parsed = JSON.parse(part.input);
|
|
1309
|
-
if (!isRecord(parsed) || Array.isArray(parsed))
|
|
1310
|
-
return part;
|
|
1311
|
-
contentMutated = true;
|
|
1312
|
-
return {
|
|
1313
|
-
...part,
|
|
1314
|
-
input: parsed
|
|
1315
|
-
};
|
|
1316
|
-
} catch {
|
|
1430
|
+
try {
|
|
1431
|
+
const parsed = JSON.parse(part.input);
|
|
1432
|
+
if (!isRecord(parsed) || Array.isArray(parsed))
|
|
1317
1433
|
return part;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
mutated = true;
|
|
1323
|
-
return {
|
|
1324
|
-
...message,
|
|
1325
|
-
content: nextContent
|
|
1326
|
-
};
|
|
1434
|
+
return { ...part, input: parsed };
|
|
1435
|
+
} catch {
|
|
1436
|
+
return part;
|
|
1437
|
+
}
|
|
1327
1438
|
});
|
|
1328
|
-
return mutated ? result : messages;
|
|
1329
1439
|
}
|
|
1330
1440
|
function stripOpenAIHistory(messages, modelString, options) {
|
|
1331
1441
|
if (!isOpenAIGPT(modelString))
|
|
@@ -1400,7 +1510,7 @@ function makeInterruptMessage(reason) {
|
|
|
1400
1510
|
return { role: "assistant", content: text };
|
|
1401
1511
|
}
|
|
1402
1512
|
function isAbortError(error) {
|
|
1403
|
-
return error.name === "AbortError" || error.name === "Error" && error.message
|
|
1513
|
+
return error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR" || error.name === "Error" && error.message === "aborted";
|
|
1404
1514
|
}
|
|
1405
1515
|
function buildAbortMessages(partialMessages, accumulatedText) {
|
|
1406
1516
|
const stub = makeInterruptMessage("user");
|
|
@@ -1647,7 +1757,7 @@ function writePreviewLines(opts) {
|
|
|
1647
1757
|
function truncateOneLine(value, max = 100, verboseOutput = false) {
|
|
1648
1758
|
if (verboseOutput)
|
|
1649
1759
|
return value;
|
|
1650
|
-
return value
|
|
1760
|
+
return truncateText(value, max);
|
|
1651
1761
|
}
|
|
1652
1762
|
function normalizeShellText(value) {
|
|
1653
1763
|
return value.replace(/[\r\n]+$/, "");
|
|
@@ -1895,7 +2005,7 @@ function shellCmdGlyph(cmd, fullCmd) {
|
|
|
1895
2005
|
case "touch":
|
|
1896
2006
|
return G.write;
|
|
1897
2007
|
case "sed":
|
|
1898
|
-
return /\s-i\b/.test(fullCmd) ? G.write : G.
|
|
2008
|
+
return /\s-i\b/.test(fullCmd) ? G.write : G.read;
|
|
1899
2009
|
case "git":
|
|
1900
2010
|
case "bun":
|
|
1901
2011
|
case "echo":
|
|
@@ -1921,15 +2031,12 @@ function extractFirstCommand(cmd) {
|
|
|
1921
2031
|
}
|
|
1922
2032
|
return tokens[0] ?? "";
|
|
1923
2033
|
}
|
|
1924
|
-
function truncate(s, max) {
|
|
1925
|
-
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
1926
|
-
}
|
|
1927
2034
|
function formatShellCallLine(cmd) {
|
|
1928
2035
|
const { cwd, rest } = parseShellCdPrefix(cmd);
|
|
1929
2036
|
const firstCmd = extractFirstCommand(rest);
|
|
1930
2037
|
const glyph = shellCmdGlyph(firstCmd, rest);
|
|
1931
2038
|
const cwdSuffix = cwd ? ` ${c6.dim(`in ${cwd}`)}` : "";
|
|
1932
|
-
const display =
|
|
2039
|
+
const display = truncateText(rest, 80);
|
|
1933
2040
|
return `${glyph} ${display}${cwdSuffix}`;
|
|
1934
2041
|
}
|
|
1935
2042
|
function buildToolCallLine(name, args) {
|
|
@@ -2115,6 +2222,9 @@ async function renderTurn(events, spinner, opts) {
|
|
|
2115
2222
|
liveReasoning.finish();
|
|
2116
2223
|
content.flushOpenContent();
|
|
2117
2224
|
spinner.stop();
|
|
2225
|
+
inputTokens = event.inputTokens;
|
|
2226
|
+
outputTokens = event.outputTokens;
|
|
2227
|
+
contextTokens = event.contextTokens;
|
|
2118
2228
|
if (isAbortError(event.error)) {
|
|
2119
2229
|
newMessages = buildAbortMessages(event.partialMessages, content.getText());
|
|
2120
2230
|
} else {
|
|
@@ -2135,10 +2245,9 @@ async function renderTurn(events, spinner, opts) {
|
|
|
2135
2245
|
}
|
|
2136
2246
|
|
|
2137
2247
|
// src/cli/output.ts
|
|
2138
|
-
var
|
|
2139
|
-
var PACKAGE_VERSION = "0.2.0";
|
|
2248
|
+
var HOME2 = homedir4();
|
|
2140
2249
|
function tildePath(p) {
|
|
2141
|
-
return p.startsWith(
|
|
2250
|
+
return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
|
|
2142
2251
|
}
|
|
2143
2252
|
function restoreTerminal() {
|
|
2144
2253
|
terminal.restoreTerminal();
|
|
@@ -2203,28 +2312,12 @@ function renderError(err, context = "render") {
|
|
|
2203
2312
|
writeln(` ${c8.dim(parsed.hint)}`);
|
|
2204
2313
|
}
|
|
2205
2314
|
}
|
|
2206
|
-
function discoverContextFiles(cwd) {
|
|
2207
|
-
const found = [];
|
|
2208
|
-
const globalDir = join4(HOME, ".agents");
|
|
2209
|
-
const candidates = [
|
|
2210
|
-
[join4(globalDir, "AGENTS.md"), "~/.agents/AGENTS.md"],
|
|
2211
|
-
[join4(globalDir, "CLAUDE.md"), "~/.agents/CLAUDE.md"],
|
|
2212
|
-
[join4(cwd, ".agents", "AGENTS.md"), ".agents/AGENTS.md"],
|
|
2213
|
-
[join4(cwd, "CLAUDE.md"), "CLAUDE.md"],
|
|
2214
|
-
[join4(cwd, "AGENTS.md"), "AGENTS.md"]
|
|
2215
|
-
];
|
|
2216
|
-
for (const [abs, label] of candidates) {
|
|
2217
|
-
if (existsSync3(abs))
|
|
2218
|
-
found.push(label);
|
|
2219
|
-
}
|
|
2220
|
-
return found;
|
|
2221
|
-
}
|
|
2222
2315
|
function renderBanner(model, cwd) {
|
|
2223
2316
|
writeln();
|
|
2224
2317
|
const title = PACKAGE_VERSION ? `mini-coder \xB7 v${PACKAGE_VERSION}` : "mini-coder";
|
|
2225
2318
|
writeln(` ${c8.cyan("mc")} ${c8.dim(title)}`);
|
|
2226
2319
|
writeln(` ${c8.dim(model)} ${c8.dim("\xB7")} ${c8.dim(tildePath(cwd))}`);
|
|
2227
|
-
writeln(` ${c8.dim("/help for commands \xB7 esc cancel \xB7 ctrl+
|
|
2320
|
+
writeln(` ${c8.dim("/help for commands \xB7 esc cancel \xB7 ctrl+d exit")}`);
|
|
2228
2321
|
const items = [];
|
|
2229
2322
|
if (getPreferredShowReasoning())
|
|
2230
2323
|
items.push("reasoning: on");
|
|
@@ -2369,22 +2462,23 @@ function getPromptHistory(limit = 200) {
|
|
|
2369
2462
|
}
|
|
2370
2463
|
// src/session/db/model-info-repo.ts
|
|
2371
2464
|
function listModelCapabilities() {
|
|
2372
|
-
return getDb().query("SELECT canonical_model_id, context_window, reasoning, source_provider, raw_json, updated_at FROM model_capabilities").all();
|
|
2465
|
+
return getDb().query("SELECT canonical_model_id, context_window, max_output_tokens, reasoning, source_provider, raw_json, updated_at FROM model_capabilities").all();
|
|
2373
2466
|
}
|
|
2374
2467
|
function replaceModelCapabilities(rows) {
|
|
2375
2468
|
const db = getDb();
|
|
2376
2469
|
const insertStmt = db.prepare(`INSERT INTO model_capabilities (
|
|
2377
2470
|
canonical_model_id,
|
|
2378
2471
|
context_window,
|
|
2472
|
+
max_output_tokens,
|
|
2379
2473
|
reasoning,
|
|
2380
2474
|
source_provider,
|
|
2381
2475
|
raw_json,
|
|
2382
2476
|
updated_at
|
|
2383
|
-
) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
2477
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
2384
2478
|
const run = db.transaction(() => {
|
|
2385
2479
|
db.run("DELETE FROM model_capabilities");
|
|
2386
2480
|
for (const row of rows) {
|
|
2387
|
-
insertStmt.run(row.canonical_model_id, row.context_window, row.reasoning, row.source_provider, row.raw_json, row.updated_at);
|
|
2481
|
+
insertStmt.run(row.canonical_model_id, row.context_window, row.max_output_tokens, row.reasoning, row.source_provider, row.raw_json, row.updated_at);
|
|
2388
2482
|
}
|
|
2389
2483
|
});
|
|
2390
2484
|
run();
|
|
@@ -2446,6 +2540,12 @@ function touchSession(id, model) {
|
|
|
2446
2540
|
id
|
|
2447
2541
|
]);
|
|
2448
2542
|
}
|
|
2543
|
+
function setSessionTitle(id, title) {
|
|
2544
|
+
getDb().run("UPDATE sessions SET title = ? WHERE id = ? AND title = ''", [
|
|
2545
|
+
title,
|
|
2546
|
+
id
|
|
2547
|
+
]);
|
|
2548
|
+
}
|
|
2449
2549
|
function listSessions(limit = 20) {
|
|
2450
2550
|
return getDb().query("SELECT * FROM sessions ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2451
2551
|
}
|
|
@@ -2586,7 +2686,7 @@ function buildPromptDisplay(text, cursor, maxLen) {
|
|
|
2586
2686
|
}
|
|
2587
2687
|
|
|
2588
2688
|
// src/cli/completions.ts
|
|
2589
|
-
import { join as
|
|
2689
|
+
import { join as join4, relative } from "path";
|
|
2590
2690
|
|
|
2591
2691
|
// src/session/oauth/anthropic.ts
|
|
2592
2692
|
import { createServer } from "http";
|
|
@@ -2623,7 +2723,7 @@ var SUCCESS_HTML = `<!doctype html>
|
|
|
2623
2723
|
<html lang="en"><head><meta charset="utf-8"><title>Authenticated</title></head>
|
|
2624
2724
|
<body><p>Authentication successful. Return to your terminal.</p></body></html>`;
|
|
2625
2725
|
function startCallbackServer(expectedState) {
|
|
2626
|
-
return new Promise((
|
|
2726
|
+
return new Promise((resolve3, reject) => {
|
|
2627
2727
|
let result = null;
|
|
2628
2728
|
let cancelled = false;
|
|
2629
2729
|
const server = createServer((req, res) => {
|
|
@@ -2644,7 +2744,7 @@ function startCallbackServer(expectedState) {
|
|
|
2644
2744
|
});
|
|
2645
2745
|
server.on("error", reject);
|
|
2646
2746
|
server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
|
|
2647
|
-
|
|
2747
|
+
resolve3({
|
|
2648
2748
|
server,
|
|
2649
2749
|
cancel: () => {
|
|
2650
2750
|
cancelled = true;
|
|
@@ -2818,6 +2918,15 @@ function parseContextWindow(model) {
|
|
|
2818
2918
|
return null;
|
|
2819
2919
|
return Math.max(0, Math.trunc(context));
|
|
2820
2920
|
}
|
|
2921
|
+
function parseMaxOutputTokens(model) {
|
|
2922
|
+
const limit = model.limit;
|
|
2923
|
+
if (!isRecord(limit))
|
|
2924
|
+
return null;
|
|
2925
|
+
const output = limit.output;
|
|
2926
|
+
if (typeof output !== "number" || !Number.isFinite(output))
|
|
2927
|
+
return null;
|
|
2928
|
+
return Math.max(0, Math.trunc(output));
|
|
2929
|
+
}
|
|
2821
2930
|
function parseModelsDevCapabilities(payload, updatedAt) {
|
|
2822
2931
|
if (!isRecord(payload))
|
|
2823
2932
|
return [];
|
|
@@ -2836,6 +2945,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
|
|
|
2836
2945
|
if (!canonicalModelId)
|
|
2837
2946
|
continue;
|
|
2838
2947
|
const contextWindow = parseContextWindow(modelValue);
|
|
2948
|
+
const maxOutputTokens = parseMaxOutputTokens(modelValue);
|
|
2839
2949
|
const reasoning = modelValue.reasoning === true;
|
|
2840
2950
|
const rawJson = JSON.stringify(modelValue);
|
|
2841
2951
|
const prev = merged.get(canonicalModelId);
|
|
@@ -2843,6 +2953,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
|
|
|
2843
2953
|
merged.set(canonicalModelId, {
|
|
2844
2954
|
canonicalModelId,
|
|
2845
2955
|
contextWindow,
|
|
2956
|
+
maxOutputTokens,
|
|
2846
2957
|
reasoning,
|
|
2847
2958
|
sourceProvider: provider,
|
|
2848
2959
|
rawJson
|
|
@@ -2852,6 +2963,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
|
|
|
2852
2963
|
merged.set(canonicalModelId, {
|
|
2853
2964
|
canonicalModelId,
|
|
2854
2965
|
contextWindow: prev.contextWindow ?? contextWindow,
|
|
2966
|
+
maxOutputTokens: prev.maxOutputTokens ?? maxOutputTokens,
|
|
2855
2967
|
reasoning: prev.reasoning || reasoning,
|
|
2856
2968
|
sourceProvider: prev.sourceProvider,
|
|
2857
2969
|
rawJson: prev.rawJson ?? rawJson
|
|
@@ -2861,6 +2973,7 @@ function parseModelsDevCapabilities(payload, updatedAt) {
|
|
|
2861
2973
|
return Array.from(merged.values()).map((entry) => ({
|
|
2862
2974
|
canonical_model_id: entry.canonicalModelId,
|
|
2863
2975
|
context_window: entry.contextWindow,
|
|
2976
|
+
max_output_tokens: entry.maxOutputTokens,
|
|
2864
2977
|
reasoning: entry.reasoning ? 1 : 0,
|
|
2865
2978
|
source_provider: entry.sourceProvider,
|
|
2866
2979
|
raw_json: entry.rawJson,
|
|
@@ -2945,6 +3058,7 @@ function buildRuntimeCache(capabilityRows, providerRows, stateRows) {
|
|
|
2945
3058
|
capabilitiesByCanonical.set(canonical, {
|
|
2946
3059
|
canonicalModelId: canonical,
|
|
2947
3060
|
contextWindow: row.context_window,
|
|
3061
|
+
maxOutputTokens: row.max_output_tokens,
|
|
2948
3062
|
reasoning: row.reasoning === 1,
|
|
2949
3063
|
sourceProvider: row.source_provider
|
|
2950
3064
|
});
|
|
@@ -2992,6 +3106,7 @@ function resolveFromProviderRow(row, cache) {
|
|
|
2992
3106
|
return {
|
|
2993
3107
|
canonicalModelId: capability.canonicalModelId,
|
|
2994
3108
|
contextWindow: capability.contextWindow ?? row.contextWindow,
|
|
3109
|
+
maxOutputTokens: capability.maxOutputTokens,
|
|
2995
3110
|
reasoning: capability.reasoning
|
|
2996
3111
|
};
|
|
2997
3112
|
}
|
|
@@ -2999,6 +3114,7 @@ function resolveFromProviderRow(row, cache) {
|
|
|
2999
3114
|
return {
|
|
3000
3115
|
canonicalModelId: row.canonicalModelId,
|
|
3001
3116
|
contextWindow: row.contextWindow,
|
|
3117
|
+
maxOutputTokens: null,
|
|
3002
3118
|
reasoning: false
|
|
3003
3119
|
};
|
|
3004
3120
|
}
|
|
@@ -3019,6 +3135,7 @@ function resolveModelInfoInCache(modelString, cache) {
|
|
|
3019
3135
|
return {
|
|
3020
3136
|
canonicalModelId: capability.canonicalModelId,
|
|
3021
3137
|
contextWindow: capability.contextWindow,
|
|
3138
|
+
maxOutputTokens: capability.maxOutputTokens,
|
|
3022
3139
|
reasoning: capability.reasoning
|
|
3023
3140
|
};
|
|
3024
3141
|
}
|
|
@@ -3381,6 +3498,9 @@ function resolveModelInfo(modelString) {
|
|
|
3381
3498
|
function getContextWindow(modelString) {
|
|
3382
3499
|
return resolveModelInfo(modelString)?.contextWindow ?? null;
|
|
3383
3500
|
}
|
|
3501
|
+
function getMaxOutputTokens(modelString) {
|
|
3502
|
+
return resolveModelInfo(modelString)?.maxOutputTokens ?? null;
|
|
3503
|
+
}
|
|
3384
3504
|
function supportsThinking(modelString) {
|
|
3385
3505
|
return resolveModelInfo(modelString)?.reasoning ?? false;
|
|
3386
3506
|
}
|
|
@@ -3502,7 +3622,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
3502
3622
|
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
3503
3623
|
if (file.includes("node_modules") || file.includes(".git"))
|
|
3504
3624
|
continue;
|
|
3505
|
-
results.push(`@${relative(cwd,
|
|
3625
|
+
results.push(`@${relative(cwd, join4(cwd, file))}`);
|
|
3506
3626
|
if (results.length >= MAX)
|
|
3507
3627
|
break;
|
|
3508
3628
|
}
|
|
@@ -3599,7 +3719,7 @@ function deleteWordBackward(buf, cursor) {
|
|
|
3599
3719
|
}
|
|
3600
3720
|
|
|
3601
3721
|
// src/cli/input-images.ts
|
|
3602
|
-
import { join as
|
|
3722
|
+
import { join as join5 } from "path";
|
|
3603
3723
|
|
|
3604
3724
|
// src/cli/image-types.ts
|
|
3605
3725
|
var IMAGE_EXTENSIONS = new Set([
|
|
@@ -3652,7 +3772,7 @@ async function tryExtractImageFromPaste(pasted, cwd, loadImage = loadImageFile)
|
|
|
3652
3772
|
}
|
|
3653
3773
|
}
|
|
3654
3774
|
if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
|
|
3655
|
-
const filePath = trimmed.startsWith("/") ? trimmed :
|
|
3775
|
+
const filePath = trimmed.startsWith("/") ? trimmed : join5(cwd, trimmed);
|
|
3656
3776
|
const attachment = await loadImage(filePath);
|
|
3657
3777
|
if (attachment) {
|
|
3658
3778
|
const name = filePath.split("/").pop() ?? trimmed;
|
|
@@ -3685,11 +3805,17 @@ var CTRL_L = "\f";
|
|
|
3685
3805
|
var CTRL_R = "\x12";
|
|
3686
3806
|
var TAB = "\t";
|
|
3687
3807
|
var _stdinReader = null;
|
|
3808
|
+
var _stdinGated = false;
|
|
3809
|
+
function setStdinGated(gated) {
|
|
3810
|
+
_stdinGated = gated;
|
|
3811
|
+
}
|
|
3688
3812
|
function getStdinReader() {
|
|
3689
3813
|
if (!_stdinReader) {
|
|
3690
3814
|
const stream = new ReadableStream({
|
|
3691
3815
|
start(controller) {
|
|
3692
3816
|
process.stdin.on("data", (chunk) => {
|
|
3817
|
+
if (_stdinGated)
|
|
3818
|
+
return;
|
|
3693
3819
|
try {
|
|
3694
3820
|
controller.enqueue(new Uint8Array(chunk));
|
|
3695
3821
|
} catch {}
|
|
@@ -3727,6 +3853,7 @@ function exitOnCtrlC(opts) {
|
|
|
3727
3853
|
function watchForCancel(abortController) {
|
|
3728
3854
|
if (!terminal.isTTY)
|
|
3729
3855
|
return () => {};
|
|
3856
|
+
setStdinGated(true);
|
|
3730
3857
|
const onCancel = () => {
|
|
3731
3858
|
cleanup();
|
|
3732
3859
|
abortController.abort();
|
|
@@ -3747,6 +3874,7 @@ function watchForCancel(abortController) {
|
|
|
3747
3874
|
process.stdin.removeListener("data", onData);
|
|
3748
3875
|
terminal.setRawMode(false);
|
|
3749
3876
|
process.stdin.pause();
|
|
3877
|
+
setStdinGated(false);
|
|
3750
3878
|
};
|
|
3751
3879
|
terminal.setRawMode(true);
|
|
3752
3880
|
process.stdin.resume();
|
|
@@ -3818,8 +3946,11 @@ async function readline(opts) {
|
|
|
3818
3946
|
try {
|
|
3819
3947
|
while (true) {
|
|
3820
3948
|
const raw = await readKey(reader);
|
|
3821
|
-
if (!raw)
|
|
3822
|
-
|
|
3949
|
+
if (!raw) {
|
|
3950
|
+
process.stdout.write(`
|
|
3951
|
+
`);
|
|
3952
|
+
return { type: "eof" };
|
|
3953
|
+
}
|
|
3823
3954
|
if (searchMode) {
|
|
3824
3955
|
if (raw === ESC2) {
|
|
3825
3956
|
searchMode = false;
|
|
@@ -4060,79 +4191,6 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
4060
4191
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
4061
4192
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
4062
4193
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
4063
|
-
|
|
4064
|
-
// src/llm-api/api-log.ts
|
|
4065
|
-
import { writeFileSync as writeFileSync2 } from "fs";
|
|
4066
|
-
var writer2 = null;
|
|
4067
|
-
var MAX_ENTRY_BYTES = 8 * 1024;
|
|
4068
|
-
function initApiLog() {
|
|
4069
|
-
if (writer2)
|
|
4070
|
-
return;
|
|
4071
|
-
const logPath = pidLogPath("api");
|
|
4072
|
-
writeFileSync2(logPath, "");
|
|
4073
|
-
writer2 = Bun.file(logPath).writer();
|
|
4074
|
-
}
|
|
4075
|
-
function isObject2(v) {
|
|
4076
|
-
return typeof v === "object" && v !== null;
|
|
4077
|
-
}
|
|
4078
|
-
var LOG_DROP_KEYS = new Set([
|
|
4079
|
-
"requestBodyValues",
|
|
4080
|
-
"responseBody",
|
|
4081
|
-
"responseHeaders",
|
|
4082
|
-
"stack"
|
|
4083
|
-
]);
|
|
4084
|
-
function sanitizeForLog(data) {
|
|
4085
|
-
if (!isObject2(data))
|
|
4086
|
-
return data;
|
|
4087
|
-
const result = {};
|
|
4088
|
-
for (const key in data) {
|
|
4089
|
-
if (LOG_DROP_KEYS.has(key))
|
|
4090
|
-
continue;
|
|
4091
|
-
const value = data[key];
|
|
4092
|
-
if (key === "errors" && Array.isArray(value)) {
|
|
4093
|
-
result[key] = value.map((e) => sanitizeForLog(e));
|
|
4094
|
-
} else if (key === "lastError" && isObject2(value)) {
|
|
4095
|
-
result[key] = sanitizeForLog(value);
|
|
4096
|
-
} else {
|
|
4097
|
-
result[key] = value;
|
|
4098
|
-
}
|
|
4099
|
-
}
|
|
4100
|
-
return result;
|
|
4101
|
-
}
|
|
4102
|
-
function isApiLogEnabled() {
|
|
4103
|
-
return writer2 !== null;
|
|
4104
|
-
}
|
|
4105
|
-
function logApiEvent(event, data) {
|
|
4106
|
-
if (!writer2)
|
|
4107
|
-
return;
|
|
4108
|
-
const timestamp = new Date().toISOString();
|
|
4109
|
-
let entry = `[${timestamp}] ${event}
|
|
4110
|
-
`;
|
|
4111
|
-
if (data !== undefined) {
|
|
4112
|
-
try {
|
|
4113
|
-
const safe = sanitizeForLog(data);
|
|
4114
|
-
let serialized = JSON.stringify(safe, null, 2);
|
|
4115
|
-
if (serialized.length > MAX_ENTRY_BYTES) {
|
|
4116
|
-
serialized = `${serialized.slice(0, MAX_ENTRY_BYTES)}
|
|
4117
|
-
\u2026truncated`;
|
|
4118
|
-
}
|
|
4119
|
-
entry += serialized.split(`
|
|
4120
|
-
`).map((line) => ` ${line}`).join(`
|
|
4121
|
-
`);
|
|
4122
|
-
entry += `
|
|
4123
|
-
`;
|
|
4124
|
-
} catch (err) {
|
|
4125
|
-
entry += ` [Error stringifying data: ${String(err)}]
|
|
4126
|
-
`;
|
|
4127
|
-
}
|
|
4128
|
-
}
|
|
4129
|
-
entry += `---
|
|
4130
|
-
`;
|
|
4131
|
-
writer2.write(entry);
|
|
4132
|
-
writer2.flush();
|
|
4133
|
-
}
|
|
4134
|
-
|
|
4135
|
-
// src/llm-api/providers.ts
|
|
4136
4194
|
var SUPPORTED_PROVIDERS = [
|
|
4137
4195
|
"zen",
|
|
4138
4196
|
"anthropic",
|
|
@@ -4141,46 +4199,8 @@ var SUPPORTED_PROVIDERS = [
|
|
|
4141
4199
|
"ollama"
|
|
4142
4200
|
];
|
|
4143
4201
|
var ZEN_BASE2 = "https://opencode.ai/zen/v1";
|
|
4144
|
-
var REDACTED_HEADERS = new Set(["authorization"]);
|
|
4145
|
-
function redactHeaders(headers) {
|
|
4146
|
-
if (!headers)
|
|
4147
|
-
return;
|
|
4148
|
-
const h = new Headers(headers);
|
|
4149
|
-
const out = {};
|
|
4150
|
-
h.forEach((v, k) => {
|
|
4151
|
-
out[k] = REDACTED_HEADERS.has(k) ? "[REDACTED]" : v;
|
|
4152
|
-
});
|
|
4153
|
-
return out;
|
|
4154
|
-
}
|
|
4155
|
-
function createFetchWithLogging() {
|
|
4156
|
-
const customFetch = async (input, init) => {
|
|
4157
|
-
if (init?.body) {
|
|
4158
|
-
try {
|
|
4159
|
-
const bodyStr = init.body.toString();
|
|
4160
|
-
const bodyJson = JSON.parse(bodyStr);
|
|
4161
|
-
logApiEvent("Provider Request", {
|
|
4162
|
-
url: input.toString(),
|
|
4163
|
-
method: init.method,
|
|
4164
|
-
headers: redactHeaders(init.headers),
|
|
4165
|
-
body: bodyJson
|
|
4166
|
-
});
|
|
4167
|
-
} catch {
|
|
4168
|
-
logApiEvent("Provider Request", {
|
|
4169
|
-
url: input.toString(),
|
|
4170
|
-
method: init.method,
|
|
4171
|
-
headers: redactHeaders(init.headers),
|
|
4172
|
-
body: init.body
|
|
4173
|
-
});
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
return fetch(input, init);
|
|
4177
|
-
};
|
|
4178
|
-
return customFetch;
|
|
4179
|
-
}
|
|
4180
|
-
var fetchWithLogging = createFetchWithLogging();
|
|
4181
4202
|
var OAUTH_STRIP_BETAS = new Set;
|
|
4182
4203
|
function createOAuthFetch(accessToken) {
|
|
4183
|
-
const baseFetch = createFetchWithLogging();
|
|
4184
4204
|
const oauthFetch = async (input, init) => {
|
|
4185
4205
|
let opts = init;
|
|
4186
4206
|
if (opts?.headers) {
|
|
@@ -4196,7 +4216,7 @@ function createOAuthFetch(accessToken) {
|
|
|
4196
4216
|
h.set("x-app", "cli");
|
|
4197
4217
|
opts = { ...opts, headers: Object.fromEntries(h.entries()) };
|
|
4198
4218
|
}
|
|
4199
|
-
return
|
|
4219
|
+
return fetch(input, opts);
|
|
4200
4220
|
};
|
|
4201
4221
|
return oauthFetch;
|
|
4202
4222
|
}
|
|
@@ -4225,22 +4245,22 @@ function lazy(factory) {
|
|
|
4225
4245
|
}
|
|
4226
4246
|
var zenProviders = {
|
|
4227
4247
|
anthropic: lazy(() => createAnthropic({
|
|
4228
|
-
fetch
|
|
4248
|
+
fetch,
|
|
4229
4249
|
apiKey: requireEnv("OPENCODE_API_KEY"),
|
|
4230
4250
|
baseURL: ZEN_BASE2
|
|
4231
4251
|
})),
|
|
4232
4252
|
openai: lazy(() => createOpenAI({
|
|
4233
|
-
fetch
|
|
4253
|
+
fetch,
|
|
4234
4254
|
apiKey: requireEnv("OPENCODE_API_KEY"),
|
|
4235
4255
|
baseURL: ZEN_BASE2
|
|
4236
4256
|
})),
|
|
4237
4257
|
google: lazy(() => createGoogleGenerativeAI({
|
|
4238
|
-
fetch
|
|
4258
|
+
fetch,
|
|
4239
4259
|
apiKey: requireEnv("OPENCODE_API_KEY"),
|
|
4240
4260
|
baseURL: ZEN_BASE2
|
|
4241
4261
|
})),
|
|
4242
4262
|
compat: lazy(() => createOpenAICompatible({
|
|
4243
|
-
fetch
|
|
4263
|
+
fetch,
|
|
4244
4264
|
name: "zen-compat",
|
|
4245
4265
|
apiKey: requireEnv("OPENCODE_API_KEY"),
|
|
4246
4266
|
baseURL: ZEN_BASE2
|
|
@@ -4248,15 +4268,15 @@ var zenProviders = {
|
|
|
4248
4268
|
};
|
|
4249
4269
|
var directProviders = {
|
|
4250
4270
|
anthropic: lazy(() => createAnthropic({
|
|
4251
|
-
fetch
|
|
4271
|
+
fetch,
|
|
4252
4272
|
apiKey: requireEnv("ANTHROPIC_API_KEY")
|
|
4253
4273
|
})),
|
|
4254
4274
|
openai: lazy(() => createOpenAI({
|
|
4255
|
-
fetch
|
|
4275
|
+
fetch,
|
|
4256
4276
|
apiKey: requireEnv("OPENAI_API_KEY")
|
|
4257
4277
|
})),
|
|
4258
4278
|
google: lazy(() => createGoogleGenerativeAI({
|
|
4259
|
-
fetch
|
|
4279
|
+
fetch,
|
|
4260
4280
|
apiKey: requireAnyEnv(["GOOGLE_API_KEY", "GEMINI_API_KEY"])
|
|
4261
4281
|
})),
|
|
4262
4282
|
ollama: lazy(() => {
|
|
@@ -4265,7 +4285,7 @@ var directProviders = {
|
|
|
4265
4285
|
name: "ollama",
|
|
4266
4286
|
baseURL: `${baseURL}/v1`,
|
|
4267
4287
|
apiKey: "ollama",
|
|
4268
|
-
fetch
|
|
4288
|
+
fetch
|
|
4269
4289
|
});
|
|
4270
4290
|
})
|
|
4271
4291
|
};
|
|
@@ -4699,6 +4719,11 @@ async function* mapFullStreamToTurnEvents(stream, opts) {
|
|
|
4699
4719
|
const toolCallTracker = new StreamToolCallTracker;
|
|
4700
4720
|
const textPhaseTracker = new StreamTextPhaseTracker;
|
|
4701
4721
|
for await (const originalChunk of stream) {
|
|
4722
|
+
if (originalChunk.type === "start-step" && opts.stepPruneQueue) {
|
|
4723
|
+
for (const rec of opts.stepPruneQueue.splice(0)) {
|
|
4724
|
+
yield { type: "context-pruned", ...rec };
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4702
4727
|
const prepared = toolCallTracker.prepare(originalChunk);
|
|
4703
4728
|
const chunk = prepared.chunk;
|
|
4704
4729
|
const route = textPhaseTracker.route(chunk);
|
|
@@ -4713,9 +4738,6 @@ async function* mapFullStreamToTurnEvents(stream, opts) {
|
|
|
4713
4738
|
}
|
|
4714
4739
|
}
|
|
4715
4740
|
|
|
4716
|
-
// src/llm-api/turn-request.ts
|
|
4717
|
-
import { wrapLanguageModel } from "ai";
|
|
4718
|
-
|
|
4719
4741
|
// src/llm-api/turn-context.ts
|
|
4720
4742
|
import { pruneMessages } from "ai";
|
|
4721
4743
|
var DEFAULT_TOOL_RESULT_PAYLOAD_CAP_BYTES = 16 * 1024;
|
|
@@ -4802,6 +4824,15 @@ function applyContextPruning(messages) {
|
|
|
4802
4824
|
emptyMessages: "remove"
|
|
4803
4825
|
});
|
|
4804
4826
|
}
|
|
4827
|
+
function applyStepPruning(messages, initialMessageCount) {
|
|
4828
|
+
const newMessageCount = Math.max(0, messages.length - initialMessageCount);
|
|
4829
|
+
return pruneMessages({
|
|
4830
|
+
messages,
|
|
4831
|
+
reasoning: "none",
|
|
4832
|
+
toolCalls: `before-last-${40 + newMessageCount}-messages`,
|
|
4833
|
+
emptyMessages: "remove"
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4805
4836
|
function compactHeadTail(serialized, maxChars = 4096) {
|
|
4806
4837
|
const chars = Math.max(512, maxChars);
|
|
4807
4838
|
const headLength = Math.floor(chars / 2);
|
|
@@ -4873,6 +4904,22 @@ function compactToolResultPayloads(messages) {
|
|
|
4873
4904
|
});
|
|
4874
4905
|
return mutated ? compacted : messages;
|
|
4875
4906
|
}
|
|
4907
|
+
function stripCacheBreakpoint(msg) {
|
|
4908
|
+
const anthropic = msg?.providerOptions?.anthropic;
|
|
4909
|
+
if (!anthropic?.cacheControl)
|
|
4910
|
+
return msg;
|
|
4911
|
+
const { cacheControl: _, ...rest } = anthropic;
|
|
4912
|
+
const hasOtherKeys = Object.keys(rest).length > 0;
|
|
4913
|
+
const { anthropic: __, ...otherProviders } = msg.providerOptions;
|
|
4914
|
+
const hasOtherProviders = Object.keys(otherProviders).length > 0;
|
|
4915
|
+
return {
|
|
4916
|
+
...msg,
|
|
4917
|
+
providerOptions: {
|
|
4918
|
+
...hasOtherProviders ? otherProviders : {},
|
|
4919
|
+
...hasOtherKeys ? { anthropic: rest } : {}
|
|
4920
|
+
}
|
|
4921
|
+
};
|
|
4922
|
+
}
|
|
4876
4923
|
function withCacheBreakpoint(msg) {
|
|
4877
4924
|
return {
|
|
4878
4925
|
...msg,
|
|
@@ -4886,20 +4933,29 @@ function withCacheBreakpoint(msg) {
|
|
|
4886
4933
|
};
|
|
4887
4934
|
}
|
|
4888
4935
|
function annotateAnthropicCacheBreakpoints(prompt) {
|
|
4889
|
-
const result =
|
|
4890
|
-
|
|
4891
|
-
|
|
4936
|
+
const result = prompt.map(stripCacheBreakpoint);
|
|
4937
|
+
let firstSystemIdx = -1;
|
|
4938
|
+
let lastUserIdx = -1;
|
|
4939
|
+
let lastNonSystemIdx = -1;
|
|
4892
4940
|
for (let i = 0;i < result.length; i++) {
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4941
|
+
const role = result[i]?.role;
|
|
4942
|
+
if (role === "system") {
|
|
4943
|
+
if (firstSystemIdx === -1)
|
|
4944
|
+
firstSystemIdx = i;
|
|
4945
|
+
} else {
|
|
4946
|
+
lastNonSystemIdx = i;
|
|
4947
|
+
if (role === "user")
|
|
4948
|
+
lastUserIdx = i;
|
|
4949
|
+
}
|
|
4897
4950
|
}
|
|
4898
|
-
|
|
4899
|
-
result[
|
|
4951
|
+
if (firstSystemIdx >= 0) {
|
|
4952
|
+
result[firstSystemIdx] = withCacheBreakpoint(result[firstSystemIdx]);
|
|
4900
4953
|
}
|
|
4901
|
-
|
|
4902
|
-
result[
|
|
4954
|
+
if (lastUserIdx >= 0) {
|
|
4955
|
+
result[lastUserIdx] = withCacheBreakpoint(result[lastUserIdx]);
|
|
4956
|
+
}
|
|
4957
|
+
if (lastNonSystemIdx >= 0 && lastNonSystemIdx !== lastUserIdx) {
|
|
4958
|
+
result[lastNonSystemIdx] = withCacheBreakpoint(result[lastNonSystemIdx]);
|
|
4903
4959
|
}
|
|
4904
4960
|
return result;
|
|
4905
4961
|
}
|
|
@@ -4907,7 +4963,7 @@ function annotateAnthropicCacheBreakpoints(prompt) {
|
|
|
4907
4963
|
// src/llm-api/turn-prepare-messages.ts
|
|
4908
4964
|
function prepareTurnMessages(input) {
|
|
4909
4965
|
const { messages, modelString, toolCount, systemPrompt } = input;
|
|
4910
|
-
const apiLogOn =
|
|
4966
|
+
const apiLogOn = getLogContext() !== null;
|
|
4911
4967
|
const strippedRuntimeToolFields = stripToolRuntimeInputFields(messages);
|
|
4912
4968
|
if (strippedRuntimeToolFields !== messages && apiLogOn) {
|
|
4913
4969
|
logApiEvent("runtime tool input fields stripped", { modelString });
|
|
@@ -4970,7 +5026,7 @@ function prepareTurnMessages(input) {
|
|
|
4970
5026
|
}
|
|
4971
5027
|
finalMessages = [...systemMessages, ...finalMessages];
|
|
4972
5028
|
}
|
|
4973
|
-
const wasPruned = postStats.messageCount < preStats.messageCount
|
|
5029
|
+
const wasPruned = postStats.messageCount < preStats.messageCount;
|
|
4974
5030
|
return {
|
|
4975
5031
|
messages: finalMessages,
|
|
4976
5032
|
systemPrompt: finalSystemPrompt,
|
|
@@ -4983,7 +5039,7 @@ function prepareTurnMessages(input) {
|
|
|
4983
5039
|
}
|
|
4984
5040
|
|
|
4985
5041
|
// src/llm-api/provider-options.ts
|
|
4986
|
-
var
|
|
5042
|
+
var ANTHROPIC_ZEN_BUDGET = {
|
|
4987
5043
|
low: 4096,
|
|
4988
5044
|
medium: 8192,
|
|
4989
5045
|
high: 16384,
|
|
@@ -5001,27 +5057,31 @@ function clampEffort(effort, max) {
|
|
|
5001
5057
|
const maxIdx = ORDER.indexOf(max);
|
|
5002
5058
|
return ORDER[Math.min(effortIdx, maxIdx)];
|
|
5003
5059
|
}
|
|
5004
|
-
function getAnthropicThinkingOptions(
|
|
5005
|
-
const
|
|
5006
|
-
if (
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5060
|
+
function getAnthropicThinkingOptions(modelString, effort) {
|
|
5061
|
+
const { provider, modelId } = parseModelString(modelString);
|
|
5062
|
+
if (provider === "zen") {
|
|
5063
|
+
return {
|
|
5064
|
+
anthropic: {
|
|
5065
|
+
thinking: {
|
|
5066
|
+
type: "enabled",
|
|
5067
|
+
budgetTokens: ANTHROPIC_ZEN_BUDGET[effort]
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
};
|
|
5011
5071
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
}
|
|
5017
|
-
};
|
|
5072
|
+
const isOpus = /^claude-opus-4/.test(modelId);
|
|
5073
|
+
const xhighMapping = isOpus ? "max" : "high";
|
|
5074
|
+
const mapped = effort === "xhigh" ? xhighMapping : effort;
|
|
5075
|
+
return { anthropic: { thinking: { type: "adaptive" }, effort: mapped } };
|
|
5018
5076
|
}
|
|
5019
|
-
function getOpenAIThinkingOptions(
|
|
5077
|
+
function getOpenAIThinkingOptions(modelString, effort) {
|
|
5078
|
+
const { modelId } = parseModelString(modelString);
|
|
5020
5079
|
const supportsXhigh = /^gpt-5\.[2-9]/.test(modelId) || /^o4/.test(modelId);
|
|
5021
5080
|
const clamped = supportsXhigh ? effort : clampEffort(effort, "high");
|
|
5022
5081
|
return { openai: { reasoningEffort: clamped, reasoningSummary: "auto" } };
|
|
5023
5082
|
}
|
|
5024
|
-
function getGeminiThinkingOptions(
|
|
5083
|
+
function getGeminiThinkingOptions(modelString, effort) {
|
|
5084
|
+
const { modelId } = parseModelString(modelString);
|
|
5025
5085
|
if (/^gemini-3/.test(modelId)) {
|
|
5026
5086
|
return {
|
|
5027
5087
|
google: {
|
|
@@ -5058,11 +5118,10 @@ var THINKING_STRATEGIES = [
|
|
|
5058
5118
|
function getThinkingProviderOptions(modelString, effort) {
|
|
5059
5119
|
if (!supportsThinking(modelString))
|
|
5060
5120
|
return null;
|
|
5061
|
-
const { modelId } = parseModelString(modelString);
|
|
5062
5121
|
for (const strategy of THINKING_STRATEGIES) {
|
|
5063
5122
|
if (!strategy.supports(modelString))
|
|
5064
5123
|
continue;
|
|
5065
|
-
return strategy.build(
|
|
5124
|
+
return strategy.build(modelString, effort);
|
|
5066
5125
|
}
|
|
5067
5126
|
return null;
|
|
5068
5127
|
}
|
|
@@ -5118,28 +5177,39 @@ function buildTurnPreparation(input) {
|
|
|
5118
5177
|
}
|
|
5119
5178
|
function buildStreamTextRequest(input) {
|
|
5120
5179
|
const isAnthropic = isAnthropicModelFamily(input.modelString);
|
|
5121
|
-
const
|
|
5122
|
-
model: input.model,
|
|
5123
|
-
middleware: [
|
|
5124
|
-
{
|
|
5125
|
-
specificationVersion: "v3",
|
|
5126
|
-
transformParams: async ({ params }) => {
|
|
5127
|
-
const prompt = params.prompt;
|
|
5128
|
-
const pruned = applyContextPruning(prompt);
|
|
5129
|
-
const compacted = compactToolResultPayloads(pruned);
|
|
5130
|
-
const final = isAnthropic ? annotateAnthropicCacheBreakpoints(compacted) : compacted;
|
|
5131
|
-
return { ...params, prompt: final };
|
|
5132
|
-
}
|
|
5133
|
-
}
|
|
5134
|
-
]
|
|
5135
|
-
});
|
|
5180
|
+
const initialMessageCount = input.prepared.messages.length;
|
|
5136
5181
|
return {
|
|
5137
|
-
model,
|
|
5138
|
-
maxOutputTokens: 16384,
|
|
5182
|
+
model: input.model,
|
|
5183
|
+
maxOutputTokens: getMaxOutputTokens(input.modelString) ?? 16384,
|
|
5139
5184
|
messages: input.prepared.messages,
|
|
5140
5185
|
tools: input.toolSet,
|
|
5141
5186
|
stopWhen: continueUntilModelStops,
|
|
5142
5187
|
onStepFinish: input.onStepFinish,
|
|
5188
|
+
prepareStep: ({ stepNumber, messages }) => {
|
|
5189
|
+
if (stepNumber === 0) {
|
|
5190
|
+
return isAnthropic ? {
|
|
5191
|
+
messages: annotateAnthropicCacheBreakpoints(messages)
|
|
5192
|
+
} : {};
|
|
5193
|
+
}
|
|
5194
|
+
const preCount = messages.length;
|
|
5195
|
+
const pruned = applyStepPruning(messages, initialMessageCount);
|
|
5196
|
+
const postCount = pruned.length;
|
|
5197
|
+
if (postCount < preCount) {
|
|
5198
|
+
const pre = getMessageStats(messages);
|
|
5199
|
+
const post = getMessageStats(pruned);
|
|
5200
|
+
input.stepPruneQueue.push({
|
|
5201
|
+
beforeMessageCount: pre.messageCount,
|
|
5202
|
+
afterMessageCount: post.messageCount,
|
|
5203
|
+
removedMessageCount: pre.messageCount - post.messageCount,
|
|
5204
|
+
beforeTotalBytes: pre.totalBytes,
|
|
5205
|
+
afterTotalBytes: post.totalBytes,
|
|
5206
|
+
removedBytes: pre.totalBytes - post.totalBytes
|
|
5207
|
+
});
|
|
5208
|
+
}
|
|
5209
|
+
const compacted = compactToolResultPayloads(pruned);
|
|
5210
|
+
const final = isAnthropic ? annotateAnthropicCacheBreakpoints(compacted) : compacted;
|
|
5211
|
+
return { messages: final };
|
|
5212
|
+
},
|
|
5143
5213
|
...input.prepared.systemPrompt ? { system: input.prepared.systemPrompt } : {},
|
|
5144
5214
|
...Object.keys(input.providerOptions).length > 0 ? {
|
|
5145
5215
|
providerOptions: input.providerOptions
|
|
@@ -5196,11 +5266,7 @@ async function* runTurn(options) {
|
|
|
5196
5266
|
removedBytes: prepared.prePruneTotalBytes - prepared.postPruneTotalBytes
|
|
5197
5267
|
};
|
|
5198
5268
|
}
|
|
5199
|
-
|
|
5200
|
-
logApiEvent("prompt caching configured", {
|
|
5201
|
-
cacheFamily: providerOptionsResult.cacheFamily
|
|
5202
|
-
});
|
|
5203
|
-
}
|
|
5269
|
+
const stepPruneQueue = [];
|
|
5204
5270
|
const result = streamText(buildStreamTextRequest({
|
|
5205
5271
|
model,
|
|
5206
5272
|
modelString,
|
|
@@ -5208,19 +5274,21 @@ async function* runTurn(options) {
|
|
|
5208
5274
|
toolSet,
|
|
5209
5275
|
onStepFinish: turnState.onStepFinish,
|
|
5210
5276
|
signal,
|
|
5211
|
-
providerOptions: providerOptionsResult.providerOptions
|
|
5277
|
+
providerOptions: providerOptionsResult.providerOptions,
|
|
5278
|
+
stepPruneQueue
|
|
5212
5279
|
}));
|
|
5213
5280
|
result.response.catch(() => {});
|
|
5214
5281
|
for await (const event of mapFullStreamToTurnEvents(result.fullStream, {
|
|
5282
|
+
stepPruneQueue,
|
|
5215
5283
|
onChunk: (streamChunk) => {
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
}
|
|
5284
|
+
if (streamChunk.type === "tool-call" || streamChunk.type === "tool-result") {
|
|
5285
|
+
logApiEvent("stream chunk", {
|
|
5286
|
+
type: streamChunk.type,
|
|
5287
|
+
toolCallId: streamChunk.toolCallId,
|
|
5288
|
+
toolName: streamChunk.toolName,
|
|
5289
|
+
isError: streamChunk.isError
|
|
5290
|
+
});
|
|
5291
|
+
}
|
|
5224
5292
|
}
|
|
5225
5293
|
})) {
|
|
5226
5294
|
yield event;
|
|
@@ -5245,7 +5313,10 @@ async function* runTurn(options) {
|
|
|
5245
5313
|
yield {
|
|
5246
5314
|
type: "turn-error",
|
|
5247
5315
|
error: normalizedError,
|
|
5248
|
-
partialMessages: finalState.partialMessages
|
|
5316
|
+
partialMessages: finalState.partialMessages,
|
|
5317
|
+
inputTokens: finalState.inputTokens,
|
|
5318
|
+
outputTokens: finalState.outputTokens,
|
|
5319
|
+
contextTokens: finalState.contextTokens
|
|
5249
5320
|
};
|
|
5250
5321
|
}
|
|
5251
5322
|
}
|
|
@@ -5267,6 +5338,14 @@ function resumeSession(id) {
|
|
|
5267
5338
|
function touchActiveSession(session) {
|
|
5268
5339
|
touchSession(session.id, session.model);
|
|
5269
5340
|
}
|
|
5341
|
+
function autoTitleSession(sessionId, userText) {
|
|
5342
|
+
const line = userText.split(`
|
|
5343
|
+
`)[0]?.trim() ?? "";
|
|
5344
|
+
if (!line)
|
|
5345
|
+
return;
|
|
5346
|
+
const title = line.length > 60 ? `${line.slice(0, 57)}...` : line;
|
|
5347
|
+
setSessionTitle(sessionId, title);
|
|
5348
|
+
}
|
|
5270
5349
|
function renderSessionTable(footer) {
|
|
5271
5350
|
const sessions = listSessions(20);
|
|
5272
5351
|
if (sessions.length === 0)
|
|
@@ -5294,84 +5373,33 @@ function getMostRecentSession() {
|
|
|
5294
5373
|
}
|
|
5295
5374
|
|
|
5296
5375
|
// src/agent/system-prompt.ts
|
|
5297
|
-
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
5298
5376
|
import { homedir as homedir5 } from "os";
|
|
5299
|
-
import { dirname as dirname2, join as join7, resolve as resolve2 } from "path";
|
|
5300
|
-
function tryReadFile(p) {
|
|
5301
|
-
if (!existsSync4(p))
|
|
5302
|
-
return null;
|
|
5303
|
-
try {
|
|
5304
|
-
return readFileSync2(p, "utf-8");
|
|
5305
|
-
} catch {
|
|
5306
|
-
return null;
|
|
5307
|
-
}
|
|
5308
|
-
}
|
|
5309
|
-
function collectFiles(...paths) {
|
|
5310
|
-
const parts = [];
|
|
5311
|
-
for (const p of paths) {
|
|
5312
|
-
const content = tryReadFile(p);
|
|
5313
|
-
if (content)
|
|
5314
|
-
parts.push(content);
|
|
5315
|
-
}
|
|
5316
|
-
return parts.length > 0 ? parts.join(`
|
|
5317
|
-
|
|
5318
|
-
`) : null;
|
|
5319
|
-
}
|
|
5320
|
-
function loadGlobalContextFile(homeDir) {
|
|
5321
|
-
return collectFiles(join7(homeDir, ".agents", "AGENTS.md"), join7(homeDir, ".agents", "CLAUDE.md"), join7(homeDir, ".claude", "CLAUDE.md"));
|
|
5322
|
-
}
|
|
5323
|
-
function loadContextFileAt(dir) {
|
|
5324
|
-
return collectFiles(join7(dir, ".agents", "AGENTS.md"), join7(dir, ".agents", "CLAUDE.md"), join7(dir, ".claude", "CLAUDE.md"), join7(dir, "CLAUDE.md"), join7(dir, "AGENTS.md"));
|
|
5325
|
-
}
|
|
5326
|
-
function loadLocalContextFile(cwd) {
|
|
5327
|
-
const start = resolve2(cwd);
|
|
5328
|
-
let current = start;
|
|
5329
|
-
while (true) {
|
|
5330
|
-
const content = loadContextFileAt(current);
|
|
5331
|
-
if (content)
|
|
5332
|
-
return content;
|
|
5333
|
-
if (existsSync4(join7(current, ".git")))
|
|
5334
|
-
break;
|
|
5335
|
-
const parent = dirname2(current);
|
|
5336
|
-
if (parent === current)
|
|
5337
|
-
break;
|
|
5338
|
-
current = parent;
|
|
5339
|
-
}
|
|
5340
|
-
return null;
|
|
5341
|
-
}
|
|
5342
5377
|
var AUTONOMY = `
|
|
5343
5378
|
|
|
5344
|
-
# Autonomy
|
|
5345
|
-
-
|
|
5346
|
-
- Carry changes through to
|
|
5347
|
-
-
|
|
5348
|
-
- Don't ask "shall I proceed?" or "shall I start?" at the beginning of a turn. Just begin.
|
|
5349
|
-
- Do not guess unknown facts. Inspect files and web or run commands to find out. Don't make assumptions, verify.
|
|
5350
|
-
- Avoid excessive looping: if you find yourself re-reading or re-editing the same files without clear progress, stop and summarise what's blocking you.`;
|
|
5379
|
+
# Autonomy
|
|
5380
|
+
- Begin work immediately using tools. Gather context, implement, and verify \u2014 do not ask for permission to start.
|
|
5381
|
+
- Carry changes through to completion. If blocked, summarise what is preventing progress instead of looping.
|
|
5382
|
+
- Verify facts by inspecting files or running commands \u2014 never guess unknown state.`;
|
|
5351
5383
|
var SAFETY = `
|
|
5352
5384
|
|
|
5353
|
-
# Safety
|
|
5354
|
-
- Never expose, print, or commit secrets
|
|
5355
|
-
- Never invent URLs
|
|
5356
|
-
- For destructive or irreversible actions (for example deleting data or force-resetting git history), ask one targeted confirmation question before proceeding.`;
|
|
5357
|
-
var WORKSPACE_GUARDRAILS = `
|
|
5358
|
-
|
|
5359
|
-
# Workspace guardrails
|
|
5385
|
+
# Safety
|
|
5386
|
+
- Never expose, print, or commit secrets, tokens, or keys.
|
|
5387
|
+
- Never invent URLs \u2014 only use URLs the user provided or that exist in project files.
|
|
5360
5388
|
- Never revert user-authored changes unless explicitly asked.
|
|
5361
|
-
-
|
|
5362
|
-
-
|
|
5363
|
-
var
|
|
5364
|
-
|
|
5365
|
-
#
|
|
5366
|
-
-
|
|
5367
|
-
- For long
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
# Final response style
|
|
5371
|
-
- Default to non verbose, concise output (short bullets or a brief paragraph).
|
|
5372
|
-
- For substantial code changes, state what changed, where, and why.
|
|
5373
|
-
- Reference files with line numbers when helpful.
|
|
5389
|
+
- Before any destructive or irreversible action (deleting data, force-pushing, resetting history), ask one targeted confirmation question \u2014 mistakes here are unrecoverable.
|
|
5390
|
+
- If files you are editing change unexpectedly, pause and ask how to proceed.`;
|
|
5391
|
+
var COMMUNICATION = `
|
|
5392
|
+
|
|
5393
|
+
# Communication
|
|
5394
|
+
- Be concise: short bullets or a brief paragraph. No ceremonial preambles.
|
|
5395
|
+
- For long tasks, send a one-sentence progress update every 3-5 tool calls.
|
|
5396
|
+
- For code changes, state what changed, where, and why. Reference files with line numbers.
|
|
5374
5397
|
- Do not paste large file contents unless asked.`;
|
|
5398
|
+
var ERROR_HANDLING = `
|
|
5399
|
+
|
|
5400
|
+
# Error handling
|
|
5401
|
+
- On tool failure: read the error, adjust your approach, and retry once. If it fails again, explain the blocker to the user.
|
|
5402
|
+
- If you find yourself re-reading or re-editing the same files without progress, stop and summarise what is blocking you.`;
|
|
5375
5403
|
function buildSystemPrompt(sessionTimeAnchor, cwd, homeDir) {
|
|
5376
5404
|
const globalContext = loadGlobalContextFile(homeDir ?? homedir5());
|
|
5377
5405
|
const localContext = loadLocalContextFile(cwd);
|
|
@@ -5383,32 +5411,24 @@ Current working directory: ${cwdDisplay}
|
|
|
5383
5411
|
Current date/time: ${sessionTimeAnchor}
|
|
5384
5412
|
|
|
5385
5413
|
Guidelines:
|
|
5386
|
-
-
|
|
5387
|
-
- Inspect code and files primarily through shell commands.
|
|
5388
|
-
-
|
|
5389
|
-
-
|
|
5390
|
-
-
|
|
5391
|
-
-
|
|
5392
|
-
- Make parallel tool calls when independent searches/lookups can happen concurrently.
|
|
5393
|
-
- Keep your context clean and focused on the user request.
|
|
5394
|
-
|
|
5395
|
-
# Preferred file workflow
|
|
5396
|
-
- Use shell for repo inspection, verification, temp-file orchestration, and any non-edit file operation.
|
|
5397
|
-
- \`mc-edit\` is available inside shell commands.
|
|
5398
|
-
- \`mc-edit\` applies one exact-text edit and fails if the expected old text is missing or ambiguous.
|
|
5414
|
+
- You are a capable senior engineer. Proactively gather context and implement \u2014 work the problem, not just the symptom. Prefer root-cause fixes over patches.
|
|
5415
|
+
- Inspect code and files primarily through shell commands. Use temp files for large content to avoid filling your context window.
|
|
5416
|
+
- For file edits, invoke \`mc-edit\` via shell. Prefer small, targeted edits over full rewrites so diffs stay reviewable.
|
|
5417
|
+
- Make parallel tool calls when the lookups are independent \u2014 this speeds up multi-file investigation.
|
|
5418
|
+
- Before starting work, scan the skills list below. If there is even a small chance a skill applies to your task, load it with \`readSkill\` and follow its instructions before writing code or responding. Skills are mandatory when they match \u2014 not optional references.
|
|
5419
|
+
- Keep it simple: DRY, KISS, YAGNI. Avoid unnecessary complexity.
|
|
5399
5420
|
|
|
5400
|
-
|
|
5401
|
-
|
|
5421
|
+
# File editing with mc-edit
|
|
5422
|
+
\`mc-edit\` applies one exact-text replacement per invocation. It fails deterministically if the old text is missing or matches more than once.
|
|
5402
5423
|
|
|
5403
|
-
|
|
5404
|
-
- The expected old text must match exactly once.
|
|
5424
|
+
Usage: mc-edit <path> (--old <text> | --old-file <path>) [--new <text> | --new-file <path>] [--cwd <path>]
|
|
5405
5425
|
- Omit --new / --new-file to delete the matched text.
|
|
5426
|
+
- To create new files, use shell commands (e.g. \`cat > file.txt << 'EOF'\\n...\\nEOF\`).
|
|
5406
5427
|
`;
|
|
5407
5428
|
prompt += AUTONOMY;
|
|
5408
5429
|
prompt += SAFETY;
|
|
5409
|
-
prompt +=
|
|
5410
|
-
prompt +=
|
|
5411
|
-
prompt += FINAL_MESSAGE;
|
|
5430
|
+
prompt += COMMUNICATION;
|
|
5431
|
+
prompt += ERROR_HANDLING;
|
|
5412
5432
|
if (globalContext || localContext) {
|
|
5413
5433
|
prompt += `
|
|
5414
5434
|
|
|
@@ -5428,15 +5448,29 @@ ${localContext}`;
|
|
|
5428
5448
|
if (skills.length > 0) {
|
|
5429
5449
|
prompt += `
|
|
5430
5450
|
|
|
5431
|
-
#
|
|
5432
|
-
prompt += "\
|
|
5451
|
+
# Skills`;
|
|
5452
|
+
prompt += "\nSkills provide specialized instructions for specific tasks. When a task matches a skill description, call `readSkill` with that skill name before doing anything else \u2014 including asking clarifying questions. Check ALL skills against the current task, not just the first match. When a skill references relative paths, resolve them against the skill directory (parent of SKILL.md).";
|
|
5433
5453
|
prompt += `
|
|
5434
|
-
|
|
5435
|
-
|
|
5454
|
+
|
|
5455
|
+
<available_skills>`;
|
|
5436
5456
|
for (const skill of skills) {
|
|
5457
|
+
const compat = skill.compatibility ? `
|
|
5458
|
+
<compatibility>${skill.compatibility}</compatibility>` : "";
|
|
5459
|
+
prompt += `
|
|
5460
|
+
<skill>`;
|
|
5437
5461
|
prompt += `
|
|
5438
|
-
|
|
5462
|
+
<name>${skill.name}</name>`;
|
|
5463
|
+
prompt += `
|
|
5464
|
+
<description>${skill.description}</description>`;
|
|
5465
|
+
prompt += `
|
|
5466
|
+
<location>${skill.filePath}</location>`;
|
|
5467
|
+
prompt += `
|
|
5468
|
+
<source>${skill.source}</source>${compat}`;
|
|
5469
|
+
prompt += `
|
|
5470
|
+
</skill>`;
|
|
5439
5471
|
}
|
|
5472
|
+
prompt += `
|
|
5473
|
+
</available_skills>`;
|
|
5440
5474
|
}
|
|
5441
5475
|
return prompt;
|
|
5442
5476
|
}
|
|
@@ -5492,7 +5526,6 @@ class SessionRunner {
|
|
|
5492
5526
|
model: this.currentModel,
|
|
5493
5527
|
sessionId: this.session.id,
|
|
5494
5528
|
thinkingEffort: this.currentThinkingEffort,
|
|
5495
|
-
showReasoning: this.showReasoning,
|
|
5496
5529
|
totalIn: this.totalIn,
|
|
5497
5530
|
totalOut: this.totalOut,
|
|
5498
5531
|
lastContextTokens: this.lastContextTokens
|
|
@@ -5589,30 +5622,38 @@ ${output}
|
|
|
5589
5622
|
let lastAssistantText = "";
|
|
5590
5623
|
try {
|
|
5591
5624
|
this.reporter.startSpinner("thinking");
|
|
5592
|
-
const
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
this.
|
|
5609
|
-
|
|
5625
|
+
const logsRepo = new LogsRepo(getDb());
|
|
5626
|
+
setLogContext({ sessionId: this.session.id, logsRepo });
|
|
5627
|
+
try {
|
|
5628
|
+
const events = runTurn({
|
|
5629
|
+
model: llm,
|
|
5630
|
+
modelString: this.currentModel,
|
|
5631
|
+
messages: this.coreHistory,
|
|
5632
|
+
tools: this.tools,
|
|
5633
|
+
...systemPrompt ? { systemPrompt } : {},
|
|
5634
|
+
signal: abortController.signal,
|
|
5635
|
+
...this.currentThinkingEffort ? { thinkingEffort: this.currentThinkingEffort } : {}
|
|
5636
|
+
});
|
|
5637
|
+
const { inputTokens, outputTokens, contextTokens, newMessages } = await this.reporter.renderTurn(events, {
|
|
5638
|
+
showReasoning: this.showReasoning,
|
|
5639
|
+
verboseOutput: this.verboseOutput
|
|
5640
|
+
});
|
|
5641
|
+
const historyMessages = sanitizeModelAuthoredMessages(newMessages, this.currentModel);
|
|
5642
|
+
if (historyMessages.length > 0) {
|
|
5643
|
+
this.coreHistory.push(...historyMessages);
|
|
5644
|
+
this.session.messages.push(...historyMessages);
|
|
5645
|
+
saveMessages(this.session.id, historyMessages, thisTurn);
|
|
5646
|
+
}
|
|
5647
|
+
lastAssistantText = extractAssistantText(historyMessages);
|
|
5648
|
+
this.totalIn += inputTokens;
|
|
5649
|
+
this.totalOut += outputTokens;
|
|
5650
|
+
this.lastContextTokens = contextTokens;
|
|
5651
|
+
touchActiveSession(this.session);
|
|
5652
|
+
autoTitleSession(this.session.id, text);
|
|
5653
|
+
this.coreHistory = compactToolResultPayloads(applyContextPruning(this.coreHistory));
|
|
5654
|
+
} finally {
|
|
5655
|
+
setLogContext(null);
|
|
5610
5656
|
}
|
|
5611
|
-
lastAssistantText = extractAssistantText(historyMessages);
|
|
5612
|
-
this.totalIn += inputTokens;
|
|
5613
|
-
this.totalOut += outputTokens;
|
|
5614
|
-
this.lastContextTokens = contextTokens;
|
|
5615
|
-
touchActiveSession(this.session);
|
|
5616
5657
|
} catch (err) {
|
|
5617
5658
|
const stubMsg = makeInterruptMessage("error");
|
|
5618
5659
|
this.coreHistory.push(stubMsg);
|
|
@@ -5689,17 +5730,9 @@ var webContentTool = {
|
|
|
5689
5730
|
import { z as z2 } from "zod";
|
|
5690
5731
|
|
|
5691
5732
|
// src/internal/file-edit/command.ts
|
|
5692
|
-
import { existsSync as
|
|
5693
|
-
import { dirname as dirname3, extname, join as
|
|
5733
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5734
|
+
import { dirname as dirname3, extname, join as join6 } from "path";
|
|
5694
5735
|
import { fileURLToPath } from "url";
|
|
5695
|
-
|
|
5696
|
-
// src/internal/runtime/script.ts
|
|
5697
|
-
function resolveProcessScriptPath(mainModule, argv1) {
|
|
5698
|
-
const script = mainModule && !mainModule.endsWith("/[eval]") && !mainModule.endsWith("\\[eval]") ? mainModule : argv1;
|
|
5699
|
-
return script && /\.(?:[cm]?[jt]s)$/.test(script) ? script : null;
|
|
5700
|
-
}
|
|
5701
|
-
|
|
5702
|
-
// src/internal/file-edit/command.ts
|
|
5703
5736
|
function quoteShellArg(value) {
|
|
5704
5737
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
5705
5738
|
}
|
|
@@ -5710,7 +5743,7 @@ function resolveSiblingFileEditScript(scriptPath) {
|
|
|
5710
5743
|
const mainDir = dirname3(scriptPath);
|
|
5711
5744
|
const mainBase = scriptPath.slice(mainDir.length + 1);
|
|
5712
5745
|
if (mainBase === `index${ext}` || mainBase === `mc${ext}`) {
|
|
5713
|
-
return
|
|
5746
|
+
return join6(mainDir, `mc-edit${ext}`);
|
|
5714
5747
|
}
|
|
5715
5748
|
return null;
|
|
5716
5749
|
}
|
|
@@ -5719,8 +5752,12 @@ function resolveModuleLocalFileEditScript(moduleUrl) {
|
|
|
5719
5752
|
const ext = extname(modulePath);
|
|
5720
5753
|
if (!ext)
|
|
5721
5754
|
return null;
|
|
5722
|
-
const helperPath =
|
|
5723
|
-
return
|
|
5755
|
+
const helperPath = join6(dirname3(modulePath), "..", "..", `mc-edit${ext}`);
|
|
5756
|
+
return existsSync4(helperPath) ? helperPath : null;
|
|
5757
|
+
}
|
|
5758
|
+
function resolveProcessScriptPath(mainModule, argv1) {
|
|
5759
|
+
const script = mainModule && !mainModule.endsWith("/[eval]") && !mainModule.endsWith("\\[eval]") ? mainModule : argv1;
|
|
5760
|
+
return script && /\.(?:[cm]?[jt]s)$/.test(script) ? script : null;
|
|
5724
5761
|
}
|
|
5725
5762
|
function resolveFileEditCommand(execPath, mainModule, argv1, moduleUrl = import.meta.url) {
|
|
5726
5763
|
const scriptPath = resolveProcessScriptPath(mainModule, argv1);
|
|
@@ -5797,7 +5834,9 @@ ${input.command}`], {
|
|
|
5797
5834
|
if (!value)
|
|
5798
5835
|
continue;
|
|
5799
5836
|
if (totalBytes + value.length > MAX_OUTPUT_BYTES) {
|
|
5800
|
-
|
|
5837
|
+
const partial = value.slice(0, MAX_OUTPUT_BYTES - totalBytes);
|
|
5838
|
+
const lastNl = partial.lastIndexOf(10);
|
|
5839
|
+
chunks.push(lastNl >= 0 ? partial.slice(0, lastNl + 1) : partial);
|
|
5801
5840
|
truncated = true;
|
|
5802
5841
|
reader.cancel().catch(() => {});
|
|
5803
5842
|
break;
|
|
@@ -5864,7 +5903,8 @@ var listSkillsTool = {
|
|
|
5864
5903
|
name: skill.name,
|
|
5865
5904
|
description: skill.description,
|
|
5866
5905
|
source: skill.source,
|
|
5867
|
-
...skill.context && { context: skill.context }
|
|
5906
|
+
...skill.context && { context: skill.context },
|
|
5907
|
+
...skill.compatibility && { compatibility: skill.compatibility }
|
|
5868
5908
|
}));
|
|
5869
5909
|
return { skills };
|
|
5870
5910
|
}
|
|
@@ -6102,38 +6142,50 @@ ${c13.bold("Examples:")}`);
|
|
|
6102
6142
|
}
|
|
6103
6143
|
|
|
6104
6144
|
// src/cli/bootstrap.ts
|
|
6105
|
-
import { existsSync as
|
|
6145
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
6106
6146
|
import { homedir as homedir6 } from "os";
|
|
6107
|
-
import { join as
|
|
6147
|
+
import { join as join7 } from "path";
|
|
6108
6148
|
import * as c14 from "yoctocolors";
|
|
6109
6149
|
var REVIEW_SKILL_CONTENT = `---
|
|
6110
6150
|
name: review
|
|
6111
|
-
description: Review recent changes for correctness, code quality, and performance
|
|
6151
|
+
description: "Review recent changes for correctness, code quality, and performance. Use when the user asks to review, check, or audit recent code changes, diffs, or pull requests."
|
|
6112
6152
|
context: fork
|
|
6113
6153
|
---
|
|
6114
|
-
You are a code reviewer. Review recent changes and provide actionable feedback.
|
|
6115
6154
|
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
- Is the code performant?
|
|
6120
|
-
- Never flag style choices as bugs, don't be a zealot.
|
|
6121
|
-
- Never flag false positives, check before raising an issue.
|
|
6155
|
+
Review recent changes and provide actionable feedback.
|
|
6156
|
+
|
|
6157
|
+
## Steps
|
|
6122
6158
|
|
|
6123
|
-
|
|
6159
|
+
1. Identify the changes to review \u2014 check \`git diff\`, \`git log\`, and staged files.
|
|
6160
|
+
2. Read the changed files and understand the intent behind each change.
|
|
6161
|
+
3. Evaluate each change against the criteria below.
|
|
6162
|
+
4. Output a concise summary with only the issues found. If nothing is wrong, say so.
|
|
6163
|
+
|
|
6164
|
+
## Review criteria
|
|
6165
|
+
|
|
6166
|
+
- **Correctness** \u2014 Are the changes aligned with their stated goal? Do they introduce bugs or regressions?
|
|
6167
|
+
- **Code quality** \u2014 Is there duplicate, dead, or overly complex code? Are abstractions appropriate?
|
|
6168
|
+
- **Performance** \u2014 Are there unnecessary allocations, redundant I/O, or algorithmic concerns?
|
|
6169
|
+
- **Edge cases** \u2014 Are boundary conditions and error paths handled?
|
|
6170
|
+
|
|
6171
|
+
## Guidelines
|
|
6172
|
+
|
|
6173
|
+
- Never flag style choices as bugs \u2014 don't be a zealot.
|
|
6174
|
+
- Never flag false positives \u2014 verify before raising an issue.
|
|
6175
|
+
- Keep feedback actionable: say what's wrong and suggest a fix.
|
|
6124
6176
|
`;
|
|
6125
6177
|
function bootstrapGlobalDefaults() {
|
|
6126
|
-
const skillDir =
|
|
6127
|
-
const skillPath =
|
|
6128
|
-
if (!
|
|
6129
|
-
|
|
6130
|
-
|
|
6178
|
+
const skillDir = join7(homedir6(), ".agents", "skills", "review");
|
|
6179
|
+
const skillPath = join7(skillDir, "SKILL.md");
|
|
6180
|
+
if (!existsSync5(skillPath)) {
|
|
6181
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
6182
|
+
writeFileSync(skillPath, REVIEW_SKILL_CONTENT, "utf-8");
|
|
6131
6183
|
writeln(`${c14.green("\u2713")} created ${c14.dim("~/.agents/skills/review/SKILL.md")} ${c14.dim("(edit it to customise your reviews)")}`);
|
|
6132
6184
|
}
|
|
6133
6185
|
}
|
|
6134
6186
|
|
|
6135
6187
|
// src/cli/file-refs.ts
|
|
6136
|
-
import { join as
|
|
6188
|
+
import { join as join8 } from "path";
|
|
6137
6189
|
async function resolveFileRefs(text, cwd) {
|
|
6138
6190
|
const atPattern = /@([\w./\-_]+)/g;
|
|
6139
6191
|
let result = text;
|
|
@@ -6143,7 +6195,7 @@ async function resolveFileRefs(text, cwd) {
|
|
|
6143
6195
|
const ref = match[1];
|
|
6144
6196
|
if (!ref)
|
|
6145
6197
|
continue;
|
|
6146
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
6198
|
+
const filePath = ref.startsWith("/") ? ref : join8(cwd, ref);
|
|
6147
6199
|
if (isImageFilename(ref)) {
|
|
6148
6200
|
const attachment = await loadImageFile(filePath);
|
|
6149
6201
|
if (attachment) {
|
|
@@ -6154,14 +6206,9 @@ async function resolveFileRefs(text, cwd) {
|
|
|
6154
6206
|
}
|
|
6155
6207
|
try {
|
|
6156
6208
|
const content = await Bun.file(filePath).text();
|
|
6157
|
-
const lines = content.split(`
|
|
6158
|
-
`);
|
|
6159
|
-
const preview = lines.length > 200 ? `${lines.slice(0, 200).join(`
|
|
6160
|
-
`)}
|
|
6161
|
-
[truncated]` : content;
|
|
6162
6209
|
const replacement = `\`${ref}\`:
|
|
6163
6210
|
\`\`\`
|
|
6164
|
-
${
|
|
6211
|
+
${content}
|
|
6165
6212
|
\`\`\``;
|
|
6166
6213
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
6167
6214
|
} catch {}
|
|
@@ -6170,84 +6217,25 @@ ${preview}
|
|
|
6170
6217
|
}
|
|
6171
6218
|
|
|
6172
6219
|
// src/cli/input-loop.ts
|
|
6173
|
-
import * as
|
|
6174
|
-
|
|
6175
|
-
// src/cli/cli-helpers.ts
|
|
6176
|
-
async function getGitBranch(cwd) {
|
|
6177
|
-
try {
|
|
6178
|
-
const proc = Bun.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
6179
|
-
cwd,
|
|
6180
|
-
stdout: "pipe",
|
|
6181
|
-
stderr: "pipe"
|
|
6182
|
-
});
|
|
6183
|
-
const out = await new Response(proc.stdout).text();
|
|
6184
|
-
const code = await proc.exited;
|
|
6185
|
-
if (code !== 0)
|
|
6186
|
-
return null;
|
|
6187
|
-
return out.trim() || null;
|
|
6188
|
-
} catch {
|
|
6189
|
-
return null;
|
|
6190
|
-
}
|
|
6191
|
-
}
|
|
6220
|
+
import * as c21 from "yoctocolors";
|
|
6192
6221
|
|
|
6193
6222
|
// src/cli/commands.ts
|
|
6194
6223
|
import { randomBytes } from "crypto";
|
|
6195
|
-
import { unlinkSync as
|
|
6224
|
+
import { unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
6196
6225
|
import { tmpdir } from "os";
|
|
6197
|
-
import { join as
|
|
6198
|
-
import * as
|
|
6199
|
-
|
|
6200
|
-
// src/cli/commands-config.ts
|
|
6201
|
-
import * as c15 from "yoctocolors";
|
|
6202
|
-
function handleBooleanToggleCommand(opts) {
|
|
6203
|
-
const mode = opts.args.trim().toLowerCase();
|
|
6204
|
-
if (!mode) {
|
|
6205
|
-
const nextValue = !opts.current;
|
|
6206
|
-
opts.set(nextValue);
|
|
6207
|
-
writeln(`${PREFIX.success} ${opts.label} ${nextValue ? c15.green("on") : c15.dim("off")}`);
|
|
6208
|
-
return;
|
|
6209
|
-
}
|
|
6210
|
-
if (mode === "on") {
|
|
6211
|
-
opts.set(true);
|
|
6212
|
-
writeln(`${PREFIX.success} ${opts.label} ${c15.green("on")}`);
|
|
6213
|
-
return;
|
|
6214
|
-
}
|
|
6215
|
-
if (mode === "off") {
|
|
6216
|
-
opts.set(false);
|
|
6217
|
-
writeln(`${PREFIX.success} ${opts.label} ${c15.dim("off")}`);
|
|
6218
|
-
return;
|
|
6219
|
-
}
|
|
6220
|
-
writeln(`${PREFIX.error} usage: ${opts.usage}`);
|
|
6221
|
-
}
|
|
6222
|
-
function handleReasoningCommand(ctx, args) {
|
|
6223
|
-
handleBooleanToggleCommand({
|
|
6224
|
-
args,
|
|
6225
|
-
current: ctx.showReasoning,
|
|
6226
|
-
set: (value) => ctx.setShowReasoning(value),
|
|
6227
|
-
label: "reasoning display",
|
|
6228
|
-
usage: "/reasoning <on|off>"
|
|
6229
|
-
});
|
|
6230
|
-
}
|
|
6231
|
-
function handleVerboseCommand(ctx, args) {
|
|
6232
|
-
handleBooleanToggleCommand({
|
|
6233
|
-
args,
|
|
6234
|
-
current: ctx.verboseOutput,
|
|
6235
|
-
set: (value) => ctx.setVerboseOutput(value),
|
|
6236
|
-
label: "verbose output",
|
|
6237
|
-
usage: "/verbose <on|off>"
|
|
6238
|
-
});
|
|
6239
|
-
}
|
|
6226
|
+
import { join as join9 } from "path";
|
|
6227
|
+
import * as c20 from "yoctocolors";
|
|
6240
6228
|
|
|
6241
6229
|
// src/cli/commands-help.ts
|
|
6242
|
-
import * as
|
|
6230
|
+
import * as c15 from "yoctocolors";
|
|
6243
6231
|
function renderEntries(entries) {
|
|
6244
6232
|
for (const [label, description] of entries) {
|
|
6245
|
-
writeln(` ${
|
|
6233
|
+
writeln(` ${c15.cyan(label.padEnd(28))} ${c15.dim(description)}`);
|
|
6246
6234
|
}
|
|
6247
6235
|
}
|
|
6248
6236
|
function renderHelpCommand(ctx) {
|
|
6249
6237
|
writeln();
|
|
6250
|
-
writeln(` ${
|
|
6238
|
+
writeln(` ${c15.dim("session")}`);
|
|
6251
6239
|
renderEntries([
|
|
6252
6240
|
["/session [id]", "list sessions or switch to one"],
|
|
6253
6241
|
["/new", "start a fresh session"],
|
|
@@ -6255,7 +6243,7 @@ function renderHelpCommand(ctx) {
|
|
|
6255
6243
|
["/exit", "quit"]
|
|
6256
6244
|
]);
|
|
6257
6245
|
writeln();
|
|
6258
|
-
writeln(` ${
|
|
6246
|
+
writeln(` ${c15.dim("model + context")}`);
|
|
6259
6247
|
renderEntries([
|
|
6260
6248
|
["/model [id]", "list or switch models"],
|
|
6261
6249
|
["/reasoning [on|off]", "toggle reasoning display"],
|
|
@@ -6268,7 +6256,7 @@ function renderHelpCommand(ctx) {
|
|
|
6268
6256
|
["/help", "show this help"]
|
|
6269
6257
|
]);
|
|
6270
6258
|
writeln();
|
|
6271
|
-
writeln(` ${
|
|
6259
|
+
writeln(` ${c15.dim("prompt")}`);
|
|
6272
6260
|
renderEntries([
|
|
6273
6261
|
["ask normally", "send a prompt to the current agent"],
|
|
6274
6262
|
["!cmd", "run a shell command and keep the result in context"],
|
|
@@ -6278,43 +6266,44 @@ function renderHelpCommand(ctx) {
|
|
|
6278
6266
|
const skills = loadSkillsIndex(ctx.cwd);
|
|
6279
6267
|
if (skills.size > 0) {
|
|
6280
6268
|
writeln();
|
|
6281
|
-
writeln(` ${
|
|
6269
|
+
writeln(` ${c15.dim("skills")}`);
|
|
6282
6270
|
for (const skill of skills.values()) {
|
|
6283
|
-
const source = skill.source === "local" ?
|
|
6284
|
-
|
|
6271
|
+
const source = skill.source === "local" ? c15.dim("local") : c15.dim("global");
|
|
6272
|
+
const desc = truncateText(skill.description, 80);
|
|
6273
|
+
writeln(` ${c15.green(`/${skill.name}`.padEnd(28))} ${c15.dim(desc)} ${c15.dim("\xB7")} ${source}`);
|
|
6285
6274
|
}
|
|
6286
6275
|
}
|
|
6287
6276
|
writeln();
|
|
6288
|
-
writeln(` ${
|
|
6277
|
+
writeln(` ${c15.dim("keys")} ${c15.dim("esc")} cancel response ${c15.dim("\xB7")} ${c15.dim("ctrl+c / ctrl+d")} exit ${c15.dim("\xB7")} ${c15.dim("ctrl+r")} history search ${c15.dim("\xB7")} ${c15.dim("\u2191\u2193")} history`);
|
|
6289
6278
|
writeln();
|
|
6290
6279
|
}
|
|
6291
6280
|
|
|
6292
6281
|
// src/cli/commands-login.ts
|
|
6293
|
-
import * as
|
|
6282
|
+
import * as c16 from "yoctocolors";
|
|
6294
6283
|
function renderLoginHelp() {
|
|
6295
6284
|
writeln();
|
|
6296
|
-
writeln(` ${
|
|
6297
|
-
writeln(` /login ${
|
|
6298
|
-
writeln(` /login <provider> ${
|
|
6299
|
-
writeln(` /logout <provider> ${
|
|
6285
|
+
writeln(` ${c16.dim("usage:")}`);
|
|
6286
|
+
writeln(` /login ${c16.dim("show login status")}`);
|
|
6287
|
+
writeln(` /login <provider> ${c16.dim("login via OAuth")}`);
|
|
6288
|
+
writeln(` /logout <provider> ${c16.dim("clear saved tokens")}`);
|
|
6300
6289
|
writeln();
|
|
6301
|
-
writeln(` ${
|
|
6290
|
+
writeln(` ${c16.dim("providers:")}`);
|
|
6302
6291
|
for (const p of getOAuthProviders()) {
|
|
6303
|
-
const status = isLoggedIn(p.id) ?
|
|
6304
|
-
writeln(` ${
|
|
6292
|
+
const status = isLoggedIn(p.id) ? c16.green("logged in") : c16.dim("not logged in");
|
|
6293
|
+
writeln(` ${c16.cyan(p.id.padEnd(20))} ${p.name} ${c16.dim("\xB7")} ${status}`);
|
|
6305
6294
|
}
|
|
6306
6295
|
writeln();
|
|
6307
6296
|
}
|
|
6308
6297
|
function renderStatus() {
|
|
6309
6298
|
const loggedIn = listLoggedInProviders();
|
|
6310
6299
|
if (loggedIn.length === 0) {
|
|
6311
|
-
writeln(`${PREFIX.info} ${
|
|
6300
|
+
writeln(`${PREFIX.info} ${c16.dim("no OAuth logins \u2014 use")} /login <provider>`);
|
|
6312
6301
|
return;
|
|
6313
6302
|
}
|
|
6314
6303
|
for (const id of loggedIn) {
|
|
6315
6304
|
const provider = getOAuthProvider(id);
|
|
6316
6305
|
const name = provider?.name ?? id;
|
|
6317
|
-
writeln(`${PREFIX.success} ${
|
|
6306
|
+
writeln(`${PREFIX.success} ${c16.cyan(id)} ${c16.dim(name)}`);
|
|
6318
6307
|
}
|
|
6319
6308
|
}
|
|
6320
6309
|
async function handleLoginCommand(ctx, args) {
|
|
@@ -6335,7 +6324,7 @@ async function handleLoginCommand(ctx, args) {
|
|
|
6335
6324
|
if (isLoggedIn(providerId)) {
|
|
6336
6325
|
const token = await getAccessToken(providerId);
|
|
6337
6326
|
if (token) {
|
|
6338
|
-
writeln(`${PREFIX.success} already logged in to ${
|
|
6327
|
+
writeln(`${PREFIX.success} already logged in to ${c16.cyan(provider.name)}`);
|
|
6339
6328
|
return;
|
|
6340
6329
|
}
|
|
6341
6330
|
}
|
|
@@ -6346,7 +6335,7 @@ async function handleLoginCommand(ctx, args) {
|
|
|
6346
6335
|
ctx.stopSpinner();
|
|
6347
6336
|
writeln(`${PREFIX.info} ${instructions}`);
|
|
6348
6337
|
writeln();
|
|
6349
|
-
writeln(` ${
|
|
6338
|
+
writeln(` ${c16.cyan(url)}`);
|
|
6350
6339
|
writeln();
|
|
6351
6340
|
let open = "xdg-open";
|
|
6352
6341
|
if (process.platform === "darwin")
|
|
@@ -6358,12 +6347,12 @@ async function handleLoginCommand(ctx, args) {
|
|
|
6358
6347
|
},
|
|
6359
6348
|
onProgress: (msg) => {
|
|
6360
6349
|
ctx.stopSpinner();
|
|
6361
|
-
writeln(`${PREFIX.info} ${
|
|
6350
|
+
writeln(`${PREFIX.info} ${c16.dim(msg)}`);
|
|
6362
6351
|
ctx.startSpinner("exchanging tokens");
|
|
6363
6352
|
}
|
|
6364
6353
|
});
|
|
6365
6354
|
ctx.stopSpinner();
|
|
6366
|
-
writeln(`${PREFIX.success} logged in to ${
|
|
6355
|
+
writeln(`${PREFIX.success} logged in to ${c16.cyan(provider.name)}`);
|
|
6367
6356
|
} catch (err) {
|
|
6368
6357
|
ctx.stopSpinner();
|
|
6369
6358
|
writeln(`${PREFIX.error} login failed: ${err.message}`);
|
|
@@ -6376,15 +6365,15 @@ function handleLogoutCommand(_ctx, args) {
|
|
|
6376
6365
|
return;
|
|
6377
6366
|
}
|
|
6378
6367
|
if (!isLoggedIn(providerId)) {
|
|
6379
|
-
writeln(`${PREFIX.info} ${
|
|
6368
|
+
writeln(`${PREFIX.info} ${c16.dim("not logged in to")} ${providerId}`);
|
|
6380
6369
|
return;
|
|
6381
6370
|
}
|
|
6382
6371
|
logout(providerId);
|
|
6383
|
-
writeln(`${PREFIX.success} logged out of ${
|
|
6372
|
+
writeln(`${PREFIX.success} logged out of ${c16.cyan(providerId)}`);
|
|
6384
6373
|
}
|
|
6385
6374
|
|
|
6386
6375
|
// src/cli/commands-mcp.ts
|
|
6387
|
-
import * as
|
|
6376
|
+
import * as c17 from "yoctocolors";
|
|
6388
6377
|
async function handleMcpCommand(ctx, args) {
|
|
6389
6378
|
const parts = args.trim().split(/\s+/);
|
|
6390
6379
|
const sub = parts[0] ?? "list";
|
|
@@ -6392,15 +6381,15 @@ async function handleMcpCommand(ctx, args) {
|
|
|
6392
6381
|
case "list": {
|
|
6393
6382
|
const servers = listMcpServers();
|
|
6394
6383
|
if (servers.length === 0) {
|
|
6395
|
-
writeln(
|
|
6396
|
-
writeln(
|
|
6384
|
+
writeln(c17.dim(" no MCP servers configured"));
|
|
6385
|
+
writeln(c17.dim(" /mcp add <name> http <url> \xB7 /mcp add <name> stdio <cmd> [args...]"));
|
|
6397
6386
|
return;
|
|
6398
6387
|
}
|
|
6399
6388
|
writeln();
|
|
6400
6389
|
for (const s of servers) {
|
|
6401
6390
|
const detailText = s.url ?? s.command ?? "";
|
|
6402
|
-
const detail = detailText ?
|
|
6403
|
-
writeln(` ${
|
|
6391
|
+
const detail = detailText ? c17.dim(` ${detailText}`) : "";
|
|
6392
|
+
writeln(` ${c17.yellow("\u2699")} ${c17.bold(s.name)} ${c17.dim(s.transport)}${detail}`);
|
|
6404
6393
|
}
|
|
6405
6394
|
return;
|
|
6406
6395
|
}
|
|
@@ -6445,9 +6434,9 @@ async function handleMcpCommand(ctx, args) {
|
|
|
6445
6434
|
}
|
|
6446
6435
|
try {
|
|
6447
6436
|
await ctx.connectMcpServer(name);
|
|
6448
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
6437
|
+
writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} added and connected`);
|
|
6449
6438
|
} catch (e) {
|
|
6450
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
6439
|
+
writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} saved ${c17.dim(`(connection failed: ${String(e)})`)}`);
|
|
6451
6440
|
}
|
|
6452
6441
|
return;
|
|
6453
6442
|
}
|
|
@@ -6459,17 +6448,17 @@ async function handleMcpCommand(ctx, args) {
|
|
|
6459
6448
|
return;
|
|
6460
6449
|
}
|
|
6461
6450
|
deleteMcpServer(name);
|
|
6462
|
-
writeln(`${PREFIX.success} mcp server ${
|
|
6451
|
+
writeln(`${PREFIX.success} mcp server ${c17.cyan(name)} removed`);
|
|
6463
6452
|
return;
|
|
6464
6453
|
}
|
|
6465
6454
|
default:
|
|
6466
6455
|
writeln(`${PREFIX.error} unknown: /mcp ${sub}`);
|
|
6467
|
-
writeln(
|
|
6456
|
+
writeln(c17.dim(" subcommands: list \xB7 add \xB7 remove"));
|
|
6468
6457
|
}
|
|
6469
6458
|
}
|
|
6470
6459
|
|
|
6471
6460
|
// src/cli/commands-model.ts
|
|
6472
|
-
import * as
|
|
6461
|
+
import * as c18 from "yoctocolors";
|
|
6473
6462
|
var THINKING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
6474
6463
|
function parseThinkingEffort(value) {
|
|
6475
6464
|
return THINKING_EFFORTS.includes(value) ? value : null;
|
|
@@ -6488,21 +6477,21 @@ function renderModelUpdatedMessage(ctx, modelId, effortArg) {
|
|
|
6488
6477
|
if (effortArg) {
|
|
6489
6478
|
if (effortArg === "off") {
|
|
6490
6479
|
ctx.setThinkingEffort(null);
|
|
6491
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
6480
|
+
writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)} ${c18.dim("(thinking disabled)")}`);
|
|
6492
6481
|
return;
|
|
6493
6482
|
}
|
|
6494
6483
|
const effort = parseThinkingEffort(effortArg);
|
|
6495
6484
|
if (effort) {
|
|
6496
6485
|
ctx.setThinkingEffort(effort);
|
|
6497
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
6486
|
+
writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)} ${c18.dim(`(\u2726 ${effort})`)}`);
|
|
6498
6487
|
return;
|
|
6499
6488
|
}
|
|
6500
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
6501
|
-
writeln(`${PREFIX.error} unknown effort level ${
|
|
6489
|
+
writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)}`);
|
|
6490
|
+
writeln(`${PREFIX.error} unknown effort level ${c18.cyan(effortArg)} (use low, medium, high, xhigh, off)`);
|
|
6502
6491
|
return;
|
|
6503
6492
|
}
|
|
6504
|
-
const effortTag = ctx.thinkingEffort ?
|
|
6505
|
-
writeln(`${PREFIX.success} model \u2192 ${
|
|
6493
|
+
const effortTag = ctx.thinkingEffort ? c18.dim(` (\u2726 ${ctx.thinkingEffort})`) : "";
|
|
6494
|
+
writeln(`${PREFIX.success} model \u2192 ${c18.cyan(modelId)}${effortTag}`);
|
|
6506
6495
|
}
|
|
6507
6496
|
async function handleModelSet(ctx, args) {
|
|
6508
6497
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
@@ -6515,7 +6504,7 @@ async function handleModelSet(ctx, args) {
|
|
|
6515
6504
|
const snapshot = await fetchAvailableModels();
|
|
6516
6505
|
const match = findModelIdByAlias(idArg, snapshot.models.map((model) => model.id));
|
|
6517
6506
|
if (!match) {
|
|
6518
|
-
writeln(`${PREFIX.error} unknown model ${
|
|
6507
|
+
writeln(`${PREFIX.error} unknown model ${c18.cyan(idArg)} ${c18.dim("\u2014 run /models for the full list")}`);
|
|
6519
6508
|
return;
|
|
6520
6509
|
}
|
|
6521
6510
|
modelId = match;
|
|
@@ -6535,7 +6524,7 @@ function handleModelEffort(ctx, effortArg) {
|
|
|
6535
6524
|
return;
|
|
6536
6525
|
}
|
|
6537
6526
|
ctx.setThinkingEffort(effort);
|
|
6538
|
-
writeln(`${PREFIX.success} thinking effort \u2192 ${
|
|
6527
|
+
writeln(`${PREFIX.success} thinking effort \u2192 ${c18.cyan(effort)}`);
|
|
6539
6528
|
}
|
|
6540
6529
|
async function renderModelList(ctx) {
|
|
6541
6530
|
ctx.startSpinner("fetching models");
|
|
@@ -6543,13 +6532,13 @@ async function renderModelList(ctx) {
|
|
|
6543
6532
|
ctx.stopSpinner();
|
|
6544
6533
|
if (snapshot.models.length === 0) {
|
|
6545
6534
|
writeln(`${PREFIX.error} No models found. Check your API keys or Ollama connection.`);
|
|
6546
|
-
writeln(
|
|
6535
|
+
writeln(c18.dim(" Set OPENCODE_API_KEY for Zen, or start Ollama for local models."));
|
|
6547
6536
|
return;
|
|
6548
6537
|
}
|
|
6549
6538
|
if (snapshot.stale) {
|
|
6550
6539
|
const lastSync = snapshot.lastSyncAt ? new Date(snapshot.lastSyncAt).toLocaleString() : "never";
|
|
6551
6540
|
const refreshTag = snapshot.refreshing ? " (refreshing in background)" : "";
|
|
6552
|
-
writeln(
|
|
6541
|
+
writeln(c18.dim(` model metadata is stale (last sync: ${lastSync})${refreshTag}`));
|
|
6553
6542
|
}
|
|
6554
6543
|
const modelsByProvider = new Map;
|
|
6555
6544
|
for (const model of snapshot.models) {
|
|
@@ -6562,20 +6551,20 @@ async function renderModelList(ctx) {
|
|
|
6562
6551
|
}
|
|
6563
6552
|
writeln();
|
|
6564
6553
|
for (const [provider, providerModels] of modelsByProvider) {
|
|
6565
|
-
writeln(
|
|
6554
|
+
writeln(c18.bold(` ${provider}`));
|
|
6566
6555
|
for (const model of providerModels) {
|
|
6567
6556
|
const isCurrent = ctx.currentModel === model.id;
|
|
6568
|
-
const freeTag = model.free ?
|
|
6569
|
-
const contextTag = model.context ?
|
|
6570
|
-
const effortTag = isCurrent && ctx.thinkingEffort ?
|
|
6571
|
-
const currentTag = isCurrent ?
|
|
6572
|
-
writeln(` ${
|
|
6573
|
-
writeln(` ${
|
|
6557
|
+
const freeTag = model.free ? c18.green(" free") : "";
|
|
6558
|
+
const contextTag = model.context ? c18.dim(` ${Math.round(model.context / 1000)}k`) : "";
|
|
6559
|
+
const effortTag = isCurrent && ctx.thinkingEffort ? c18.dim(` \u2726 ${ctx.thinkingEffort}`) : "";
|
|
6560
|
+
const currentTag = isCurrent ? c18.cyan(" \u25C0") : "";
|
|
6561
|
+
writeln(` ${c18.dim("\xB7")} ${model.displayName}${freeTag}${contextTag}${currentTag}${effortTag}`);
|
|
6562
|
+
writeln(` ${c18.dim(model.id)}`);
|
|
6574
6563
|
}
|
|
6575
6564
|
}
|
|
6576
6565
|
writeln();
|
|
6577
|
-
writeln(
|
|
6578
|
-
writeln(
|
|
6566
|
+
writeln(c18.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
|
|
6567
|
+
writeln(c18.dim(" /model effort <low|medium|high|xhigh|off> to set thinking effort"));
|
|
6579
6568
|
}
|
|
6580
6569
|
async function handleModelCommand(ctx, args) {
|
|
6581
6570
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
@@ -6591,7 +6580,7 @@ async function handleModelCommand(ctx, args) {
|
|
|
6591
6580
|
}
|
|
6592
6581
|
|
|
6593
6582
|
// src/cli/commands-session.ts
|
|
6594
|
-
import * as
|
|
6583
|
+
import * as c19 from "yoctocolors";
|
|
6595
6584
|
function handleSessionCommand(ctx, args) {
|
|
6596
6585
|
const id = args.trim();
|
|
6597
6586
|
if (id) {
|
|
@@ -6599,15 +6588,15 @@ function handleSessionCommand(ctx, args) {
|
|
|
6599
6588
|
const ok = ctx.switchSession(id);
|
|
6600
6589
|
ctx.stopSpinner();
|
|
6601
6590
|
if (ok) {
|
|
6602
|
-
writeln(`${PREFIX.success} switched to session ${
|
|
6591
|
+
writeln(`${PREFIX.success} switched to session ${c19.cyan(id)} (${c19.cyan(ctx.currentModel)})`);
|
|
6603
6592
|
} else {
|
|
6604
|
-
writeln(`${PREFIX.error} session ${
|
|
6593
|
+
writeln(`${PREFIX.error} session ${c19.cyan(id)} not found`);
|
|
6605
6594
|
}
|
|
6606
6595
|
return;
|
|
6607
6596
|
}
|
|
6608
|
-
const shown = renderSessionTable(`${
|
|
6597
|
+
const shown = renderSessionTable(`${c19.dim("Use")} /session <id> ${c19.dim("to switch to a session.")}`);
|
|
6609
6598
|
if (!shown) {
|
|
6610
|
-
writeln(`${PREFIX.info} ${
|
|
6599
|
+
writeln(`${PREFIX.info} ${c19.dim("no sessions found")}`);
|
|
6611
6600
|
}
|
|
6612
6601
|
}
|
|
6613
6602
|
|
|
@@ -6617,9 +6606,9 @@ async function handleUndo(ctx) {
|
|
|
6617
6606
|
try {
|
|
6618
6607
|
const ok = await ctx.undoLastTurn();
|
|
6619
6608
|
if (ok) {
|
|
6620
|
-
writeln(`${PREFIX.success} ${
|
|
6609
|
+
writeln(`${PREFIX.success} ${c20.dim("last conversation turn removed")}`);
|
|
6621
6610
|
} else {
|
|
6622
|
-
writeln(`${PREFIX.info} ${
|
|
6611
|
+
writeln(`${PREFIX.info} ${c20.dim("nothing to undo")}`);
|
|
6623
6612
|
}
|
|
6624
6613
|
} finally {
|
|
6625
6614
|
ctx.stopSpinner();
|
|
@@ -6677,7 +6666,7 @@ async function handleCommand(command, args, ctx) {
|
|
|
6677
6666
|
if (loaded2) {
|
|
6678
6667
|
const srcPath = skill.source === "local" ? `.agents/skills/${skill.name}/SKILL.md` : `~/.agents/skills/${skill.name}/SKILL.md`;
|
|
6679
6668
|
if (skill.context === "fork") {
|
|
6680
|
-
writeln(`${PREFIX.info} ${
|
|
6669
|
+
writeln(`${PREFIX.info} ${c20.cyan(skill.name)} ${c20.dim(`[${srcPath}] (forked subagent)`)}`);
|
|
6681
6670
|
writeln();
|
|
6682
6671
|
const subagentPrompt = args ? `${loaded2.content}
|
|
6683
6672
|
|
|
@@ -6685,7 +6674,7 @@ ${args}` : loaded2.content;
|
|
|
6685
6674
|
const result = await runForkedSkill(skill.name, subagentPrompt, ctx.cwd);
|
|
6686
6675
|
return { type: "inject-user-message", text: result };
|
|
6687
6676
|
}
|
|
6688
|
-
writeln(`${PREFIX.info} ${
|
|
6677
|
+
writeln(`${PREFIX.info} ${c20.cyan(skill.name)} ${c20.dim(`[${srcPath}]`)}`);
|
|
6689
6678
|
writeln();
|
|
6690
6679
|
const prompt = args ? `${loaded2.content}
|
|
6691
6680
|
|
|
@@ -6693,17 +6682,16 @@ ${args}` : loaded2.content;
|
|
|
6693
6682
|
return { type: "inject-user-message", text: prompt };
|
|
6694
6683
|
}
|
|
6695
6684
|
}
|
|
6696
|
-
writeln(`${PREFIX.error} unknown: /${command} ${
|
|
6685
|
+
writeln(`${PREFIX.error} unknown: /${command} ${c20.dim("\u2014 /help for commands")}`);
|
|
6697
6686
|
return { type: "unknown", command };
|
|
6698
6687
|
}
|
|
6699
6688
|
}
|
|
6700
6689
|
}
|
|
6701
|
-
var FORK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6702
6690
|
async function runForkedSkill(skillName, prompt, cwd) {
|
|
6703
|
-
const tmpFile =
|
|
6704
|
-
|
|
6691
|
+
const tmpFile = join9(tmpdir(), `mc-fork-${randomBytes(8).toString("hex")}.md`);
|
|
6692
|
+
writeFileSync2(tmpFile, prompt, "utf8");
|
|
6705
6693
|
try {
|
|
6706
|
-
writeln(`${PREFIX.info} ${
|
|
6694
|
+
writeln(`${PREFIX.info} ${c20.dim("running subagent\u2026")}`);
|
|
6707
6695
|
const proc = Bun.spawn([process.execPath, Bun.main], {
|
|
6708
6696
|
cwd,
|
|
6709
6697
|
stdin: Bun.file(tmpFile),
|
|
@@ -6715,15 +6703,9 @@ async function runForkedSkill(skillName, prompt, cwd) {
|
|
|
6715
6703
|
stdout: "pipe",
|
|
6716
6704
|
stderr: "pipe"
|
|
6717
6705
|
});
|
|
6718
|
-
const timer = setTimeout(() => {
|
|
6719
|
-
try {
|
|
6720
|
-
proc.kill("SIGTERM");
|
|
6721
|
-
} catch {}
|
|
6722
|
-
}, FORK_TIMEOUT_MS);
|
|
6723
6706
|
const stdout = await new Response(proc.stdout).text();
|
|
6724
6707
|
const stderr = await new Response(proc.stderr).text();
|
|
6725
6708
|
const exitCode = await proc.exited;
|
|
6726
|
-
clearTimeout(timer);
|
|
6727
6709
|
if (exitCode !== 0 && !stdout.trim()) {
|
|
6728
6710
|
return `[Subagent skill "${skillName}" failed (exit ${exitCode})]
|
|
6729
6711
|
${stderr.trim()}`;
|
|
@@ -6734,41 +6716,45 @@ ${stderr.trim()}`;
|
|
|
6734
6716
|
${output}`;
|
|
6735
6717
|
} finally {
|
|
6736
6718
|
try {
|
|
6737
|
-
|
|
6719
|
+
unlinkSync2(tmpFile);
|
|
6738
6720
|
} catch {}
|
|
6739
6721
|
}
|
|
6740
6722
|
}
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6723
|
+
function handleBooleanToggleCommand(opts) {
|
|
6724
|
+
const mode = opts.args.trim().toLowerCase();
|
|
6725
|
+
if (!mode) {
|
|
6726
|
+
writeln(`${PREFIX.success} ${opts.label} ${opts.current ? c20.green("on") : c20.dim("off")}`);
|
|
6727
|
+
return;
|
|
6728
|
+
}
|
|
6729
|
+
if (mode === "on") {
|
|
6730
|
+
opts.set(true);
|
|
6731
|
+
writeln(`${PREFIX.success} ${opts.label} ${c20.green("on")}`);
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
if (mode === "off") {
|
|
6735
|
+
opts.set(false);
|
|
6736
|
+
writeln(`${PREFIX.success} ${opts.label} ${c20.dim("off")}`);
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
writeln(`${PREFIX.error} usage: ${opts.usage}`);
|
|
6740
|
+
}
|
|
6741
|
+
function handleReasoningCommand(ctx, args) {
|
|
6742
|
+
handleBooleanToggleCommand({
|
|
6743
|
+
args,
|
|
6744
|
+
current: ctx.showReasoning,
|
|
6745
|
+
set: (value) => ctx.setShowReasoning(value),
|
|
6746
|
+
label: "reasoning display",
|
|
6747
|
+
usage: "/reasoning <on|off>"
|
|
6748
|
+
});
|
|
6749
|
+
}
|
|
6750
|
+
function handleVerboseCommand(ctx, args) {
|
|
6751
|
+
handleBooleanToggleCommand({
|
|
6752
|
+
args,
|
|
6753
|
+
current: ctx.verboseOutput,
|
|
6754
|
+
set: (value) => ctx.setVerboseOutput(value),
|
|
6755
|
+
label: "verbose output",
|
|
6756
|
+
usage: "/verbose <on|off>"
|
|
6757
|
+
});
|
|
6772
6758
|
}
|
|
6773
6759
|
|
|
6774
6760
|
// src/cli/input-loop.ts
|
|
@@ -6783,20 +6769,33 @@ ${result.stderr}`);
|
|
|
6783
6769
|
sections.push("command timed out");
|
|
6784
6770
|
if (!result.success)
|
|
6785
6771
|
sections.push(`exit code: ${result.exitCode}`);
|
|
6772
|
+
if (sections.length === 0)
|
|
6773
|
+
sections.push(`(no output, exit ${result.exitCode})`);
|
|
6786
6774
|
return sections.join(`
|
|
6787
6775
|
|
|
6788
6776
|
`).trim();
|
|
6789
6777
|
}
|
|
6778
|
+
async function getGitBranch(cwd) {
|
|
6779
|
+
try {
|
|
6780
|
+
const proc = Bun.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
6781
|
+
cwd,
|
|
6782
|
+
stdout: "pipe",
|
|
6783
|
+
stderr: "pipe"
|
|
6784
|
+
});
|
|
6785
|
+
const out = await new Response(proc.stdout).text();
|
|
6786
|
+
const code = await proc.exited;
|
|
6787
|
+
if (code !== 0)
|
|
6788
|
+
return null;
|
|
6789
|
+
return out.trim() || null;
|
|
6790
|
+
} catch {
|
|
6791
|
+
return null;
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6790
6794
|
async function runInputLoop(opts) {
|
|
6791
6795
|
const { cwd, reporter, cmdCtx, runner } = opts;
|
|
6792
6796
|
let lastStatusSignature = null;
|
|
6793
|
-
const gitBranchCache = createGitBranchCache({
|
|
6794
|
-
cwd,
|
|
6795
|
-
fetchBranch: getGitBranch
|
|
6796
|
-
});
|
|
6797
|
-
await gitBranchCache.refresh(true);
|
|
6798
6797
|
while (true) {
|
|
6799
|
-
const branch =
|
|
6798
|
+
const branch = await getGitBranch(cwd);
|
|
6800
6799
|
const status = runner.getStatusInfo();
|
|
6801
6800
|
const cwdDisplay = tildePath(cwd);
|
|
6802
6801
|
const contextWindow = getContextWindow(status.model);
|
|
@@ -6809,8 +6808,7 @@ async function runInputLoop(opts) {
|
|
|
6809
6808
|
outputTokens: status.totalOut,
|
|
6810
6809
|
contextTokens: status.lastContextTokens,
|
|
6811
6810
|
contextWindow,
|
|
6812
|
-
thinkingEffort: status.thinkingEffort
|
|
6813
|
-
showReasoning: status.showReasoning
|
|
6811
|
+
thinkingEffort: status.thinkingEffort
|
|
6814
6812
|
};
|
|
6815
6813
|
const statusSignature = buildStatusBarSignature(statusData);
|
|
6816
6814
|
if (statusSignature !== lastStatusSignature) {
|
|
@@ -6825,15 +6823,14 @@ async function runInputLoop(opts) {
|
|
|
6825
6823
|
}
|
|
6826
6824
|
switch (input.type) {
|
|
6827
6825
|
case "eof":
|
|
6828
|
-
reporter.writeText(
|
|
6826
|
+
reporter.writeText(c21.dim("Goodbye."));
|
|
6829
6827
|
return;
|
|
6830
6828
|
case "interrupt":
|
|
6831
|
-
gitBranchCache.refreshInBackground();
|
|
6832
6829
|
continue;
|
|
6833
6830
|
case "command": {
|
|
6834
6831
|
const result = await handleCommand(input.command, input.args, cmdCtx);
|
|
6835
6832
|
if (result.type === "exit") {
|
|
6836
|
-
reporter.writeText(
|
|
6833
|
+
reporter.writeText(c21.dim("Goodbye."));
|
|
6837
6834
|
return;
|
|
6838
6835
|
}
|
|
6839
6836
|
if (result.type === "inject-user-message") {
|
|
@@ -6843,7 +6840,6 @@ async function runInputLoop(opts) {
|
|
|
6843
6840
|
await runner.processUserInput(resolvedText, refImages);
|
|
6844
6841
|
} catch {}
|
|
6845
6842
|
}
|
|
6846
|
-
gitBranchCache.refreshInBackground();
|
|
6847
6843
|
continue;
|
|
6848
6844
|
}
|
|
6849
6845
|
case "shell": {
|
|
@@ -6860,7 +6856,6 @@ async function runInputLoop(opts) {
|
|
|
6860
6856
|
if (context) {
|
|
6861
6857
|
runner.addShellContext(input.command, context);
|
|
6862
6858
|
}
|
|
6863
|
-
gitBranchCache.refreshInBackground(true);
|
|
6864
6859
|
continue;
|
|
6865
6860
|
}
|
|
6866
6861
|
case "submit": {
|
|
@@ -6870,7 +6865,6 @@ async function runInputLoop(opts) {
|
|
|
6870
6865
|
try {
|
|
6871
6866
|
await runner.processUserInput(resolvedText, allImages);
|
|
6872
6867
|
} catch {}
|
|
6873
|
-
gitBranchCache.refreshInBackground();
|
|
6874
6868
|
continue;
|
|
6875
6869
|
}
|
|
6876
6870
|
}
|
|
@@ -6906,9 +6900,6 @@ async function resolvePromptInput(promptArg, opts) {
|
|
|
6906
6900
|
|
|
6907
6901
|
// src/index.ts
|
|
6908
6902
|
registerTerminalCleanup();
|
|
6909
|
-
initErrorLog();
|
|
6910
|
-
initApiLog();
|
|
6911
|
-
cleanStaleLogs();
|
|
6912
6903
|
initModelInfoCache();
|
|
6913
6904
|
pruneOldData();
|
|
6914
6905
|
refreshModelInfoInBackground().catch(() => {});
|
|
@@ -6945,7 +6936,7 @@ async function main() {
|
|
|
6945
6936
|
if (last) {
|
|
6946
6937
|
sessionId = last.id;
|
|
6947
6938
|
} else {
|
|
6948
|
-
writeln(
|
|
6939
|
+
writeln(c22.dim("No previous session found, starting fresh."));
|
|
6949
6940
|
}
|
|
6950
6941
|
} else if (args.sessionId) {
|
|
6951
6942
|
sessionId = args.sessionId;
|
|
@@ -6968,7 +6959,7 @@ async function main() {
|
|
|
6968
6959
|
const { text: resolvedText, images: refImages } = await resolveFileRefs(prompt, args.cwd);
|
|
6969
6960
|
const responseText = await runner.processUserInput(resolvedText, refImages);
|
|
6970
6961
|
if (responseText) {
|
|
6971
|
-
writeln(responseText);
|
|
6962
|
+
writeln(responseText.trimStart());
|
|
6972
6963
|
}
|
|
6973
6964
|
return;
|
|
6974
6965
|
}
|
|
@@ -6985,7 +6976,7 @@ async function main() {
|
|
|
6985
6976
|
process.exit(1);
|
|
6986
6977
|
}
|
|
6987
6978
|
}
|
|
6988
|
-
main().
|
|
6979
|
+
main().then(() => process.exit(0), (err) => {
|
|
6989
6980
|
if (!(err instanceof RenderedError)) {
|
|
6990
6981
|
renderError(err, "main");
|
|
6991
6982
|
}
|