@livx.cc/agentx 0.94.38 → 0.95.2
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 +2 -1
- package/dist/cli.js +636 -294
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +37 -3
- package/dist/index.js +395 -83
- 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, {
|
|
@@ -1589,7 +1755,7 @@ function makeRealShellTool(options) {
|
|
|
1589
1755
|
proc.stderr?.on("data", collect);
|
|
1590
1756
|
proc.on("error", (err2) => {
|
|
1591
1757
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
|
|
1592
|
-
|
|
1758
|
+
log13.debug("shell spawn error", err2);
|
|
1593
1759
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
|
|
1594
1760
|
});
|
|
1595
1761
|
proc.on("close", (code) => {
|
|
@@ -1651,7 +1817,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
1651
1817
|
}
|
|
1652
1818
|
];
|
|
1653
1819
|
}
|
|
1654
|
-
var
|
|
1820
|
+
var log13, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1655
1821
|
var init_tools_shell = __esm({
|
|
1656
1822
|
"src/tools.shell.ts"() {
|
|
1657
1823
|
"use strict";
|
|
@@ -1659,7 +1825,7 @@ var init_tools_shell = __esm({
|
|
|
1659
1825
|
init_redact();
|
|
1660
1826
|
init_logging();
|
|
1661
1827
|
init_shell_sandbox();
|
|
1662
|
-
|
|
1828
|
+
log13 = forComponent("shell");
|
|
1663
1829
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1664
1830
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
1665
1831
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -1741,7 +1907,7 @@ var init_tools_shell = __esm({
|
|
|
1741
1907
|
|
|
1742
1908
|
// cli/cli.ts
|
|
1743
1909
|
import { createInterface } from "readline/promises";
|
|
1744
|
-
import { existsSync as existsSync10, readFileSync 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";
|
|
1745
1911
|
import { homedir as homedir9, tmpdir as tmpdir3 } from "os";
|
|
1746
1912
|
|
|
1747
1913
|
// cli/clipboard.ts
|
|
@@ -1809,7 +1975,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
1809
1975
|
}
|
|
1810
1976
|
|
|
1811
1977
|
// cli/cli.ts
|
|
1812
|
-
import { join as
|
|
1978
|
+
import { join as join14, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
|
|
1813
1979
|
import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
|
|
1814
1980
|
|
|
1815
1981
|
// src/llm.ts
|
|
@@ -2456,7 +2622,13 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
2456
2622
|
|
|
2457
2623
|
// src/subagent.ts
|
|
2458
2624
|
init_tools();
|
|
2625
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2626
|
+
import { join as join4 } from "path";
|
|
2459
2627
|
init_OverlayFilesystem();
|
|
2628
|
+
init_worktree();
|
|
2629
|
+
init_NodeDiskFilesystem();
|
|
2630
|
+
init_logging();
|
|
2631
|
+
var log3 = forComponent("subagent");
|
|
2460
2632
|
async function boundedPool(items, limit, fn) {
|
|
2461
2633
|
const out = new Array(items.length);
|
|
2462
2634
|
let next = 0;
|
|
@@ -2469,13 +2641,86 @@ async function boundedPool(items, limit, fn) {
|
|
|
2469
2641
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
2470
2642
|
return out;
|
|
2471
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
|
+
}
|
|
2472
2717
|
function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
|
|
2473
2718
|
let def;
|
|
2474
2719
|
if (agentType) {
|
|
2475
2720
|
def = (opts.agents ?? []).find((a) => a.name === agentType);
|
|
2476
2721
|
if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
|
|
2477
2722
|
}
|
|
2478
|
-
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 };
|
|
2479
2724
|
if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
|
|
2480
2725
|
if (def?.systemPrompt) childOpts.systemPrompt = def.systemPrompt;
|
|
2481
2726
|
if (def?.tools?.length) {
|
|
@@ -2492,7 +2737,7 @@ function makeTaskTool(opts) {
|
|
|
2492
2737
|
const maxDepth = opts.maxDepth ?? 2;
|
|
2493
2738
|
return {
|
|
2494
2739
|
name: "Task",
|
|
2495
|
-
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.',
|
|
2496
2741
|
parameters: {
|
|
2497
2742
|
type: "object",
|
|
2498
2743
|
required: ["description", "prompt"],
|
|
@@ -2500,36 +2745,83 @@ function makeTaskTool(opts) {
|
|
|
2500
2745
|
description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
|
|
2501
2746
|
prompt: { type: "string", description: "the full instructions the child agent should carry out" },
|
|
2502
2747
|
agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
|
|
2503
|
-
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)' }
|
|
2504
2750
|
}
|
|
2505
2751
|
},
|
|
2506
|
-
async run({ description, prompt, agentType, background }, ctx) {
|
|
2752
|
+
async run({ description, prompt, agentType, background, isolation }, ctx) {
|
|
2507
2753
|
if (depth >= maxDepth) {
|
|
2508
2754
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
2509
2755
|
}
|
|
2510
2756
|
const label = String(description ?? agentType ?? "sub-task");
|
|
2757
|
+
const useWorktree = isolation === "worktree";
|
|
2511
2758
|
if (background && ctx.jobs) {
|
|
2512
2759
|
const id = ctx.jobs.start(async ({ signal }) => {
|
|
2513
|
-
|
|
2514
|
-
|
|
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);
|
|
2515
2776
|
if (typeof childOpts2 === "string") throw new Error(childOpts2);
|
|
2516
|
-
|
|
2517
|
-
const
|
|
2518
|
-
if (
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
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;
|
|
2523
2792
|
}, { kind: "agent", label });
|
|
2524
2793
|
return `Started background sub-task ${id} ('${label}') \u2014 poll with JobOutput({id:"${id}"}).`;
|
|
2525
2794
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|
+
}
|
|
2533
2825
|
}
|
|
2534
2826
|
};
|
|
2535
2827
|
}
|
|
@@ -2539,7 +2831,7 @@ function makeTaskBatchTool(opts) {
|
|
|
2539
2831
|
const maxParallel = opts.maxParallel ?? 4;
|
|
2540
2832
|
return {
|
|
2541
2833
|
name: "TaskBatch",
|
|
2542
|
-
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.',
|
|
2543
2835
|
parameters: {
|
|
2544
2836
|
type: "object",
|
|
2545
2837
|
required: ["tasks"],
|
|
@@ -2556,30 +2848,53 @@ function makeTaskBatchTool(opts) {
|
|
|
2556
2848
|
agentType: { type: "string", description: "optional named subagent type" }
|
|
2557
2849
|
}
|
|
2558
2850
|
}
|
|
2559
|
-
}
|
|
2851
|
+
},
|
|
2852
|
+
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
2560
2853
|
}
|
|
2561
2854
|
},
|
|
2562
|
-
async run({ tasks },
|
|
2855
|
+
async run({ tasks, isolation }, ctx) {
|
|
2563
2856
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
2564
2857
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
2565
2858
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
2859
|
+
const useWorktree = isolation === "worktree";
|
|
2566
2860
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
2567
2861
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
2568
|
-
|
|
2569
|
-
|
|
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);
|
|
2570
2875
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
2571
2876
|
try {
|
|
2572
|
-
const
|
|
2573
|
-
|
|
2574
|
-
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" };
|
|
2575
2880
|
} catch (e) {
|
|
2576
|
-
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 };
|
|
2577
2882
|
}
|
|
2578
2883
|
});
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
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");
|
|
2583
2898
|
}
|
|
2584
2899
|
};
|
|
2585
2900
|
}
|
|
@@ -2824,7 +3139,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
2824
3139
|
}
|
|
2825
3140
|
|
|
2826
3141
|
// src/Agent.ts
|
|
2827
|
-
var
|
|
3142
|
+
var log4 = forComponent("Agent");
|
|
2828
3143
|
function isAbortError(err2) {
|
|
2829
3144
|
const e = err2;
|
|
2830
3145
|
const blob = `${e?.message ?? ""} ${e?.name ?? ""} ${e?.code ?? ""} ${e?.cause?.name ?? ""}`;
|
|
@@ -2900,6 +3215,11 @@ var AgentOptions = class {
|
|
|
2900
3215
|
depth = 0;
|
|
2901
3216
|
/** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
|
|
2902
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;
|
|
2903
3223
|
/** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
|
|
2904
3224
|
stream = false;
|
|
2905
3225
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
@@ -3020,7 +3340,7 @@ var Agent = class _Agent {
|
|
|
3020
3340
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
3021
3341
|
await disk.init();
|
|
3022
3342
|
this.options.fs = new JailedFilesystem2(disk);
|
|
3023
|
-
|
|
3343
|
+
log4.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
3024
3344
|
}
|
|
3025
3345
|
this.buildCtx();
|
|
3026
3346
|
}
|
|
@@ -3074,7 +3394,7 @@ var Agent = class _Agent {
|
|
|
3074
3394
|
agents = loaded.agents;
|
|
3075
3395
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
3076
3396
|
}
|
|
3077
|
-
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 };
|
|
3078
3398
|
tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
|
|
3079
3399
|
}
|
|
3080
3400
|
if (o.checkpoints) tools = [...tools, ...checkpointTools()];
|
|
@@ -3163,7 +3483,7 @@ var Agent = class _Agent {
|
|
|
3163
3483
|
let lastFp = "";
|
|
3164
3484
|
let repeats = 0;
|
|
3165
3485
|
const kill = (finishReason) => {
|
|
3166
|
-
|
|
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` : ""})`);
|
|
3167
3487
|
this.ctx.jobs?.killAll();
|
|
3168
3488
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
|
|
3169
3489
|
};
|
|
@@ -3209,7 +3529,7 @@ var Agent = class _Agent {
|
|
|
3209
3529
|
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide);
|
|
3210
3530
|
if (!transient) throw err2;
|
|
3211
3531
|
const waitMs = 1e3 * (attempt + 1);
|
|
3212
|
-
|
|
3532
|
+
log4.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
3213
3533
|
o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
|
|
3214
3534
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3215
3535
|
}
|
|
@@ -3225,7 +3545,7 @@ var Agent = class _Agent {
|
|
|
3225
3545
|
bodyStr = void 0;
|
|
3226
3546
|
}
|
|
3227
3547
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3228
|
-
|
|
3548
|
+
log4.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
|
|
3229
3549
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3230
3550
|
}
|
|
3231
3551
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3252,7 +3572,7 @@ var Agent = class _Agent {
|
|
|
3252
3572
|
});
|
|
3253
3573
|
}
|
|
3254
3574
|
if (toolCalls.length === 0) {
|
|
3255
|
-
|
|
3575
|
+
log4.verbose(`completed in ${steps} step(s)`);
|
|
3256
3576
|
await this.ctx.jobs?.drain();
|
|
3257
3577
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
3258
3578
|
return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
@@ -3318,13 +3638,13 @@ var Agent = class _Agent {
|
|
|
3318
3638
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
3319
3639
|
if (decision?.block) {
|
|
3320
3640
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
3321
|
-
|
|
3641
|
+
log4.debug(`${tc.function.name} -> ${blocked}`);
|
|
3322
3642
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
3323
3643
|
return blocked;
|
|
3324
3644
|
}
|
|
3325
3645
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
3326
3646
|
if (earlyError) {
|
|
3327
|
-
|
|
3647
|
+
log4.debug(`${tc.function.name} -> ${earlyError}`);
|
|
3328
3648
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
3329
3649
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
3330
3650
|
return earlyError;
|
|
@@ -3333,12 +3653,12 @@ var Agent = class _Agent {
|
|
|
3333
3653
|
let images;
|
|
3334
3654
|
let threw = false;
|
|
3335
3655
|
try {
|
|
3336
|
-
|
|
3656
|
+
log4.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
3337
3657
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
3338
3658
|
try {
|
|
3339
3659
|
hooks.onToolOutput(call, chunk, meta);
|
|
3340
3660
|
} catch (e) {
|
|
3341
|
-
|
|
3661
|
+
log4.debug(`onToolOutput hook error: ${e}`);
|
|
3342
3662
|
}
|
|
3343
3663
|
} : void 0;
|
|
3344
3664
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -3350,7 +3670,7 @@ var Agent = class _Agent {
|
|
|
3350
3670
|
}
|
|
3351
3671
|
} catch (e) {
|
|
3352
3672
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3353
|
-
|
|
3673
|
+
log4.debug(`${tc.function.name} -> error: ${msg}`);
|
|
3354
3674
|
result = `Error: ${msg}`;
|
|
3355
3675
|
threw = true;
|
|
3356
3676
|
} finally {
|
|
@@ -3476,7 +3796,7 @@ function fitTokenBudget(messages, maxTokens) {
|
|
|
3476
3796
|
const ids = callIdSet(messages.slice(from));
|
|
3477
3797
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
3478
3798
|
if (total > maxTokens)
|
|
3479
|
-
|
|
3799
|
+
log4.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3480
3800
|
return [...head, ...messages.slice(from)];
|
|
3481
3801
|
}
|
|
3482
3802
|
function compact(m, max, focus) {
|
|
@@ -4033,7 +4353,7 @@ init_tools_web();
|
|
|
4033
4353
|
init_tools();
|
|
4034
4354
|
init_tools_structured();
|
|
4035
4355
|
init_logging();
|
|
4036
|
-
var
|
|
4356
|
+
var log5 = forComponent("scratch");
|
|
4037
4357
|
var SCRATCH_DIR = "/scratch";
|
|
4038
4358
|
function shortArgs(args) {
|
|
4039
4359
|
try {
|
|
@@ -4073,7 +4393,7 @@ var Scratch = class {
|
|
|
4073
4393
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4074
4394
|
await this.fs.writeFile(path, header + raw);
|
|
4075
4395
|
} catch (e) {
|
|
4076
|
-
|
|
4396
|
+
log5.debug("scratch write failed; returning raw", e);
|
|
4077
4397
|
return raw;
|
|
4078
4398
|
}
|
|
4079
4399
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -4103,7 +4423,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
4103
4423
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4104
4424
|
await this.fs.writeFile(path, header + full);
|
|
4105
4425
|
} catch (e) {
|
|
4106
|
-
|
|
4426
|
+
log5.debug("scratch spill failed; cropping lossy", e);
|
|
4107
4427
|
return full.slice(0, pageBytes) + `
|
|
4108
4428
|
|
|
4109
4429
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -4149,7 +4469,7 @@ Question: ${q2}`);
|
|
|
4149
4469
|
const answer = (res.text ?? "").trim();
|
|
4150
4470
|
return answer || "(no answer found in scratch)";
|
|
4151
4471
|
} catch (e) {
|
|
4152
|
-
|
|
4472
|
+
log5.debug("Ask peek failed", e);
|
|
4153
4473
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
4154
4474
|
}
|
|
4155
4475
|
}
|
|
@@ -4158,7 +4478,7 @@ Question: ${q2}`);
|
|
|
4158
4478
|
|
|
4159
4479
|
// src/lessons.ts
|
|
4160
4480
|
init_logging();
|
|
4161
|
-
var
|
|
4481
|
+
var log6 = forComponent("Lessons");
|
|
4162
4482
|
var LessonOptionsDefaults = class {
|
|
4163
4483
|
minRepeats = 2;
|
|
4164
4484
|
};
|
|
@@ -4183,15 +4503,15 @@ function lessonCapture(options) {
|
|
|
4183
4503
|
counts.set(lesson.slug, n);
|
|
4184
4504
|
if (n < o.minRepeats) return;
|
|
4185
4505
|
written.add(lesson.slug);
|
|
4186
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
4187
|
-
|
|
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)`);
|
|
4188
4508
|
}
|
|
4189
4509
|
};
|
|
4190
4510
|
}
|
|
4191
4511
|
|
|
4192
4512
|
// src/reflect.ts
|
|
4193
4513
|
init_logging();
|
|
4194
|
-
var
|
|
4514
|
+
var log7 = forComponent("Reflect");
|
|
4195
4515
|
async function reflectOnRun(o) {
|
|
4196
4516
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
4197
4517
|
if (!digest.trim()) return null;
|
|
@@ -4207,7 +4527,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4207
4527
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
4208
4528
|
text = r?.content ?? "";
|
|
4209
4529
|
} catch (e) {
|
|
4210
|
-
|
|
4530
|
+
log7.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
4211
4531
|
return null;
|
|
4212
4532
|
}
|
|
4213
4533
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -4217,10 +4537,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4217
4537
|
try {
|
|
4218
4538
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
4219
4539
|
} catch (e) {
|
|
4220
|
-
|
|
4540
|
+
log7.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
4221
4541
|
return null;
|
|
4222
4542
|
}
|
|
4223
|
-
|
|
4543
|
+
log7.debug(`reflection persisted ${slug2}`);
|
|
4224
4544
|
return slug2;
|
|
4225
4545
|
}
|
|
4226
4546
|
function digestRun(messages, maxChars) {
|
|
@@ -4237,7 +4557,7 @@ function digestRun(messages, maxChars) {
|
|
|
4237
4557
|
// src/duplex.ts
|
|
4238
4558
|
import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
|
|
4239
4559
|
init_logging();
|
|
4240
|
-
var
|
|
4560
|
+
var log8 = forComponent("DuplexAgent");
|
|
4241
4561
|
function describeCall(call) {
|
|
4242
4562
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
4243
4563
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -4377,7 +4697,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4377
4697
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER);
|
|
4378
4698
|
if (m) {
|
|
4379
4699
|
this.fabricationCut = true;
|
|
4380
|
-
|
|
4700
|
+
log8.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
4381
4701
|
const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
|
|
4382
4702
|
if (!safe) return;
|
|
4383
4703
|
if (safe.trim()) this.spokeThisTurn = true;
|
|
@@ -4456,7 +4776,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4456
4776
|
try {
|
|
4457
4777
|
await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
|
|
4458
4778
|
} catch (e) {
|
|
4459
|
-
|
|
4779
|
+
log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
4460
4780
|
} finally {
|
|
4461
4781
|
this.nudging = false;
|
|
4462
4782
|
}
|
|
@@ -4604,7 +4924,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4604
4924
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
4605
4925
|
const cres = await new Agent(agentOpts).run(checkBrief);
|
|
4606
4926
|
if (cres.finishReason !== "stop") {
|
|
4607
|
-
|
|
4927
|
+
log8.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
4608
4928
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
4609
4929
|
}
|
|
4610
4930
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -4707,7 +5027,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4707
5027
|
}
|
|
4708
5028
|
rec.status = "done";
|
|
4709
5029
|
rec.result = res.text;
|
|
4710
|
-
|
|
5030
|
+
log8.verbose(`task ${id} done (${res.steps} steps)`);
|
|
4711
5031
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
4712
5032
|
id,
|
|
4713
5033
|
text: res.text,
|
|
@@ -4725,7 +5045,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4725
5045
|
this.dropAsk(rec.id);
|
|
4726
5046
|
rec.status = "error";
|
|
4727
5047
|
rec.result = msg;
|
|
4728
|
-
|
|
5048
|
+
log8.warn(`task ${rec.id} failed: ${msg}`);
|
|
4729
5049
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
|
|
4730
5050
|
this.queueRevoice(`[task ${rec.id} failed] ${msg}`);
|
|
4731
5051
|
}
|
|
@@ -5027,7 +5347,7 @@ init_logging();
|
|
|
5027
5347
|
|
|
5028
5348
|
// src/voice/engine.ts
|
|
5029
5349
|
init_logging();
|
|
5030
|
-
var
|
|
5350
|
+
var log9 = forComponent("VoiceEngine");
|
|
5031
5351
|
var now = () => performance.now();
|
|
5032
5352
|
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, ".");
|
|
5033
5353
|
var VoiceEngineOptions = class {
|
|
@@ -5135,7 +5455,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5135
5455
|
this.stt.onLevel = (rms) => this.handleLevel(rms);
|
|
5136
5456
|
await Promise.all([this.tts.connect(), this.stt.start()]);
|
|
5137
5457
|
this.setState("listening");
|
|
5138
|
-
|
|
5458
|
+
log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
|
|
5139
5459
|
}
|
|
5140
5460
|
get usingAec() {
|
|
5141
5461
|
return this.stt.usingAec;
|
|
@@ -5187,7 +5507,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5187
5507
|
this.reply += text;
|
|
5188
5508
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
5189
5509
|
this.tts.speak(forSpeech(text), true);
|
|
5190
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
5510
|
+
if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
|
|
5191
5511
|
this.spokeDeltas = true;
|
|
5192
5512
|
this.setState("speaking");
|
|
5193
5513
|
}
|
|
@@ -5208,7 +5528,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5208
5528
|
}
|
|
5209
5529
|
this.drainTimer = null;
|
|
5210
5530
|
this.speaking = false;
|
|
5211
|
-
if (this.turnStartAt)
|
|
5531
|
+
if (this.turnStartAt) log9.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
|
|
5212
5532
|
this.echoUntil = now() + 2500;
|
|
5213
5533
|
if (!this.usingAec) this.stt.reset();
|
|
5214
5534
|
this.setState("listening");
|
|
@@ -5349,7 +5669,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5349
5669
|
this.pendingUtt = this.pendingUtt ? `${this.pendingUtt} ${text}` : text;
|
|
5350
5670
|
if (this.pendingTimer) clearTimeout(this.pendingTimer);
|
|
5351
5671
|
if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
|
|
5352
|
-
|
|
5672
|
+
log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
5353
5673
|
this.options.onHold();
|
|
5354
5674
|
if (this.options.holdFiller && !this.speaking) {
|
|
5355
5675
|
this.beginSpeech();
|
|
@@ -5447,7 +5767,7 @@ async function resolveAuth(auth) {
|
|
|
5447
5767
|
}
|
|
5448
5768
|
|
|
5449
5769
|
// src/voice/soniox.ts
|
|
5450
|
-
var
|
|
5770
|
+
var log10 = forComponent("SonioxSTT");
|
|
5451
5771
|
var now2 = () => performance.now();
|
|
5452
5772
|
var SonioxSTTOptions = class {
|
|
5453
5773
|
auth = "";
|
|
@@ -5504,9 +5824,9 @@ var SonioxSTT = class {
|
|
|
5504
5824
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
5505
5825
|
this.ws.onclose = (ev) => {
|
|
5506
5826
|
if (this.stopped) return;
|
|
5507
|
-
|
|
5827
|
+
log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
5508
5828
|
this.reset();
|
|
5509
|
-
this.connectWs().catch((e) =>
|
|
5829
|
+
this.connectWs().catch((e) => log10.error(`soniox reconnect failed: ${e.message}`));
|
|
5510
5830
|
};
|
|
5511
5831
|
}
|
|
5512
5832
|
async start() {
|
|
@@ -5516,7 +5836,7 @@ var SonioxSTT = class {
|
|
|
5516
5836
|
this.endpointTimer = setInterval(() => {
|
|
5517
5837
|
const combined = (this.finalText + this.partialText).trim();
|
|
5518
5838
|
if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
5519
|
-
if (this.firstTokenAt)
|
|
5839
|
+
if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
5520
5840
|
this.reset();
|
|
5521
5841
|
this.onUtterance(combined, now2());
|
|
5522
5842
|
}, 120);
|
|
@@ -5533,7 +5853,7 @@ var SonioxSTT = class {
|
|
|
5533
5853
|
});
|
|
5534
5854
|
}
|
|
5535
5855
|
handle(m) {
|
|
5536
|
-
if (m.error_message) return
|
|
5856
|
+
if (m.error_message) return log10.error(`soniox: ${m.error_message}`);
|
|
5537
5857
|
let endpoint = false;
|
|
5538
5858
|
for (const t of m.tokens ?? []) {
|
|
5539
5859
|
if (t.text === "<end>") endpoint = true;
|
|
@@ -5549,7 +5869,7 @@ var SonioxSTT = class {
|
|
|
5549
5869
|
this.onPartial(combined);
|
|
5550
5870
|
if (endpoint && this.finalText.trim()) {
|
|
5551
5871
|
const utterance = this.finalText.trim();
|
|
5552
|
-
if (this.firstTokenAt)
|
|
5872
|
+
if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
5553
5873
|
this.reset();
|
|
5554
5874
|
this.onUtterance(utterance, now2());
|
|
5555
5875
|
}
|
|
@@ -5571,7 +5891,7 @@ var SonioxSTT = class {
|
|
|
5571
5891
|
|
|
5572
5892
|
// src/voice/cartesia.ts
|
|
5573
5893
|
init_logging();
|
|
5574
|
-
var
|
|
5894
|
+
var log11 = forComponent("CartesiaTTS");
|
|
5575
5895
|
var now3 = () => performance.now();
|
|
5576
5896
|
var CartesiaTTSOptions = class {
|
|
5577
5897
|
auth = "";
|
|
@@ -5621,9 +5941,9 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5621
5941
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
5622
5942
|
});
|
|
5623
5943
|
this.ws.onclose = (ev) => {
|
|
5624
|
-
|
|
5944
|
+
log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
5625
5945
|
if (!this.closed) {
|
|
5626
|
-
this.connecting = this.doConnect().catch((e) =>
|
|
5946
|
+
this.connecting = this.doConnect().catch((e) => log11.error(`cartesia reconnect failed: ${e.message}`));
|
|
5627
5947
|
}
|
|
5628
5948
|
};
|
|
5629
5949
|
this.ws.onmessage = (ev) => {
|
|
@@ -5645,11 +5965,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5645
5965
|
this.down = true;
|
|
5646
5966
|
this.downAt = now3();
|
|
5647
5967
|
this.consecutiveOk = 0;
|
|
5648
|
-
|
|
5968
|
+
log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
5649
5969
|
this.onDone();
|
|
5650
5970
|
this.startProbe();
|
|
5651
5971
|
} else if (!this.down) {
|
|
5652
|
-
|
|
5972
|
+
log11.warn(`cartesia: ${JSON.stringify(m)}`);
|
|
5653
5973
|
}
|
|
5654
5974
|
}
|
|
5655
5975
|
};
|
|
@@ -5663,7 +5983,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
5663
5983
|
this.consecutiveOk = 0;
|
|
5664
5984
|
this.stopProbe();
|
|
5665
5985
|
const downMs = this.downAt ? now3() - this.downAt : 0;
|
|
5666
|
-
(downMs < 2e3 ?
|
|
5986
|
+
(downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
5667
5987
|
}
|
|
5668
5988
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
5669
5989
|
async ensureConnected() {
|
|
@@ -5736,13 +6056,14 @@ function base64ToBytes(b64) {
|
|
|
5736
6056
|
}
|
|
5737
6057
|
|
|
5738
6058
|
// src/index.ts
|
|
6059
|
+
init_worktree();
|
|
5739
6060
|
import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
|
|
5740
6061
|
|
|
5741
6062
|
// src/mcp.client.ts
|
|
5742
6063
|
init_logging();
|
|
5743
6064
|
import { spawn } from "child_process";
|
|
5744
6065
|
import { createHash } from "crypto";
|
|
5745
|
-
var
|
|
6066
|
+
var log12 = forComponent("mcp");
|
|
5746
6067
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
5747
6068
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
5748
6069
|
var StdioTransport = class {
|
|
@@ -5761,7 +6082,7 @@ var StdioTransport = class {
|
|
|
5761
6082
|
proc.stdout.setEncoding("utf8");
|
|
5762
6083
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
5763
6084
|
proc.stderr.setEncoding("utf8");
|
|
5764
|
-
proc.stderr.on("data", (chunk) =>
|
|
6085
|
+
proc.stderr.on("data", (chunk) => log12.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
5765
6086
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
5766
6087
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
5767
6088
|
}
|
|
@@ -5775,7 +6096,7 @@ var StdioTransport = class {
|
|
|
5775
6096
|
try {
|
|
5776
6097
|
this.dispatch(JSON.parse(line));
|
|
5777
6098
|
} catch (e) {
|
|
5778
|
-
|
|
6099
|
+
log12.debug("dropping non-JSON line from MCP server:", line, e);
|
|
5779
6100
|
}
|
|
5780
6101
|
}
|
|
5781
6102
|
}
|
|
@@ -5824,7 +6145,7 @@ var StdioTransport = class {
|
|
|
5824
6145
|
try {
|
|
5825
6146
|
this.proc?.stdin?.end();
|
|
5826
6147
|
} catch (e) {
|
|
5827
|
-
|
|
6148
|
+
log12.debug("stdin end failed", e);
|
|
5828
6149
|
}
|
|
5829
6150
|
this.proc?.kill();
|
|
5830
6151
|
}
|
|
@@ -5893,7 +6214,7 @@ function parseSseResponse(body) {
|
|
|
5893
6214
|
const obj = JSON.parse(trimmed.slice(5).trim());
|
|
5894
6215
|
if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
|
|
5895
6216
|
} catch (e) {
|
|
5896
|
-
|
|
6217
|
+
log12.debug("skipping unparseable SSE data line", e);
|
|
5897
6218
|
}
|
|
5898
6219
|
}
|
|
5899
6220
|
return {};
|
|
@@ -5961,7 +6282,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
5961
6282
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
5962
6283
|
})(), mountTimeoutMs, name);
|
|
5963
6284
|
} catch (e) {
|
|
5964
|
-
await client.close().catch((err2) =>
|
|
6285
|
+
await client.close().catch((err2) => log12.debug(`close after failed mount of "${name}": ${err2}`));
|
|
5965
6286
|
throw e;
|
|
5966
6287
|
}
|
|
5967
6288
|
}
|
|
@@ -5972,15 +6293,15 @@ function validEntries(servers) {
|
|
|
5972
6293
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
5973
6294
|
if (!cfg || cfg.disabled) return false;
|
|
5974
6295
|
if (!cfg.command && !cfg.url) {
|
|
5975
|
-
|
|
6296
|
+
log12.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
5976
6297
|
return false;
|
|
5977
6298
|
}
|
|
5978
6299
|
return true;
|
|
5979
6300
|
});
|
|
5980
6301
|
}
|
|
5981
6302
|
function logMountFailure(name, e) {
|
|
5982
|
-
if (e instanceof McpAuthError)
|
|
5983
|
-
else
|
|
6303
|
+
if (e instanceof McpAuthError) log12.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
6304
|
+
else log12.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
5984
6305
|
}
|
|
5985
6306
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
5986
6307
|
const entries = validEntries(servers);
|
|
@@ -5990,7 +6311,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
5990
6311
|
const name = entries[i][0];
|
|
5991
6312
|
if (r.status === "fulfilled") {
|
|
5992
6313
|
out.push(r.value);
|
|
5993
|
-
|
|
6314
|
+
log12.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
5994
6315
|
} else logMountFailure(name, r.reason);
|
|
5995
6316
|
});
|
|
5996
6317
|
return out;
|
|
@@ -6030,7 +6351,7 @@ var McpPool = class {
|
|
|
6030
6351
|
const prev = this.warm.get(key);
|
|
6031
6352
|
if (prev) {
|
|
6032
6353
|
clearTimeout(prev.timer);
|
|
6033
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
6354
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log12.debug(`warm-pool replace close failed: ${err2}`));
|
|
6034
6355
|
}
|
|
6035
6356
|
const e = { client, timer: void 0 };
|
|
6036
6357
|
this.warm.set(key, e);
|
|
@@ -6047,7 +6368,7 @@ var McpPool = class {
|
|
|
6047
6368
|
const e = this.warm.get(key);
|
|
6048
6369
|
if (!e) return;
|
|
6049
6370
|
this.warm.delete(key);
|
|
6050
|
-
await e.client.close().catch((err2) =>
|
|
6371
|
+
await e.client.close().catch((err2) => log12.debug(`warm-pool evict close failed: ${err2}`));
|
|
6051
6372
|
}
|
|
6052
6373
|
async closeAll() {
|
|
6053
6374
|
for (const e of this.warm.values()) {
|
|
@@ -6064,7 +6385,7 @@ var defaultPool = new McpPool();
|
|
|
6064
6385
|
// cli/mcpOAuth.ts
|
|
6065
6386
|
import { createServer } from "http";
|
|
6066
6387
|
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
6067
|
-
import { readFileSync, writeFileSync as
|
|
6388
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
6068
6389
|
import { dirname as dirname2 } from "path";
|
|
6069
6390
|
var McpOAuthOptions = class {
|
|
6070
6391
|
/** Where to persist tokens. Mode 0600. */
|
|
@@ -6092,14 +6413,14 @@ var McpOAuth = class {
|
|
|
6092
6413
|
// -- store I/O ----------------------------------------------------------------
|
|
6093
6414
|
load() {
|
|
6094
6415
|
try {
|
|
6095
|
-
return existsSync(this.options.storePath) ? JSON.parse(
|
|
6416
|
+
return existsSync(this.options.storePath) ? JSON.parse(readFileSync2(this.options.storePath, "utf8")) : {};
|
|
6096
6417
|
} catch {
|
|
6097
6418
|
return {};
|
|
6098
6419
|
}
|
|
6099
6420
|
}
|
|
6100
6421
|
save(store) {
|
|
6101
|
-
|
|
6102
|
-
|
|
6422
|
+
mkdirSync3(dirname2(this.options.storePath), { recursive: true });
|
|
6423
|
+
writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
|
|
6103
6424
|
}
|
|
6104
6425
|
// -- ATTENDED: one-time authorization ----------------------------------------
|
|
6105
6426
|
/** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
|
|
@@ -6257,15 +6578,15 @@ function defaultOpenBrowser(url) {
|
|
|
6257
6578
|
// cli/core.ts
|
|
6258
6579
|
import { randomUUID } from "crypto";
|
|
6259
6580
|
import { execFile as execFile2 } from "child_process";
|
|
6260
|
-
import { resolve, basename, join as
|
|
6261
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
6581
|
+
import { resolve, basename, join as join5 } from "path";
|
|
6582
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
6262
6583
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
6263
6584
|
init_tools_shell();
|
|
6264
6585
|
|
|
6265
6586
|
// src/tools.notify.ts
|
|
6266
6587
|
init_logging();
|
|
6267
6588
|
import { execFile } from "child_process";
|
|
6268
|
-
var
|
|
6589
|
+
var log14 = forComponent("notify");
|
|
6269
6590
|
function makeNotifyTool(opts = {}) {
|
|
6270
6591
|
const platform2 = opts.platform ?? process.platform;
|
|
6271
6592
|
const run = opts.exec ?? execFile;
|
|
@@ -6289,7 +6610,7 @@ function makeNotifyTool(opts = {}) {
|
|
|
6289
6610
|
return new Promise((resolve4) => {
|
|
6290
6611
|
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
6291
6612
|
if (e) {
|
|
6292
|
-
|
|
6613
|
+
log14.debug("notification failed", e);
|
|
6293
6614
|
resolve4(`Notification failed: ${e.message}`);
|
|
6294
6615
|
} else resolve4("Notification shown.");
|
|
6295
6616
|
});
|
|
@@ -6303,9 +6624,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
6303
6624
|
|
|
6304
6625
|
// cli/util.ts
|
|
6305
6626
|
init_logging();
|
|
6306
|
-
import { existsSync as existsSync2, readFileSync as
|
|
6627
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
6307
6628
|
import { homedir } from "os";
|
|
6308
|
-
var
|
|
6629
|
+
var log15 = forComponent("cli-util");
|
|
6309
6630
|
function dotDirs(base, sub, opts = {}) {
|
|
6310
6631
|
const home = opts.home ?? homedir();
|
|
6311
6632
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -6322,7 +6643,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
6322
6643
|
try {
|
|
6323
6644
|
return JSON.parse(text);
|
|
6324
6645
|
} catch (e) {
|
|
6325
|
-
|
|
6646
|
+
log15.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
6326
6647
|
return fallback;
|
|
6327
6648
|
}
|
|
6328
6649
|
}
|
|
@@ -6330,9 +6651,9 @@ function readJsonFile(path, fallback) {
|
|
|
6330
6651
|
if (!existsSync2(path)) return fallback;
|
|
6331
6652
|
let text;
|
|
6332
6653
|
try {
|
|
6333
|
-
text =
|
|
6654
|
+
text = readFileSync3(path, "utf8");
|
|
6334
6655
|
} catch (e) {
|
|
6335
|
-
|
|
6656
|
+
log15.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
6336
6657
|
return fallback;
|
|
6337
6658
|
}
|
|
6338
6659
|
return parseJson(text, fallback, path);
|
|
@@ -6416,8 +6737,8 @@ async function buildAgent(o) {
|
|
|
6416
6737
|
fs = mem;
|
|
6417
6738
|
} else if (o.boddb) {
|
|
6418
6739
|
const root = resolve(o.boddb);
|
|
6419
|
-
|
|
6420
|
-
const db = new BodDB2({ path:
|
|
6740
|
+
mkdirSync4(root, { recursive: true });
|
|
6741
|
+
const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
|
|
6421
6742
|
const bod = new BodDbFilesystem(db);
|
|
6422
6743
|
const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
|
|
6423
6744
|
await mkdirp(bod, cwd);
|
|
@@ -6544,6 +6865,9 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
6544
6865
|
...scratch ? { capToolResult: (full, info) => scratch.spill(full, info) } : {},
|
|
6545
6866
|
backgroundJobs: o.backgroundJobs ?? virtual,
|
|
6546
6867
|
// default ON in virtual modes (no real shell there); disk uses ShellJobRegistry
|
|
6868
|
+
// Subagent transcript traces, origin-anchored under .agent/traces (disk mode only — sandbox/db modes
|
|
6869
|
+
// must never write the real disk). Lets an interrupted/failed child leave a readable trail + pointer.
|
|
6870
|
+
...!virtual ? { traceDir: `${cwd}/.agent/traces` } : {},
|
|
6547
6871
|
skillsDir: dots("skills"),
|
|
6548
6872
|
commandsDir: dots("commands"),
|
|
6549
6873
|
memoryDir,
|
|
@@ -6576,12 +6900,12 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
6576
6900
|
|
|
6577
6901
|
// cli/voice.ts
|
|
6578
6902
|
init_logging();
|
|
6579
|
-
import { spawn as spawn2, spawnSync } from "child_process";
|
|
6580
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
6903
|
+
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
6904
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
|
|
6581
6905
|
import { homedir as homedir3 } from "os";
|
|
6582
|
-
import { dirname as dirname3, join as
|
|
6906
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
6583
6907
|
import { fileURLToPath } from "url";
|
|
6584
|
-
var
|
|
6908
|
+
var log16 = forComponent("VoiceIO");
|
|
6585
6909
|
var now4 = () => performance.now();
|
|
6586
6910
|
var Player = class {
|
|
6587
6911
|
proc = null;
|
|
@@ -6595,7 +6919,7 @@ var Player = class {
|
|
|
6595
6919
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
6596
6920
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
6597
6921
|
);
|
|
6598
|
-
this.proc.on("error", (e) =>
|
|
6922
|
+
this.proc.on("error", (e) => log16.warn(`ffplay error: ${e.message}`));
|
|
6599
6923
|
this.proc.stdin.on("error", () => {
|
|
6600
6924
|
});
|
|
6601
6925
|
this.bytesWritten = 0;
|
|
@@ -6622,24 +6946,24 @@ var Player = class {
|
|
|
6622
6946
|
this.proc = null;
|
|
6623
6947
|
}
|
|
6624
6948
|
};
|
|
6625
|
-
var nativeDir = () =>
|
|
6949
|
+
var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
|
|
6626
6950
|
function detectFfmpegMic() {
|
|
6627
6951
|
if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
|
|
6628
|
-
const out =
|
|
6952
|
+
const out = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" }).stderr;
|
|
6629
6953
|
const audio = out.slice(out.indexOf("audio devices"));
|
|
6630
6954
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
6631
6955
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
6632
6956
|
if (!mic) throw new Error("no audio input device found");
|
|
6633
|
-
|
|
6957
|
+
log16.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
6634
6958
|
return `:${mic.idx}`;
|
|
6635
6959
|
}
|
|
6636
6960
|
function detectedInputDevice() {
|
|
6637
6961
|
if (process.platform !== "darwin") return null;
|
|
6638
6962
|
let name = "";
|
|
6639
|
-
if (
|
|
6640
|
-
name = (
|
|
6963
|
+
if (spawnSync2("which", ["SwitchAudioSource"]).status === 0) {
|
|
6964
|
+
name = (spawnSync2("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
|
|
6641
6965
|
} else {
|
|
6642
|
-
const out =
|
|
6966
|
+
const out = spawnSync2("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
|
|
6643
6967
|
let last = "";
|
|
6644
6968
|
for (const ln of out.split("\n")) {
|
|
6645
6969
|
const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
|
|
@@ -6658,23 +6982,23 @@ function detectedInputDevice() {
|
|
|
6658
6982
|
}
|
|
6659
6983
|
function resolveAecBinary() {
|
|
6660
6984
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
6661
|
-
const src =
|
|
6662
|
-
const plist =
|
|
6985
|
+
const src = join6(nativeDir(), "mic-aec.swift");
|
|
6986
|
+
const plist = join6(nativeDir(), "Info.plist");
|
|
6663
6987
|
if (!existsSync4(src)) return null;
|
|
6664
|
-
const cacheDir =
|
|
6665
|
-
const bin =
|
|
6666
|
-
if (existsSync4(bin) &&
|
|
6667
|
-
if (
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
const build =
|
|
6988
|
+
const cacheDir = join6(homedir3(), ".agent", "cache");
|
|
6989
|
+
const bin = join6(cacheDir, "mic-aec");
|
|
6990
|
+
if (existsSync4(bin) && statSync3(bin).mtimeMs >= statSync3(src).mtimeMs) return bin;
|
|
6991
|
+
if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
|
|
6992
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
6993
|
+
log16.info("compiling AEC mic helper (first run)\u2026");
|
|
6994
|
+
const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
6671
6995
|
if (build.status !== 0) {
|
|
6672
|
-
|
|
6996
|
+
log16.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
6673
6997
|
return null;
|
|
6674
6998
|
}
|
|
6675
|
-
const sign =
|
|
6999
|
+
const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
6676
7000
|
if (sign.status !== 0) {
|
|
6677
|
-
|
|
7001
|
+
log16.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
6678
7002
|
return null;
|
|
6679
7003
|
}
|
|
6680
7004
|
return bin;
|
|
@@ -6692,17 +7016,17 @@ var NodeMicSource = class {
|
|
|
6692
7016
|
if (this.bin) {
|
|
6693
7017
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
6694
7018
|
} else {
|
|
6695
|
-
if (
|
|
6696
|
-
|
|
7019
|
+
if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
7020
|
+
log16.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
6697
7021
|
this.proc = spawn2(
|
|
6698
7022
|
"ffmpeg",
|
|
6699
7023
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
6700
7024
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
6701
7025
|
);
|
|
6702
|
-
this.proc.stderr.on("data", (d) =>
|
|
7026
|
+
this.proc.stderr.on("data", (d) => log16.warn(`ffmpeg: ${String(d).trim()}`));
|
|
6703
7027
|
}
|
|
6704
7028
|
this.proc.on("exit", (c) => {
|
|
6705
|
-
if (c && !this.stopped)
|
|
7029
|
+
if (c && !this.stopped) log16.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
6706
7030
|
});
|
|
6707
7031
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6708
7032
|
}
|
|
@@ -6736,11 +7060,11 @@ var AecDuplexAudio = class {
|
|
|
6736
7060
|
this.proc.stdin.on("error", () => {
|
|
6737
7061
|
});
|
|
6738
7062
|
this.proc.on("exit", (c) => {
|
|
6739
|
-
if (c && !this.stopped)
|
|
7063
|
+
if (c && !this.stopped) log16.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
6740
7064
|
});
|
|
6741
7065
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6742
7066
|
this.proc.stderr.on("data", (d) => {
|
|
6743
|
-
for (const ln of String(d).split("\n")) if (ln.trim())
|
|
7067
|
+
for (const ln of String(d).split("\n")) if (ln.trim()) log16.debug(`mic-aec: ${ln.trim()}`);
|
|
6744
7068
|
});
|
|
6745
7069
|
}
|
|
6746
7070
|
stop() {
|
|
@@ -6861,13 +7185,13 @@ var VoiceIO = class extends VoiceEngine {
|
|
|
6861
7185
|
|
|
6862
7186
|
// cli/config.ts
|
|
6863
7187
|
import { homedir as homedir4 } from "os";
|
|
6864
|
-
import { existsSync as existsSync5, readFileSync as
|
|
6865
|
-
import { join as
|
|
7188
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
7189
|
+
import { join as join7 } from "path";
|
|
6866
7190
|
import { pathToFileURL } from "url";
|
|
6867
7191
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
6868
7192
|
async function loadFrom(dir) {
|
|
6869
7193
|
for (const f of FILES) {
|
|
6870
|
-
const p =
|
|
7194
|
+
const p = join7(dir, ".agent", f);
|
|
6871
7195
|
if (!existsSync5(p)) continue;
|
|
6872
7196
|
try {
|
|
6873
7197
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
@@ -6880,10 +7204,10 @@ async function loadFrom(dir) {
|
|
|
6880
7204
|
return {};
|
|
6881
7205
|
}
|
|
6882
7206
|
function loadSettings(dir) {
|
|
6883
|
-
const p =
|
|
7207
|
+
const p = join7(dir, ".agent", "settings.json");
|
|
6884
7208
|
if (!existsSync5(p)) return {};
|
|
6885
7209
|
try {
|
|
6886
|
-
const raw = JSON.parse(
|
|
7210
|
+
const raw = JSON.parse(readFileSync4(p, "utf8"));
|
|
6887
7211
|
const cfg = {};
|
|
6888
7212
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
6889
7213
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -6916,8 +7240,8 @@ async function loadConfig(cwd) {
|
|
|
6916
7240
|
}
|
|
6917
7241
|
|
|
6918
7242
|
// cli/hooks-config.ts
|
|
6919
|
-
import { spawnSync as
|
|
6920
|
-
var
|
|
7243
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7244
|
+
var log17 = forComponent("hooks");
|
|
6921
7245
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6922
7246
|
function ruleMatches(rule, toolName) {
|
|
6923
7247
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -6926,7 +7250,7 @@ function ruleMatches(rule, toolName) {
|
|
|
6926
7250
|
}
|
|
6927
7251
|
function runCmd(rule, env) {
|
|
6928
7252
|
try {
|
|
6929
|
-
const r =
|
|
7253
|
+
const r = spawnSync3(rule.command, {
|
|
6930
7254
|
shell: true,
|
|
6931
7255
|
encoding: "utf8",
|
|
6932
7256
|
timeout: rule.timeoutMs ?? 1e4,
|
|
@@ -6934,7 +7258,7 @@ function runCmd(rule, env) {
|
|
|
6934
7258
|
});
|
|
6935
7259
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
6936
7260
|
} catch (e) {
|
|
6937
|
-
|
|
7261
|
+
log17.debug(`hook command failed: ${rule.command}`, e);
|
|
6938
7262
|
return { code: 1, out: String(e?.message ?? e) };
|
|
6939
7263
|
}
|
|
6940
7264
|
}
|
|
@@ -7038,15 +7362,15 @@ function formatDiff(ops, opts = {}) {
|
|
|
7038
7362
|
}
|
|
7039
7363
|
|
|
7040
7364
|
// cli/session.ts
|
|
7041
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
7365
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
|
|
7042
7366
|
import { homedir as homedir5 } from "os";
|
|
7043
|
-
import { join as
|
|
7044
|
-
var
|
|
7045
|
-
var globalDir = () =>
|
|
7367
|
+
import { join as join8 } from "path";
|
|
7368
|
+
var log18 = forComponent("session");
|
|
7369
|
+
var globalDir = () => join8(homedir5(), ".agent", "sessions");
|
|
7046
7370
|
var SessionStore = class {
|
|
7047
7371
|
dir;
|
|
7048
7372
|
constructor(cwd) {
|
|
7049
|
-
this.dir =
|
|
7373
|
+
this.dir = join8(cwd, ".agent", "sessions");
|
|
7050
7374
|
}
|
|
7051
7375
|
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
7052
7376
|
newId(now5 = Date.now(), cwd) {
|
|
@@ -7054,10 +7378,10 @@ var SessionStore = class {
|
|
|
7054
7378
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
7055
7379
|
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
7056
7380
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
7057
|
-
if (existsSync6(this.dir) && existsSync6(
|
|
7381
|
+
if (existsSync6(this.dir) && existsSync6(join8(this.dir, `${id}.json`))) {
|
|
7058
7382
|
for (let i = 2; i <= 99; i++) {
|
|
7059
7383
|
const c = `${id}-${i}`;
|
|
7060
|
-
if (!existsSync6(
|
|
7384
|
+
if (!existsSync6(join8(this.dir, `${c}.json`))) {
|
|
7061
7385
|
id = c;
|
|
7062
7386
|
break;
|
|
7063
7387
|
}
|
|
@@ -7071,30 +7395,30 @@ var SessionStore = class {
|
|
|
7071
7395
|
}
|
|
7072
7396
|
save(data) {
|
|
7073
7397
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
7074
|
-
if (!existsSync6(this.dir))
|
|
7075
|
-
const path =
|
|
7398
|
+
if (!existsSync6(this.dir)) mkdirSync6(this.dir, { recursive: true });
|
|
7399
|
+
const path = join8(this.dir, `${data.meta.id}.json`);
|
|
7076
7400
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
7077
|
-
|
|
7401
|
+
writeFileSync4(tmp, JSON.stringify(data));
|
|
7078
7402
|
renameSync(tmp, path);
|
|
7079
7403
|
try {
|
|
7080
7404
|
const gd = globalDir();
|
|
7081
|
-
if (!existsSync6(gd))
|
|
7082
|
-
const link2 =
|
|
7083
|
-
if (!existsSync6(link2))
|
|
7405
|
+
if (!existsSync6(gd)) mkdirSync6(gd, { recursive: true });
|
|
7406
|
+
const link2 = join8(gd, `${data.meta.id}.json`);
|
|
7407
|
+
if (!existsSync6(link2)) symlinkSync2(path, link2);
|
|
7084
7408
|
} catch {
|
|
7085
7409
|
}
|
|
7086
7410
|
}
|
|
7087
7411
|
load(id) {
|
|
7088
7412
|
if (!this.safeId(id)) {
|
|
7089
|
-
|
|
7413
|
+
log18.debug(`rejecting unsafe session id: ${id}`);
|
|
7090
7414
|
return void 0;
|
|
7091
7415
|
}
|
|
7092
|
-
const path =
|
|
7416
|
+
const path = join8(this.dir, `${id}.json`);
|
|
7093
7417
|
if (!existsSync6(path)) return void 0;
|
|
7094
7418
|
try {
|
|
7095
|
-
return JSON.parse(
|
|
7419
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
7096
7420
|
} catch (e) {
|
|
7097
|
-
|
|
7421
|
+
log18.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
7098
7422
|
return void 0;
|
|
7099
7423
|
}
|
|
7100
7424
|
}
|
|
@@ -7105,9 +7429,9 @@ var SessionStore = class {
|
|
|
7105
7429
|
for (const f of readdirSync(this.dir)) {
|
|
7106
7430
|
if (!f.endsWith(".json")) continue;
|
|
7107
7431
|
try {
|
|
7108
|
-
metas.push(JSON.parse(
|
|
7432
|
+
metas.push(JSON.parse(readFileSync5(join8(this.dir, f), "utf8")).meta);
|
|
7109
7433
|
} catch (e) {
|
|
7110
|
-
|
|
7434
|
+
log18.debug(`skipping unreadable session file ${f}`, e);
|
|
7111
7435
|
}
|
|
7112
7436
|
}
|
|
7113
7437
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -7124,11 +7448,11 @@ var SessionStore = class {
|
|
|
7124
7448
|
function globalSessionLoad(idOrPrefix) {
|
|
7125
7449
|
const gd = globalDir();
|
|
7126
7450
|
if (!existsSync6(gd)) return void 0;
|
|
7127
|
-
const exact =
|
|
7451
|
+
const exact = join8(gd, `${idOrPrefix}.json`);
|
|
7128
7452
|
if (existsSync6(exact)) {
|
|
7129
7453
|
try {
|
|
7130
7454
|
const target = readlinkSync(exact);
|
|
7131
|
-
return JSON.parse(
|
|
7455
|
+
return JSON.parse(readFileSync5(target, "utf8"));
|
|
7132
7456
|
} catch {
|
|
7133
7457
|
return void 0;
|
|
7134
7458
|
}
|
|
@@ -7138,8 +7462,8 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
7138
7462
|
if (!f.endsWith(".json")) continue;
|
|
7139
7463
|
const base = f.slice(0, -5);
|
|
7140
7464
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
7141
|
-
const target = readlinkSync(
|
|
7142
|
-
return JSON.parse(
|
|
7465
|
+
const target = readlinkSync(join8(gd, f));
|
|
7466
|
+
return JSON.parse(readFileSync5(target, "utf8"));
|
|
7143
7467
|
}
|
|
7144
7468
|
}
|
|
7145
7469
|
} catch {
|
|
@@ -7153,15 +7477,15 @@ function globalSessionList() {
|
|
|
7153
7477
|
for (const f of readdirSync(gd)) {
|
|
7154
7478
|
if (!f.endsWith(".json")) continue;
|
|
7155
7479
|
try {
|
|
7156
|
-
const target = readlinkSync(
|
|
7480
|
+
const target = readlinkSync(join8(gd, f));
|
|
7157
7481
|
if (!existsSync6(target)) {
|
|
7158
7482
|
try {
|
|
7159
|
-
unlinkSync(
|
|
7483
|
+
unlinkSync(join8(gd, f));
|
|
7160
7484
|
} catch {
|
|
7161
7485
|
}
|
|
7162
7486
|
continue;
|
|
7163
7487
|
}
|
|
7164
|
-
metas.push(JSON.parse(
|
|
7488
|
+
metas.push(JSON.parse(readFileSync5(target, "utf8")).meta);
|
|
7165
7489
|
} catch {
|
|
7166
7490
|
}
|
|
7167
7491
|
}
|
|
@@ -7273,18 +7597,18 @@ ${formatDiff(ops)}`);
|
|
|
7273
7597
|
// cli/gitCheckpoints.ts
|
|
7274
7598
|
import { execFile as execFile3 } from "child_process";
|
|
7275
7599
|
import { promisify } from "util";
|
|
7276
|
-
import { writeFileSync as
|
|
7277
|
-
import { join as
|
|
7278
|
-
var
|
|
7600
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
|
|
7601
|
+
import { join as join9, resolve as resolve2, sep as sep2 } from "path";
|
|
7602
|
+
var log19 = forComponent("checkpoints");
|
|
7279
7603
|
var exec = promisify(execFile3);
|
|
7280
7604
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
7281
7605
|
var ShadowRepo = class {
|
|
7282
7606
|
// undefined = unprobed; false = git/this root unusable
|
|
7283
|
-
constructor(workTree, gitDir, exclude,
|
|
7607
|
+
constructor(workTree, gitDir, exclude, git2) {
|
|
7284
7608
|
this.workTree = workTree;
|
|
7285
7609
|
this.gitDir = gitDir;
|
|
7286
7610
|
this.exclude = exclude;
|
|
7287
|
-
this.git =
|
|
7611
|
+
this.git = git2;
|
|
7288
7612
|
}
|
|
7289
7613
|
workTree;
|
|
7290
7614
|
gitDir;
|
|
@@ -7307,13 +7631,13 @@ var ShadowRepo = class {
|
|
|
7307
7631
|
try {
|
|
7308
7632
|
await exec(this.git, ["--version"]);
|
|
7309
7633
|
if (!existsSync7(this.gitDir)) {
|
|
7310
|
-
|
|
7634
|
+
mkdirSync7(this.gitDir, { recursive: true });
|
|
7311
7635
|
await this.run("init", "-q");
|
|
7312
7636
|
}
|
|
7313
|
-
|
|
7637
|
+
writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
7314
7638
|
this.ready = true;
|
|
7315
7639
|
} catch (e) {
|
|
7316
|
-
|
|
7640
|
+
log19.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
7317
7641
|
this.ready = false;
|
|
7318
7642
|
}
|
|
7319
7643
|
return this.ready;
|
|
@@ -7324,7 +7648,7 @@ var ShadowRepo = class {
|
|
|
7324
7648
|
}
|
|
7325
7649
|
async commit(label, forced = []) {
|
|
7326
7650
|
await this.run("add", "-A");
|
|
7327
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
7651
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-add failed: ${p}`, e));
|
|
7328
7652
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
7329
7653
|
}
|
|
7330
7654
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -7332,8 +7656,8 @@ var ShadowRepo = class {
|
|
|
7332
7656
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
7333
7657
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
7334
7658
|
async amendForced(paths) {
|
|
7335
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
7336
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
7659
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-capture failed: ${p}`, e));
|
|
7660
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log19.debug("amend failed", e));
|
|
7337
7661
|
}
|
|
7338
7662
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
7339
7663
|
async log(ref) {
|
|
@@ -7393,7 +7717,7 @@ var ShadowRepo = class {
|
|
|
7393
7717
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
7394
7718
|
});
|
|
7395
7719
|
} catch (e) {
|
|
7396
|
-
|
|
7720
|
+
log19.debug("checkpoint prune failed", e);
|
|
7397
7721
|
}
|
|
7398
7722
|
}
|
|
7399
7723
|
};
|
|
@@ -7425,7 +7749,7 @@ var GitCheckpoints = class {
|
|
|
7425
7749
|
const abs = resolve2(d);
|
|
7426
7750
|
if (abs === cwd || abs.startsWith(cwd + sep2)) continue;
|
|
7427
7751
|
if (cwd.startsWith(abs + sep2)) continue;
|
|
7428
|
-
out.push({ workTree: abs, gitDir:
|
|
7752
|
+
out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
|
|
7429
7753
|
}
|
|
7430
7754
|
return out;
|
|
7431
7755
|
}
|
|
@@ -7452,7 +7776,7 @@ var GitCheckpoints = class {
|
|
|
7452
7776
|
use(sessionId) {
|
|
7453
7777
|
if (sessionId === this.session) return;
|
|
7454
7778
|
this.session = sessionId;
|
|
7455
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
7779
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log19.debug("re-point failed", e));
|
|
7456
7780
|
}
|
|
7457
7781
|
async begin(label) {
|
|
7458
7782
|
if (!await this.start()) return;
|
|
@@ -7464,7 +7788,7 @@ var GitCheckpoints = class {
|
|
|
7464
7788
|
try {
|
|
7465
7789
|
await this.repos[i].commit(msg, forced);
|
|
7466
7790
|
} catch (e) {
|
|
7467
|
-
|
|
7791
|
+
log19.debug("checkpoint commit failed", e);
|
|
7468
7792
|
}
|
|
7469
7793
|
}
|
|
7470
7794
|
if (slow) clearTimeout(slow);
|
|
@@ -7494,7 +7818,7 @@ var GitCheckpoints = class {
|
|
|
7494
7818
|
if (this.forced.has(abs)) continue;
|
|
7495
7819
|
this.forced.add(abs);
|
|
7496
7820
|
const i = this.repoIndexFor(abs);
|
|
7497
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
7821
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log19.debug("amendForced failed", e));
|
|
7498
7822
|
}
|
|
7499
7823
|
}
|
|
7500
7824
|
};
|
|
@@ -7546,7 +7870,7 @@ var GitCheckpointsOptions = class {
|
|
|
7546
7870
|
/** Real working tree to snapshot (the launch cwd). */
|
|
7547
7871
|
workTree = process.cwd();
|
|
7548
7872
|
/** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
|
|
7549
|
-
gitDir =
|
|
7873
|
+
gitDir = join9(process.cwd(), ".agent", "checkpoints.git");
|
|
7550
7874
|
/** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
|
|
7551
7875
|
addDirs = [];
|
|
7552
7876
|
/** Conversation id → per-session restore-point ref. */
|
|
@@ -7560,9 +7884,9 @@ var GitCheckpointsOptions = class {
|
|
|
7560
7884
|
};
|
|
7561
7885
|
|
|
7562
7886
|
// cli/permissions.ts
|
|
7563
|
-
import { writeFileSync as
|
|
7887
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
|
|
7564
7888
|
import { homedir as homedir6 } from "os";
|
|
7565
|
-
import { join as
|
|
7889
|
+
import { join as join10 } from "path";
|
|
7566
7890
|
var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
|
|
7567
7891
|
function parseOne(raw, decision) {
|
|
7568
7892
|
const m = RULE_RE.exec(raw.trim());
|
|
@@ -7584,13 +7908,13 @@ function parsePermRules(perms) {
|
|
|
7584
7908
|
function describeRule(r) {
|
|
7585
7909
|
return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
|
|
7586
7910
|
}
|
|
7587
|
-
var PERM_FILE = (cwd) =>
|
|
7911
|
+
var PERM_FILE = (cwd) => join10(cwd, ".agent", "permissions.json");
|
|
7588
7912
|
function loadPersistedRules(cwd) {
|
|
7589
7913
|
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
7590
7914
|
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
7591
7915
|
}
|
|
7592
7916
|
function loadClaudeSettings(cwd, home = homedir6()) {
|
|
7593
|
-
const files = [
|
|
7917
|
+
const files = [join10(home, ".claude", "settings.json"), join10(cwd, ".claude", "settings.json"), join10(cwd, ".claude", "settings.local.json")];
|
|
7594
7918
|
let out = {};
|
|
7595
7919
|
for (const p of files) {
|
|
7596
7920
|
const perms = readJsonFile(p, null)?.permissions;
|
|
@@ -7603,8 +7927,8 @@ function persistRule(cwd, decision, ruleStr) {
|
|
|
7603
7927
|
const list = cur[decision] ??= [];
|
|
7604
7928
|
if (!list.includes(ruleStr)) list.push(ruleStr);
|
|
7605
7929
|
try {
|
|
7606
|
-
|
|
7607
|
-
|
|
7930
|
+
mkdirSync8(join10(cwd, ".agent"), { recursive: true });
|
|
7931
|
+
writeFileSync6(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
|
|
7608
7932
|
} catch {
|
|
7609
7933
|
}
|
|
7610
7934
|
}
|
|
@@ -7617,7 +7941,7 @@ function mergePerms(a, b) {
|
|
|
7617
7941
|
}
|
|
7618
7942
|
return Object.keys(out).length ? out : void 0;
|
|
7619
7943
|
}
|
|
7620
|
-
var TRUST_FILE =
|
|
7944
|
+
var TRUST_FILE = join10(homedir6(), ".agent", "trusted.json");
|
|
7621
7945
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
7622
7946
|
const list = readJsonFile(file, []);
|
|
7623
7947
|
return Array.isArray(list) && list.includes(cwd);
|
|
@@ -7627,8 +7951,8 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
7627
7951
|
if (!Array.isArray(list)) list = [];
|
|
7628
7952
|
if (!list.includes(cwd)) list.push(cwd);
|
|
7629
7953
|
try {
|
|
7630
|
-
|
|
7631
|
-
|
|
7954
|
+
mkdirSync8(join10(file, ".."), { recursive: true });
|
|
7955
|
+
writeFileSync6(file, JSON.stringify(list, null, 2) + "\n");
|
|
7632
7956
|
} catch {
|
|
7633
7957
|
}
|
|
7634
7958
|
}
|
|
@@ -7687,10 +8011,10 @@ function completePath(listDir, ref) {
|
|
|
7687
8011
|
|
|
7688
8012
|
// cli/lineEditor.ts
|
|
7689
8013
|
import { emitKeypressEvents } from "readline";
|
|
7690
|
-
import { spawnSync as
|
|
7691
|
-
import { writeFileSync as
|
|
8014
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
8015
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
7692
8016
|
import { tmpdir as tmpdir2 } from "os";
|
|
7693
|
-
import { join as
|
|
8017
|
+
import { join as join11 } from "path";
|
|
7694
8018
|
|
|
7695
8019
|
// cli/bidi.ts
|
|
7696
8020
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -8767,14 +9091,14 @@ function createLineEditor(out) {
|
|
|
8767
9091
|
function externalEdit(s) {
|
|
8768
9092
|
const spec = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
8769
9093
|
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
8770
|
-
const file =
|
|
9094
|
+
const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
8771
9095
|
try {
|
|
8772
|
-
|
|
9096
|
+
writeFileSync7(file, s.buf);
|
|
8773
9097
|
process.stdin.setRawMode(false);
|
|
8774
9098
|
out.write("\x1B[?2004l");
|
|
8775
|
-
const r =
|
|
9099
|
+
const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
|
|
8776
9100
|
if (r.status === 0) {
|
|
8777
|
-
const text =
|
|
9101
|
+
const text = readFileSync6(file, "utf8").replace(/\n$/, "");
|
|
8778
9102
|
s.reset();
|
|
8779
9103
|
if (text) s.insert(text);
|
|
8780
9104
|
}
|
|
@@ -9196,24 +9520,24 @@ function readPlainLine() {
|
|
|
9196
9520
|
}
|
|
9197
9521
|
|
|
9198
9522
|
// cli/osScheduler.ts
|
|
9199
|
-
import { spawnSync as
|
|
9200
|
-
import { writeFileSync as
|
|
9523
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
9524
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
|
|
9201
9525
|
import { homedir as homedir7 } from "os";
|
|
9202
|
-
import { join as
|
|
9203
|
-
var
|
|
9526
|
+
import { join as join12 } from "path";
|
|
9527
|
+
var log20 = forComponent("os-sched");
|
|
9204
9528
|
var OsScheduler = class {
|
|
9205
9529
|
options;
|
|
9206
9530
|
constructor(options) {
|
|
9207
9531
|
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
9208
9532
|
}
|
|
9209
9533
|
get dir() {
|
|
9210
|
-
return
|
|
9534
|
+
return join12(this.options.home, ".agent", "sched");
|
|
9211
9535
|
}
|
|
9212
9536
|
label(id) {
|
|
9213
9537
|
return `cc.livx.agentx.sched-${id}`;
|
|
9214
9538
|
}
|
|
9215
9539
|
plistPath(id) {
|
|
9216
|
-
return
|
|
9540
|
+
return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
9217
9541
|
}
|
|
9218
9542
|
run(cmd, args, input) {
|
|
9219
9543
|
return this.options.exec(cmd, args, input);
|
|
@@ -9224,16 +9548,16 @@ var OsScheduler = class {
|
|
|
9224
9548
|
/** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
|
|
9225
9549
|
schedule(spec) {
|
|
9226
9550
|
if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
|
|
9227
|
-
|
|
9551
|
+
mkdirSync9(this.dir, { recursive: true });
|
|
9228
9552
|
const oneOff = "at" in spec.trigger;
|
|
9229
9553
|
const script = this.writeScript(spec, oneOff);
|
|
9230
9554
|
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
9231
9555
|
const meta = { ...spec, created: Date.now(), mechanism };
|
|
9232
|
-
|
|
9556
|
+
writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
9233
9557
|
return mechanism;
|
|
9234
9558
|
}
|
|
9235
9559
|
cancel(id) {
|
|
9236
|
-
const meta = readJsonFile(
|
|
9560
|
+
const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
|
|
9237
9561
|
if (!meta) return false;
|
|
9238
9562
|
try {
|
|
9239
9563
|
if (this.options.platform === "darwin") {
|
|
@@ -9262,11 +9586,11 @@ var OsScheduler = class {
|
|
|
9262
9586
|
}
|
|
9263
9587
|
}
|
|
9264
9588
|
} catch (e) {
|
|
9265
|
-
|
|
9589
|
+
log20.debug(`cancel ${id}`, e);
|
|
9266
9590
|
}
|
|
9267
9591
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
9268
9592
|
try {
|
|
9269
|
-
unlinkSync3(
|
|
9593
|
+
unlinkSync3(join12(this.dir, f));
|
|
9270
9594
|
} catch {
|
|
9271
9595
|
}
|
|
9272
9596
|
}
|
|
@@ -9274,19 +9598,19 @@ var OsScheduler = class {
|
|
|
9274
9598
|
}
|
|
9275
9599
|
list() {
|
|
9276
9600
|
if (!existsSync8(this.dir)) return [];
|
|
9277
|
-
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(
|
|
9601
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
|
|
9278
9602
|
}
|
|
9279
9603
|
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
9280
9604
|
writeScript(spec, oneOff) {
|
|
9281
|
-
const p =
|
|
9605
|
+
const p = join12(this.dir, `${spec.id}.sh`);
|
|
9282
9606
|
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9283
|
-
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(
|
|
9284
|
-
` : `rm -f ${q2(
|
|
9607
|
+
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)}
|
|
9608
|
+
` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
9285
9609
|
` : "";
|
|
9286
|
-
|
|
9610
|
+
writeFileSync8(p, `#!/bin/sh
|
|
9287
9611
|
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
9288
9612
|
cd ${q2(spec.cwd)} || exit 1
|
|
9289
|
-
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(
|
|
9613
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
|
|
9290
9614
|
${cleanup}`);
|
|
9291
9615
|
chmodSync(p, 493);
|
|
9292
9616
|
return p;
|
|
@@ -9323,8 +9647,8 @@ ${trigger}
|
|
|
9323
9647
|
<key>RunAtLoad</key><false/>
|
|
9324
9648
|
</dict></plist>
|
|
9325
9649
|
`;
|
|
9326
|
-
|
|
9327
|
-
|
|
9650
|
+
mkdirSync9(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
9651
|
+
writeFileSync8(this.plistPath(spec.id), plist);
|
|
9328
9652
|
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
9329
9653
|
return `launchd:${this.label(spec.id)}`;
|
|
9330
9654
|
}
|
|
@@ -9361,7 +9685,7 @@ var OsSchedulerOptions = class {
|
|
|
9361
9685
|
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
9362
9686
|
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
9363
9687
|
exec = (cmd, args, input) => {
|
|
9364
|
-
const r =
|
|
9688
|
+
const r = spawnSync5(cmd, args, { input, encoding: "utf8" });
|
|
9365
9689
|
if (r.error) throw r.error;
|
|
9366
9690
|
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
9367
9691
|
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
@@ -9376,12 +9700,12 @@ function routeTrigger(trigger, backendHint, now5 = Date.now()) {
|
|
|
9376
9700
|
// cli/remoteTrigger.ts
|
|
9377
9701
|
import { createServer as createServer2, createConnection } from "net";
|
|
9378
9702
|
import { spawn as spawn3 } from "child_process";
|
|
9379
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
9703
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync10, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
9380
9704
|
import { homedir as homedir8 } from "os";
|
|
9381
|
-
import { join as
|
|
9382
|
-
var
|
|
9383
|
-
var TRIGGER_DIR = () =>
|
|
9384
|
-
var sockPath = (sessionId, dir = TRIGGER_DIR()) =>
|
|
9705
|
+
import { join as join13 } from "path";
|
|
9706
|
+
var log21 = forComponent("remote-trigger");
|
|
9707
|
+
var TRIGGER_DIR = () => join13(homedir8(), ".agent", "triggers");
|
|
9708
|
+
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
|
|
9385
9709
|
var TriggerServer = class {
|
|
9386
9710
|
constructor(onTrigger) {
|
|
9387
9711
|
this.onTrigger = onTrigger;
|
|
@@ -9393,7 +9717,7 @@ var TriggerServer = class {
|
|
|
9393
9717
|
start(sessionId, dir = TRIGGER_DIR()) {
|
|
9394
9718
|
this.stop();
|
|
9395
9719
|
try {
|
|
9396
|
-
|
|
9720
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
9397
9721
|
const p = sockPath(sessionId, dir);
|
|
9398
9722
|
try {
|
|
9399
9723
|
unlinkSync4(p);
|
|
@@ -9417,13 +9741,13 @@ var TriggerServer = class {
|
|
|
9417
9741
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
9418
9742
|
}
|
|
9419
9743
|
});
|
|
9420
|
-
conn.on("error", (e) =>
|
|
9744
|
+
conn.on("error", (e) => log21.debug("trigger conn error", e));
|
|
9421
9745
|
});
|
|
9422
|
-
this.server.on("error", (e) =>
|
|
9746
|
+
this.server.on("error", (e) => log21.debug("trigger server error", e));
|
|
9423
9747
|
this.server.listen(p);
|
|
9424
9748
|
this.path = p;
|
|
9425
9749
|
} catch (e) {
|
|
9426
|
-
|
|
9750
|
+
log21.debug("trigger server unavailable", e);
|
|
9427
9751
|
}
|
|
9428
9752
|
}
|
|
9429
9753
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -9544,10 +9868,10 @@ var italic = C("3");
|
|
|
9544
9868
|
var strike = C("9");
|
|
9545
9869
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
9546
9870
|
var err = (s) => process.stderr.write(s);
|
|
9547
|
-
var
|
|
9871
|
+
var log22 = forComponent("cli");
|
|
9548
9872
|
var VERSION = (() => {
|
|
9549
9873
|
try {
|
|
9550
|
-
return JSON.parse(
|
|
9874
|
+
return JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
9551
9875
|
} catch {
|
|
9552
9876
|
return "?";
|
|
9553
9877
|
}
|
|
@@ -9659,6 +9983,7 @@ function parseArgs(argv) {
|
|
|
9659
9983
|
else if (x === "--reasoning") a.reasoning = parseReasoning(val(++i, x));
|
|
9660
9984
|
else if (x === "--session-id") a.sessionId = val(++i, x);
|
|
9661
9985
|
else if (x === "--fork-session") a.fork = true;
|
|
9986
|
+
else if (x === "--worktree" || x === "-w") a.worktree = val(++i, x);
|
|
9662
9987
|
else if (x === "--max-steps") a.maxSteps = numFlag(argv[++i], "--max-steps");
|
|
9663
9988
|
else if (x === "--max-tokens") a.maxTokens = numFlag(argv[++i], "--max-tokens");
|
|
9664
9989
|
else if (x === "--timeout") a.timeoutMs = numFlag(argv[++i], "--timeout") * 1e3;
|
|
@@ -9671,6 +9996,7 @@ function parseArgs(argv) {
|
|
|
9671
9996
|
if (!a.task && rest.length) a.task = rest.join(" ");
|
|
9672
9997
|
if (a.boddb && a.vfs) throw new Error("--boddb and --sandbox are mutually exclusive (both are non-disk filesystems; pick one)");
|
|
9673
9998
|
if (a.seed && !a.boddb) throw new Error("--seed only applies with --boddb (it seeds the database from cwd on first run)");
|
|
9999
|
+
if (a.worktree && (a.vfs || a.boddb)) throw new Error("--worktree is a real-disk concept \u2014 incompatible with --vfs/--boddb");
|
|
9674
10000
|
if (a.duplex && (a.task || a.print)) throw new Error("--duplex is interactive-only (a conversational mode) \u2014 drop the task/-p");
|
|
9675
10001
|
if (a.duplex && a.plan) throw new Error("--plan is not supported in --duplex (workers are non-interactive; a plan could never be approved)");
|
|
9676
10002
|
if (a.voiceModel && !a.duplex) throw new Error("--voice-model only applies with --duplex");
|
|
@@ -9722,6 +10048,7 @@ Flags:
|
|
|
9722
10048
|
--reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
|
|
9723
10049
|
--session-id <id> use this id for the session (instead of an auto-generated one)
|
|
9724
10050
|
--fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
|
|
10051
|
+
-w, --worktree <slug> run in an isolated git worktree (.claude/worktrees/<slug>); auto-cleaned on exit if no changes
|
|
9725
10052
|
--max-steps <n> step budget (default 30)
|
|
9726
10053
|
--max-tokens <n> token budget kill-switch (default 200000)
|
|
9727
10054
|
--timeout <sec> wall-clock kill-switch (default 120)
|
|
@@ -9769,11 +10096,11 @@ function resolveModelOrNewest(model) {
|
|
|
9769
10096
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
9770
10097
|
function loadInstallEnv() {
|
|
9771
10098
|
let dir = dirname4(import.meta.path);
|
|
9772
|
-
for (let i = 0; i < 5 && !existsSync10(
|
|
10099
|
+
for (let i = 0; i < 5 && !existsSync10(join14(dir, "package.json")); i++) dir = dirname4(dir);
|
|
9773
10100
|
for (const name of [".env", ".env.local"]) {
|
|
9774
|
-
const file =
|
|
10101
|
+
const file = join14(dir, name);
|
|
9775
10102
|
if (!existsSync10(file)) continue;
|
|
9776
|
-
for (const line of
|
|
10103
|
+
for (const line of readFileSync7(file, "utf8").split("\n")) {
|
|
9777
10104
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
9778
10105
|
if (!m || m[1] in process.env) continue;
|
|
9779
10106
|
let val = m[2].trim();
|
|
@@ -10132,7 +10459,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
10132
10459
|
function turnCost(model, usage) {
|
|
10133
10460
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
10134
10461
|
}
|
|
10135
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
10462
|
+
async function evaluateGoal(ai, condition, transcript, log23) {
|
|
10136
10463
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
10137
10464
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
10138
10465
|
return text.slice(0, 600);
|
|
@@ -10152,7 +10479,7 @@ ${recent}` }
|
|
|
10152
10479
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
10153
10480
|
if (match) return JSON.parse(match[0]);
|
|
10154
10481
|
} catch (e) {
|
|
10155
|
-
|
|
10482
|
+
log23(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
10156
10483
|
`));
|
|
10157
10484
|
}
|
|
10158
10485
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -10359,13 +10686,13 @@ function mcpAgentTools(mounted, opts) {
|
|
|
10359
10686
|
return tools;
|
|
10360
10687
|
}
|
|
10361
10688
|
async function closeMcp(mounted) {
|
|
10362
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
10689
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log22.debug("mcp close failed", e))));
|
|
10363
10690
|
}
|
|
10364
10691
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
10365
10692
|
function mentionRefs(line) {
|
|
10366
10693
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
10367
10694
|
}
|
|
10368
|
-
var untilde = (p) => p.startsWith("~/") ?
|
|
10695
|
+
var untilde = (p) => p.startsWith("~/") ? join14(homedir9(), p.slice(2)) : p;
|
|
10369
10696
|
function readImageParts(cwd, line) {
|
|
10370
10697
|
const refs = mentionRefs(line);
|
|
10371
10698
|
const parts = [];
|
|
@@ -10374,7 +10701,7 @@ function readImageParts(cwd, line) {
|
|
|
10374
10701
|
if (!mime) continue;
|
|
10375
10702
|
const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
|
|
10376
10703
|
try {
|
|
10377
|
-
parts.push(imagePart(`data:${mime};base64,${
|
|
10704
|
+
parts.push(imagePart(`data:${mime};base64,${readFileSync7(abs).toString("base64")}`));
|
|
10378
10705
|
} catch {
|
|
10379
10706
|
}
|
|
10380
10707
|
}
|
|
@@ -10388,7 +10715,7 @@ function pastePathClassifier(cwd) {
|
|
|
10388
10715
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
10389
10716
|
const abs = t.startsWith("~/") ? untilde(t) : resolve3(cwd, t);
|
|
10390
10717
|
try {
|
|
10391
|
-
if (!
|
|
10718
|
+
if (!statSync4(abs).isFile()) return null;
|
|
10392
10719
|
} catch {
|
|
10393
10720
|
return null;
|
|
10394
10721
|
}
|
|
@@ -10409,7 +10736,7 @@ async function expandMentions(fs, line) {
|
|
|
10409
10736
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
10410
10737
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
10411
10738
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
10412
|
-
|
|
10739
|
+
log22.debug("mcp mention resolve failed", e);
|
|
10413
10740
|
return null;
|
|
10414
10741
|
});
|
|
10415
10742
|
if (body != null) {
|
|
@@ -10492,7 +10819,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
10492
10819
|
try {
|
|
10493
10820
|
store.save(session);
|
|
10494
10821
|
} catch (ex) {
|
|
10495
|
-
|
|
10822
|
+
log22.debug("mid-turn session flush failed", ex);
|
|
10496
10823
|
}
|
|
10497
10824
|
}
|
|
10498
10825
|
return origNotify(e);
|
|
@@ -10605,25 +10932,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
10605
10932
|
`;
|
|
10606
10933
|
function initInstructions(cwd) {
|
|
10607
10934
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
10608
|
-
if (existsSync10(
|
|
10935
|
+
if (existsSync10(join14(cwd, f))) {
|
|
10609
10936
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
10610
10937
|
`));
|
|
10611
10938
|
return;
|
|
10612
10939
|
}
|
|
10613
10940
|
}
|
|
10614
|
-
const path =
|
|
10615
|
-
|
|
10941
|
+
const path = join14(cwd, "AGENTS.md");
|
|
10942
|
+
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
|
|
10616
10943
|
err(green(` created ${path}
|
|
10617
10944
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
10618
10945
|
}
|
|
10619
10946
|
function persistSetting(cwd, key, value) {
|
|
10620
|
-
const path =
|
|
10947
|
+
const path = join14(cwd, ".agent", "settings.json");
|
|
10621
10948
|
try {
|
|
10622
|
-
const obj = existsSync10(path) ? JSON.parse(
|
|
10949
|
+
const obj = existsSync10(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
|
|
10623
10950
|
if (obj[key] === value) return;
|
|
10624
10951
|
obj[key] = value;
|
|
10625
|
-
|
|
10626
|
-
|
|
10952
|
+
mkdirSync11(dirname4(path), { recursive: true });
|
|
10953
|
+
writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
|
|
10627
10954
|
} catch (e) {
|
|
10628
10955
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
10629
10956
|
`));
|
|
@@ -10639,14 +10966,14 @@ var isCancelTeardown = (e) => {
|
|
|
10639
10966
|
function installCancelGuards(mounted) {
|
|
10640
10967
|
process.on("unhandledRejection", (e) => {
|
|
10641
10968
|
if (isCancelTeardown(e)) {
|
|
10642
|
-
|
|
10969
|
+
log22.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
10643
10970
|
return;
|
|
10644
10971
|
}
|
|
10645
|
-
|
|
10972
|
+
log22.error("unhandledRejection", e);
|
|
10646
10973
|
});
|
|
10647
10974
|
process.on("uncaughtException", (e) => {
|
|
10648
10975
|
if (isCancelTeardown(e)) {
|
|
10649
|
-
|
|
10976
|
+
log22.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
10650
10977
|
return;
|
|
10651
10978
|
}
|
|
10652
10979
|
console.error(e);
|
|
@@ -10655,7 +10982,7 @@ function installCancelGuards(mounted) {
|
|
|
10655
10982
|
});
|
|
10656
10983
|
}
|
|
10657
10984
|
async function repl(args, ai, cfg, cwd) {
|
|
10658
|
-
const oauth = new McpOAuth({ storePath:
|
|
10985
|
+
const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
|
|
10659
10986
|
const mounted = await mountMcp(cfg, oauth);
|
|
10660
10987
|
let mcpToolNames = [];
|
|
10661
10988
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -10863,7 +11190,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10863
11190
|
quickLook: {
|
|
10864
11191
|
branch: () => {
|
|
10865
11192
|
try {
|
|
10866
|
-
const head =
|
|
11193
|
+
const head = readFileSync7(join14(cwd, ".git", "HEAD"), "utf8").trim();
|
|
10867
11194
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
10868
11195
|
} catch {
|
|
10869
11196
|
return "not a git repository";
|
|
@@ -10992,9 +11319,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10992
11319
|
const pendingImages = [];
|
|
10993
11320
|
const bangContext = [];
|
|
10994
11321
|
const grabClipboardAttachment = () => {
|
|
10995
|
-
const dir =
|
|
11322
|
+
const dir = join14(tmpdir3(), "agentx-pasted");
|
|
10996
11323
|
try {
|
|
10997
|
-
|
|
11324
|
+
mkdirSync11(dir, { recursive: true });
|
|
10998
11325
|
} catch {
|
|
10999
11326
|
}
|
|
11000
11327
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
@@ -11045,7 +11372,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
11045
11372
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
11046
11373
|
`));
|
|
11047
11374
|
}
|
|
11048
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
11375
|
+
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 });
|
|
11049
11376
|
const cpHooks = checkpoints.hooks?.();
|
|
11050
11377
|
if (cpHooks) {
|
|
11051
11378
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -11097,14 +11424,14 @@ ${lines.join("\n")}
|
|
|
11097
11424
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
11098
11425
|
</system-reminder>`;
|
|
11099
11426
|
};
|
|
11100
|
-
const histPath =
|
|
11101
|
-
const history = existsSync10(histPath) ?
|
|
11427
|
+
const histPath = join14(cwd, ".agent", "history");
|
|
11428
|
+
const history = existsSync10(histPath) ? readFileSync7(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
11102
11429
|
const remember = (line) => {
|
|
11103
11430
|
try {
|
|
11104
|
-
|
|
11431
|
+
mkdirSync11(join14(cwd, ".agent"), { recursive: true });
|
|
11105
11432
|
appendFileSync(histPath, line + "\n");
|
|
11106
11433
|
} catch (e) {
|
|
11107
|
-
|
|
11434
|
+
log22.debug("history write failed", e);
|
|
11108
11435
|
}
|
|
11109
11436
|
};
|
|
11110
11437
|
const ago = (t) => {
|
|
@@ -11175,7 +11502,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
11175
11502
|
try {
|
|
11176
11503
|
store.save(session);
|
|
11177
11504
|
} catch (e) {
|
|
11178
|
-
|
|
11505
|
+
log22.debug("session save after rewind failed", e);
|
|
11179
11506
|
}
|
|
11180
11507
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
11181
11508
|
`));
|
|
@@ -11209,7 +11536,7 @@ ${task}`;
|
|
|
11209
11536
|
bangContext.length = 0;
|
|
11210
11537
|
}
|
|
11211
11538
|
const delta = await refreshCatalogs().catch((e) => {
|
|
11212
|
-
|
|
11539
|
+
log22.debug("catalog refresh failed", e);
|
|
11213
11540
|
return "";
|
|
11214
11541
|
});
|
|
11215
11542
|
if (delta) {
|
|
@@ -11401,8 +11728,8 @@ ${task}`;
|
|
|
11401
11728
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
11402
11729
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
11403
11730
|
try {
|
|
11404
|
-
const { spawnSync:
|
|
11405
|
-
const r =
|
|
11731
|
+
const { spawnSync: spawnSync6 } = await import("child_process");
|
|
11732
|
+
const r = spawnSync6("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
11406
11733
|
if (r.error) err(text);
|
|
11407
11734
|
} finally {
|
|
11408
11735
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -11426,8 +11753,8 @@ ${task}`;
|
|
|
11426
11753
|
cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
|
|
11427
11754
|
try {
|
|
11428
11755
|
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
11429
|
-
|
|
11430
|
-
|
|
11756
|
+
mkdirSync11(`${cwd}/.agent/sessions`, { recursive: true });
|
|
11757
|
+
writeFileSync9(probe, "ok");
|
|
11431
11758
|
unlinkSync5(probe);
|
|
11432
11759
|
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
11433
11760
|
} catch (e) {
|
|
@@ -11455,7 +11782,7 @@ ${task}`;
|
|
|
11455
11782
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
11456
11783
|
run: async () => {
|
|
11457
11784
|
await refreshCatalogs().catch((e) => {
|
|
11458
|
-
|
|
11785
|
+
log22.debug("catalog refresh failed", e);
|
|
11459
11786
|
});
|
|
11460
11787
|
face.reprepare();
|
|
11461
11788
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -11792,8 +12119,8 @@ ${task}`;
|
|
|
11792
12119
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
11793
12120
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
11794
12121
|
try {
|
|
11795
|
-
const { spawnSync:
|
|
11796
|
-
|
|
12122
|
+
const { spawnSync: spawnSync6 } = await import("child_process");
|
|
12123
|
+
spawnSync6(ed, [idx], { stdio: "inherit" });
|
|
11797
12124
|
} finally {
|
|
11798
12125
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
11799
12126
|
}
|
|
@@ -11912,7 +12239,7 @@ ${task}`;
|
|
|
11912
12239
|
try {
|
|
11913
12240
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
11914
12241
|
} catch (e) {
|
|
11915
|
-
|
|
12242
|
+
log22.debug(`loadAgents(${d}) failed`, e);
|
|
11916
12243
|
}
|
|
11917
12244
|
}
|
|
11918
12245
|
if (!seen.size) {
|
|
@@ -12000,7 +12327,7 @@ ${task}`;
|
|
|
12000
12327
|
}
|
|
12001
12328
|
if (idx >= 0) {
|
|
12002
12329
|
const old = mounted.splice(idx, 1)[0];
|
|
12003
|
-
await old.client.close().catch((e) =>
|
|
12330
|
+
await old.client.close().catch((e) => log22.debug("mcp close failed", e));
|
|
12004
12331
|
}
|
|
12005
12332
|
try {
|
|
12006
12333
|
const m = await mountMcpServer(name, conf);
|
|
@@ -12028,7 +12355,7 @@ ${task}`;
|
|
|
12028
12355
|
}
|
|
12029
12356
|
const m = mounted.splice(idx, 1)[0];
|
|
12030
12357
|
remountMcpTools();
|
|
12031
|
-
await m.client.close().catch((e) =>
|
|
12358
|
+
await m.client.close().catch((e) => log22.debug("mcp close failed", e));
|
|
12032
12359
|
err(dim(` removed "${name}"
|
|
12033
12360
|
`));
|
|
12034
12361
|
return;
|
|
@@ -12160,11 +12487,11 @@ ${task}`;
|
|
|
12160
12487
|
return;
|
|
12161
12488
|
}
|
|
12162
12489
|
const md = exportMarkdown(session.meta, shown);
|
|
12163
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
12490
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
|
|
12164
12491
|
const path = resolve3(cwd, name);
|
|
12165
12492
|
try {
|
|
12166
|
-
|
|
12167
|
-
|
|
12493
|
+
mkdirSync11(dirname4(path), { recursive: true });
|
|
12494
|
+
writeFileSync9(path, md);
|
|
12168
12495
|
err(green(` \u2713 exported \u2192 ${path}
|
|
12169
12496
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
12170
12497
|
`));
|
|
@@ -12226,9 +12553,9 @@ ${task}`;
|
|
|
12226
12553
|
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)`));
|
|
12227
12554
|
const listDir = (absDir) => {
|
|
12228
12555
|
try {
|
|
12229
|
-
return readdirSync4(
|
|
12556
|
+
return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
12230
12557
|
} catch (e) {
|
|
12231
|
-
|
|
12558
|
+
log22.debug("completion readdir failed", absDir, e);
|
|
12232
12559
|
return null;
|
|
12233
12560
|
}
|
|
12234
12561
|
};
|
|
@@ -12355,18 +12682,18 @@ ${out}
|
|
|
12355
12682
|
return;
|
|
12356
12683
|
}
|
|
12357
12684
|
await refreshCatalogs().catch((e) => {
|
|
12358
|
-
|
|
12685
|
+
log22.debug("catalog refresh failed", e);
|
|
12359
12686
|
});
|
|
12360
|
-
const c = cmds.find((x) => x.name === name);
|
|
12361
|
-
if (c) {
|
|
12362
|
-
await runCommand(c, a.join(" "));
|
|
12363
|
-
return;
|
|
12364
|
-
}
|
|
12365
12687
|
const sk = skills.find((x) => x.name === name);
|
|
12366
12688
|
if (sk) {
|
|
12367
12689
|
await runSkill(sk, a.join(" "));
|
|
12368
12690
|
return;
|
|
12369
12691
|
}
|
|
12692
|
+
const c = cmds.find((x) => x.name === name);
|
|
12693
|
+
if (c) {
|
|
12694
|
+
await runCommand(c, a.join(" "));
|
|
12695
|
+
return;
|
|
12696
|
+
}
|
|
12370
12697
|
const known = Object.keys(builtins).filter((k) => k !== "quit").map((k) => "/" + k);
|
|
12371
12698
|
const custom = [...cmds.map((x) => x.name), ...skills.map((x) => x.name)].map((n) => "/" + n);
|
|
12372
12699
|
err(red(` unknown command /${name}
|
|
@@ -12697,6 +13024,19 @@ async function main() {
|
|
|
12697
13024
|
}
|
|
12698
13025
|
if (args.debug) process.env.DEBUG ||= "*";
|
|
12699
13026
|
if (args.print && !args.task && !process.stdin.isTTY) args.task = (await readAllStdin()).trim();
|
|
13027
|
+
let worktreeCleanup;
|
|
13028
|
+
if (args.worktree) {
|
|
13029
|
+
const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
13030
|
+
const session = enterWorktree2(args.worktree, resolve3(args.cwd ?? process.cwd()));
|
|
13031
|
+
err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
|
|
13032
|
+
`));
|
|
13033
|
+
worktreeCleanup = () => {
|
|
13034
|
+
const r = exitWorktree2();
|
|
13035
|
+
if (r?.removed) err(dim(" \u2296 worktree cleaned up (no changes)\n"));
|
|
13036
|
+
else if (r && !r.removed) err(dim(` \u2295 worktree kept: ${r.reason}
|
|
13037
|
+
`));
|
|
13038
|
+
};
|
|
13039
|
+
}
|
|
12700
13040
|
const cwd = resolve3(args.cwd ?? process.cwd());
|
|
12701
13041
|
const cfg = await loadConfig(cwd);
|
|
12702
13042
|
cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
|
|
@@ -12742,7 +13082,7 @@ async function main() {
|
|
|
12742
13082
|
}
|
|
12743
13083
|
});
|
|
12744
13084
|
if (args.task) {
|
|
12745
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
13085
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
|
|
12746
13086
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted));
|
|
12747
13087
|
const store = new SessionStore(cwd);
|
|
12748
13088
|
const session = startSession(args, store, agent, cwd);
|
|
@@ -12759,9 +13099,11 @@ async function main() {
|
|
|
12759
13099
|
const obj = res ? jsonResult(res, session) : { ok: false, finishReason: "error", sessionId: session.meta.id };
|
|
12760
13100
|
process.stdout.write(JSON.stringify(args.outputFormat === "stream-json" ? { type: "result", ...obj } : obj) + "\n");
|
|
12761
13101
|
}
|
|
13102
|
+
worktreeCleanup?.();
|
|
12762
13103
|
process.exit(ok ? 0 : 1);
|
|
12763
13104
|
}
|
|
12764
13105
|
await repl(args, ai, cfg, cwd);
|
|
13106
|
+
worktreeCleanup?.();
|
|
12765
13107
|
}
|
|
12766
13108
|
if (import.meta.main) main().catch((e) => {
|
|
12767
13109
|
console.error(e?.message ?? e);
|