@livx.cc/agentx 0.95.1 → 0.95.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Agent-1DRfsYaK.d.ts → Agent-DRe91tAy.d.ts} +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +592 -430
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.js +401 -241
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1059,6 +1059,172 @@ var init_tools = __esm({
|
|
|
1059
1059
|
}
|
|
1060
1060
|
});
|
|
1061
1061
|
|
|
1062
|
+
// src/worktree.ts
|
|
1063
|
+
var worktree_exports = {};
|
|
1064
|
+
__export(worktree_exports, {
|
|
1065
|
+
cleanupWorktree: () => cleanupWorktree,
|
|
1066
|
+
enterWorktree: () => enterWorktree,
|
|
1067
|
+
exitWorktree: () => exitWorktree,
|
|
1068
|
+
findGitRoot: () => findGitRoot,
|
|
1069
|
+
getOrCreateWorktree: () => getOrCreateWorktree,
|
|
1070
|
+
getWorktreeSession: () => getWorktreeSession,
|
|
1071
|
+
validateWorktreeSlug: () => validateWorktreeSlug,
|
|
1072
|
+
worktreeBranchName: () => worktreeBranchName
|
|
1073
|
+
});
|
|
1074
|
+
import { spawnSync } from "child_process";
|
|
1075
|
+
import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync, rmSync } from "fs";
|
|
1076
|
+
import { join as join2 } from "path";
|
|
1077
|
+
function validateWorktreeSlug(slug2) {
|
|
1078
|
+
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
1079
|
+
if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
|
|
1080
|
+
for (const seg of slug2.split("/")) {
|
|
1081
|
+
if (seg === "." || seg === "..") throw new Error(`worktree slug must not contain "." or ".." segments`);
|
|
1082
|
+
if (!VALID_SEGMENT.test(seg)) throw new Error(`worktree slug segment "${seg}" contains invalid characters (allowed: a-z A-Z 0-9 . _ -)`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function flattenSlug(slug2) {
|
|
1086
|
+
return slug2.replaceAll("/", "+");
|
|
1087
|
+
}
|
|
1088
|
+
function worktreeBranchName(slug2) {
|
|
1089
|
+
return `worktree-${flattenSlug(slug2)}`;
|
|
1090
|
+
}
|
|
1091
|
+
function worktreesDir(repoRoot) {
|
|
1092
|
+
return join2(repoRoot, ".claude", "worktrees");
|
|
1093
|
+
}
|
|
1094
|
+
function worktreePathFor(repoRoot, slug2) {
|
|
1095
|
+
return join2(worktreesDir(repoRoot), flattenSlug(slug2));
|
|
1096
|
+
}
|
|
1097
|
+
function git(args, cwd) {
|
|
1098
|
+
const r = spawnSync("git", args, {
|
|
1099
|
+
cwd,
|
|
1100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1101
|
+
env: { ...process.env, ...GIT_NO_PROMPT_ENV }
|
|
1102
|
+
});
|
|
1103
|
+
return { ok: r.status === 0, stdout: (r.stdout ?? "").toString().trim(), stderr: (r.stderr ?? "").toString().trim() };
|
|
1104
|
+
}
|
|
1105
|
+
function findGitRoot(from) {
|
|
1106
|
+
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
1107
|
+
return r.ok ? r.stdout : null;
|
|
1108
|
+
}
|
|
1109
|
+
function readWorktreeHead(worktreePath) {
|
|
1110
|
+
try {
|
|
1111
|
+
const gitFile = readFileSync(join2(worktreePath, ".git"), "utf-8").trim();
|
|
1112
|
+
const m = gitFile.match(/^gitdir:\s*(.+)$/);
|
|
1113
|
+
if (!m) return null;
|
|
1114
|
+
const headPath = join2(m[1], "HEAD");
|
|
1115
|
+
const head = readFileSync(headPath, "utf-8").trim();
|
|
1116
|
+
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
1117
|
+
if (!refMatch) return head.length === 40 ? head : null;
|
|
1118
|
+
const refPath = join2(m[1], "..", refMatch[1]);
|
|
1119
|
+
try {
|
|
1120
|
+
return readFileSync(refPath, "utf-8").trim();
|
|
1121
|
+
} catch {
|
|
1122
|
+
}
|
|
1123
|
+
const r = git(["rev-parse", "HEAD"], worktreePath);
|
|
1124
|
+
return r.ok ? r.stdout : null;
|
|
1125
|
+
} catch {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function getOrCreateWorktree(repoRoot, slug2) {
|
|
1130
|
+
validateWorktreeSlug(slug2);
|
|
1131
|
+
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
1132
|
+
const branch = worktreeBranchName(slug2);
|
|
1133
|
+
const existingHead = readWorktreeHead(worktreePath);
|
|
1134
|
+
if (existingHead) return { worktreePath, branch, headCommit: existingHead, existed: true };
|
|
1135
|
+
mkdirSync(worktreesDir(repoRoot), { recursive: true });
|
|
1136
|
+
const defaultBranch = git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
|
|
1137
|
+
let baseBranch = defaultBranch.ok ? defaultBranch.stdout.replace(/^origin\//, "") : "HEAD";
|
|
1138
|
+
const originRef = `origin/${baseBranch}`;
|
|
1139
|
+
const hasOrigin = git(["rev-parse", "--verify", originRef], repoRoot);
|
|
1140
|
+
if (!hasOrigin.ok) {
|
|
1141
|
+
const fetched = git(["fetch", "origin", baseBranch], repoRoot);
|
|
1142
|
+
if (!fetched.ok) baseBranch = "HEAD";
|
|
1143
|
+
}
|
|
1144
|
+
const base = baseBranch === "HEAD" ? "HEAD" : originRef;
|
|
1145
|
+
const r = git(["worktree", "add", "-B", branch, worktreePath, base], repoRoot);
|
|
1146
|
+
if (!r.ok) throw new Error(`failed to create worktree: ${r.stderr}`);
|
|
1147
|
+
const headSha = git(["rev-parse", "HEAD"], worktreePath);
|
|
1148
|
+
symlinkDirs(repoRoot, worktreePath, ["node_modules", ".bun"]);
|
|
1149
|
+
return { worktreePath, branch, headCommit: headSha.stdout, existed: false, baseBranch };
|
|
1150
|
+
}
|
|
1151
|
+
function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
1152
|
+
for (const dir of dirs) {
|
|
1153
|
+
const src = join2(repoRoot, dir);
|
|
1154
|
+
const dst = join2(worktreePath, dir);
|
|
1155
|
+
try {
|
|
1156
|
+
statSync2(src);
|
|
1157
|
+
} catch {
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
symlinkSync(src, dst, "dir");
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function cleanupWorktree(repoRoot, slug2, opts) {
|
|
1168
|
+
validateWorktreeSlug(slug2);
|
|
1169
|
+
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
1170
|
+
const branch = worktreeBranchName(slug2);
|
|
1171
|
+
try {
|
|
1172
|
+
statSync2(worktreePath);
|
|
1173
|
+
} catch {
|
|
1174
|
+
return { removed: false, reason: "not found" };
|
|
1175
|
+
}
|
|
1176
|
+
if (!opts?.force) {
|
|
1177
|
+
const status = git(["status", "--porcelain", "-uno"], worktreePath);
|
|
1178
|
+
if (status.ok && status.stdout) return { removed: false, reason: "uncommitted changes" };
|
|
1179
|
+
}
|
|
1180
|
+
for (const dir of ["node_modules", ".bun"]) {
|
|
1181
|
+
try {
|
|
1182
|
+
rmSync(join2(worktreePath, dir));
|
|
1183
|
+
} catch {
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
const rm = git(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
1187
|
+
if (!rm.ok) return { removed: false, reason: rm.stderr };
|
|
1188
|
+
git(["branch", "-D", branch], repoRoot);
|
|
1189
|
+
return { removed: true };
|
|
1190
|
+
}
|
|
1191
|
+
function getWorktreeSession() {
|
|
1192
|
+
return _session;
|
|
1193
|
+
}
|
|
1194
|
+
function enterWorktree(slug2, cwd) {
|
|
1195
|
+
const repoRoot = findGitRoot(cwd);
|
|
1196
|
+
if (!repoRoot) throw new Error("--worktree requires a git repository");
|
|
1197
|
+
const info = getOrCreateWorktree(repoRoot, slug2);
|
|
1198
|
+
process.chdir(info.worktreePath);
|
|
1199
|
+
_session = {
|
|
1200
|
+
originalCwd: cwd,
|
|
1201
|
+
worktreePath: info.worktreePath,
|
|
1202
|
+
slug: slug2,
|
|
1203
|
+
branch: info.branch,
|
|
1204
|
+
headCommit: info.headCommit
|
|
1205
|
+
};
|
|
1206
|
+
return _session;
|
|
1207
|
+
}
|
|
1208
|
+
function exitWorktree() {
|
|
1209
|
+
if (!_session) return null;
|
|
1210
|
+
const repoRoot = findGitRoot(_session.originalCwd);
|
|
1211
|
+
if (!repoRoot) return null;
|
|
1212
|
+
process.chdir(_session.originalCwd);
|
|
1213
|
+
const result = cleanupWorktree(repoRoot, _session.slug);
|
|
1214
|
+
_session = null;
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
var VALID_SEGMENT, MAX_SLUG_LENGTH, GIT_NO_PROMPT_ENV, _session;
|
|
1218
|
+
var init_worktree = __esm({
|
|
1219
|
+
"src/worktree.ts"() {
|
|
1220
|
+
"use strict";
|
|
1221
|
+
VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
1222
|
+
MAX_SLUG_LENGTH = 64;
|
|
1223
|
+
GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
1224
|
+
_session = null;
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1062
1228
|
// src/NodeDiskFilesystem.ts
|
|
1063
1229
|
var NodeDiskFilesystem_exports = {};
|
|
1064
1230
|
__export(NodeDiskFilesystem_exports, {
|
|
@@ -1420,172 +1586,6 @@ var init_JailedFilesystem = __esm({
|
|
|
1420
1586
|
}
|
|
1421
1587
|
});
|
|
1422
1588
|
|
|
1423
|
-
// src/worktree.ts
|
|
1424
|
-
var worktree_exports = {};
|
|
1425
|
-
__export(worktree_exports, {
|
|
1426
|
-
cleanupWorktree: () => cleanupWorktree,
|
|
1427
|
-
enterWorktree: () => enterWorktree,
|
|
1428
|
-
exitWorktree: () => exitWorktree,
|
|
1429
|
-
findGitRoot: () => findGitRoot,
|
|
1430
|
-
getOrCreateWorktree: () => getOrCreateWorktree,
|
|
1431
|
-
getWorktreeSession: () => getWorktreeSession,
|
|
1432
|
-
validateWorktreeSlug: () => validateWorktreeSlug,
|
|
1433
|
-
worktreeBranchName: () => worktreeBranchName
|
|
1434
|
-
});
|
|
1435
|
-
import { spawnSync } from "child_process";
|
|
1436
|
-
import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync, rmSync } from "fs";
|
|
1437
|
-
import { join as join3 } from "path";
|
|
1438
|
-
function validateWorktreeSlug(slug2) {
|
|
1439
|
-
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
1440
|
-
if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
|
|
1441
|
-
for (const seg of slug2.split("/")) {
|
|
1442
|
-
if (seg === "." || seg === "..") throw new Error(`worktree slug must not contain "." or ".." segments`);
|
|
1443
|
-
if (!VALID_SEGMENT.test(seg)) throw new Error(`worktree slug segment "${seg}" contains invalid characters (allowed: a-z A-Z 0-9 . _ -)`);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
function flattenSlug(slug2) {
|
|
1447
|
-
return slug2.replaceAll("/", "+");
|
|
1448
|
-
}
|
|
1449
|
-
function worktreeBranchName(slug2) {
|
|
1450
|
-
return `worktree-${flattenSlug(slug2)}`;
|
|
1451
|
-
}
|
|
1452
|
-
function worktreesDir(repoRoot) {
|
|
1453
|
-
return join3(repoRoot, ".claude", "worktrees");
|
|
1454
|
-
}
|
|
1455
|
-
function worktreePathFor(repoRoot, slug2) {
|
|
1456
|
-
return join3(worktreesDir(repoRoot), flattenSlug(slug2));
|
|
1457
|
-
}
|
|
1458
|
-
function git(args, cwd) {
|
|
1459
|
-
const r = spawnSync("git", args, {
|
|
1460
|
-
cwd,
|
|
1461
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1462
|
-
env: { ...process.env, ...GIT_NO_PROMPT_ENV }
|
|
1463
|
-
});
|
|
1464
|
-
return { ok: r.status === 0, stdout: (r.stdout ?? "").toString().trim(), stderr: (r.stderr ?? "").toString().trim() };
|
|
1465
|
-
}
|
|
1466
|
-
function findGitRoot(from) {
|
|
1467
|
-
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
1468
|
-
return r.ok ? r.stdout : null;
|
|
1469
|
-
}
|
|
1470
|
-
function readWorktreeHead(worktreePath) {
|
|
1471
|
-
try {
|
|
1472
|
-
const gitFile = readFileSync(join3(worktreePath, ".git"), "utf-8").trim();
|
|
1473
|
-
const m = gitFile.match(/^gitdir:\s*(.+)$/);
|
|
1474
|
-
if (!m) return null;
|
|
1475
|
-
const headPath = join3(m[1], "HEAD");
|
|
1476
|
-
const head = readFileSync(headPath, "utf-8").trim();
|
|
1477
|
-
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
1478
|
-
if (!refMatch) return head.length === 40 ? head : null;
|
|
1479
|
-
const refPath = join3(m[1], "..", refMatch[1]);
|
|
1480
|
-
try {
|
|
1481
|
-
return readFileSync(refPath, "utf-8").trim();
|
|
1482
|
-
} catch {
|
|
1483
|
-
}
|
|
1484
|
-
const r = git(["rev-parse", "HEAD"], worktreePath);
|
|
1485
|
-
return r.ok ? r.stdout : null;
|
|
1486
|
-
} catch {
|
|
1487
|
-
return null;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
function getOrCreateWorktree(repoRoot, slug2) {
|
|
1491
|
-
validateWorktreeSlug(slug2);
|
|
1492
|
-
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
1493
|
-
const branch = worktreeBranchName(slug2);
|
|
1494
|
-
const existingHead = readWorktreeHead(worktreePath);
|
|
1495
|
-
if (existingHead) return { worktreePath, branch, headCommit: existingHead, existed: true };
|
|
1496
|
-
mkdirSync(worktreesDir(repoRoot), { recursive: true });
|
|
1497
|
-
const defaultBranch = git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
|
|
1498
|
-
let baseBranch = defaultBranch.ok ? defaultBranch.stdout.replace(/^origin\//, "") : "HEAD";
|
|
1499
|
-
const originRef = `origin/${baseBranch}`;
|
|
1500
|
-
const hasOrigin = git(["rev-parse", "--verify", originRef], repoRoot);
|
|
1501
|
-
if (!hasOrigin.ok) {
|
|
1502
|
-
const fetched = git(["fetch", "origin", baseBranch], repoRoot);
|
|
1503
|
-
if (!fetched.ok) baseBranch = "HEAD";
|
|
1504
|
-
}
|
|
1505
|
-
const base = baseBranch === "HEAD" ? "HEAD" : originRef;
|
|
1506
|
-
const r = git(["worktree", "add", "-B", branch, worktreePath, base], repoRoot);
|
|
1507
|
-
if (!r.ok) throw new Error(`failed to create worktree: ${r.stderr}`);
|
|
1508
|
-
const headSha = git(["rev-parse", "HEAD"], worktreePath);
|
|
1509
|
-
symlinkDirs(repoRoot, worktreePath, ["node_modules", ".bun"]);
|
|
1510
|
-
return { worktreePath, branch, headCommit: headSha.stdout, existed: false, baseBranch };
|
|
1511
|
-
}
|
|
1512
|
-
function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
1513
|
-
for (const dir of dirs) {
|
|
1514
|
-
const src = join3(repoRoot, dir);
|
|
1515
|
-
const dst = join3(worktreePath, dir);
|
|
1516
|
-
try {
|
|
1517
|
-
statSync2(src);
|
|
1518
|
-
} catch {
|
|
1519
|
-
continue;
|
|
1520
|
-
}
|
|
1521
|
-
try {
|
|
1522
|
-
symlinkSync(src, dst, "dir");
|
|
1523
|
-
} catch (e) {
|
|
1524
|
-
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
function cleanupWorktree(repoRoot, slug2, opts) {
|
|
1529
|
-
validateWorktreeSlug(slug2);
|
|
1530
|
-
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
1531
|
-
const branch = worktreeBranchName(slug2);
|
|
1532
|
-
try {
|
|
1533
|
-
statSync2(worktreePath);
|
|
1534
|
-
} catch {
|
|
1535
|
-
return { removed: false, reason: "not found" };
|
|
1536
|
-
}
|
|
1537
|
-
if (!opts?.force) {
|
|
1538
|
-
const status = git(["status", "--porcelain", "-uno"], worktreePath);
|
|
1539
|
-
if (status.ok && status.stdout) return { removed: false, reason: "uncommitted changes" };
|
|
1540
|
-
}
|
|
1541
|
-
for (const dir of ["node_modules", ".bun"]) {
|
|
1542
|
-
try {
|
|
1543
|
-
rmSync(join3(worktreePath, dir));
|
|
1544
|
-
} catch {
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
const rm = git(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
1548
|
-
if (!rm.ok) return { removed: false, reason: rm.stderr };
|
|
1549
|
-
git(["branch", "-D", branch], repoRoot);
|
|
1550
|
-
return { removed: true };
|
|
1551
|
-
}
|
|
1552
|
-
function getWorktreeSession() {
|
|
1553
|
-
return _session;
|
|
1554
|
-
}
|
|
1555
|
-
function enterWorktree(slug2, cwd) {
|
|
1556
|
-
const repoRoot = findGitRoot(cwd);
|
|
1557
|
-
if (!repoRoot) throw new Error("--worktree requires a git repository");
|
|
1558
|
-
const info = getOrCreateWorktree(repoRoot, slug2);
|
|
1559
|
-
process.chdir(info.worktreePath);
|
|
1560
|
-
_session = {
|
|
1561
|
-
originalCwd: cwd,
|
|
1562
|
-
worktreePath: info.worktreePath,
|
|
1563
|
-
slug: slug2,
|
|
1564
|
-
branch: info.branch,
|
|
1565
|
-
headCommit: info.headCommit
|
|
1566
|
-
};
|
|
1567
|
-
return _session;
|
|
1568
|
-
}
|
|
1569
|
-
function exitWorktree() {
|
|
1570
|
-
if (!_session) return null;
|
|
1571
|
-
const repoRoot = findGitRoot(_session.originalCwd);
|
|
1572
|
-
if (!repoRoot) return null;
|
|
1573
|
-
process.chdir(_session.originalCwd);
|
|
1574
|
-
const result = cleanupWorktree(repoRoot, _session.slug);
|
|
1575
|
-
_session = null;
|
|
1576
|
-
return result;
|
|
1577
|
-
}
|
|
1578
|
-
var VALID_SEGMENT, MAX_SLUG_LENGTH, GIT_NO_PROMPT_ENV, _session;
|
|
1579
|
-
var init_worktree = __esm({
|
|
1580
|
-
"src/worktree.ts"() {
|
|
1581
|
-
"use strict";
|
|
1582
|
-
VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
1583
|
-
MAX_SLUG_LENGTH = 64;
|
|
1584
|
-
GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
1585
|
-
_session = null;
|
|
1586
|
-
}
|
|
1587
|
-
});
|
|
1588
|
-
|
|
1589
1589
|
// src/shell.sandbox.ts
|
|
1590
1590
|
function writable(cwd, o, tmpDir) {
|
|
1591
1591
|
const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
|
|
@@ -1755,7 +1755,7 @@ function makeRealShellTool(options) {
|
|
|
1755
1755
|
proc.stderr?.on("data", collect);
|
|
1756
1756
|
proc.on("error", (err2) => {
|
|
1757
1757
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
|
|
1758
|
-
|
|
1758
|
+
log13.debug("shell spawn error", err2);
|
|
1759
1759
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
|
|
1760
1760
|
});
|
|
1761
1761
|
proc.on("close", (code) => {
|
|
@@ -1817,7 +1817,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
1817
1817
|
}
|
|
1818
1818
|
];
|
|
1819
1819
|
}
|
|
1820
|
-
var
|
|
1820
|
+
var log13, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1821
1821
|
var init_tools_shell = __esm({
|
|
1822
1822
|
"src/tools.shell.ts"() {
|
|
1823
1823
|
"use strict";
|
|
@@ -1825,7 +1825,7 @@ var init_tools_shell = __esm({
|
|
|
1825
1825
|
init_redact();
|
|
1826
1826
|
init_logging();
|
|
1827
1827
|
init_shell_sandbox();
|
|
1828
|
-
|
|
1828
|
+
log13 = forComponent("shell");
|
|
1829
1829
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1830
1830
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
1831
1831
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -1907,7 +1907,7 @@ var init_tools_shell = __esm({
|
|
|
1907
1907
|
|
|
1908
1908
|
// cli/cli.ts
|
|
1909
1909
|
import { createInterface } from "readline/promises";
|
|
1910
|
-
import { existsSync as existsSync10, readFileSync as readFileSync7, appendFileSync, mkdirSync as
|
|
1910
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, appendFileSync, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
1911
1911
|
import { homedir as homedir9, tmpdir as tmpdir3 } from "os";
|
|
1912
1912
|
|
|
1913
1913
|
// cli/clipboard.ts
|
|
@@ -1975,7 +1975,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
1975
1975
|
}
|
|
1976
1976
|
|
|
1977
1977
|
// cli/cli.ts
|
|
1978
|
-
import { join as
|
|
1978
|
+
import { join as join14, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
|
|
1979
1979
|
import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
|
|
1980
1980
|
|
|
1981
1981
|
// src/llm.ts
|
|
@@ -2622,7 +2622,13 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
2622
2622
|
|
|
2623
2623
|
// src/subagent.ts
|
|
2624
2624
|
init_tools();
|
|
2625
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2626
|
+
import { join as join4 } from "path";
|
|
2625
2627
|
init_OverlayFilesystem();
|
|
2628
|
+
init_worktree();
|
|
2629
|
+
init_NodeDiskFilesystem();
|
|
2630
|
+
init_logging();
|
|
2631
|
+
var log3 = forComponent("subagent");
|
|
2626
2632
|
async function boundedPool(items, limit, fn) {
|
|
2627
2633
|
const out = new Array(items.length);
|
|
2628
2634
|
let next = 0;
|
|
@@ -2635,13 +2641,86 @@ async function boundedPool(items, limit, fn) {
|
|
|
2635
2641
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
2636
2642
|
return out;
|
|
2637
2643
|
}
|
|
2644
|
+
function sanitizeSlug(raw) {
|
|
2645
|
+
return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
2646
|
+
}
|
|
2647
|
+
function createWorktreeFs(cwd, label, index) {
|
|
2648
|
+
const repoRoot = findGitRoot(cwd);
|
|
2649
|
+
if (!repoRoot) return 'isolation: "worktree" requires a git repository';
|
|
2650
|
+
const slug2 = `sub-${index}-${sanitizeSlug(label)}`;
|
|
2651
|
+
try {
|
|
2652
|
+
const info = getOrCreateWorktree(repoRoot, slug2);
|
|
2653
|
+
return { repoRoot, slug: slug2, worktreePath: info.worktreePath, branch: info.branch };
|
|
2654
|
+
} catch (e) {
|
|
2655
|
+
return `worktree creation failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
function releaseWorktree(handle) {
|
|
2659
|
+
return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
|
|
2660
|
+
}
|
|
2661
|
+
function worktreePromptPrefix(branch) {
|
|
2662
|
+
return `You are running in an isolated git worktree on branch "${branch}". When you finish making changes, commit them with a clear message before replying with your summary. Your worktree will be removed after you finish \u2014 only committed work survives (on the branch).
|
|
2663
|
+
|
|
2664
|
+
`;
|
|
2665
|
+
}
|
|
2666
|
+
var traceSeq = 0;
|
|
2667
|
+
function traceFileFor(dir, label, depth) {
|
|
2668
|
+
try {
|
|
2669
|
+
mkdirSync2(dir, { recursive: true });
|
|
2670
|
+
const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
|
|
2671
|
+
return join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
|
|
2672
|
+
} catch (e) {
|
|
2673
|
+
log3.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
|
|
2674
|
+
return void 0;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
function flushTrace(file, meta, messages, status) {
|
|
2678
|
+
try {
|
|
2679
|
+
writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
|
|
2680
|
+
} catch (e) {
|
|
2681
|
+
log3.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
function installTrace(child, file, meta) {
|
|
2685
|
+
const prev = child.options.host;
|
|
2686
|
+
child.options.host = {
|
|
2687
|
+
...prev,
|
|
2688
|
+
notify: (e) => {
|
|
2689
|
+
if (e.kind === "turn_start") flushTrace(file, meta, child.transcript, "running");
|
|
2690
|
+
prev?.notify?.(e);
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
function abortHint(label, res, file) {
|
|
2695
|
+
const last = (res.text || "").trim().slice(0, 500);
|
|
2696
|
+
const lines = [
|
|
2697
|
+
`[sub-task '${label}' did NOT finish \u2014 ${res.finishReason} after ${res.steps} step(s). Treat its work as INCOMPLETE.]`,
|
|
2698
|
+
last ? `Progress so far: ${last}` : "It produced no summary before stopping."
|
|
2699
|
+
];
|
|
2700
|
+
if (file) lines.push(`Full transcript (every step it took): ${file} \u2014 Read it to see what was actually done.`);
|
|
2701
|
+
lines.push("Decide: ignore it, re-delegate a fresh Task that continues from the trace, or start over. Do NOT assume the sub-task completed.");
|
|
2702
|
+
return lines.join("\n");
|
|
2703
|
+
}
|
|
2704
|
+
async function runChildTraced(args) {
|
|
2705
|
+
const { opts, label, agentType, prompt } = args;
|
|
2706
|
+
if (args.signal) args.childOpts.signal = args.signal;
|
|
2707
|
+
const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
|
|
2708
|
+
const meta = { label, agentType, prompt };
|
|
2709
|
+
const child = new Agent(args.childOpts);
|
|
2710
|
+
if (file) installTrace(child, file, meta);
|
|
2711
|
+
const res = await child.run(prompt);
|
|
2712
|
+
if (file) flushTrace(file, meta, child.transcript, res.finishReason);
|
|
2713
|
+
const result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
2714
|
+
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
2715
|
+
return { res, result };
|
|
2716
|
+
}
|
|
2638
2717
|
function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
|
|
2639
2718
|
let def;
|
|
2640
2719
|
if (agentType) {
|
|
2641
2720
|
def = (opts.agents ?? []).find((a) => a.name === agentType);
|
|
2642
2721
|
if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
|
|
2643
2722
|
}
|
|
2644
|
-
const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth };
|
|
2723
|
+
const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
|
|
2645
2724
|
if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
|
|
2646
2725
|
if (def?.systemPrompt) childOpts.systemPrompt = def.systemPrompt;
|
|
2647
2726
|
if (def?.tools?.length) {
|
|
@@ -2658,7 +2737,7 @@ function makeTaskTool(opts) {
|
|
|
2658
2737
|
const maxDepth = opts.maxDepth ?? 2;
|
|
2659
2738
|
return {
|
|
2660
2739
|
name: "Task",
|
|
2661
|
-
description:
|
|
2740
|
+
description: 'Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. If it is interrupted or fails, the result is a STATUS HINT with a pointer to its full transcript (Read it to recover what it did, then decide whether to continue or restart) \u2014 do not assume an incomplete sub-task succeeded. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
|
|
2662
2741
|
parameters: {
|
|
2663
2742
|
type: "object",
|
|
2664
2743
|
required: ["description", "prompt"],
|
|
@@ -2666,36 +2745,83 @@ function makeTaskTool(opts) {
|
|
|
2666
2745
|
description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
|
|
2667
2746
|
prompt: { type: "string", description: "the full instructions the child agent should carry out" },
|
|
2668
2747
|
agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
|
|
2669
|
-
background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" }
|
|
2748
|
+
background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
|
|
2749
|
+
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
2670
2750
|
}
|
|
2671
2751
|
},
|
|
2672
|
-
async run({ description, prompt, agentType, background }, ctx) {
|
|
2752
|
+
async run({ description, prompt, agentType, background, isolation }, ctx) {
|
|
2673
2753
|
if (depth >= maxDepth) {
|
|
2674
2754
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
2675
2755
|
}
|
|
2676
2756
|
const label = String(description ?? agentType ?? "sub-task");
|
|
2757
|
+
const useWorktree = isolation === "worktree";
|
|
2677
2758
|
if (background && ctx.jobs) {
|
|
2678
2759
|
const id = ctx.jobs.start(async ({ signal }) => {
|
|
2679
|
-
|
|
2680
|
-
|
|
2760
|
+
let fs2;
|
|
2761
|
+
let commit;
|
|
2762
|
+
let wtHandle2;
|
|
2763
|
+
if (useWorktree) {
|
|
2764
|
+
const h = createWorktreeFs(process.cwd(), label, 0);
|
|
2765
|
+
if (typeof h === "string") throw new Error(h);
|
|
2766
|
+
wtHandle2 = h;
|
|
2767
|
+
fs2 = new NodeDiskFilesystem(h.worktreePath);
|
|
2768
|
+
commit = async () => {
|
|
2769
|
+
};
|
|
2770
|
+
} else {
|
|
2771
|
+
const overlay = new OverlayFilesystem(opts.fs);
|
|
2772
|
+
fs2 = overlay;
|
|
2773
|
+
commit = () => overlay.commit();
|
|
2774
|
+
}
|
|
2775
|
+
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
|
|
2681
2776
|
if (typeof childOpts2 === "string") throw new Error(childOpts2);
|
|
2682
|
-
|
|
2683
|
-
const
|
|
2684
|
-
if (
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2777
|
+
const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
|
|
2778
|
+
const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
|
|
2779
|
+
if (res.finishReason === "aborted" || signal.aborted) {
|
|
2780
|
+
wtHandle2 && releaseWorktree(wtHandle2);
|
|
2781
|
+
return result;
|
|
2782
|
+
}
|
|
2783
|
+
await commit();
|
|
2784
|
+
let summary = result;
|
|
2785
|
+
if (wtHandle2) {
|
|
2786
|
+
const r = releaseWorktree(wtHandle2);
|
|
2787
|
+
if (!r.removed) summary += `
|
|
2788
|
+
|
|
2789
|
+
[worktree removal failed: ${r.reason}]`;
|
|
2790
|
+
}
|
|
2791
|
+
return summary;
|
|
2689
2792
|
}, { kind: "agent", label });
|
|
2690
2793
|
return `Started background sub-task ${id} ('${label}') \u2014 poll with JobOutput({id:"${id}"}).`;
|
|
2691
2794
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2795
|
+
let fs;
|
|
2796
|
+
let wtHandle;
|
|
2797
|
+
if (useWorktree) {
|
|
2798
|
+
const h = createWorktreeFs(process.cwd(), label, 0);
|
|
2799
|
+
if (typeof h === "string") return `Error: ${h}`;
|
|
2800
|
+
wtHandle = h;
|
|
2801
|
+
fs = new NodeDiskFilesystem(h.worktreePath);
|
|
2802
|
+
} else {
|
|
2803
|
+
fs = opts.fs;
|
|
2804
|
+
}
|
|
2805
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
|
|
2806
|
+
if (typeof childOpts === "string") {
|
|
2807
|
+
wtHandle && releaseWorktree(wtHandle);
|
|
2808
|
+
return `Error: ${childOpts}`;
|
|
2809
|
+
}
|
|
2810
|
+
try {
|
|
2811
|
+
const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(prompt ?? "") : String(prompt ?? "");
|
|
2812
|
+
const { result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal: ctx.signal });
|
|
2813
|
+
let summary = result;
|
|
2814
|
+
if (wtHandle) {
|
|
2815
|
+
const r = releaseWorktree(wtHandle);
|
|
2816
|
+
if (!r.removed) summary += `
|
|
2817
|
+
|
|
2818
|
+
[worktree removal failed: ${r.reason}]`;
|
|
2819
|
+
}
|
|
2820
|
+
return summary;
|
|
2821
|
+
} catch (e) {
|
|
2822
|
+
if (wtHandle) releaseWorktree(wtHandle);
|
|
2823
|
+
throw e;
|
|
2824
|
+
}
|
|
2699
2825
|
}
|
|
2700
2826
|
};
|
|
2701
2827
|
}
|
|
@@ -2705,7 +2831,7 @@ function makeTaskBatchTool(opts) {
|
|
|
2705
2831
|
const maxParallel = opts.maxParallel ?? 4;
|
|
2706
2832
|
return {
|
|
2707
2833
|
name: "TaskBatch",
|
|
2708
|
-
description:
|
|
2834
|
+
description: 'Delegate SEVERAL independent sub-tasks to child agents that run concurrently; returns all their summaries. Each child is write-isolated (its file edits are merged back in array order). Use for parallel fan-out (review/search/scoped refactors across files). Provide `tasks: [{ description, prompt, agentType? }]`. Set `isolation: "worktree"` to run each child in its own git worktree (needed for real shell commands) \u2014 slower, auto-cleaned if no changes.',
|
|
2709
2835
|
parameters: {
|
|
2710
2836
|
type: "object",
|
|
2711
2837
|
required: ["tasks"],
|
|
@@ -2722,30 +2848,53 @@ function makeTaskBatchTool(opts) {
|
|
|
2722
2848
|
agentType: { type: "string", description: "optional named subagent type" }
|
|
2723
2849
|
}
|
|
2724
2850
|
}
|
|
2725
|
-
}
|
|
2851
|
+
},
|
|
2852
|
+
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
2726
2853
|
}
|
|
2727
2854
|
},
|
|
2728
|
-
async run({ tasks },
|
|
2855
|
+
async run({ tasks, isolation }, ctx) {
|
|
2729
2856
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
2730
2857
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
2731
2858
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
2859
|
+
const useWorktree = isolation === "worktree";
|
|
2732
2860
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
2733
2861
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
2734
|
-
|
|
2735
|
-
|
|
2862
|
+
let fs;
|
|
2863
|
+
let overlay;
|
|
2864
|
+
let wtHandle;
|
|
2865
|
+
if (useWorktree) {
|
|
2866
|
+
const h = createWorktreeFs(process.cwd(), label, i);
|
|
2867
|
+
if (typeof h === "string") return { label, error: h, ok: false };
|
|
2868
|
+
wtHandle = h;
|
|
2869
|
+
fs = new NodeDiskFilesystem(h.worktreePath);
|
|
2870
|
+
} else {
|
|
2871
|
+
overlay = new OverlayFilesystem(opts.fs);
|
|
2872
|
+
fs = overlay;
|
|
2873
|
+
}
|
|
2874
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
|
|
2736
2875
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
2737
2876
|
try {
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
return { label, text:
|
|
2877
|
+
const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
|
|
2878
|
+
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: ctx.signal });
|
|
2879
|
+
return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
|
|
2741
2880
|
} catch (e) {
|
|
2742
|
-
return { label, error: e instanceof Error ? e.message : String(e), ok: false };
|
|
2881
|
+
return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
|
|
2743
2882
|
}
|
|
2744
2883
|
});
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2884
|
+
const lines = [];
|
|
2885
|
+
for (const r of results) {
|
|
2886
|
+
if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
|
|
2887
|
+
});
|
|
2888
|
+
let extra = "";
|
|
2889
|
+
if (r.wtHandle) {
|
|
2890
|
+
const wr = releaseWorktree(r.wtHandle);
|
|
2891
|
+
if (!wr.removed) extra = `
|
|
2892
|
+
[worktree removal failed: ${wr.reason}]`;
|
|
2893
|
+
}
|
|
2894
|
+
lines.push(`### ${lines.length + 1}. ${r.label}
|
|
2895
|
+
${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
|
|
2896
|
+
}
|
|
2897
|
+
return lines.join("\n\n");
|
|
2749
2898
|
}
|
|
2750
2899
|
};
|
|
2751
2900
|
}
|
|
@@ -2990,7 +3139,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
2990
3139
|
}
|
|
2991
3140
|
|
|
2992
3141
|
// src/Agent.ts
|
|
2993
|
-
var
|
|
3142
|
+
var log4 = forComponent("Agent");
|
|
2994
3143
|
function isAbortError(err2) {
|
|
2995
3144
|
const e = err2;
|
|
2996
3145
|
const blob = `${e?.message ?? ""} ${e?.name ?? ""} ${e?.code ?? ""} ${e?.cause?.name ?? ""}`;
|
|
@@ -3066,6 +3215,11 @@ var AgentOptions = class {
|
|
|
3066
3215
|
depth = 0;
|
|
3067
3216
|
/** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
|
|
3068
3217
|
maxDepth = 2;
|
|
3218
|
+
/** Real-disk dir where child agents stream their transcript as they run (origin-anchored — survives
|
|
3219
|
+
* worktree teardown / a child's own cwd). When set, an interrupted/failed child commits a STATUS HINT
|
|
3220
|
+
* + a pointer to its trace instead of a dangling tool_call, so the parent can read it and decide
|
|
3221
|
+
* whether to ignore, pick it up, or restart. Unset (library/sandbox default) = no tracing. */
|
|
3222
|
+
traceDir;
|
|
3069
3223
|
/** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
|
|
3070
3224
|
stream = false;
|
|
3071
3225
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
@@ -3186,7 +3340,7 @@ var Agent = class _Agent {
|
|
|
3186
3340
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
3187
3341
|
await disk.init();
|
|
3188
3342
|
this.options.fs = new JailedFilesystem2(disk);
|
|
3189
|
-
|
|
3343
|
+
log4.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
3190
3344
|
}
|
|
3191
3345
|
this.buildCtx();
|
|
3192
3346
|
}
|
|
@@ -3240,7 +3394,7 @@ var Agent = class _Agent {
|
|
|
3240
3394
|
agents = loaded.agents;
|
|
3241
3395
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
3242
3396
|
}
|
|
3243
|
-
const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks };
|
|
3397
|
+
const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks, traceDir: o.traceDir };
|
|
3244
3398
|
tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
|
|
3245
3399
|
}
|
|
3246
3400
|
if (o.checkpoints) tools = [...tools, ...checkpointTools()];
|
|
@@ -3329,7 +3483,7 @@ var Agent = class _Agent {
|
|
|
3329
3483
|
let lastFp = "";
|
|
3330
3484
|
let repeats = 0;
|
|
3331
3485
|
const kill = (finishReason) => {
|
|
3332
|
-
|
|
3486
|
+
log4.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
|
|
3333
3487
|
this.ctx.jobs?.killAll();
|
|
3334
3488
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
|
|
3335
3489
|
};
|
|
@@ -3375,7 +3529,7 @@ var Agent = class _Agent {
|
|
|
3375
3529
|
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide);
|
|
3376
3530
|
if (!transient) throw err2;
|
|
3377
3531
|
const waitMs = 1e3 * (attempt + 1);
|
|
3378
|
-
|
|
3532
|
+
log4.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
3379
3533
|
o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
|
|
3380
3534
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3381
3535
|
}
|
|
@@ -3391,7 +3545,7 @@ var Agent = class _Agent {
|
|
|
3391
3545
|
bodyStr = void 0;
|
|
3392
3546
|
}
|
|
3393
3547
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3394
|
-
|
|
3548
|
+
log4.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
|
|
3395
3549
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3396
3550
|
}
|
|
3397
3551
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3418,7 +3572,7 @@ var Agent = class _Agent {
|
|
|
3418
3572
|
});
|
|
3419
3573
|
}
|
|
3420
3574
|
if (toolCalls.length === 0) {
|
|
3421
|
-
|
|
3575
|
+
log4.verbose(`completed in ${steps} step(s)`);
|
|
3422
3576
|
await this.ctx.jobs?.drain();
|
|
3423
3577
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
3424
3578
|
return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
@@ -3484,13 +3638,13 @@ var Agent = class _Agent {
|
|
|
3484
3638
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
3485
3639
|
if (decision?.block) {
|
|
3486
3640
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
3487
|
-
|
|
3641
|
+
log4.debug(`${tc.function.name} -> ${blocked}`);
|
|
3488
3642
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
3489
3643
|
return blocked;
|
|
3490
3644
|
}
|
|
3491
3645
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
3492
3646
|
if (earlyError) {
|
|
3493
|
-
|
|
3647
|
+
log4.debug(`${tc.function.name} -> ${earlyError}`);
|
|
3494
3648
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
3495
3649
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
3496
3650
|
return earlyError;
|
|
@@ -3499,12 +3653,12 @@ var Agent = class _Agent {
|
|
|
3499
3653
|
let images;
|
|
3500
3654
|
let threw = false;
|
|
3501
3655
|
try {
|
|
3502
|
-
|
|
3656
|
+
log4.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
3503
3657
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
3504
3658
|
try {
|
|
3505
3659
|
hooks.onToolOutput(call, chunk, meta);
|
|
3506
3660
|
} catch (e) {
|
|
3507
|
-
|
|
3661
|
+
log4.debug(`onToolOutput hook error: ${e}`);
|
|
3508
3662
|
}
|
|
3509
3663
|
} : void 0;
|
|
3510
3664
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -3516,7 +3670,7 @@ var Agent = class _Agent {
|
|
|
3516
3670
|
}
|
|
3517
3671
|
} catch (e) {
|
|
3518
3672
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3519
|
-
|
|
3673
|
+
log4.debug(`${tc.function.name} -> error: ${msg}`);
|
|
3520
3674
|
result = `Error: ${msg}`;
|
|
3521
3675
|
threw = true;
|
|
3522
3676
|
} finally {
|
|
@@ -3642,7 +3796,7 @@ function fitTokenBudget(messages, maxTokens) {
|
|
|
3642
3796
|
const ids = callIdSet(messages.slice(from));
|
|
3643
3797
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
3644
3798
|
if (total > maxTokens)
|
|
3645
|
-
|
|
3799
|
+
log4.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3646
3800
|
return [...head, ...messages.slice(from)];
|
|
3647
3801
|
}
|
|
3648
3802
|
function compact(m, max, focus) {
|
|
@@ -4199,7 +4353,7 @@ init_tools_web();
|
|
|
4199
4353
|
init_tools();
|
|
4200
4354
|
init_tools_structured();
|
|
4201
4355
|
init_logging();
|
|
4202
|
-
var
|
|
4356
|
+
var log5 = forComponent("scratch");
|
|
4203
4357
|
var SCRATCH_DIR = "/scratch";
|
|
4204
4358
|
function shortArgs(args) {
|
|
4205
4359
|
try {
|
|
@@ -4239,7 +4393,7 @@ var Scratch = class {
|
|
|
4239
4393
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4240
4394
|
await this.fs.writeFile(path, header + raw);
|
|
4241
4395
|
} catch (e) {
|
|
4242
|
-
|
|
4396
|
+
log5.debug("scratch write failed; returning raw", e);
|
|
4243
4397
|
return raw;
|
|
4244
4398
|
}
|
|
4245
4399
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -4269,7 +4423,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
4269
4423
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4270
4424
|
await this.fs.writeFile(path, header + full);
|
|
4271
4425
|
} catch (e) {
|
|
4272
|
-
|
|
4426
|
+
log5.debug("scratch spill failed; cropping lossy", e);
|
|
4273
4427
|
return full.slice(0, pageBytes) + `
|
|
4274
4428
|
|
|
4275
4429
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -4315,7 +4469,7 @@ Question: ${q2}`);
|
|
|
4315
4469
|
const answer = (res.text ?? "").trim();
|
|
4316
4470
|
return answer || "(no answer found in scratch)";
|
|
4317
4471
|
} catch (e) {
|
|
4318
|
-
|
|
4472
|
+
log5.debug("Ask peek failed", e);
|
|
4319
4473
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
4320
4474
|
}
|
|
4321
4475
|
}
|
|
@@ -4324,7 +4478,7 @@ Question: ${q2}`);
|
|
|
4324
4478
|
|
|
4325
4479
|
// src/lessons.ts
|
|
4326
4480
|
init_logging();
|
|
4327
|
-
var
|
|
4481
|
+
var log6 = forComponent("Lessons");
|
|
4328
4482
|
var LessonOptionsDefaults = class {
|
|
4329
4483
|
minRepeats = 2;
|
|
4330
4484
|
};
|
|
@@ -4349,15 +4503,15 @@ function lessonCapture(options) {
|
|
|
4349
4503
|
counts.set(lesson.slug, n);
|
|
4350
4504
|
if (n < o.minRepeats) return;
|
|
4351
4505
|
written.add(lesson.slug);
|
|
4352
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
4353
|
-
|
|
4506
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log6.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
4507
|
+
log6.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
4354
4508
|
}
|
|
4355
4509
|
};
|
|
4356
4510
|
}
|
|
4357
4511
|
|
|
4358
4512
|
// src/reflect.ts
|
|
4359
4513
|
init_logging();
|
|
4360
|
-
var
|
|
4514
|
+
var log7 = forComponent("Reflect");
|
|
4361
4515
|
async function reflectOnRun(o) {
|
|
4362
4516
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
4363
4517
|
if (!digest.trim()) return null;
|
|
@@ -4373,7 +4527,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4373
4527
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
4374
4528
|
text = r?.content ?? "";
|
|
4375
4529
|
} catch (e) {
|
|
4376
|
-
|
|
4530
|
+
log7.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
4377
4531
|
return null;
|
|
4378
4532
|
}
|
|
4379
4533
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -4383,10 +4537,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4383
4537
|
try {
|
|
4384
4538
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
4385
4539
|
} catch (e) {
|
|
4386
|
-
|
|
4540
|
+
log7.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
4387
4541
|
return null;
|
|
4388
4542
|
}
|
|
4389
|
-
|
|
4543
|
+
log7.debug(`reflection persisted ${slug2}`);
|
|
4390
4544
|
return slug2;
|
|
4391
4545
|
}
|
|
4392
4546
|
function digestRun(messages, maxChars) {
|
|
@@ -4403,7 +4557,7 @@ function digestRun(messages, maxChars) {
|
|
|
4403
4557
|
// src/duplex.ts
|
|
4404
4558
|
import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
|
|
4405
4559
|
init_logging();
|
|
4406
|
-
var
|
|
4560
|
+
var log8 = forComponent("DuplexAgent");
|
|
4407
4561
|
function describeCall(call) {
|
|
4408
4562
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
4409
4563
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -4543,7 +4697,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4543
4697
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER);
|
|
4544
4698
|
if (m) {
|
|
4545
4699
|
this.fabricationCut = true;
|
|
4546
|
-
|
|
4700
|
+
log8.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
4547
4701
|
const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
|
|
4548
4702
|
if (!safe) return;
|
|
4549
4703
|
if (safe.trim()) this.spokeThisTurn = true;
|
|
@@ -4610,23 +4764,28 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4610
4764
|
}
|
|
4611
4765
|
};
|
|
4612
4766
|
}
|
|
4613
|
-
/** True when the just-finished turn
|
|
4767
|
+
/** True when the just-finished turn voiced NOTHING — dead air to repair. Two ways this happens:
|
|
4768
|
+
* (a) a dispatch with no spoken ack, and (b) an INLINE turn whose `final` channel came back empty —
|
|
4769
|
+
* gpt-oss harmony sometimes puts the whole reply in `analysis` (→ thinking_delta, suppressed in
|
|
4770
|
+
* voice) and emits an empty `final`, so no text_delta ever streams. Both ship silence; both repair.
|
|
4614
4771
|
* Requires a host: without one there's no stream to detect speech on (and no one to speak to). */
|
|
4615
|
-
get
|
|
4616
|
-
return !!this.options.host &&
|
|
4772
|
+
get silentTurn() {
|
|
4773
|
+
return !!this.options.host && !this.spokeThisTurn;
|
|
4617
4774
|
}
|
|
4618
|
-
/** A
|
|
4619
|
-
*
|
|
4775
|
+
/** A turn that voiced nothing is dead air. Re-prompt the reflex ONCE so the LLM itself voices a short
|
|
4776
|
+
* line (no template). If it STILL says nothing, fall back to a minimal line so silence never ships.
|
|
4777
|
+
* Wording adapts to whether work was dispatched (an ack) or the inline reply was simply lost. */
|
|
4620
4778
|
async ackIfSilent() {
|
|
4779
|
+
const dispatched = this.turnDispatched;
|
|
4621
4780
|
this.nudging = true;
|
|
4622
4781
|
try {
|
|
4623
|
-
await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
|
|
4782
|
+
await this.voice.send(dispatched ? "[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools." : "[reminder] You said nothing to the user this turn. Give your ONE short spoken reply now \u2014 no tools.");
|
|
4624
4783
|
} catch (e) {
|
|
4625
|
-
|
|
4784
|
+
log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
4626
4785
|
} finally {
|
|
4627
4786
|
this.nudging = false;
|
|
4628
4787
|
}
|
|
4629
|
-
if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: "Okay, on it." });
|
|
4788
|
+
if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: dispatched ? "Okay, on it." : "Sorry, could you say that again?" });
|
|
4630
4789
|
}
|
|
4631
4790
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns. */
|
|
4632
4791
|
send(content) {
|
|
@@ -4634,7 +4793,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4634
4793
|
await this.initMemory();
|
|
4635
4794
|
this.resetTurn();
|
|
4636
4795
|
const res = await this.voice.send(content);
|
|
4637
|
-
if (this.
|
|
4796
|
+
if (this.silentTurn) await this.ackIfSilent();
|
|
4638
4797
|
return res;
|
|
4639
4798
|
});
|
|
4640
4799
|
}
|
|
@@ -4679,7 +4838,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4679
4838
|
if (!events.length) return;
|
|
4680
4839
|
this.resetTurn();
|
|
4681
4840
|
await this.voice.send(events.join("\n"));
|
|
4682
|
-
if (this.
|
|
4841
|
+
if (this.silentTurn) await this.ackIfSilent();
|
|
4683
4842
|
this.notify("revoice_done", "");
|
|
4684
4843
|
});
|
|
4685
4844
|
}
|
|
@@ -4770,7 +4929,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4770
4929
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
4771
4930
|
const cres = await new Agent(agentOpts).run(checkBrief);
|
|
4772
4931
|
if (cres.finishReason !== "stop") {
|
|
4773
|
-
|
|
4932
|
+
log8.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
4774
4933
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
4775
4934
|
}
|
|
4776
4935
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -4873,7 +5032,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4873
5032
|
}
|
|
4874
5033
|
rec.status = "done";
|
|
4875
5034
|
rec.result = res.text;
|
|
4876
|
-
|
|
5035
|
+
log8.verbose(`task ${id} done (${res.steps} steps)`);
|
|
4877
5036
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
4878
5037
|
id,
|
|
4879
5038
|
text: res.text,
|
|
@@ -4891,7 +5050,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4891
5050
|
this.dropAsk(rec.id);
|
|
4892
5051
|
rec.status = "error";
|
|
4893
5052
|
rec.result = msg;
|
|
4894
|
-
|
|
5053
|
+
log8.warn(`task ${rec.id} failed: ${msg}`);
|
|
4895
5054
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
|
|
4896
5055
|
this.queueRevoice(`[task ${rec.id} failed] ${msg}`);
|
|
4897
5056
|
}
|
|
@@ -5193,7 +5352,7 @@ init_logging();
|
|
|
5193
5352
|
|
|
5194
5353
|
// src/voice/engine.ts
|
|
5195
5354
|
init_logging();
|
|
5196
|
-
var
|
|
5355
|
+
var log9 = forComponent("VoiceEngine");
|
|
5197
5356
|
var now = () => performance.now();
|
|
5198
5357
|
var forSpeech = (t) => t.replace(/[*_`#]+/g, "").replace(/^[ \t]*[-•]\s+/gm, "").replace(/\s*[\u2013\u2014]\s*/g, ", ").replace(/[\u2010\u2011]/g, "-").replace(/\s*\|\s*/g, ", ").replace(/(\d)\s+%/g, "$1%").replace(/\.{3,}/g, ".");
|
|
5199
5358
|
var VoiceEngineOptions = class {
|
|
@@ -5301,7 +5460,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5301
5460
|
this.stt.onLevel = (rms) => this.handleLevel(rms);
|
|
5302
5461
|
await Promise.all([this.tts.connect(), this.stt.start()]);
|
|
5303
5462
|
this.setState("listening");
|
|
5304
|
-
|
|
5463
|
+
log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
|
|
5305
5464
|
}
|
|
5306
5465
|
get usingAec() {
|
|
5307
5466
|
return this.stt.usingAec;
|
|
@@ -5353,7 +5512,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5353
5512
|
this.reply += text;
|
|
5354
5513
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
5355
5514
|
this.tts.speak(forSpeech(text), true);
|
|
5356
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
5515
|
+
if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
|
|
5357
5516
|
this.spokeDeltas = true;
|
|
5358
5517
|
this.setState("speaking");
|
|
5359
5518
|
}
|
|
@@ -5374,7 +5533,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5374
5533
|
}
|
|
5375
5534
|
this.drainTimer = null;
|
|
5376
5535
|
this.speaking = false;
|
|
5377
|
-
if (this.turnStartAt)
|
|
5536
|
+
if (this.turnStartAt) log9.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
|
|
5378
5537
|
this.echoUntil = now() + 2500;
|
|
5379
5538
|
if (!this.usingAec) this.stt.reset();
|
|
5380
5539
|
this.setState("listening");
|
|
@@ -5515,7 +5674,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5515
5674
|
this.pendingUtt = this.pendingUtt ? `${this.pendingUtt} ${text}` : text;
|
|
5516
5675
|
if (this.pendingTimer) clearTimeout(this.pendingTimer);
|
|
5517
5676
|
if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
|
|
5518
|
-
|
|
5677
|
+
log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
5519
5678
|
this.options.onHold();
|
|
5520
5679
|
if (this.options.holdFiller && !this.speaking) {
|
|
5521
5680
|
this.beginSpeech();
|
|
@@ -5613,7 +5772,7 @@ async function resolveAuth(auth) {
|
|
|
5613
5772
|
}
|
|
5614
5773
|
|
|
5615
5774
|
// src/voice/soniox.ts
|
|
5616
|
-
var
|
|
5775
|
+
var log10 = forComponent("SonioxSTT");
|
|
5617
5776
|
var now2 = () => performance.now();
|
|
5618
5777
|
var SonioxSTTOptions = class {
|
|
5619
5778
|
auth = "";
|
|
@@ -5670,9 +5829,9 @@ var SonioxSTT = class {
|
|
|
5670
5829
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
5671
5830
|
this.ws.onclose = (ev) => {
|
|
5672
5831
|
if (this.stopped) return;
|
|
5673
|
-
|
|
5832
|
+
log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
5674
5833
|
this.reset();
|
|
5675
|
-
this.connectWs().catch((e) =>
|
|
5834
|
+
this.connectWs().catch((e) => log10.error(`soniox reconnect failed: ${e.message}`));
|
|
5676
5835
|
};
|
|
5677
5836
|
}
|
|
5678
5837
|
async start() {
|
|
@@ -5682,7 +5841,7 @@ var SonioxSTT = class {
|
|
|
5682
5841
|
this.endpointTimer = setInterval(() => {
|
|
5683
5842
|
const combined = (this.finalText + this.partialText).trim();
|
|
5684
5843
|
if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
5685
|
-
if (this.firstTokenAt)
|
|
5844
|
+
if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
5686
5845
|
this.reset();
|
|
5687
5846
|
this.onUtterance(combined, now2());
|
|
5688
5847
|
}, 120);
|
|
@@ -5699,7 +5858,7 @@ var SonioxSTT = class {
|
|
|
5699
5858
|
});
|
|
5700
5859
|
}
|
|
5701
5860
|
handle(m) {
|
|
5702
|
-
if (m.error_message) return
|
|
5861
|
+
if (m.error_message) return log10.error(`soniox: ${m.error_message}`);
|
|
5703
5862
|
let endpoint = false;
|
|
5704
5863
|
for (const t of m.tokens ?? []) {
|
|
5705
5864
|
if (t.text === "<end>") endpoint = true;
|
|
@@ -5715,7 +5874,7 @@ var SonioxSTT = class {
|
|
|
5715
5874
|
this.onPartial(combined);
|
|
5716
5875
|
if (endpoint && this.finalText.trim()) {
|
|
5717
5876
|
const utterance = this.finalText.trim();
|
|
5718
|
-
if (this.firstTokenAt)
|
|
5877
|
+
if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
5719
5878
|
this.reset();
|
|
5720
5879
|
this.onUtterance(utterance, now2());
|
|
5721
5880
|
}
|
|
@@ -5737,7 +5896,7 @@ var SonioxSTT = class {
|
|
|
5737
5896
|
|
|
5738
5897
|
// src/voice/cartesia.ts
|
|
5739
5898
|
init_logging();
|
|
5740
|
-
var
|
|
5899
|
+
var log11 = forComponent("CartesiaTTS");
|
|
5741
5900
|
var now3 = () => performance.now();
|
|
5742
5901
|
var CartesiaTTSOptions = class {
|
|
5743
5902
|
auth = "";
|
|
@@ -5787,9 +5946,9 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5787
5946
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
5788
5947
|
});
|
|
5789
5948
|
this.ws.onclose = (ev) => {
|
|
5790
|
-
|
|
5949
|
+
log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
5791
5950
|
if (!this.closed) {
|
|
5792
|
-
this.connecting = this.doConnect().catch((e) =>
|
|
5951
|
+
this.connecting = this.doConnect().catch((e) => log11.error(`cartesia reconnect failed: ${e.message}`));
|
|
5793
5952
|
}
|
|
5794
5953
|
};
|
|
5795
5954
|
this.ws.onmessage = (ev) => {
|
|
@@ -5811,11 +5970,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5811
5970
|
this.down = true;
|
|
5812
5971
|
this.downAt = now3();
|
|
5813
5972
|
this.consecutiveOk = 0;
|
|
5814
|
-
|
|
5973
|
+
log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
5815
5974
|
this.onDone();
|
|
5816
5975
|
this.startProbe();
|
|
5817
5976
|
} else if (!this.down) {
|
|
5818
|
-
|
|
5977
|
+
log11.warn(`cartesia: ${JSON.stringify(m)}`);
|
|
5819
5978
|
}
|
|
5820
5979
|
}
|
|
5821
5980
|
};
|
|
@@ -5829,7 +5988,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5829
5988
|
this.consecutiveOk = 0;
|
|
5830
5989
|
this.stopProbe();
|
|
5831
5990
|
const downMs = this.downAt ? now3() - this.downAt : 0;
|
|
5832
|
-
(downMs < 2e3 ?
|
|
5991
|
+
(downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
5833
5992
|
}
|
|
5834
5993
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
5835
5994
|
async ensureConnected() {
|
|
@@ -5909,7 +6068,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
5909
6068
|
init_logging();
|
|
5910
6069
|
import { spawn } from "child_process";
|
|
5911
6070
|
import { createHash } from "crypto";
|
|
5912
|
-
var
|
|
6071
|
+
var log12 = forComponent("mcp");
|
|
5913
6072
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
5914
6073
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
5915
6074
|
var StdioTransport = class {
|
|
@@ -5928,7 +6087,7 @@ var StdioTransport = class {
|
|
|
5928
6087
|
proc.stdout.setEncoding("utf8");
|
|
5929
6088
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
5930
6089
|
proc.stderr.setEncoding("utf8");
|
|
5931
|
-
proc.stderr.on("data", (chunk) =>
|
|
6090
|
+
proc.stderr.on("data", (chunk) => log12.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
5932
6091
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
5933
6092
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
5934
6093
|
}
|
|
@@ -5942,7 +6101,7 @@ var StdioTransport = class {
|
|
|
5942
6101
|
try {
|
|
5943
6102
|
this.dispatch(JSON.parse(line));
|
|
5944
6103
|
} catch (e) {
|
|
5945
|
-
|
|
6104
|
+
log12.debug("dropping non-JSON line from MCP server:", line, e);
|
|
5946
6105
|
}
|
|
5947
6106
|
}
|
|
5948
6107
|
}
|
|
@@ -5991,7 +6150,7 @@ var StdioTransport = class {
|
|
|
5991
6150
|
try {
|
|
5992
6151
|
this.proc?.stdin?.end();
|
|
5993
6152
|
} catch (e) {
|
|
5994
|
-
|
|
6153
|
+
log12.debug("stdin end failed", e);
|
|
5995
6154
|
}
|
|
5996
6155
|
this.proc?.kill();
|
|
5997
6156
|
}
|
|
@@ -6060,7 +6219,7 @@ function parseSseResponse(body) {
|
|
|
6060
6219
|
const obj = JSON.parse(trimmed.slice(5).trim());
|
|
6061
6220
|
if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
|
|
6062
6221
|
} catch (e) {
|
|
6063
|
-
|
|
6222
|
+
log12.debug("skipping unparseable SSE data line", e);
|
|
6064
6223
|
}
|
|
6065
6224
|
}
|
|
6066
6225
|
return {};
|
|
@@ -6128,7 +6287,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
6128
6287
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
6129
6288
|
})(), mountTimeoutMs, name);
|
|
6130
6289
|
} catch (e) {
|
|
6131
|
-
await client.close().catch((err2) =>
|
|
6290
|
+
await client.close().catch((err2) => log12.debug(`close after failed mount of "${name}": ${err2}`));
|
|
6132
6291
|
throw e;
|
|
6133
6292
|
}
|
|
6134
6293
|
}
|
|
@@ -6139,15 +6298,15 @@ function validEntries(servers) {
|
|
|
6139
6298
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
6140
6299
|
if (!cfg || cfg.disabled) return false;
|
|
6141
6300
|
if (!cfg.command && !cfg.url) {
|
|
6142
|
-
|
|
6301
|
+
log12.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
6143
6302
|
return false;
|
|
6144
6303
|
}
|
|
6145
6304
|
return true;
|
|
6146
6305
|
});
|
|
6147
6306
|
}
|
|
6148
6307
|
function logMountFailure(name, e) {
|
|
6149
|
-
if (e instanceof McpAuthError)
|
|
6150
|
-
else
|
|
6308
|
+
if (e instanceof McpAuthError) log12.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
6309
|
+
else log12.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
6151
6310
|
}
|
|
6152
6311
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
6153
6312
|
const entries = validEntries(servers);
|
|
@@ -6157,7 +6316,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
6157
6316
|
const name = entries[i][0];
|
|
6158
6317
|
if (r.status === "fulfilled") {
|
|
6159
6318
|
out.push(r.value);
|
|
6160
|
-
|
|
6319
|
+
log12.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
6161
6320
|
} else logMountFailure(name, r.reason);
|
|
6162
6321
|
});
|
|
6163
6322
|
return out;
|
|
@@ -6197,7 +6356,7 @@ var McpPool = class {
|
|
|
6197
6356
|
const prev = this.warm.get(key);
|
|
6198
6357
|
if (prev) {
|
|
6199
6358
|
clearTimeout(prev.timer);
|
|
6200
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
6359
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log12.debug(`warm-pool replace close failed: ${err2}`));
|
|
6201
6360
|
}
|
|
6202
6361
|
const e = { client, timer: void 0 };
|
|
6203
6362
|
this.warm.set(key, e);
|
|
@@ -6214,7 +6373,7 @@ var McpPool = class {
|
|
|
6214
6373
|
const e = this.warm.get(key);
|
|
6215
6374
|
if (!e) return;
|
|
6216
6375
|
this.warm.delete(key);
|
|
6217
|
-
await e.client.close().catch((err2) =>
|
|
6376
|
+
await e.client.close().catch((err2) => log12.debug(`warm-pool evict close failed: ${err2}`));
|
|
6218
6377
|
}
|
|
6219
6378
|
async closeAll() {
|
|
6220
6379
|
for (const e of this.warm.values()) {
|
|
@@ -6231,7 +6390,7 @@ var defaultPool = new McpPool();
|
|
|
6231
6390
|
// cli/mcpOAuth.ts
|
|
6232
6391
|
import { createServer } from "http";
|
|
6233
6392
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
6234
|
-
import { readFileSync as readFileSync2, writeFileSync as
|
|
6393
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
6235
6394
|
import { dirname as dirname2 } from "path";
|
|
6236
6395
|
var McpOAuthOptions = class {
|
|
6237
6396
|
/** Where to persist tokens. Mode 0600. */
|
|
@@ -6265,8 +6424,8 @@ var McpOAuth = class {
|
|
|
6265
6424
|
}
|
|
6266
6425
|
}
|
|
6267
6426
|
save(store) {
|
|
6268
|
-
|
|
6269
|
-
|
|
6427
|
+
mkdirSync3(dirname2(this.options.storePath), { recursive: true });
|
|
6428
|
+
writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
|
|
6270
6429
|
}
|
|
6271
6430
|
// -- ATTENDED: one-time authorization ----------------------------------------
|
|
6272
6431
|
/** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
|
|
@@ -6424,15 +6583,15 @@ function defaultOpenBrowser(url) {
|
|
|
6424
6583
|
// cli/core.ts
|
|
6425
6584
|
import { randomUUID } from "crypto";
|
|
6426
6585
|
import { execFile as execFile2 } from "child_process";
|
|
6427
|
-
import { resolve, basename, join as
|
|
6428
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
6586
|
+
import { resolve, basename, join as join5 } from "path";
|
|
6587
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
6429
6588
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
6430
6589
|
init_tools_shell();
|
|
6431
6590
|
|
|
6432
6591
|
// src/tools.notify.ts
|
|
6433
6592
|
init_logging();
|
|
6434
6593
|
import { execFile } from "child_process";
|
|
6435
|
-
var
|
|
6594
|
+
var log14 = forComponent("notify");
|
|
6436
6595
|
function makeNotifyTool(opts = {}) {
|
|
6437
6596
|
const platform2 = opts.platform ?? process.platform;
|
|
6438
6597
|
const run = opts.exec ?? execFile;
|
|
@@ -6456,7 +6615,7 @@ function makeNotifyTool(opts = {}) {
|
|
|
6456
6615
|
return new Promise((resolve4) => {
|
|
6457
6616
|
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
6458
6617
|
if (e) {
|
|
6459
|
-
|
|
6618
|
+
log14.debug("notification failed", e);
|
|
6460
6619
|
resolve4(`Notification failed: ${e.message}`);
|
|
6461
6620
|
} else resolve4("Notification shown.");
|
|
6462
6621
|
});
|
|
@@ -6472,7 +6631,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
6472
6631
|
init_logging();
|
|
6473
6632
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
6474
6633
|
import { homedir } from "os";
|
|
6475
|
-
var
|
|
6634
|
+
var log15 = forComponent("cli-util");
|
|
6476
6635
|
function dotDirs(base, sub, opts = {}) {
|
|
6477
6636
|
const home = opts.home ?? homedir();
|
|
6478
6637
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -6489,7 +6648,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
6489
6648
|
try {
|
|
6490
6649
|
return JSON.parse(text);
|
|
6491
6650
|
} catch (e) {
|
|
6492
|
-
|
|
6651
|
+
log15.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
6493
6652
|
return fallback;
|
|
6494
6653
|
}
|
|
6495
6654
|
}
|
|
@@ -6499,7 +6658,7 @@ function readJsonFile(path, fallback) {
|
|
|
6499
6658
|
try {
|
|
6500
6659
|
text = readFileSync3(path, "utf8");
|
|
6501
6660
|
} catch (e) {
|
|
6502
|
-
|
|
6661
|
+
log15.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
6503
6662
|
return fallback;
|
|
6504
6663
|
}
|
|
6505
6664
|
return parseJson(text, fallback, path);
|
|
@@ -6583,8 +6742,8 @@ async function buildAgent(o) {
|
|
|
6583
6742
|
fs = mem;
|
|
6584
6743
|
} else if (o.boddb) {
|
|
6585
6744
|
const root = resolve(o.boddb);
|
|
6586
|
-
|
|
6587
|
-
const db = new BodDB2({ path:
|
|
6745
|
+
mkdirSync4(root, { recursive: true });
|
|
6746
|
+
const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
|
|
6588
6747
|
const bod = new BodDbFilesystem(db);
|
|
6589
6748
|
const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
|
|
6590
6749
|
await mkdirp(bod, cwd);
|
|
@@ -6711,6 +6870,9 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
6711
6870
|
...scratch ? { capToolResult: (full, info) => scratch.spill(full, info) } : {},
|
|
6712
6871
|
backgroundJobs: o.backgroundJobs ?? virtual,
|
|
6713
6872
|
// default ON in virtual modes (no real shell there); disk uses ShellJobRegistry
|
|
6873
|
+
// Subagent transcript traces, origin-anchored under .agent/traces (disk mode only — sandbox/db modes
|
|
6874
|
+
// must never write the real disk). Lets an interrupted/failed child leave a readable trail + pointer.
|
|
6875
|
+
...!virtual ? { traceDir: `${cwd}/.agent/traces` } : {},
|
|
6714
6876
|
skillsDir: dots("skills"),
|
|
6715
6877
|
commandsDir: dots("commands"),
|
|
6716
6878
|
memoryDir,
|
|
@@ -6744,11 +6906,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
6744
6906
|
// cli/voice.ts
|
|
6745
6907
|
init_logging();
|
|
6746
6908
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
6747
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
6909
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
|
|
6748
6910
|
import { homedir as homedir3 } from "os";
|
|
6749
|
-
import { dirname as dirname3, join as
|
|
6911
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
6750
6912
|
import { fileURLToPath } from "url";
|
|
6751
|
-
var
|
|
6913
|
+
var log16 = forComponent("VoiceIO");
|
|
6752
6914
|
var now4 = () => performance.now();
|
|
6753
6915
|
var Player = class {
|
|
6754
6916
|
proc = null;
|
|
@@ -6762,7 +6924,7 @@ var Player = class {
|
|
|
6762
6924
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
6763
6925
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
6764
6926
|
);
|
|
6765
|
-
this.proc.on("error", (e) =>
|
|
6927
|
+
this.proc.on("error", (e) => log16.warn(`ffplay error: ${e.message}`));
|
|
6766
6928
|
this.proc.stdin.on("error", () => {
|
|
6767
6929
|
});
|
|
6768
6930
|
this.bytesWritten = 0;
|
|
@@ -6789,7 +6951,7 @@ var Player = class {
|
|
|
6789
6951
|
this.proc = null;
|
|
6790
6952
|
}
|
|
6791
6953
|
};
|
|
6792
|
-
var nativeDir = () =>
|
|
6954
|
+
var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
|
|
6793
6955
|
function detectFfmpegMic() {
|
|
6794
6956
|
if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
|
|
6795
6957
|
const out = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" }).stderr;
|
|
@@ -6797,7 +6959,7 @@ function detectFfmpegMic() {
|
|
|
6797
6959
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
6798
6960
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
6799
6961
|
if (!mic) throw new Error("no audio input device found");
|
|
6800
|
-
|
|
6962
|
+
log16.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
6801
6963
|
return `:${mic.idx}`;
|
|
6802
6964
|
}
|
|
6803
6965
|
function detectedInputDevice() {
|
|
@@ -6825,23 +6987,23 @@ function detectedInputDevice() {
|
|
|
6825
6987
|
}
|
|
6826
6988
|
function resolveAecBinary() {
|
|
6827
6989
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
6828
|
-
const src =
|
|
6829
|
-
const plist =
|
|
6990
|
+
const src = join6(nativeDir(), "mic-aec.swift");
|
|
6991
|
+
const plist = join6(nativeDir(), "Info.plist");
|
|
6830
6992
|
if (!existsSync4(src)) return null;
|
|
6831
|
-
const cacheDir =
|
|
6832
|
-
const bin =
|
|
6993
|
+
const cacheDir = join6(homedir3(), ".agent", "cache");
|
|
6994
|
+
const bin = join6(cacheDir, "mic-aec");
|
|
6833
6995
|
if (existsSync4(bin) && statSync3(bin).mtimeMs >= statSync3(src).mtimeMs) return bin;
|
|
6834
6996
|
if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
|
|
6835
|
-
|
|
6836
|
-
|
|
6997
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
6998
|
+
log16.info("compiling AEC mic helper (first run)\u2026");
|
|
6837
6999
|
const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
6838
7000
|
if (build.status !== 0) {
|
|
6839
|
-
|
|
7001
|
+
log16.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
6840
7002
|
return null;
|
|
6841
7003
|
}
|
|
6842
7004
|
const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
6843
7005
|
if (sign.status !== 0) {
|
|
6844
|
-
|
|
7006
|
+
log16.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
6845
7007
|
return null;
|
|
6846
7008
|
}
|
|
6847
7009
|
return bin;
|
|
@@ -6860,16 +7022,16 @@ var NodeMicSource = class {
|
|
|
6860
7022
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
6861
7023
|
} else {
|
|
6862
7024
|
if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
6863
|
-
|
|
7025
|
+
log16.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
6864
7026
|
this.proc = spawn2(
|
|
6865
7027
|
"ffmpeg",
|
|
6866
7028
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
6867
7029
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
6868
7030
|
);
|
|
6869
|
-
this.proc.stderr.on("data", (d) =>
|
|
7031
|
+
this.proc.stderr.on("data", (d) => log16.warn(`ffmpeg: ${String(d).trim()}`));
|
|
6870
7032
|
}
|
|
6871
7033
|
this.proc.on("exit", (c) => {
|
|
6872
|
-
if (c && !this.stopped)
|
|
7034
|
+
if (c && !this.stopped) log16.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
6873
7035
|
});
|
|
6874
7036
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6875
7037
|
}
|
|
@@ -6903,11 +7065,11 @@ var AecDuplexAudio = class {
|
|
|
6903
7065
|
this.proc.stdin.on("error", () => {
|
|
6904
7066
|
});
|
|
6905
7067
|
this.proc.on("exit", (c) => {
|
|
6906
|
-
if (c && !this.stopped)
|
|
7068
|
+
if (c && !this.stopped) log16.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
6907
7069
|
});
|
|
6908
7070
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6909
7071
|
this.proc.stderr.on("data", (d) => {
|
|
6910
|
-
for (const ln of String(d).split("\n")) if (ln.trim())
|
|
7072
|
+
for (const ln of String(d).split("\n")) if (ln.trim()) log16.debug(`mic-aec: ${ln.trim()}`);
|
|
6911
7073
|
});
|
|
6912
7074
|
}
|
|
6913
7075
|
stop() {
|
|
@@ -7029,12 +7191,12 @@ var VoiceIO = class extends VoiceEngine {
|
|
|
7029
7191
|
// cli/config.ts
|
|
7030
7192
|
import { homedir as homedir4 } from "os";
|
|
7031
7193
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
7032
|
-
import { join as
|
|
7194
|
+
import { join as join7 } from "path";
|
|
7033
7195
|
import { pathToFileURL } from "url";
|
|
7034
7196
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
7035
7197
|
async function loadFrom(dir) {
|
|
7036
7198
|
for (const f of FILES) {
|
|
7037
|
-
const p =
|
|
7199
|
+
const p = join7(dir, ".agent", f);
|
|
7038
7200
|
if (!existsSync5(p)) continue;
|
|
7039
7201
|
try {
|
|
7040
7202
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
@@ -7047,7 +7209,7 @@ async function loadFrom(dir) {
|
|
|
7047
7209
|
return {};
|
|
7048
7210
|
}
|
|
7049
7211
|
function loadSettings(dir) {
|
|
7050
|
-
const p =
|
|
7212
|
+
const p = join7(dir, ".agent", "settings.json");
|
|
7051
7213
|
if (!existsSync5(p)) return {};
|
|
7052
7214
|
try {
|
|
7053
7215
|
const raw = JSON.parse(readFileSync4(p, "utf8"));
|
|
@@ -7084,7 +7246,7 @@ async function loadConfig(cwd) {
|
|
|
7084
7246
|
|
|
7085
7247
|
// cli/hooks-config.ts
|
|
7086
7248
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
7087
|
-
var
|
|
7249
|
+
var log17 = forComponent("hooks");
|
|
7088
7250
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
7089
7251
|
function ruleMatches(rule, toolName) {
|
|
7090
7252
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -7101,7 +7263,7 @@ function runCmd(rule, env) {
|
|
|
7101
7263
|
});
|
|
7102
7264
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
7103
7265
|
} catch (e) {
|
|
7104
|
-
|
|
7266
|
+
log17.debug(`hook command failed: ${rule.command}`, e);
|
|
7105
7267
|
return { code: 1, out: String(e?.message ?? e) };
|
|
7106
7268
|
}
|
|
7107
7269
|
}
|
|
@@ -7205,15 +7367,15 @@ function formatDiff(ops, opts = {}) {
|
|
|
7205
7367
|
}
|
|
7206
7368
|
|
|
7207
7369
|
// cli/session.ts
|
|
7208
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
7370
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
|
|
7209
7371
|
import { homedir as homedir5 } from "os";
|
|
7210
|
-
import { join as
|
|
7211
|
-
var
|
|
7212
|
-
var globalDir = () =>
|
|
7372
|
+
import { join as join8 } from "path";
|
|
7373
|
+
var log18 = forComponent("session");
|
|
7374
|
+
var globalDir = () => join8(homedir5(), ".agent", "sessions");
|
|
7213
7375
|
var SessionStore = class {
|
|
7214
7376
|
dir;
|
|
7215
7377
|
constructor(cwd) {
|
|
7216
|
-
this.dir =
|
|
7378
|
+
this.dir = join8(cwd, ".agent", "sessions");
|
|
7217
7379
|
}
|
|
7218
7380
|
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
7219
7381
|
newId(now5 = Date.now(), cwd) {
|
|
@@ -7221,10 +7383,10 @@ var SessionStore = class {
|
|
|
7221
7383
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
7222
7384
|
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
7223
7385
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
7224
|
-
if (existsSync6(this.dir) && existsSync6(
|
|
7386
|
+
if (existsSync6(this.dir) && existsSync6(join8(this.dir, `${id}.json`))) {
|
|
7225
7387
|
for (let i = 2; i <= 99; i++) {
|
|
7226
7388
|
const c = `${id}-${i}`;
|
|
7227
|
-
if (!existsSync6(
|
|
7389
|
+
if (!existsSync6(join8(this.dir, `${c}.json`))) {
|
|
7228
7390
|
id = c;
|
|
7229
7391
|
break;
|
|
7230
7392
|
}
|
|
@@ -7238,30 +7400,30 @@ var SessionStore = class {
|
|
|
7238
7400
|
}
|
|
7239
7401
|
save(data) {
|
|
7240
7402
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
7241
|
-
if (!existsSync6(this.dir))
|
|
7242
|
-
const path =
|
|
7403
|
+
if (!existsSync6(this.dir)) mkdirSync6(this.dir, { recursive: true });
|
|
7404
|
+
const path = join8(this.dir, `${data.meta.id}.json`);
|
|
7243
7405
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
7244
|
-
|
|
7406
|
+
writeFileSync4(tmp, JSON.stringify(data));
|
|
7245
7407
|
renameSync(tmp, path);
|
|
7246
7408
|
try {
|
|
7247
7409
|
const gd = globalDir();
|
|
7248
|
-
if (!existsSync6(gd))
|
|
7249
|
-
const link2 =
|
|
7410
|
+
if (!existsSync6(gd)) mkdirSync6(gd, { recursive: true });
|
|
7411
|
+
const link2 = join8(gd, `${data.meta.id}.json`);
|
|
7250
7412
|
if (!existsSync6(link2)) symlinkSync2(path, link2);
|
|
7251
7413
|
} catch {
|
|
7252
7414
|
}
|
|
7253
7415
|
}
|
|
7254
7416
|
load(id) {
|
|
7255
7417
|
if (!this.safeId(id)) {
|
|
7256
|
-
|
|
7418
|
+
log18.debug(`rejecting unsafe session id: ${id}`);
|
|
7257
7419
|
return void 0;
|
|
7258
7420
|
}
|
|
7259
|
-
const path =
|
|
7421
|
+
const path = join8(this.dir, `${id}.json`);
|
|
7260
7422
|
if (!existsSync6(path)) return void 0;
|
|
7261
7423
|
try {
|
|
7262
7424
|
return JSON.parse(readFileSync5(path, "utf8"));
|
|
7263
7425
|
} catch (e) {
|
|
7264
|
-
|
|
7426
|
+
log18.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
7265
7427
|
return void 0;
|
|
7266
7428
|
}
|
|
7267
7429
|
}
|
|
@@ -7272,9 +7434,9 @@ var SessionStore = class {
|
|
|
7272
7434
|
for (const f of readdirSync(this.dir)) {
|
|
7273
7435
|
if (!f.endsWith(".json")) continue;
|
|
7274
7436
|
try {
|
|
7275
|
-
metas.push(JSON.parse(readFileSync5(
|
|
7437
|
+
metas.push(JSON.parse(readFileSync5(join8(this.dir, f), "utf8")).meta);
|
|
7276
7438
|
} catch (e) {
|
|
7277
|
-
|
|
7439
|
+
log18.debug(`skipping unreadable session file ${f}`, e);
|
|
7278
7440
|
}
|
|
7279
7441
|
}
|
|
7280
7442
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -7291,7 +7453,7 @@ var SessionStore = class {
|
|
|
7291
7453
|
function globalSessionLoad(idOrPrefix) {
|
|
7292
7454
|
const gd = globalDir();
|
|
7293
7455
|
if (!existsSync6(gd)) return void 0;
|
|
7294
|
-
const exact =
|
|
7456
|
+
const exact = join8(gd, `${idOrPrefix}.json`);
|
|
7295
7457
|
if (existsSync6(exact)) {
|
|
7296
7458
|
try {
|
|
7297
7459
|
const target = readlinkSync(exact);
|
|
@@ -7305,7 +7467,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
7305
7467
|
if (!f.endsWith(".json")) continue;
|
|
7306
7468
|
const base = f.slice(0, -5);
|
|
7307
7469
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
7308
|
-
const target = readlinkSync(
|
|
7470
|
+
const target = readlinkSync(join8(gd, f));
|
|
7309
7471
|
return JSON.parse(readFileSync5(target, "utf8"));
|
|
7310
7472
|
}
|
|
7311
7473
|
}
|
|
@@ -7320,10 +7482,10 @@ function globalSessionList() {
|
|
|
7320
7482
|
for (const f of readdirSync(gd)) {
|
|
7321
7483
|
if (!f.endsWith(".json")) continue;
|
|
7322
7484
|
try {
|
|
7323
|
-
const target = readlinkSync(
|
|
7485
|
+
const target = readlinkSync(join8(gd, f));
|
|
7324
7486
|
if (!existsSync6(target)) {
|
|
7325
7487
|
try {
|
|
7326
|
-
unlinkSync(
|
|
7488
|
+
unlinkSync(join8(gd, f));
|
|
7327
7489
|
} catch {
|
|
7328
7490
|
}
|
|
7329
7491
|
continue;
|
|
@@ -7440,9 +7602,9 @@ ${formatDiff(ops)}`);
|
|
|
7440
7602
|
// cli/gitCheckpoints.ts
|
|
7441
7603
|
import { execFile as execFile3 } from "child_process";
|
|
7442
7604
|
import { promisify } from "util";
|
|
7443
|
-
import { writeFileSync as
|
|
7444
|
-
import { join as
|
|
7445
|
-
var
|
|
7605
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
|
|
7606
|
+
import { join as join9, resolve as resolve2, sep as sep2 } from "path";
|
|
7607
|
+
var log19 = forComponent("checkpoints");
|
|
7446
7608
|
var exec = promisify(execFile3);
|
|
7447
7609
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
7448
7610
|
var ShadowRepo = class {
|
|
@@ -7474,13 +7636,13 @@ var ShadowRepo = class {
|
|
|
7474
7636
|
try {
|
|
7475
7637
|
await exec(this.git, ["--version"]);
|
|
7476
7638
|
if (!existsSync7(this.gitDir)) {
|
|
7477
|
-
|
|
7639
|
+
mkdirSync7(this.gitDir, { recursive: true });
|
|
7478
7640
|
await this.run("init", "-q");
|
|
7479
7641
|
}
|
|
7480
|
-
|
|
7642
|
+
writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
7481
7643
|
this.ready = true;
|
|
7482
7644
|
} catch (e) {
|
|
7483
|
-
|
|
7645
|
+
log19.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
7484
7646
|
this.ready = false;
|
|
7485
7647
|
}
|
|
7486
7648
|
return this.ready;
|
|
@@ -7491,7 +7653,7 @@ var ShadowRepo = class {
|
|
|
7491
7653
|
}
|
|
7492
7654
|
async commit(label, forced = []) {
|
|
7493
7655
|
await this.run("add", "-A");
|
|
7494
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
7656
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-add failed: ${p}`, e));
|
|
7495
7657
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
7496
7658
|
}
|
|
7497
7659
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -7499,8 +7661,8 @@ var ShadowRepo = class {
|
|
|
7499
7661
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
7500
7662
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
7501
7663
|
async amendForced(paths) {
|
|
7502
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
7503
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
7664
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-capture failed: ${p}`, e));
|
|
7665
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log19.debug("amend failed", e));
|
|
7504
7666
|
}
|
|
7505
7667
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
7506
7668
|
async log(ref) {
|
|
@@ -7560,7 +7722,7 @@ var ShadowRepo = class {
|
|
|
7560
7722
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
7561
7723
|
});
|
|
7562
7724
|
} catch (e) {
|
|
7563
|
-
|
|
7725
|
+
log19.debug("checkpoint prune failed", e);
|
|
7564
7726
|
}
|
|
7565
7727
|
}
|
|
7566
7728
|
};
|
|
@@ -7592,7 +7754,7 @@ var GitCheckpoints = class {
|
|
|
7592
7754
|
const abs = resolve2(d);
|
|
7593
7755
|
if (abs === cwd || abs.startsWith(cwd + sep2)) continue;
|
|
7594
7756
|
if (cwd.startsWith(abs + sep2)) continue;
|
|
7595
|
-
out.push({ workTree: abs, gitDir:
|
|
7757
|
+
out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
|
|
7596
7758
|
}
|
|
7597
7759
|
return out;
|
|
7598
7760
|
}
|
|
@@ -7619,7 +7781,7 @@ var GitCheckpoints = class {
|
|
|
7619
7781
|
use(sessionId) {
|
|
7620
7782
|
if (sessionId === this.session) return;
|
|
7621
7783
|
this.session = sessionId;
|
|
7622
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
7784
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log19.debug("re-point failed", e));
|
|
7623
7785
|
}
|
|
7624
7786
|
async begin(label) {
|
|
7625
7787
|
if (!await this.start()) return;
|
|
@@ -7631,7 +7793,7 @@ var GitCheckpoints = class {
|
|
|
7631
7793
|
try {
|
|
7632
7794
|
await this.repos[i].commit(msg, forced);
|
|
7633
7795
|
} catch (e) {
|
|
7634
|
-
|
|
7796
|
+
log19.debug("checkpoint commit failed", e);
|
|
7635
7797
|
}
|
|
7636
7798
|
}
|
|
7637
7799
|
if (slow) clearTimeout(slow);
|
|
@@ -7661,7 +7823,7 @@ var GitCheckpoints = class {
|
|
|
7661
7823
|
if (this.forced.has(abs)) continue;
|
|
7662
7824
|
this.forced.add(abs);
|
|
7663
7825
|
const i = this.repoIndexFor(abs);
|
|
7664
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
7826
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log19.debug("amendForced failed", e));
|
|
7665
7827
|
}
|
|
7666
7828
|
}
|
|
7667
7829
|
};
|
|
@@ -7713,7 +7875,7 @@ var GitCheckpointsOptions = class {
|
|
|
7713
7875
|
/** Real working tree to snapshot (the launch cwd). */
|
|
7714
7876
|
workTree = process.cwd();
|
|
7715
7877
|
/** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
|
|
7716
|
-
gitDir =
|
|
7878
|
+
gitDir = join9(process.cwd(), ".agent", "checkpoints.git");
|
|
7717
7879
|
/** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
|
|
7718
7880
|
addDirs = [];
|
|
7719
7881
|
/** Conversation id → per-session restore-point ref. */
|
|
@@ -7727,9 +7889,9 @@ var GitCheckpointsOptions = class {
|
|
|
7727
7889
|
};
|
|
7728
7890
|
|
|
7729
7891
|
// cli/permissions.ts
|
|
7730
|
-
import { writeFileSync as
|
|
7892
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
|
|
7731
7893
|
import { homedir as homedir6 } from "os";
|
|
7732
|
-
import { join as
|
|
7894
|
+
import { join as join10 } from "path";
|
|
7733
7895
|
var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
|
|
7734
7896
|
function parseOne(raw, decision) {
|
|
7735
7897
|
const m = RULE_RE.exec(raw.trim());
|
|
@@ -7751,13 +7913,13 @@ function parsePermRules(perms) {
|
|
|
7751
7913
|
function describeRule(r) {
|
|
7752
7914
|
return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
|
|
7753
7915
|
}
|
|
7754
|
-
var PERM_FILE = (cwd) =>
|
|
7916
|
+
var PERM_FILE = (cwd) => join10(cwd, ".agent", "permissions.json");
|
|
7755
7917
|
function loadPersistedRules(cwd) {
|
|
7756
7918
|
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
7757
7919
|
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
7758
7920
|
}
|
|
7759
7921
|
function loadClaudeSettings(cwd, home = homedir6()) {
|
|
7760
|
-
const files = [
|
|
7922
|
+
const files = [join10(home, ".claude", "settings.json"), join10(cwd, ".claude", "settings.json"), join10(cwd, ".claude", "settings.local.json")];
|
|
7761
7923
|
let out = {};
|
|
7762
7924
|
for (const p of files) {
|
|
7763
7925
|
const perms = readJsonFile(p, null)?.permissions;
|
|
@@ -7770,8 +7932,8 @@ function persistRule(cwd, decision, ruleStr) {
|
|
|
7770
7932
|
const list = cur[decision] ??= [];
|
|
7771
7933
|
if (!list.includes(ruleStr)) list.push(ruleStr);
|
|
7772
7934
|
try {
|
|
7773
|
-
|
|
7774
|
-
|
|
7935
|
+
mkdirSync8(join10(cwd, ".agent"), { recursive: true });
|
|
7936
|
+
writeFileSync6(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
|
|
7775
7937
|
} catch {
|
|
7776
7938
|
}
|
|
7777
7939
|
}
|
|
@@ -7784,7 +7946,7 @@ function mergePerms(a, b) {
|
|
|
7784
7946
|
}
|
|
7785
7947
|
return Object.keys(out).length ? out : void 0;
|
|
7786
7948
|
}
|
|
7787
|
-
var TRUST_FILE =
|
|
7949
|
+
var TRUST_FILE = join10(homedir6(), ".agent", "trusted.json");
|
|
7788
7950
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
7789
7951
|
const list = readJsonFile(file, []);
|
|
7790
7952
|
return Array.isArray(list) && list.includes(cwd);
|
|
@@ -7794,8 +7956,8 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
7794
7956
|
if (!Array.isArray(list)) list = [];
|
|
7795
7957
|
if (!list.includes(cwd)) list.push(cwd);
|
|
7796
7958
|
try {
|
|
7797
|
-
|
|
7798
|
-
|
|
7959
|
+
mkdirSync8(join10(file, ".."), { recursive: true });
|
|
7960
|
+
writeFileSync6(file, JSON.stringify(list, null, 2) + "\n");
|
|
7799
7961
|
} catch {
|
|
7800
7962
|
}
|
|
7801
7963
|
}
|
|
@@ -7855,9 +8017,9 @@ function completePath(listDir, ref) {
|
|
|
7855
8017
|
// cli/lineEditor.ts
|
|
7856
8018
|
import { emitKeypressEvents } from "readline";
|
|
7857
8019
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
7858
|
-
import { writeFileSync as
|
|
8020
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
7859
8021
|
import { tmpdir as tmpdir2 } from "os";
|
|
7860
|
-
import { join as
|
|
8022
|
+
import { join as join11 } from "path";
|
|
7861
8023
|
|
|
7862
8024
|
// cli/bidi.ts
|
|
7863
8025
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -8934,9 +9096,9 @@ function createLineEditor(out) {
|
|
|
8934
9096
|
function externalEdit(s) {
|
|
8935
9097
|
const spec = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
8936
9098
|
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
8937
|
-
const file =
|
|
9099
|
+
const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
8938
9100
|
try {
|
|
8939
|
-
|
|
9101
|
+
writeFileSync7(file, s.buf);
|
|
8940
9102
|
process.stdin.setRawMode(false);
|
|
8941
9103
|
out.write("\x1B[?2004l");
|
|
8942
9104
|
const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
|
|
@@ -9364,23 +9526,23 @@ function readPlainLine() {
|
|
|
9364
9526
|
|
|
9365
9527
|
// cli/osScheduler.ts
|
|
9366
9528
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
9367
|
-
import { writeFileSync as
|
|
9529
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
|
|
9368
9530
|
import { homedir as homedir7 } from "os";
|
|
9369
|
-
import { join as
|
|
9370
|
-
var
|
|
9531
|
+
import { join as join12 } from "path";
|
|
9532
|
+
var log20 = forComponent("os-sched");
|
|
9371
9533
|
var OsScheduler = class {
|
|
9372
9534
|
options;
|
|
9373
9535
|
constructor(options) {
|
|
9374
9536
|
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
9375
9537
|
}
|
|
9376
9538
|
get dir() {
|
|
9377
|
-
return
|
|
9539
|
+
return join12(this.options.home, ".agent", "sched");
|
|
9378
9540
|
}
|
|
9379
9541
|
label(id) {
|
|
9380
9542
|
return `cc.livx.agentx.sched-${id}`;
|
|
9381
9543
|
}
|
|
9382
9544
|
plistPath(id) {
|
|
9383
|
-
return
|
|
9545
|
+
return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
9384
9546
|
}
|
|
9385
9547
|
run(cmd, args, input) {
|
|
9386
9548
|
return this.options.exec(cmd, args, input);
|
|
@@ -9391,16 +9553,16 @@ var OsScheduler = class {
|
|
|
9391
9553
|
/** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
|
|
9392
9554
|
schedule(spec) {
|
|
9393
9555
|
if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
|
|
9394
|
-
|
|
9556
|
+
mkdirSync9(this.dir, { recursive: true });
|
|
9395
9557
|
const oneOff = "at" in spec.trigger;
|
|
9396
9558
|
const script = this.writeScript(spec, oneOff);
|
|
9397
9559
|
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
9398
9560
|
const meta = { ...spec, created: Date.now(), mechanism };
|
|
9399
|
-
|
|
9561
|
+
writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
9400
9562
|
return mechanism;
|
|
9401
9563
|
}
|
|
9402
9564
|
cancel(id) {
|
|
9403
|
-
const meta = readJsonFile(
|
|
9565
|
+
const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
|
|
9404
9566
|
if (!meta) return false;
|
|
9405
9567
|
try {
|
|
9406
9568
|
if (this.options.platform === "darwin") {
|
|
@@ -9429,11 +9591,11 @@ var OsScheduler = class {
|
|
|
9429
9591
|
}
|
|
9430
9592
|
}
|
|
9431
9593
|
} catch (e) {
|
|
9432
|
-
|
|
9594
|
+
log20.debug(`cancel ${id}`, e);
|
|
9433
9595
|
}
|
|
9434
9596
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
9435
9597
|
try {
|
|
9436
|
-
unlinkSync3(
|
|
9598
|
+
unlinkSync3(join12(this.dir, f));
|
|
9437
9599
|
} catch {
|
|
9438
9600
|
}
|
|
9439
9601
|
}
|
|
@@ -9441,19 +9603,19 @@ var OsScheduler = class {
|
|
|
9441
9603
|
}
|
|
9442
9604
|
list() {
|
|
9443
9605
|
if (!existsSync8(this.dir)) return [];
|
|
9444
|
-
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(
|
|
9606
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
|
|
9445
9607
|
}
|
|
9446
9608
|
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
9447
9609
|
writeScript(spec, oneOff) {
|
|
9448
|
-
const p =
|
|
9610
|
+
const p = join12(this.dir, `${spec.id}.sh`);
|
|
9449
9611
|
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9450
|
-
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(
|
|
9451
|
-
` : `rm -f ${q2(
|
|
9612
|
+
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
9613
|
+
` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
9452
9614
|
` : "";
|
|
9453
|
-
|
|
9615
|
+
writeFileSync8(p, `#!/bin/sh
|
|
9454
9616
|
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
9455
9617
|
cd ${q2(spec.cwd)} || exit 1
|
|
9456
|
-
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(
|
|
9618
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
|
|
9457
9619
|
${cleanup}`);
|
|
9458
9620
|
chmodSync(p, 493);
|
|
9459
9621
|
return p;
|
|
@@ -9490,8 +9652,8 @@ ${trigger}
|
|
|
9490
9652
|
<key>RunAtLoad</key><false/>
|
|
9491
9653
|
</dict></plist>
|
|
9492
9654
|
`;
|
|
9493
|
-
|
|
9494
|
-
|
|
9655
|
+
mkdirSync9(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
9656
|
+
writeFileSync8(this.plistPath(spec.id), plist);
|
|
9495
9657
|
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
9496
9658
|
return `launchd:${this.label(spec.id)}`;
|
|
9497
9659
|
}
|
|
@@ -9543,12 +9705,12 @@ function routeTrigger(trigger, backendHint, now5 = Date.now()) {
|
|
|
9543
9705
|
// cli/remoteTrigger.ts
|
|
9544
9706
|
import { createServer as createServer2, createConnection } from "net";
|
|
9545
9707
|
import { spawn as spawn3 } from "child_process";
|
|
9546
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
9708
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync10, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
9547
9709
|
import { homedir as homedir8 } from "os";
|
|
9548
|
-
import { join as
|
|
9549
|
-
var
|
|
9550
|
-
var TRIGGER_DIR = () =>
|
|
9551
|
-
var sockPath = (sessionId, dir = TRIGGER_DIR()) =>
|
|
9710
|
+
import { join as join13 } from "path";
|
|
9711
|
+
var log21 = forComponent("remote-trigger");
|
|
9712
|
+
var TRIGGER_DIR = () => join13(homedir8(), ".agent", "triggers");
|
|
9713
|
+
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
|
|
9552
9714
|
var TriggerServer = class {
|
|
9553
9715
|
constructor(onTrigger) {
|
|
9554
9716
|
this.onTrigger = onTrigger;
|
|
@@ -9560,7 +9722,7 @@ var TriggerServer = class {
|
|
|
9560
9722
|
start(sessionId, dir = TRIGGER_DIR()) {
|
|
9561
9723
|
this.stop();
|
|
9562
9724
|
try {
|
|
9563
|
-
|
|
9725
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
9564
9726
|
const p = sockPath(sessionId, dir);
|
|
9565
9727
|
try {
|
|
9566
9728
|
unlinkSync4(p);
|
|
@@ -9584,13 +9746,13 @@ var TriggerServer = class {
|
|
|
9584
9746
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
9585
9747
|
}
|
|
9586
9748
|
});
|
|
9587
|
-
conn.on("error", (e) =>
|
|
9749
|
+
conn.on("error", (e) => log21.debug("trigger conn error", e));
|
|
9588
9750
|
});
|
|
9589
|
-
this.server.on("error", (e) =>
|
|
9751
|
+
this.server.on("error", (e) => log21.debug("trigger server error", e));
|
|
9590
9752
|
this.server.listen(p);
|
|
9591
9753
|
this.path = p;
|
|
9592
9754
|
} catch (e) {
|
|
9593
|
-
|
|
9755
|
+
log21.debug("trigger server unavailable", e);
|
|
9594
9756
|
}
|
|
9595
9757
|
}
|
|
9596
9758
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -9711,7 +9873,7 @@ var italic = C("3");
|
|
|
9711
9873
|
var strike = C("9");
|
|
9712
9874
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
9713
9875
|
var err = (s) => process.stderr.write(s);
|
|
9714
|
-
var
|
|
9876
|
+
var log22 = forComponent("cli");
|
|
9715
9877
|
var VERSION = (() => {
|
|
9716
9878
|
try {
|
|
9717
9879
|
return JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
@@ -9939,9 +10101,9 @@ function resolveModelOrNewest(model) {
|
|
|
9939
10101
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
9940
10102
|
function loadInstallEnv() {
|
|
9941
10103
|
let dir = dirname4(import.meta.path);
|
|
9942
|
-
for (let i = 0; i < 5 && !existsSync10(
|
|
10104
|
+
for (let i = 0; i < 5 && !existsSync10(join14(dir, "package.json")); i++) dir = dirname4(dir);
|
|
9943
10105
|
for (const name of [".env", ".env.local"]) {
|
|
9944
|
-
const file =
|
|
10106
|
+
const file = join14(dir, name);
|
|
9945
10107
|
if (!existsSync10(file)) continue;
|
|
9946
10108
|
for (const line of readFileSync7(file, "utf8").split("\n")) {
|
|
9947
10109
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
@@ -10302,7 +10464,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
10302
10464
|
function turnCost(model, usage) {
|
|
10303
10465
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
10304
10466
|
}
|
|
10305
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
10467
|
+
async function evaluateGoal(ai, condition, transcript, log23) {
|
|
10306
10468
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
10307
10469
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
10308
10470
|
return text.slice(0, 600);
|
|
@@ -10322,7 +10484,7 @@ ${recent}` }
|
|
|
10322
10484
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
10323
10485
|
if (match) return JSON.parse(match[0]);
|
|
10324
10486
|
} catch (e) {
|
|
10325
|
-
|
|
10487
|
+
log23(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
10326
10488
|
`));
|
|
10327
10489
|
}
|
|
10328
10490
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -10529,13 +10691,13 @@ function mcpAgentTools(mounted, opts) {
|
|
|
10529
10691
|
return tools;
|
|
10530
10692
|
}
|
|
10531
10693
|
async function closeMcp(mounted) {
|
|
10532
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
10694
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log22.debug("mcp close failed", e))));
|
|
10533
10695
|
}
|
|
10534
10696
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
10535
10697
|
function mentionRefs(line) {
|
|
10536
10698
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
10537
10699
|
}
|
|
10538
|
-
var untilde = (p) => p.startsWith("~/") ?
|
|
10700
|
+
var untilde = (p) => p.startsWith("~/") ? join14(homedir9(), p.slice(2)) : p;
|
|
10539
10701
|
function readImageParts(cwd, line) {
|
|
10540
10702
|
const refs = mentionRefs(line);
|
|
10541
10703
|
const parts = [];
|
|
@@ -10579,7 +10741,7 @@ async function expandMentions(fs, line) {
|
|
|
10579
10741
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
10580
10742
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
10581
10743
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
10582
|
-
|
|
10744
|
+
log22.debug("mcp mention resolve failed", e);
|
|
10583
10745
|
return null;
|
|
10584
10746
|
});
|
|
10585
10747
|
if (body != null) {
|
|
@@ -10662,7 +10824,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
10662
10824
|
try {
|
|
10663
10825
|
store.save(session);
|
|
10664
10826
|
} catch (ex) {
|
|
10665
|
-
|
|
10827
|
+
log22.debug("mid-turn session flush failed", ex);
|
|
10666
10828
|
}
|
|
10667
10829
|
}
|
|
10668
10830
|
return origNotify(e);
|
|
@@ -10775,25 +10937,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
10775
10937
|
`;
|
|
10776
10938
|
function initInstructions(cwd) {
|
|
10777
10939
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
10778
|
-
if (existsSync10(
|
|
10940
|
+
if (existsSync10(join14(cwd, f))) {
|
|
10779
10941
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
10780
10942
|
`));
|
|
10781
10943
|
return;
|
|
10782
10944
|
}
|
|
10783
10945
|
}
|
|
10784
|
-
const path =
|
|
10785
|
-
|
|
10946
|
+
const path = join14(cwd, "AGENTS.md");
|
|
10947
|
+
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
|
|
10786
10948
|
err(green(` created ${path}
|
|
10787
10949
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
10788
10950
|
}
|
|
10789
10951
|
function persistSetting(cwd, key, value) {
|
|
10790
|
-
const path =
|
|
10952
|
+
const path = join14(cwd, ".agent", "settings.json");
|
|
10791
10953
|
try {
|
|
10792
10954
|
const obj = existsSync10(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
|
|
10793
10955
|
if (obj[key] === value) return;
|
|
10794
10956
|
obj[key] = value;
|
|
10795
|
-
|
|
10796
|
-
|
|
10957
|
+
mkdirSync11(dirname4(path), { recursive: true });
|
|
10958
|
+
writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
|
|
10797
10959
|
} catch (e) {
|
|
10798
10960
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
10799
10961
|
`));
|
|
@@ -10809,14 +10971,14 @@ var isCancelTeardown = (e) => {
|
|
|
10809
10971
|
function installCancelGuards(mounted) {
|
|
10810
10972
|
process.on("unhandledRejection", (e) => {
|
|
10811
10973
|
if (isCancelTeardown(e)) {
|
|
10812
|
-
|
|
10974
|
+
log22.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
10813
10975
|
return;
|
|
10814
10976
|
}
|
|
10815
|
-
|
|
10977
|
+
log22.error("unhandledRejection", e);
|
|
10816
10978
|
});
|
|
10817
10979
|
process.on("uncaughtException", (e) => {
|
|
10818
10980
|
if (isCancelTeardown(e)) {
|
|
10819
|
-
|
|
10981
|
+
log22.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
10820
10982
|
return;
|
|
10821
10983
|
}
|
|
10822
10984
|
console.error(e);
|
|
@@ -10825,7 +10987,7 @@ function installCancelGuards(mounted) {
|
|
|
10825
10987
|
});
|
|
10826
10988
|
}
|
|
10827
10989
|
async function repl(args, ai, cfg, cwd) {
|
|
10828
|
-
const oauth = new McpOAuth({ storePath:
|
|
10990
|
+
const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
|
|
10829
10991
|
const mounted = await mountMcp(cfg, oauth);
|
|
10830
10992
|
let mcpToolNames = [];
|
|
10831
10993
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -11033,7 +11195,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
11033
11195
|
quickLook: {
|
|
11034
11196
|
branch: () => {
|
|
11035
11197
|
try {
|
|
11036
|
-
const head = readFileSync7(
|
|
11198
|
+
const head = readFileSync7(join14(cwd, ".git", "HEAD"), "utf8").trim();
|
|
11037
11199
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
11038
11200
|
} catch {
|
|
11039
11201
|
return "not a git repository";
|
|
@@ -11162,9 +11324,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
11162
11324
|
const pendingImages = [];
|
|
11163
11325
|
const bangContext = [];
|
|
11164
11326
|
const grabClipboardAttachment = () => {
|
|
11165
|
-
const dir =
|
|
11327
|
+
const dir = join14(tmpdir3(), "agentx-pasted");
|
|
11166
11328
|
try {
|
|
11167
|
-
|
|
11329
|
+
mkdirSync11(dir, { recursive: true });
|
|
11168
11330
|
} catch {
|
|
11169
11331
|
}
|
|
11170
11332
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
@@ -11215,7 +11377,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
11215
11377
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
11216
11378
|
`));
|
|
11217
11379
|
}
|
|
11218
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
11380
|
+
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join14(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
|
|
11219
11381
|
const cpHooks = checkpoints.hooks?.();
|
|
11220
11382
|
if (cpHooks) {
|
|
11221
11383
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -11267,14 +11429,14 @@ ${lines.join("\n")}
|
|
|
11267
11429
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
11268
11430
|
</system-reminder>`;
|
|
11269
11431
|
};
|
|
11270
|
-
const histPath =
|
|
11432
|
+
const histPath = join14(cwd, ".agent", "history");
|
|
11271
11433
|
const history = existsSync10(histPath) ? readFileSync7(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
11272
11434
|
const remember = (line) => {
|
|
11273
11435
|
try {
|
|
11274
|
-
|
|
11436
|
+
mkdirSync11(join14(cwd, ".agent"), { recursive: true });
|
|
11275
11437
|
appendFileSync(histPath, line + "\n");
|
|
11276
11438
|
} catch (e) {
|
|
11277
|
-
|
|
11439
|
+
log22.debug("history write failed", e);
|
|
11278
11440
|
}
|
|
11279
11441
|
};
|
|
11280
11442
|
const ago = (t) => {
|
|
@@ -11345,7 +11507,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
11345
11507
|
try {
|
|
11346
11508
|
store.save(session);
|
|
11347
11509
|
} catch (e) {
|
|
11348
|
-
|
|
11510
|
+
log22.debug("session save after rewind failed", e);
|
|
11349
11511
|
}
|
|
11350
11512
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
11351
11513
|
`));
|
|
@@ -11379,7 +11541,7 @@ ${task}`;
|
|
|
11379
11541
|
bangContext.length = 0;
|
|
11380
11542
|
}
|
|
11381
11543
|
const delta = await refreshCatalogs().catch((e) => {
|
|
11382
|
-
|
|
11544
|
+
log22.debug("catalog refresh failed", e);
|
|
11383
11545
|
return "";
|
|
11384
11546
|
});
|
|
11385
11547
|
if (delta) {
|
|
@@ -11596,8 +11758,8 @@ ${task}`;
|
|
|
11596
11758
|
cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
|
|
11597
11759
|
try {
|
|
11598
11760
|
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
11599
|
-
|
|
11600
|
-
|
|
11761
|
+
mkdirSync11(`${cwd}/.agent/sessions`, { recursive: true });
|
|
11762
|
+
writeFileSync9(probe, "ok");
|
|
11601
11763
|
unlinkSync5(probe);
|
|
11602
11764
|
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
11603
11765
|
} catch (e) {
|
|
@@ -11625,7 +11787,7 @@ ${task}`;
|
|
|
11625
11787
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
11626
11788
|
run: async () => {
|
|
11627
11789
|
await refreshCatalogs().catch((e) => {
|
|
11628
|
-
|
|
11790
|
+
log22.debug("catalog refresh failed", e);
|
|
11629
11791
|
});
|
|
11630
11792
|
face.reprepare();
|
|
11631
11793
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -12082,7 +12244,7 @@ ${task}`;
|
|
|
12082
12244
|
try {
|
|
12083
12245
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
12084
12246
|
} catch (e) {
|
|
12085
|
-
|
|
12247
|
+
log22.debug(`loadAgents(${d}) failed`, e);
|
|
12086
12248
|
}
|
|
12087
12249
|
}
|
|
12088
12250
|
if (!seen.size) {
|
|
@@ -12170,7 +12332,7 @@ ${task}`;
|
|
|
12170
12332
|
}
|
|
12171
12333
|
if (idx >= 0) {
|
|
12172
12334
|
const old = mounted.splice(idx, 1)[0];
|
|
12173
|
-
await old.client.close().catch((e) =>
|
|
12335
|
+
await old.client.close().catch((e) => log22.debug("mcp close failed", e));
|
|
12174
12336
|
}
|
|
12175
12337
|
try {
|
|
12176
12338
|
const m = await mountMcpServer(name, conf);
|
|
@@ -12198,7 +12360,7 @@ ${task}`;
|
|
|
12198
12360
|
}
|
|
12199
12361
|
const m = mounted.splice(idx, 1)[0];
|
|
12200
12362
|
remountMcpTools();
|
|
12201
|
-
await m.client.close().catch((e) =>
|
|
12363
|
+
await m.client.close().catch((e) => log22.debug("mcp close failed", e));
|
|
12202
12364
|
err(dim(` removed "${name}"
|
|
12203
12365
|
`));
|
|
12204
12366
|
return;
|
|
@@ -12330,11 +12492,11 @@ ${task}`;
|
|
|
12330
12492
|
return;
|
|
12331
12493
|
}
|
|
12332
12494
|
const md = exportMarkdown(session.meta, shown);
|
|
12333
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
12495
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
|
|
12334
12496
|
const path = resolve3(cwd, name);
|
|
12335
12497
|
try {
|
|
12336
|
-
|
|
12337
|
-
|
|
12498
|
+
mkdirSync11(dirname4(path), { recursive: true });
|
|
12499
|
+
writeFileSync9(path, md);
|
|
12338
12500
|
err(green(` \u2713 exported \u2192 ${path}
|
|
12339
12501
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
12340
12502
|
`));
|
|
@@ -12396,9 +12558,9 @@ ${task}`;
|
|
|
12396
12558
|
if (dx) banner(dim(`\u25D1 duplex \u2014 reflex: ${dx.options.reflexModel} \xB7 act: ${work.model}${dx.options.thinkModel !== false ? ` \xB7 think: ${dx.options.thinkModel}` : ""} (real work runs in background tasks, re-voiced when done)`));
|
|
12397
12559
|
const listDir = (absDir) => {
|
|
12398
12560
|
try {
|
|
12399
|
-
return readdirSync4(
|
|
12561
|
+
return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
12400
12562
|
} catch (e) {
|
|
12401
|
-
|
|
12563
|
+
log22.debug("completion readdir failed", absDir, e);
|
|
12402
12564
|
return null;
|
|
12403
12565
|
}
|
|
12404
12566
|
};
|
|
@@ -12525,18 +12687,18 @@ ${out}
|
|
|
12525
12687
|
return;
|
|
12526
12688
|
}
|
|
12527
12689
|
await refreshCatalogs().catch((e) => {
|
|
12528
|
-
|
|
12690
|
+
log22.debug("catalog refresh failed", e);
|
|
12529
12691
|
});
|
|
12530
|
-
const c = cmds.find((x) => x.name === name);
|
|
12531
|
-
if (c) {
|
|
12532
|
-
await runCommand(c, a.join(" "));
|
|
12533
|
-
return;
|
|
12534
|
-
}
|
|
12535
12692
|
const sk = skills.find((x) => x.name === name);
|
|
12536
12693
|
if (sk) {
|
|
12537
12694
|
await runSkill(sk, a.join(" "));
|
|
12538
12695
|
return;
|
|
12539
12696
|
}
|
|
12697
|
+
const c = cmds.find((x) => x.name === name);
|
|
12698
|
+
if (c) {
|
|
12699
|
+
await runCommand(c, a.join(" "));
|
|
12700
|
+
return;
|
|
12701
|
+
}
|
|
12540
12702
|
const known = Object.keys(builtins).filter((k) => k !== "quit").map((k) => "/" + k);
|
|
12541
12703
|
const custom = [...cmds.map((x) => x.name), ...skills.map((x) => x.name)].map((n) => "/" + n);
|
|
12542
12704
|
err(red(` unknown command /${name}
|
|
@@ -12925,7 +13087,7 @@ async function main() {
|
|
|
12925
13087
|
}
|
|
12926
13088
|
});
|
|
12927
13089
|
if (args.task) {
|
|
12928
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
13090
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
|
|
12929
13091
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted));
|
|
12930
13092
|
const store = new SessionStore(cwd);
|
|
12931
13093
|
const session = startSession(args, store, agent, cwd);
|