@livx.cc/agentx 0.99.78 → 0.99.80
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-BBlKI8uD.d.ts → Agent-BK-heHrw.d.ts} +4 -0
- package/dist/cli.d.ts +24 -2
- package/dist/cli.js +686 -245
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +296 -47
- package/dist/index.js.map +1 -1
- package/dist/mcp.client.js +59 -2
- package/dist/mcp.client.js.map +1 -1
- package/dist/tools.shell.d.ts +23 -1
- package/dist/tools.shell.js +157 -19
- package/dist/tools.shell.js.map +1 -1
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -403,6 +403,68 @@ var init_redact = __esm({
|
|
|
403
403
|
}
|
|
404
404
|
});
|
|
405
405
|
|
|
406
|
+
// src/hostPath.ts
|
|
407
|
+
function toVfsPath(p, platform2 = currentPlatform()) {
|
|
408
|
+
if (!isWinPaths(platform2) || typeof p !== "string") return p;
|
|
409
|
+
let s = p.replace(/\\/g, "/");
|
|
410
|
+
s = s.replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
|
|
411
|
+
const drive = /^([A-Za-z]):(?=\/|$)/.exec(s);
|
|
412
|
+
if (drive) return `/${drive[1].toLowerCase()}${s.slice(2) || "/"}`.replace(/\/+$/, "") || "/";
|
|
413
|
+
if (/^\/\/[^/]/.test(s)) return `/unc/${s.slice(2)}`;
|
|
414
|
+
return s;
|
|
415
|
+
}
|
|
416
|
+
function toHostPath(vpath, platform2 = currentPlatform()) {
|
|
417
|
+
if (!isWinPaths(platform2)) return vpath;
|
|
418
|
+
const segs = vpath.split("/").filter(Boolean);
|
|
419
|
+
if (segs.length >= 1 && /^[A-Za-z]$/.test(segs[0])) return `${segs[0].toUpperCase()}:\\${segs.slice(1).join("\\")}`;
|
|
420
|
+
if (segs.length >= 3 && segs[0].toLowerCase() === "unc") return `\\\\${segs.slice(1).join("\\")}`;
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
function withHostPaths(fs, platform2 = currentPlatform()) {
|
|
424
|
+
if (!isWinPaths(platform2)) return fs;
|
|
425
|
+
const PATH_ARGS = {
|
|
426
|
+
readFile: 1,
|
|
427
|
+
writeFile: 1,
|
|
428
|
+
deleteFile: 1,
|
|
429
|
+
readDir: 1,
|
|
430
|
+
createDir: 1,
|
|
431
|
+
exists: 1,
|
|
432
|
+
stat: 1,
|
|
433
|
+
isDirectory: 1,
|
|
434
|
+
isFile: 1,
|
|
435
|
+
setCwd: 1,
|
|
436
|
+
resolvePath: 2,
|
|
437
|
+
readFileBytes: 1,
|
|
438
|
+
realPath: 1,
|
|
439
|
+
canonicalPath: 1
|
|
440
|
+
};
|
|
441
|
+
return new Proxy(fs, {
|
|
442
|
+
get(target, prop, receiver) {
|
|
443
|
+
const v = Reflect.get(target, prop, receiver);
|
|
444
|
+
const n = typeof prop === "string" ? PATH_ARGS[prop] : void 0;
|
|
445
|
+
if (typeof v !== "function" || prop === "constructor") return v;
|
|
446
|
+
if (!n) return v.bind(target);
|
|
447
|
+
return (...args) => v.apply(target, args.map((a, i) => i < n && typeof a === "string" ? toVfsPath(a, platform2) : a));
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function winSegmentProblem(seg) {
|
|
452
|
+
if (seg.includes(":")) return "alternate data stream / drive-relative name";
|
|
453
|
+
if (/[. ]$/.test(seg) && seg !== "." && seg !== "..") return "trailing dot or space";
|
|
454
|
+
if (WIN_DEVICE.test(seg)) return "reserved device name";
|
|
455
|
+
if (/[<>"|?*\x00-\x1f]/.test(seg)) return "invalid character";
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
var currentPlatform, isWinPaths, WIN_DEVICE;
|
|
459
|
+
var init_hostPath = __esm({
|
|
460
|
+
"src/hostPath.ts"() {
|
|
461
|
+
"use strict";
|
|
462
|
+
currentPlatform = () => typeof process !== "undefined" && process.platform || "browser";
|
|
463
|
+
isWinPaths = (platform2 = currentPlatform()) => platform2 === "win32";
|
|
464
|
+
WIN_DEVICE = /^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³]|conin\$|conout\$)(\..*)?$/i;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
406
468
|
// src/tools.structured.ts
|
|
407
469
|
function ckAbort(signal) {
|
|
408
470
|
if (signal?.aborted) throw new Error("aborted");
|
|
@@ -445,7 +507,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
|
445
507
|
}
|
|
446
508
|
function scanRoots(fs, path, opts) {
|
|
447
509
|
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
448
|
-
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
510
|
+
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => toVfsPath(r)).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
449
511
|
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
450
512
|
}
|
|
451
513
|
function scanNote(states) {
|
|
@@ -674,6 +736,7 @@ function parentDir(abs) {
|
|
|
674
736
|
return i <= 0 ? "/" : abs.slice(0, i);
|
|
675
737
|
}
|
|
676
738
|
async function mkdirp(fs, dir) {
|
|
739
|
+
dir = toVfsPath(dir);
|
|
677
740
|
if (dir === "/" || await fs.exists(dir)) return;
|
|
678
741
|
await mkdirp(fs, parentDir(dir));
|
|
679
742
|
if (!await fs.exists(dir)) await fs.createDir(dir);
|
|
@@ -734,6 +797,7 @@ var init_tools_structured = __esm({
|
|
|
734
797
|
"src/tools.structured.ts"() {
|
|
735
798
|
"use strict";
|
|
736
799
|
init_redact();
|
|
800
|
+
init_hostPath();
|
|
737
801
|
DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
738
802
|
DEFAULT_MAX_DEPTH = 24;
|
|
739
803
|
SCAN_PARAMS = {
|
|
@@ -2014,7 +2078,7 @@ __export(worktree_exports, {
|
|
|
2014
2078
|
});
|
|
2015
2079
|
import { spawnSync } from "child_process";
|
|
2016
2080
|
import { symlinkSync, mkdirSync, statSync as statSync4, readFileSync as readFileSync3, rmSync } from "fs";
|
|
2017
|
-
import { join as join2 } from "path";
|
|
2081
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
2018
2082
|
function validateWorktreeSlug(slug2) {
|
|
2019
2083
|
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
2020
2084
|
if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
|
|
@@ -2045,7 +2109,7 @@ function git(args, cwd) {
|
|
|
2045
2109
|
}
|
|
2046
2110
|
function findGitRoot(from) {
|
|
2047
2111
|
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
2048
|
-
return r.ok ? r.stdout : null;
|
|
2112
|
+
return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
|
|
2049
2113
|
}
|
|
2050
2114
|
function readWorktreeHead(worktreePath) {
|
|
2051
2115
|
try {
|
|
@@ -2099,7 +2163,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
|
2099
2163
|
continue;
|
|
2100
2164
|
}
|
|
2101
2165
|
try {
|
|
2102
|
-
symlinkSync(src, dst, "dir");
|
|
2166
|
+
symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
|
|
2103
2167
|
} catch (e) {
|
|
2104
2168
|
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
2105
2169
|
}
|
|
@@ -2178,20 +2242,58 @@ var NodeDiskFilesystem;
|
|
|
2178
2242
|
var init_NodeDiskFilesystem = __esm({
|
|
2179
2243
|
"src/NodeDiskFilesystem.ts"() {
|
|
2180
2244
|
"use strict";
|
|
2245
|
+
init_hostPath();
|
|
2181
2246
|
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
2182
2247
|
constructor(baseDir, opts = {}) {
|
|
2183
2248
|
this.baseDir = baseDir;
|
|
2184
2249
|
this.opts = { denySymlinks: true, ...opts };
|
|
2250
|
+
this.drives = this.win && (baseDir === "/" || baseDir === "\\");
|
|
2185
2251
|
}
|
|
2186
2252
|
baseDir;
|
|
2187
2253
|
cwd = "/";
|
|
2188
2254
|
opts;
|
|
2255
|
+
win = isWinPaths();
|
|
2256
|
+
/** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
|
|
2257
|
+
drives;
|
|
2189
2258
|
/** Ensure the root dir exists. */
|
|
2190
2259
|
async init() {
|
|
2260
|
+
if (this.drives) return;
|
|
2191
2261
|
await fsp.mkdir(this.baseDir, { recursive: true });
|
|
2192
2262
|
}
|
|
2193
2263
|
real(vpath) {
|
|
2194
|
-
return np.join(this.baseDir, "." + vpath);
|
|
2264
|
+
if (!this.win) return np.join(this.baseDir, "." + vpath);
|
|
2265
|
+
for (const seg of vpath.split("/")) {
|
|
2266
|
+
const why = seg && winSegmentProblem(seg);
|
|
2267
|
+
if (why) throw new Error(`File not found: ${vpath} (${why})`);
|
|
2268
|
+
}
|
|
2269
|
+
if (!this.drives) return np.join(this.baseDir, "." + vpath);
|
|
2270
|
+
const host = toHostPath(vpath);
|
|
2271
|
+
if (!host) throw new Error(`File not found: ${vpath}`);
|
|
2272
|
+
return host;
|
|
2273
|
+
}
|
|
2274
|
+
/** Windows machine root ('/' when baseDir is '/'): a synthetic directory listing the drives. */
|
|
2275
|
+
isDriveRoot(vpath) {
|
|
2276
|
+
return this.drives && vpath === "/";
|
|
2277
|
+
}
|
|
2278
|
+
/** Windows: refuse an 8.3 short name (`ENV~1` opens `.env`) — the only alias that needs the disk to
|
|
2279
|
+
* detect. Only segments that look like one (`~<digit>`) cost a lookup: the OS's long name for that
|
|
2280
|
+
* component must be the name that was asked for. */
|
|
2281
|
+
assertLongNames(real) {
|
|
2282
|
+
if (!this.win || !/~\d/.test(real)) return;
|
|
2283
|
+
const root = np.parse(real).root;
|
|
2284
|
+
const parts = real.slice(root.length).split(/[\\/]/).filter(Boolean);
|
|
2285
|
+
let cur = root;
|
|
2286
|
+
for (const part of parts) {
|
|
2287
|
+
cur = np.join(cur, part);
|
|
2288
|
+
if (!/~\d/.test(part)) continue;
|
|
2289
|
+
let long;
|
|
2290
|
+
try {
|
|
2291
|
+
long = np.basename(realpathSync2.native(cur));
|
|
2292
|
+
} catch {
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (long.toLowerCase() !== part.toLowerCase()) throw new Error("File not found: short (8.3) name not permitted");
|
|
2296
|
+
}
|
|
2195
2297
|
}
|
|
2196
2298
|
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
2197
2299
|
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
@@ -2201,11 +2303,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2201
2303
|
static VERIFY_TTL_MS = 1e3;
|
|
2202
2304
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
2203
2305
|
async assertNoSymlink(real) {
|
|
2306
|
+
this.assertLongNames(real);
|
|
2204
2307
|
if (!this.opts.denySymlinks) return;
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2308
|
+
const base = this.drives ? np.parse(real).root : this.baseDir;
|
|
2309
|
+
const rel = np.relative(base, real);
|
|
2310
|
+
if (rel === "" || rel.startsWith("..") || np.isAbsolute(rel)) return;
|
|
2207
2311
|
const parts = rel.split(np.sep);
|
|
2208
|
-
let cur =
|
|
2312
|
+
let cur = base;
|
|
2209
2313
|
const now4 = Date.now();
|
|
2210
2314
|
for (let i = 0; i < parts.length; i++) {
|
|
2211
2315
|
cur = np.join(cur, parts[i]);
|
|
@@ -2225,13 +2329,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2225
2329
|
}
|
|
2226
2330
|
}
|
|
2227
2331
|
resolvePath(path, cwd) {
|
|
2228
|
-
return PathResolver.resolve(path, cwd || this.cwd);
|
|
2332
|
+
return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
|
|
2229
2333
|
}
|
|
2230
2334
|
getCwd() {
|
|
2231
2335
|
return this.cwd;
|
|
2232
2336
|
}
|
|
2233
2337
|
setCwd(path) {
|
|
2234
|
-
this.cwd = PathResolver.normalize(path);
|
|
2338
|
+
this.cwd = PathResolver.normalize(toVfsPath(path));
|
|
2235
2339
|
}
|
|
2236
2340
|
async readFile(path) {
|
|
2237
2341
|
const r = this.real(this.resolvePath(path));
|
|
@@ -2305,8 +2409,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2305
2409
|
}
|
|
2306
2410
|
}
|
|
2307
2411
|
async readDir(path) {
|
|
2308
|
-
const
|
|
2412
|
+
const v = this.resolvePath(path);
|
|
2413
|
+
if (this.isDriveRoot(v)) return this.listDrives();
|
|
2309
2414
|
try {
|
|
2415
|
+
const r = this.real(v);
|
|
2310
2416
|
await this.assertNoSymlink(r);
|
|
2311
2417
|
return await fsp.readdir(r);
|
|
2312
2418
|
} catch {
|
|
@@ -2326,8 +2432,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2326
2432
|
await fsp.mkdir(r);
|
|
2327
2433
|
}
|
|
2328
2434
|
async exists(path) {
|
|
2329
|
-
const
|
|
2435
|
+
const v = this.resolvePath(path);
|
|
2436
|
+
if (this.isDriveRoot(v)) return true;
|
|
2330
2437
|
try {
|
|
2438
|
+
const r = this.real(v);
|
|
2331
2439
|
await this.assertNoSymlink(r);
|
|
2332
2440
|
await fsp.stat(r);
|
|
2333
2441
|
return true;
|
|
@@ -2337,8 +2445,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2337
2445
|
}
|
|
2338
2446
|
async stat(path) {
|
|
2339
2447
|
let s;
|
|
2340
|
-
const
|
|
2448
|
+
const v = this.resolvePath(path);
|
|
2449
|
+
if (this.isDriveRoot(v)) return { created: /* @__PURE__ */ new Date(0), modified: /* @__PURE__ */ new Date(0), size: 0, permissions: "drwxr-xr-x", isExecutable: false };
|
|
2341
2450
|
try {
|
|
2451
|
+
const r = this.real(v);
|
|
2342
2452
|
await this.assertNoSymlink(r);
|
|
2343
2453
|
s = await fsp.stat(r);
|
|
2344
2454
|
} catch {
|
|
@@ -2353,8 +2463,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2353
2463
|
};
|
|
2354
2464
|
}
|
|
2355
2465
|
async isDirectory(path) {
|
|
2356
|
-
const
|
|
2466
|
+
const v = this.resolvePath(path);
|
|
2467
|
+
if (this.isDriveRoot(v)) return true;
|
|
2357
2468
|
try {
|
|
2469
|
+
const r = this.real(v);
|
|
2358
2470
|
await this.assertNoSymlink(r);
|
|
2359
2471
|
return (await fsp.stat(r)).isDirectory();
|
|
2360
2472
|
} catch {
|
|
@@ -2362,14 +2474,20 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2362
2474
|
}
|
|
2363
2475
|
}
|
|
2364
2476
|
async isFile(path) {
|
|
2365
|
-
const r = this.real(this.resolvePath(path));
|
|
2366
2477
|
try {
|
|
2478
|
+
const r = this.real(this.resolvePath(path));
|
|
2367
2479
|
await this.assertNoSymlink(r);
|
|
2368
2480
|
return (await fsp.stat(r)).isFile();
|
|
2369
2481
|
} catch {
|
|
2370
2482
|
return false;
|
|
2371
2483
|
}
|
|
2372
2484
|
}
|
|
2485
|
+
/** Windows machine root: the drive letters that are mounted (`/c`, `/d`, …). */
|
|
2486
|
+
async listDrives() {
|
|
2487
|
+
const letters = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
2488
|
+
const up = await Promise.all(letters.map((l) => fsp.stat(`${l.toUpperCase()}:\\`).then(() => true, () => false)));
|
|
2489
|
+
return letters.filter((_, i) => up[i]);
|
|
2490
|
+
}
|
|
2373
2491
|
};
|
|
2374
2492
|
}
|
|
2375
2493
|
});
|
|
@@ -2386,6 +2504,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2386
2504
|
"src/JailedFilesystem.ts"() {
|
|
2387
2505
|
"use strict";
|
|
2388
2506
|
init_tools_structured();
|
|
2507
|
+
init_hostPath();
|
|
2389
2508
|
JailedFilesystem = class {
|
|
2390
2509
|
constructor(inner, options) {
|
|
2391
2510
|
this.inner = inner;
|
|
@@ -2399,8 +2518,10 @@ var init_JailedFilesystem = __esm({
|
|
|
2399
2518
|
denyRe;
|
|
2400
2519
|
roRe;
|
|
2401
2520
|
confineRe;
|
|
2521
|
+
/** Resolved VFS path — host-path syntax (Windows `C:\…`, backslashes) is normalized FIRST, so the
|
|
2522
|
+
* deny/readonly/confine globs always match the path that is actually opened. */
|
|
2402
2523
|
abs(path) {
|
|
2403
|
-
return this.inner.resolvePath(path, this.inner.getCwd());
|
|
2524
|
+
return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
|
|
2404
2525
|
}
|
|
2405
2526
|
/** Throw if reading `path` is not permitted (denied or out of confinement). */
|
|
2406
2527
|
guardRead(op, path) {
|
|
@@ -2485,7 +2606,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2485
2606
|
return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
|
|
2486
2607
|
}
|
|
2487
2608
|
resolvePath(path, cwd) {
|
|
2488
|
-
return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
|
|
2609
|
+
return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
|
|
2489
2610
|
}
|
|
2490
2611
|
getCwd() {
|
|
2491
2612
|
return this.inner.getCwd();
|
|
@@ -2551,6 +2672,159 @@ var init_JailedFilesystem = __esm({
|
|
|
2551
2672
|
}
|
|
2552
2673
|
});
|
|
2553
2674
|
|
|
2675
|
+
// src/platform.ts
|
|
2676
|
+
import { spawnSync as spawnSync2, execFile } from "child_process";
|
|
2677
|
+
import { statSync as statSync5 } from "fs";
|
|
2678
|
+
import * as np2 from "path";
|
|
2679
|
+
function specFor(bin) {
|
|
2680
|
+
const name = np2.basename(bin).toLowerCase().replace(/\.exe$/, "");
|
|
2681
|
+
if (name === "powershell" || name === "pwsh") {
|
|
2682
|
+
const pre = "$ProgressPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$OutputEncoding=[Text.Encoding]::UTF8;";
|
|
2683
|
+
return {
|
|
2684
|
+
bin,
|
|
2685
|
+
kind: "powershell",
|
|
2686
|
+
label: name === "pwsh" ? "PowerShell (pwsh)" : "Windows PowerShell",
|
|
2687
|
+
args: (c) => ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(pre + c, "utf16le").toString("base64")]
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
if (name === "cmd") return { bin, kind: "cmd", label: "cmd.exe", args: (c) => ["/d", "/s", "/c", `"${c}"`], spawnOptions: { windowsVerbatimArguments: true } };
|
|
2691
|
+
return { bin, kind: "sh", label: /bash/.test(name) && /git/i.test(bin) ? "Git Bash" : name, args: (c) => ["-c", c] };
|
|
2692
|
+
}
|
|
2693
|
+
function findGitBash(env = process.env) {
|
|
2694
|
+
const roots = [];
|
|
2695
|
+
const git2 = findOnPath("git", env);
|
|
2696
|
+
if (git2) {
|
|
2697
|
+
const dir = np2.dirname(git2);
|
|
2698
|
+
const up = (p, n) => {
|
|
2699
|
+
for (let i = 0; i < n; i++) p = np2.dirname(p);
|
|
2700
|
+
return p;
|
|
2701
|
+
};
|
|
2702
|
+
roots.push(/[\\/](mingw(32|64)|ucrt64|clang(32|64|arm64))[\\/]bin$/i.test(dir) ? up(dir, 2) : up(dir, 1));
|
|
2703
|
+
}
|
|
2704
|
+
for (const base of [env.ProgramFiles, env["ProgramFiles(x86)"], env.ProgramW6432, env.LOCALAPPDATA && np2.join(env.LOCALAPPDATA, "Programs")])
|
|
2705
|
+
if (base) roots.push(np2.join(base, "Git"));
|
|
2706
|
+
for (const r of roots) {
|
|
2707
|
+
const b = np2.join(r, "bin", "bash.exe");
|
|
2708
|
+
if (isFile(b)) return b;
|
|
2709
|
+
}
|
|
2710
|
+
return void 0;
|
|
2711
|
+
}
|
|
2712
|
+
function defaultShell() {
|
|
2713
|
+
if (!IS_WIN) return POSIX_SH;
|
|
2714
|
+
if (_shell) return _shell;
|
|
2715
|
+
const override = process.env.AGENTX_SHELL;
|
|
2716
|
+
if (override && (isFile(override) || findOnPath(override))) return _shell = specFor(isFile(override) ? override : findOnPath(override));
|
|
2717
|
+
const bash = findGitBash();
|
|
2718
|
+
if (bash) return _shell = specFor(bash);
|
|
2719
|
+
const sysRoot = process.env.SystemRoot || "C:\\Windows";
|
|
2720
|
+
const ps = np2.join(sysRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
2721
|
+
if (isFile(ps)) return _shell = specFor(ps);
|
|
2722
|
+
return _shell = specFor(process.env.ComSpec || "cmd.exe");
|
|
2723
|
+
}
|
|
2724
|
+
function shellFor(shell) {
|
|
2725
|
+
if (!IS_WIN) return shell ? { ...POSIX_SH, bin: shell } : POSIX_SH;
|
|
2726
|
+
return shell && isFile(shell) ? specFor(shell) : defaultShell();
|
|
2727
|
+
}
|
|
2728
|
+
function shellOption() {
|
|
2729
|
+
if (!IS_WIN) return true;
|
|
2730
|
+
const s = defaultShell();
|
|
2731
|
+
return s.kind === "cmd" ? true : s.bin;
|
|
2732
|
+
}
|
|
2733
|
+
function killTree(pid, signal) {
|
|
2734
|
+
if (!pid) return false;
|
|
2735
|
+
if (!IS_WIN) {
|
|
2736
|
+
try {
|
|
2737
|
+
process.kill(-pid, signal);
|
|
2738
|
+
return true;
|
|
2739
|
+
} catch {
|
|
2740
|
+
return false;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
try {
|
|
2744
|
+
const r = spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 1e4 });
|
|
2745
|
+
return r.status === 0;
|
|
2746
|
+
} catch {
|
|
2747
|
+
return false;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
async function winTreeCpuSeconds(root) {
|
|
2751
|
+
const script = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId) $([uint64]$_.UserModeTime + [uint64]$_.KernelModeTime)" }';
|
|
2752
|
+
const out = await new Promise((res, rej) => execFile(
|
|
2753
|
+
"powershell.exe",
|
|
2754
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
2755
|
+
{ timeout: 15e3, windowsHide: true },
|
|
2756
|
+
(e, so) => e ? rej(e) : res(String(so))
|
|
2757
|
+
));
|
|
2758
|
+
const rows = out.split(/\r?\n/).map((l) => l.trim().split(/\s+/)).filter((r) => r.length === 3).map(([pid, ppid, ticks]) => ({ pid: Number(pid), ppid: Number(ppid), sec: Number(ticks) / 1e7 }));
|
|
2759
|
+
const inTree = new Set(rows.filter((r) => r.pid === root).map((r) => r.pid));
|
|
2760
|
+
for (let grew = true; grew; ) {
|
|
2761
|
+
grew = false;
|
|
2762
|
+
for (const r of rows) if (!inTree.has(r.pid) && inTree.has(r.ppid) && r.pid !== r.ppid) {
|
|
2763
|
+
inTree.add(r.pid);
|
|
2764
|
+
grew = true;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
let total;
|
|
2768
|
+
for (const r of rows) if (inTree.has(r.pid) && Number.isFinite(r.sec)) total = (total ?? 0) + r.sec;
|
|
2769
|
+
return total;
|
|
2770
|
+
}
|
|
2771
|
+
function findOnPath(cmd, env = process.env) {
|
|
2772
|
+
const exts = IS_WIN && !/\.[^\\/.]+$/.test(cmd) ? (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
2773
|
+
const cands = (base) => exts.map((e) => base + e);
|
|
2774
|
+
if (/[\\/]/.test(cmd)) return cands(cmd).find(isFile);
|
|
2775
|
+
const PATH = env.PATH ?? env.Path ?? "";
|
|
2776
|
+
for (const dir of PATH.split(np2.delimiter)) {
|
|
2777
|
+
if (!dir) continue;
|
|
2778
|
+
for (const p of cands(np2.join(dir, cmd))) if (isFile(p)) return p;
|
|
2779
|
+
}
|
|
2780
|
+
return void 0;
|
|
2781
|
+
}
|
|
2782
|
+
function hasCommand(cmd) {
|
|
2783
|
+
if (!IS_WIN) return spawnSync2("which", [cmd]).status === 0;
|
|
2784
|
+
return !!findOnPath(cmd);
|
|
2785
|
+
}
|
|
2786
|
+
function cmdArg(arg, shim) {
|
|
2787
|
+
let a = arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
|
|
2788
|
+
a = `"${a}"`.replace(CMD_META, "^$1");
|
|
2789
|
+
return shim ? a.replace(CMD_META, "^$1") : a;
|
|
2790
|
+
}
|
|
2791
|
+
function resolveSpawn(command, args = [], env = process.env) {
|
|
2792
|
+
if (!IS_WIN) return { command, args };
|
|
2793
|
+
const found = findOnPath(command, env);
|
|
2794
|
+
if (!found || !/\.(cmd|bat)$/i.test(found)) return { command: found ?? command, args };
|
|
2795
|
+
const shim = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(found);
|
|
2796
|
+
const line = [found.replace(CMD_META, "^$1"), ...args.map((a) => cmdArg(a, shim))].join(" ");
|
|
2797
|
+
return { command: env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true };
|
|
2798
|
+
}
|
|
2799
|
+
function plainEnv(env, omit = []) {
|
|
2800
|
+
const out = {};
|
|
2801
|
+
const drop = new Set(IS_WIN ? omit.map((k) => k.toUpperCase()) : omit);
|
|
2802
|
+
for (const [k, v] of Object.entries(env)) {
|
|
2803
|
+
if (v === void 0 || drop.has(IS_WIN ? k.toUpperCase() : k)) continue;
|
|
2804
|
+
out[IS_WIN && k.toUpperCase() === "PATH" ? "PATH" : k] = v;
|
|
2805
|
+
}
|
|
2806
|
+
return out;
|
|
2807
|
+
}
|
|
2808
|
+
var IS_WIN, POSIX_SH, isFile, _shell, DETACH_OPTIONS, CMD_META, pathTail, DEFAULT_EDITOR;
|
|
2809
|
+
var init_platform = __esm({
|
|
2810
|
+
"src/platform.ts"() {
|
|
2811
|
+
"use strict";
|
|
2812
|
+
IS_WIN = process.platform === "win32";
|
|
2813
|
+
POSIX_SH = { bin: "/bin/sh", args: (c) => ["-c", c], label: "/bin/sh", kind: "sh" };
|
|
2814
|
+
isFile = (p) => {
|
|
2815
|
+
try {
|
|
2816
|
+
return statSync5(p).isFile();
|
|
2817
|
+
} catch {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
DETACH_OPTIONS = IS_WIN ? { windowsHide: true } : { detached: true };
|
|
2822
|
+
CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
2823
|
+
pathTail = (p) => p.split(IS_WIN ? /[\\/]/ : "/").pop();
|
|
2824
|
+
DEFAULT_EDITOR = IS_WIN ? "notepad" : "vi";
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
|
|
2554
2828
|
// src/shell.sandbox.ts
|
|
2555
2829
|
function writable(cwd, o, tmpDir) {
|
|
2556
2830
|
const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
|
|
@@ -2612,19 +2886,14 @@ __export(tools_shell_exports, {
|
|
|
2612
2886
|
makeShellJobTools: () => makeShellJobTools,
|
|
2613
2887
|
parsePsTime: () => parsePsTime,
|
|
2614
2888
|
psGroupCpuSeconds: () => psGroupCpuSeconds,
|
|
2615
|
-
sumTreeCpu: () => sumTreeCpu
|
|
2889
|
+
sumTreeCpu: () => sumTreeCpu,
|
|
2890
|
+
windowsShellNote: () => windowsShellNote
|
|
2616
2891
|
});
|
|
2617
2892
|
function killGroup(proc, signal) {
|
|
2618
|
-
|
|
2619
|
-
try {
|
|
2620
|
-
process.kill(-proc.pid, signal);
|
|
2621
|
-
return true;
|
|
2622
|
-
} catch {
|
|
2623
|
-
return false;
|
|
2624
|
-
}
|
|
2893
|
+
return killTree(proc?.pid, signal);
|
|
2625
2894
|
}
|
|
2626
2895
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
2627
|
-
if (!osSandbox) return
|
|
2896
|
+
if (!osSandbox) return shellArgv(defaultShell(), command);
|
|
2628
2897
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
2629
2898
|
const wrapper = await findSandboxWrapper();
|
|
2630
2899
|
const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
|
|
@@ -2652,9 +2921,17 @@ function parsePsTime(t) {
|
|
|
2652
2921
|
return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
|
|
2653
2922
|
}
|
|
2654
2923
|
async function psGroupCpuSeconds(pgid) {
|
|
2924
|
+
if (IS_WIN) {
|
|
2925
|
+
try {
|
|
2926
|
+
return await winTreeCpuSeconds(pgid);
|
|
2927
|
+
} catch (e) {
|
|
2928
|
+
log24.debug(`cpu probe failed for ${pgid}: ${e}`);
|
|
2929
|
+
return void 0;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2655
2932
|
try {
|
|
2656
|
-
const { execFile:
|
|
2657
|
-
const out = await new Promise((res, rej) =>
|
|
2933
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
2934
|
+
const out = await new Promise((res, rej) => execFile5("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2658
2935
|
return sumTreeCpu(out, pgid);
|
|
2659
2936
|
} catch (e) {
|
|
2660
2937
|
log24.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
@@ -2705,7 +2982,7 @@ function makeRealShellTool(options) {
|
|
|
2705
2982
|
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2706
2983
|
},
|
|
2707
2984
|
get description() {
|
|
2708
|
-
return
|
|
2985
|
+
return `Run a shell command via ${shellFor(options.shell).label} in the working directory. ` + windowsShellNote(shellFor(options.shell)) + "Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. `sudo -n`), or ask the user to run the command themselves. " + (options.registry?.adopt ? `A command still running after ${defaultTimeoutMs}ms is NOT killed \u2014 it is handed to a background job and you get its id, so you can keep checking on it (\`[still running]\`). Pass \`timeoutMs\` (max ${maxTimeoutMs}) to wait longer in the foreground, and always bound network commands yourself (e.g. \`curl -m 10\`). ` : `Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). `) + backgroundDoc();
|
|
2709
2986
|
},
|
|
2710
2987
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2711
2988
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -2729,7 +3006,7 @@ function makeRealShellTool(options) {
|
|
|
2729
3006
|
return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
|
|
2730
3007
|
}
|
|
2731
3008
|
const spawn4 = options.spawn ?? await nodeSpawn();
|
|
2732
|
-
let argv =
|
|
3009
|
+
let argv = shellArgv(shellFor(options.shell), cmd);
|
|
2733
3010
|
if (options.osSandbox) {
|
|
2734
3011
|
try {
|
|
2735
3012
|
argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
|
|
@@ -2764,22 +3041,23 @@ function makeRealShellTool(options) {
|
|
|
2764
3041
|
}
|
|
2765
3042
|
};
|
|
2766
3043
|
try {
|
|
2767
|
-
return await new Promise((
|
|
3044
|
+
return await new Promise((resolve6) => {
|
|
2768
3045
|
let out = "";
|
|
2769
3046
|
let settled = false;
|
|
2770
3047
|
const finish = (s) => {
|
|
2771
3048
|
if (settled) return;
|
|
2772
3049
|
settled = true;
|
|
2773
|
-
|
|
3050
|
+
resolve6(s);
|
|
2774
3051
|
};
|
|
2775
3052
|
let proc;
|
|
2776
3053
|
try {
|
|
2777
|
-
|
|
3054
|
+
if (IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
3055
|
+
proc = spawn4(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED, ...argv.spawnOptions });
|
|
2778
3056
|
} catch (e) {
|
|
2779
3057
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
2780
3058
|
}
|
|
2781
3059
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
2782
|
-
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
3060
|
+
else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2783
3061
|
const registry = options.registry;
|
|
2784
3062
|
if (registry?.adopt) {
|
|
2785
3063
|
onDeadline = () => {
|
|
@@ -2824,6 +3102,12 @@ function makeRealShellTool(options) {
|
|
|
2824
3102
|
}
|
|
2825
3103
|
};
|
|
2826
3104
|
}
|
|
3105
|
+
function windowsShellNote(sh) {
|
|
3106
|
+
if (!IS_WIN) return "";
|
|
3107
|
+
if (sh.kind === "sh") return "This is Windows: the shell is bash (POSIX syntax); drive paths are written /c/Users/\u2026 or C:/Users/\u2026, and Windows programs (.exe/.cmd) run directly. ";
|
|
3108
|
+
if (sh.kind === "powershell") return "This is Windows and the shell is PowerShell \u2014 use PowerShell syntax (`;` not `&&`, `$env:VAR`, Get-ChildItem/Select-String), not bash. ";
|
|
3109
|
+
return "This is Windows and the shell is cmd.exe \u2014 use cmd syntax (`&&`, `%VAR%`, dir/type/findstr), not bash. ";
|
|
3110
|
+
}
|
|
2827
3111
|
function handoffFor(timeoutMs, id, body, notifies) {
|
|
2828
3112
|
const head = `[still running] exceeded ${timeoutMs}ms, so it was handed to background job ${id} \u2014 NOT killed, it is still going (it is only stopped if it goes silent with no CPU activity for a long time, and then you are told why). Check on it with ShellOutput({id:"${id}"}) / ShellStatus({id:"${id}"}), stop it with ShellKill({id:"${id}"}). ` + (notifies ? "Its completion will be reported to you, so you may continue with other work meanwhile." : "Nothing will tell you when it finishes \u2014 poll it yourself before you rely on its result.");
|
|
2829
3113
|
return body ? `${head}
|
|
@@ -2878,7 +3162,7 @@ ${clean2(out) || "(no output yet)"}`;
|
|
|
2878
3162
|
}
|
|
2879
3163
|
];
|
|
2880
3164
|
}
|
|
2881
|
-
var log24, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
3165
|
+
var log24, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2882
3166
|
var init_tools_shell = __esm({
|
|
2883
3167
|
"src/tools.shell.ts"() {
|
|
2884
3168
|
"use strict";
|
|
@@ -2887,9 +3171,11 @@ var init_tools_shell = __esm({
|
|
|
2887
3171
|
init_logging();
|
|
2888
3172
|
init_shell_sandbox();
|
|
2889
3173
|
init_tools_jobs();
|
|
3174
|
+
init_platform();
|
|
2890
3175
|
log24 = forComponent("shell");
|
|
2891
3176
|
clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2892
|
-
DETACHED = { stdio: ["ignore", "pipe", "pipe"],
|
|
3177
|
+
DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
|
|
3178
|
+
shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
|
|
2893
3179
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
2894
3180
|
ShellJobRegistry = class {
|
|
2895
3181
|
constructor(cfg) {
|
|
@@ -2904,8 +3190,8 @@ var init_tools_shell = __esm({
|
|
|
2904
3190
|
const h = this.track(command, { idleMs: 0 });
|
|
2905
3191
|
try {
|
|
2906
3192
|
const spawn4 = this.cfg.spawn ?? await nodeSpawn();
|
|
2907
|
-
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) :
|
|
2908
|
-
this.attach(h, spawn4(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED }));
|
|
3193
|
+
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : shellArgv(defaultShell(), command);
|
|
3194
|
+
this.attach(h, spawn4(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED, ...argv.spawnOptions }));
|
|
2909
3195
|
} catch (e) {
|
|
2910
3196
|
h.chunk(`failed to spawn: ${e?.message ?? e}`);
|
|
2911
3197
|
h.finish("error", void 0, e?.message ?? String(e));
|
|
@@ -3035,9 +3321,9 @@ var init_tools_shell = __esm({
|
|
|
3035
3321
|
|
|
3036
3322
|
// cli/cli.ts
|
|
3037
3323
|
import { createInterface } from "readline/promises";
|
|
3038
|
-
import { existsSync as existsSync9, readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as
|
|
3324
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync8, unlinkSync as unlinkSync5 } from "fs";
|
|
3039
3325
|
import { homedir as homedir10 } from "os";
|
|
3040
|
-
import { spawnSync as
|
|
3326
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
3041
3327
|
|
|
3042
3328
|
// cli/clipboard.ts
|
|
3043
3329
|
import { execFileSync } from "child_process";
|
|
@@ -3068,6 +3354,15 @@ function grabClipboardImage(dir, id, run = execFileSync, platform2 = process.pla
|
|
|
3068
3354
|
}
|
|
3069
3355
|
return null;
|
|
3070
3356
|
}
|
|
3357
|
+
if (platform2 === "win32") {
|
|
3358
|
+
const ps = "Add-Type -AssemblyName System.Windows.Forms,System.Drawing; $i = [Windows.Forms.Clipboard]::GetImage(); if (-not $i) { exit 1 }; $i.Save($env:AGENTX_CLIP_OUT, [Drawing.Imaging.ImageFormat]::Png)";
|
|
3359
|
+
try {
|
|
3360
|
+
run("powershell.exe", ["-NoProfile", "-NonInteractive", "-STA", "-Command", ps], { ...SILENT, env: { ...process.env, AGENTX_CLIP_OUT: png }, windowsHide: true });
|
|
3361
|
+
if (nonEmpty(png)) return { path: png, mime: "image/png" };
|
|
3362
|
+
} catch {
|
|
3363
|
+
}
|
|
3364
|
+
return null;
|
|
3365
|
+
}
|
|
3071
3366
|
if (platform2 !== "darwin") return null;
|
|
3072
3367
|
const osa = (klass, out) => `set d to (the clipboard as \xABclass ${klass}\xBB)
|
|
3073
3368
|
set f to open for access POSIX file ${q(out)} with write permission
|
|
@@ -3092,10 +3387,13 @@ close access f`;
|
|
|
3092
3387
|
return null;
|
|
3093
3388
|
}
|
|
3094
3389
|
function copyTextToClipboard(text, platform2 = process.platform) {
|
|
3095
|
-
const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] :
|
|
3390
|
+
const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] : (
|
|
3391
|
+
// Windows: Set-Clipboard reading stdin as UTF-8 (clip.exe would decode it with the OEM code page).
|
|
3392
|
+
platform2 === "win32" ? [["powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "[Console]::InputEncoding = [Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())"]]] : []
|
|
3393
|
+
);
|
|
3096
3394
|
for (const [cmd, args] of candidates) {
|
|
3097
3395
|
try {
|
|
3098
|
-
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
3396
|
+
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"], ...platform2 === "win32" ? { windowsHide: true } : {} });
|
|
3099
3397
|
return true;
|
|
3100
3398
|
} catch {
|
|
3101
3399
|
}
|
|
@@ -3104,7 +3402,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
3104
3402
|
}
|
|
3105
3403
|
|
|
3106
3404
|
// cli/cli.ts
|
|
3107
|
-
import { join as
|
|
3405
|
+
import { join as join15, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
|
|
3108
3406
|
import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
|
|
3109
3407
|
|
|
3110
3408
|
// src/ccBridge.ts
|
|
@@ -4340,6 +4638,10 @@ var AgentOptions = class {
|
|
|
4340
4638
|
model = "anthropic/claude-sonnet-4-6";
|
|
4341
4639
|
systemPrompt = "You are a coding agent operating on a virtual filesystem. Use the `bash` tool to explore (ls, grep, find, cat) and the `Read`/`Edit` tools to view and modify files. Always Read a file before Editing it \u2014 and before overwriting it with `Write` or a whole-file `ApplyEdits`: Write replaces the ENTIRE file, so blindly writing over an existing one destroys its unseen contents. If a file already exists, Read it first, then merge your changes (or Edit the specific part). When multiple tool calls are independent (e.g. several Reads or Greps with no dependency between them), issue them together in a single turn rather than one at a time. When the task is complete, reply with a short summary and make no further tool calls.\nRecovery: every turn's file changes are checkpointed. If the user asks to undo/revert/recover edits (including a file you overwrote), tell them to run `/rewind` (or `/undo` for the last turn) to restore the working tree \u2014 checkpoints cover files you edited even in .gitignored dirs. You cannot run `/rewind` yourself; surface it as the recovery path.\nWhen working with a knowledge base or document workspace: use `RepoMap` with scope \"docs\" to see all document headings and summaries (orient first); use `MemorySearch` to find memories by content when you don't know the exact slug; use `Recall` with multiple slugs to batch-load related facts in one call. Prefer targeted loads over reading everything \u2014 orient \u2192 search \u2192 load \u2192 act.";
|
|
4342
4640
|
tools = defaultTools();
|
|
4641
|
+
/** claude-code/* only: which agentx tools reach it as the in-process `agentx` MCP server (ccBridge.ts). 'memory' =
|
|
4642
|
+
* the ones Claude Code has no native twin for (CC_BRIDGED); 'all' = every tool this agent has — for an agent whose
|
|
4643
|
+
* tools ARE its job (the duplex reflex: Act/Think/Hold…), run with Claude Code's own tools off (providerOptions.tools: []). */
|
|
4644
|
+
ccBridge = "memory";
|
|
4343
4645
|
maxSteps = 25;
|
|
4344
4646
|
// --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
|
|
4345
4647
|
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
@@ -4935,8 +5237,9 @@ var Agent = class _Agent {
|
|
|
4935
5237
|
};
|
|
4936
5238
|
const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
|
|
4937
5239
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
4938
|
-
|
|
4939
|
-
|
|
5240
|
+
const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
|
|
5241
|
+
if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
|
|
5242
|
+
this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
|
|
4940
5243
|
claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
4941
5244
|
}
|
|
4942
5245
|
if (this.ccServer) this.ccExec = hostExec;
|
|
@@ -9331,6 +9634,10 @@ function describeCall(call) {
|
|
|
9331
9634
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
9332
9635
|
return `${call.name}${hint}`;
|
|
9333
9636
|
}
|
|
9637
|
+
function refusedCall(e) {
|
|
9638
|
+
const code = Number(e?.statusCode ?? e?.status);
|
|
9639
|
+
return code === 401 || code === 403 || /API key|unauthori[sz]ed|authentication|invalid x-api-key|permission denied/i.test(String(e?.message ?? e ?? ""));
|
|
9640
|
+
}
|
|
9334
9641
|
var DuplexAgentOptions = class {
|
|
9335
9642
|
/** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
|
|
9336
9643
|
ai;
|
|
@@ -9620,12 +9927,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9620
9927
|
timeoutMs: 3e4,
|
|
9621
9928
|
failEmptyTurns: false,
|
|
9622
9929
|
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
9930
|
+
...this.claudeCodeReflex(),
|
|
9623
9931
|
...o.reflexOptions,
|
|
9624
9932
|
tools,
|
|
9625
9933
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
9626
9934
|
hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
|
|
9627
9935
|
});
|
|
9628
9936
|
}
|
|
9937
|
+
/** A claude-code/* reflex (the keyless stand-in when the fast model's provider has no key): Claude Code runs its own
|
|
9938
|
+
* tool loop and takes no host tool schemas, so the reflex's tools (Act/Think/Hold…) reach it through the in-process
|
|
9939
|
+
* `agentx` MCP bridge (ccBridge 'all', which needs a warm session), with Claude Code's OWN tools off (`tools: []`) and
|
|
9940
|
+
* no other MCP server — the reflex only talks and routes; real work still goes through Act and its policy. */
|
|
9941
|
+
claudeCodeReflex() {
|
|
9942
|
+
const o = this.options;
|
|
9943
|
+
if (!o.reflexModel.startsWith("claude-code/")) return {};
|
|
9944
|
+
const po = o.providerOptionsFor?.(o.reflexModel) ?? {};
|
|
9945
|
+
return {
|
|
9946
|
+
ccBridge: "all",
|
|
9947
|
+
providerOptions: { ...po, claudeCodeSession: po.claudeCodeSession ?? crypto.randomUUID(), tools: [], mcpServers: {}, strictMcpConfig: true }
|
|
9948
|
+
};
|
|
9949
|
+
}
|
|
9629
9950
|
/** Resolve memory tools + inject index into voice system prompt (once). */
|
|
9630
9951
|
async initMemory() {
|
|
9631
9952
|
if (!this.memoryReady) return;
|
|
@@ -9790,7 +10111,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9790
10111
|
await this.drainReflex();
|
|
9791
10112
|
this.scrubLeakFromTranscript();
|
|
9792
10113
|
this.scrubHeldText();
|
|
9793
|
-
if (this.silentTurn)
|
|
10114
|
+
if (this.silentTurn && res?.finishReason === "error" && refusedCall(res.error)) this.emitHost({ kind: "text_delta", message: fallback ?? _DuplexAgent.FALLBACK_ERROR });
|
|
10115
|
+
else if (this.silentTurn) await this.ackIfSilent(fallback);
|
|
9794
10116
|
}
|
|
9795
10117
|
/** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
|
|
9796
10118
|
* Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
|
|
@@ -9896,6 +10218,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9896
10218
|
* hosts/tests key on it. */
|
|
9897
10219
|
static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
|
|
9898
10220
|
static FALLBACK_RETRY = ["Sorry, could you say that again?", "Hm, I missed that \u2014 could you say that again?"];
|
|
10221
|
+
static FALLBACK_ERROR = "Sorry, I couldn't answer that \u2014 something went wrong on my side.";
|
|
9899
10222
|
static FALLBACK_OWN = ["Sorry, I lost my train of thought there \u2014 give me that one more time?", "Hm, I got tangled up \u2014 ask me that once more?"];
|
|
9900
10223
|
lastFallback = "";
|
|
9901
10224
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
|
|
@@ -10554,14 +10877,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10554
10877
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
10555
10878
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
10556
10879
|
parkQuestion(askId, question) {
|
|
10557
|
-
return new Promise((
|
|
10880
|
+
return new Promise((resolve6) => {
|
|
10558
10881
|
let settled = false;
|
|
10559
10882
|
const finish = (answer) => {
|
|
10560
10883
|
if (settled) return;
|
|
10561
10884
|
settled = true;
|
|
10562
10885
|
clearTimeout(timer);
|
|
10563
10886
|
this.pendingAsks.delete(askId);
|
|
10564
|
-
|
|
10887
|
+
resolve6(answer);
|
|
10565
10888
|
};
|
|
10566
10889
|
const timer = setTimeout(() => {
|
|
10567
10890
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -11308,11 +11631,11 @@ function buildMcpCatalog(servers) {
|
|
|
11308
11631
|
}
|
|
11309
11632
|
return { specs, routes };
|
|
11310
11633
|
}
|
|
11311
|
-
function searchOverCatalog(servers, specs, routes,
|
|
11634
|
+
function searchOverCatalog(servers, specs, routes, resolve6, options) {
|
|
11312
11635
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
|
|
11313
11636
|
const r = routes.get(name);
|
|
11314
11637
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
11315
|
-
return
|
|
11638
|
+
return resolve6(r.server, r.rawName, args ?? {}, o);
|
|
11316
11639
|
}, options) : [];
|
|
11317
11640
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
11318
11641
|
}
|
|
@@ -11330,6 +11653,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
11330
11653
|
|
|
11331
11654
|
// src/mcp.client.ts
|
|
11332
11655
|
init_logging();
|
|
11656
|
+
init_platform();
|
|
11333
11657
|
import { spawn } from "child_process";
|
|
11334
11658
|
import { createHash } from "crypto";
|
|
11335
11659
|
var log23 = forComponent("mcp");
|
|
@@ -11357,7 +11681,9 @@ var StdioTransport = class {
|
|
|
11357
11681
|
pending = /* @__PURE__ */ new Map();
|
|
11358
11682
|
async start() {
|
|
11359
11683
|
const { command, args = [], env, cwd } = this.spec;
|
|
11360
|
-
const
|
|
11684
|
+
const childEnv2 = { ...process.env, ...env };
|
|
11685
|
+
const sp = resolveSpawn(command, args, childEnv2);
|
|
11686
|
+
const proc = spawn(sp.command, sp.args, { stdio: ["pipe", "pipe", "pipe"], env: childEnv2, cwd, ...IS_WIN ? { windowsHide: true, windowsVerbatimArguments: sp.windowsVerbatimArguments } : {} });
|
|
11361
11687
|
this.proc = proc;
|
|
11362
11688
|
proc.stdout.setEncoding("utf8");
|
|
11363
11689
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -11414,7 +11740,7 @@ var StdioTransport = class {
|
|
|
11414
11740
|
const id = this.nextId++;
|
|
11415
11741
|
const timeoutMs = deadlineFor(method, this.spec, opts);
|
|
11416
11742
|
const { signal, onProgress } = opts;
|
|
11417
|
-
return new Promise((
|
|
11743
|
+
return new Promise((resolve6, reject) => {
|
|
11418
11744
|
if (signal?.aborted) return reject(abortError(signal));
|
|
11419
11745
|
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
11420
11746
|
this.pending.delete(id);
|
|
@@ -11429,7 +11755,7 @@ var StdioTransport = class {
|
|
|
11429
11755
|
};
|
|
11430
11756
|
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
11431
11757
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11432
|
-
this.pending.set(id, { resolve:
|
|
11758
|
+
this.pending.set(id, { resolve: resolve6, reject, timer, onProgress, done });
|
|
11433
11759
|
try {
|
|
11434
11760
|
this.write({ jsonrpc: "2.0", id, method, params: onProgress ? withProgressToken(params, id) : params });
|
|
11435
11761
|
} catch (e) {
|
|
@@ -11450,7 +11776,7 @@ var StdioTransport = class {
|
|
|
11450
11776
|
} catch (e) {
|
|
11451
11777
|
log23.debug("stdin end failed", e);
|
|
11452
11778
|
}
|
|
11453
|
-
this.proc?.kill();
|
|
11779
|
+
if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
|
|
11454
11780
|
}
|
|
11455
11781
|
};
|
|
11456
11782
|
var McpAuthError = class extends Error {
|
|
@@ -11613,12 +11939,12 @@ function buildTransport(cfg) {
|
|
|
11613
11939
|
}
|
|
11614
11940
|
function withTimeout(p, ms, label) {
|
|
11615
11941
|
if (!ms || ms <= 0) return p;
|
|
11616
|
-
return new Promise((
|
|
11942
|
+
return new Promise((resolve6, reject) => {
|
|
11617
11943
|
const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
|
|
11618
11944
|
timer.unref?.();
|
|
11619
11945
|
p.then((v) => {
|
|
11620
11946
|
clearTimeout(timer);
|
|
11621
|
-
|
|
11947
|
+
resolve6(v);
|
|
11622
11948
|
}, (e) => {
|
|
11623
11949
|
clearTimeout(timer);
|
|
11624
11950
|
reject(e);
|
|
@@ -11739,7 +12065,7 @@ var defaultPool = new McpPool();
|
|
|
11739
12065
|
import { createServer } from "http";
|
|
11740
12066
|
import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
|
|
11741
12067
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
11742
|
-
import { dirname as
|
|
12068
|
+
import { dirname as dirname3 } from "path";
|
|
11743
12069
|
var McpOAuthOptions = class {
|
|
11744
12070
|
/** Where to persist tokens. Mode 0600. */
|
|
11745
12071
|
storePath = ".agent/mcp-auth.json";
|
|
@@ -11772,7 +12098,7 @@ var McpOAuth = class {
|
|
|
11772
12098
|
}
|
|
11773
12099
|
}
|
|
11774
12100
|
save(store) {
|
|
11775
|
-
mkdirSync3(
|
|
12101
|
+
mkdirSync3(dirname3(this.options.storePath), { recursive: true });
|
|
11776
12102
|
writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
|
|
11777
12103
|
}
|
|
11778
12104
|
// -- ATTENDED: one-time authorization ----------------------------------------
|
|
@@ -11864,7 +12190,7 @@ var McpOAuth = class {
|
|
|
11864
12190
|
}
|
|
11865
12191
|
/** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
|
|
11866
12192
|
captureCode(authEndpoint, clientId, challenge, state) {
|
|
11867
|
-
return new Promise((
|
|
12193
|
+
return new Promise((resolve6, reject) => {
|
|
11868
12194
|
const server = createServer((req, res) => {
|
|
11869
12195
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
11870
12196
|
if (url.pathname !== "/callback") {
|
|
@@ -11878,7 +12204,7 @@ var McpOAuth = class {
|
|
|
11878
12204
|
clearTimeout(timer);
|
|
11879
12205
|
if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
|
|
11880
12206
|
if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
|
|
11881
|
-
|
|
12207
|
+
resolve6({ code, redirectUri });
|
|
11882
12208
|
});
|
|
11883
12209
|
let redirectUri = "";
|
|
11884
12210
|
const timer = setTimeout(() => {
|
|
@@ -11923,26 +12249,33 @@ function b64url(buf) {
|
|
|
11923
12249
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
11924
12250
|
}
|
|
11925
12251
|
function defaultOpenBrowser(url) {
|
|
11926
|
-
const
|
|
11927
|
-
const
|
|
11928
|
-
|
|
12252
|
+
const win = process.platform === "win32";
|
|
12253
|
+
const cmd = process.platform === "darwin" ? "open" : win ? "rundll32.exe" : "xdg-open";
|
|
12254
|
+
const args = win ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
12255
|
+
import("child_process").then(({ spawn: spawn4 }) => spawn4(cmd, args, { stdio: "ignore", ...win ? { windowsHide: true } : { detached: true } }).unref());
|
|
11929
12256
|
}
|
|
11930
12257
|
|
|
11931
12258
|
// cli/core.ts
|
|
11932
12259
|
import { randomUUID } from "crypto";
|
|
11933
|
-
import { execFile as
|
|
11934
|
-
import { resolve as
|
|
12260
|
+
import { execFile as execFile3 } from "child_process";
|
|
12261
|
+
import { resolve as resolve3, basename as basename3, join as join6 } from "path";
|
|
11935
12262
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
11936
12263
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
11937
12264
|
init_tools_shell();
|
|
11938
12265
|
|
|
11939
12266
|
// src/tools.notify.ts
|
|
11940
12267
|
init_logging();
|
|
11941
|
-
import { execFile } from "child_process";
|
|
12268
|
+
import { execFile as execFile2 } from "child_process";
|
|
11942
12269
|
var log25 = forComponent("notify");
|
|
12270
|
+
var WIN_TOAST = [
|
|
12271
|
+
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
|
|
12272
|
+
"$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
|
|
12273
|
+
'$n = $t.GetElementsByTagName("text"); $n.Item(0).AppendChild($t.CreateTextNode($env:AGENTX_NOTIFY_TITLE)) | Out-Null; $n.Item(1).AppendChild($t.CreateTextNode($env:AGENTX_NOTIFY_BODY)) | Out-Null',
|
|
12274
|
+
'[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe").Show([Windows.UI.Notifications.ToastNotification]::new($t))'
|
|
12275
|
+
].join("; ");
|
|
11943
12276
|
function makeNotifyTool(opts = {}) {
|
|
11944
12277
|
const platform2 = opts.platform ?? process.platform;
|
|
11945
|
-
const run = opts.exec ??
|
|
12278
|
+
const run = opts.exec ?? execFile2;
|
|
11946
12279
|
return {
|
|
11947
12280
|
name: "PushNotification",
|
|
11948
12281
|
description: "Show an OS desktop notification to the user (out-of-band alert \u2014 e.g. a long task finished, input is needed). Use sparingly: only for events the user would want to be interrupted for.",
|
|
@@ -11958,14 +12291,15 @@ function makeNotifyTool(opts = {}) {
|
|
|
11958
12291
|
const msg = String(message ?? "").slice(0, 256);
|
|
11959
12292
|
const head = String(title ?? "agentx").slice(0, 64);
|
|
11960
12293
|
if (!msg) return "Error: empty message";
|
|
11961
|
-
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
|
|
12294
|
+
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : platform2 === "win32" ? ["powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_TOAST]] : null;
|
|
11962
12295
|
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
11963
|
-
|
|
11964
|
-
|
|
12296
|
+
const extra = platform2 === "win32" ? { windowsHide: true, env: { ...process.env, AGENTX_NOTIFY_TITLE: head, AGENTX_NOTIFY_BODY: msg } } : {};
|
|
12297
|
+
return new Promise((resolve6) => {
|
|
12298
|
+
run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
|
|
11965
12299
|
if (e) {
|
|
11966
12300
|
log25.debug("notification failed", e);
|
|
11967
|
-
|
|
11968
|
-
} else
|
|
12301
|
+
resolve6(`Notification failed: ${e.message}`);
|
|
12302
|
+
} else resolve6("Notification shown.");
|
|
11969
12303
|
});
|
|
11970
12304
|
});
|
|
11971
12305
|
}
|
|
@@ -12013,13 +12347,15 @@ function readJsonFile(path, fallback) {
|
|
|
12013
12347
|
}
|
|
12014
12348
|
|
|
12015
12349
|
// cli/core.ts
|
|
12350
|
+
init_hostPath();
|
|
12351
|
+
init_platform();
|
|
12016
12352
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
12017
12353
|
function autoWebTools() {
|
|
12018
12354
|
return ["WebFetch", "WebSearch"];
|
|
12019
12355
|
}
|
|
12020
12356
|
function pdfTextViaPoppler(path) {
|
|
12021
12357
|
return new Promise((res, rej) => {
|
|
12022
|
-
|
|
12358
|
+
execFile3("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
|
|
12023
12359
|
if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
|
|
12024
12360
|
else res(stdout);
|
|
12025
12361
|
});
|
|
@@ -12083,7 +12419,7 @@ function toClaudeCodeMcp(servers) {
|
|
|
12083
12419
|
function claudeCodeProviderOptions(model, cwd, mcpServers) {
|
|
12084
12420
|
if (!(model ?? "").startsWith("claude-code/")) return void 0;
|
|
12085
12421
|
const env = (() => {
|
|
12086
|
-
const base = process.env.AGENTX_CC_USE_API_KEY ?
|
|
12422
|
+
const base = plainEnv(process.env, process.env.AGENTX_CC_USE_API_KEY ? [] : ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"]);
|
|
12087
12423
|
return { ENABLE_CLAUDEAI_MCP_SERVERS: "false", ...base };
|
|
12088
12424
|
})();
|
|
12089
12425
|
const warm = process.env.AGENTX_CC_WARM === "0" ? {} : { claudeCodeSession: randomUUID() };
|
|
@@ -12097,7 +12433,7 @@ async function buildAgent(o) {
|
|
|
12097
12433
|
throw new Error(
|
|
12098
12434
|
"buildAgent: explicit `sandbox` required \u2014 false = disk (the REAL filesystem, full reach like Claude Code; secrets still hidden) or true = sandbox (an in-memory copy of cwd; the real disk is never modified). This forces a conscious sandboxing decision."
|
|
12099
12435
|
);
|
|
12100
|
-
const cwd =
|
|
12436
|
+
const cwd = resolve3(o.cwd ?? process.cwd());
|
|
12101
12437
|
const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
|
|
12102
12438
|
await disk.init();
|
|
12103
12439
|
const jailedDisk = new JailedFilesystem(disk);
|
|
@@ -12111,21 +12447,22 @@ async function buildAgent(o) {
|
|
|
12111
12447
|
`${isCursor ? "cursor" : "claude-code"}/* models cannot run in --sandbox/--boddb: the delegated agent runs its own real-disk tools and bypasses the VFS jail. Use disk mode (default).`
|
|
12112
12448
|
);
|
|
12113
12449
|
let fs = jailedDisk;
|
|
12450
|
+
const vcwd = toVfsPath(cwd);
|
|
12114
12451
|
if (o.sandbox) {
|
|
12115
|
-
const mem = new MemFilesystem3();
|
|
12116
|
-
await mkdirp(mem,
|
|
12117
|
-
await hydrate(jailedDisk, mem,
|
|
12118
|
-
mem.setCwd(
|
|
12452
|
+
const mem = withHostPaths(new MemFilesystem3());
|
|
12453
|
+
await mkdirp(mem, vcwd);
|
|
12454
|
+
await hydrate(jailedDisk, mem, vcwd);
|
|
12455
|
+
mem.setCwd(vcwd);
|
|
12119
12456
|
fs = mem;
|
|
12120
12457
|
} else if (o.boddb) {
|
|
12121
|
-
const root =
|
|
12458
|
+
const root = resolve3(o.boddb);
|
|
12122
12459
|
mkdirSync4(root, { recursive: true });
|
|
12123
|
-
const db = new BodDB2({ path:
|
|
12124
|
-
const bod = new BodDbFilesystem(db);
|
|
12460
|
+
const db = new BodDB2({ path: join6(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join6(root, "files") } });
|
|
12461
|
+
const bod = withHostPaths(new BodDbFilesystem(db));
|
|
12125
12462
|
const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
|
|
12126
|
-
await mkdirp(bod,
|
|
12127
|
-
if (o.seed && firstRun) await hydrate(jailedDisk, bod,
|
|
12128
|
-
bod.setCwd(
|
|
12463
|
+
await mkdirp(bod, vcwd);
|
|
12464
|
+
if (o.seed && firstRun) await hydrate(jailedDisk, bod, vcwd);
|
|
12465
|
+
bod.setCwd(vcwd);
|
|
12129
12466
|
fs = bod;
|
|
12130
12467
|
}
|
|
12131
12468
|
let mountNote = "";
|
|
@@ -12134,8 +12471,8 @@ async function buildAgent(o) {
|
|
|
12134
12471
|
const mounts = [];
|
|
12135
12472
|
const notes = [];
|
|
12136
12473
|
for (const d of o.addDirs) {
|
|
12137
|
-
const abs =
|
|
12138
|
-
let name =
|
|
12474
|
+
const abs = resolve3(d);
|
|
12475
|
+
let name = basename3(abs) || "dir", prefix = `/${name}`, n = 2;
|
|
12139
12476
|
while (used.has(prefix)) prefix = `/${name}-${n++}`;
|
|
12140
12477
|
used.add(prefix);
|
|
12141
12478
|
const md = new NodeDiskFilesystem(abs);
|
|
@@ -12187,7 +12524,7 @@ Reference files in them by their mount path (the left side).`;
|
|
|
12187
12524
|
});
|
|
12188
12525
|
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
|
|
12189
12526
|
}
|
|
12190
|
-
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${
|
|
12527
|
+
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${vcwd}/.agent/scratch` : toVfsPath(`${tmpdir()}/agentx-scratch-${process.pid}`)) : void 0;
|
|
12191
12528
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
12192
12529
|
return agentRef.agent = new Agent({
|
|
12193
12530
|
ai: o.ai,
|
|
@@ -12212,12 +12549,13 @@ Reference files in them by their mount path (the left side).`;
|
|
|
12212
12549
|
const envNote = `Current date: ${now4.toLocaleDateString("en-CA")} ${now4.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })} (${Intl.DateTimeFormat().resolvedOptions().timeZone})
|
|
12213
12550
|
Platform: ${platformNames[platform()] ?? platform()} ${arch()} (${release()})
|
|
12214
12551
|
User: ${userInfo().username}
|
|
12215
|
-
Shell: ${process.env.SHELL ?? "unknown"}`;
|
|
12552
|
+
Shell: ${process.env.SHELL ?? (isWinPaths() ? defaultShell().label : "unknown")}`;
|
|
12216
12553
|
const cwdNote = o.boddb ? `Working directory: ${cwd}
|
|
12217
12554
|
DATABASE WORKSPACE: your files live in a database (bod-db), not on the real disk \u2014 the real disk is never modified and nothing outside this tree exists. Use paths relative to the working directory, or absolute paths under it.` : o.sandbox ? `Working directory: ${cwd}
|
|
12218
12555
|
SANDBOX: you operate on an in-memory COPY of this directory \u2014 the real disk is never modified and nothing outside it exists. Use paths relative to the working directory, or absolute paths under it.` : `Working directory: ${cwd}
|
|
12219
|
-
The filesystem root '/' is the real machine root \u2014 you have full filesystem access, like a normal shell. Use paths relative to the working directory, or absolute paths. Secret files (.env, .ssh, keys, .git) are hidden for safety
|
|
12220
|
-
|
|
12556
|
+
The filesystem root '/' is the real machine root \u2014 you have full filesystem access, like a normal shell. Use paths relative to the working directory, or absolute paths. Secret files (.env, .ssh, keys, .git) are hidden for safety.` + (isWinPaths() ? `
|
|
12557
|
+
Windows paths: file tools accept ${cwd} style paths and the same path written ${vcwd} (drive letter as the first segment); tool results list paths in the ${vcwd.split("/").slice(0, 2).join("/")}/\u2026 form. '/' itself lists the drives.` : "");
|
|
12558
|
+
const shellNote = useRealShell && !virtual ? `The \`Shell\` tool runs ${defaultShell().label} \u2014 you can run any installed binary (git, bun, node, curl, etc). Use \`Shell\` for all shell commands.` : void 0;
|
|
12221
12559
|
const toneNote = "Be concise. Do not use emojis unless the user asks. Do not explain what you are about to do \u2014 just do it.";
|
|
12222
12560
|
const mcpNote = (() => {
|
|
12223
12561
|
const names = new Set(Object.keys(o.mcpServers ?? {}));
|
|
@@ -12300,10 +12638,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
12300
12638
|
|
|
12301
12639
|
// cli/voice.ts
|
|
12302
12640
|
init_logging();
|
|
12303
|
-
|
|
12304
|
-
import {
|
|
12641
|
+
init_platform();
|
|
12642
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
12643
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync6 } from "fs";
|
|
12305
12644
|
import { homedir as homedir3 } from "os";
|
|
12306
|
-
import { dirname as
|
|
12645
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
12307
12646
|
import { fileURLToPath } from "url";
|
|
12308
12647
|
var log27 = forComponent("VoiceIO");
|
|
12309
12648
|
var now3 = () => performance.now();
|
|
@@ -12346,10 +12685,10 @@ var Player = class {
|
|
|
12346
12685
|
this.proc = null;
|
|
12347
12686
|
}
|
|
12348
12687
|
};
|
|
12349
|
-
var nativeDir = () =>
|
|
12688
|
+
var nativeDir = () => join7(dirname4(fileURLToPath(import.meta.url)), "native");
|
|
12350
12689
|
function detectFfmpegMic() {
|
|
12351
12690
|
if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
|
|
12352
|
-
const r =
|
|
12691
|
+
const r = spawnSync3("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
|
|
12353
12692
|
if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
|
|
12354
12693
|
if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
|
|
12355
12694
|
const out = r.stderr ?? "";
|
|
@@ -12360,13 +12699,24 @@ function detectFfmpegMic() {
|
|
|
12360
12699
|
log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
12361
12700
|
return `:${mic.idx}`;
|
|
12362
12701
|
}
|
|
12702
|
+
function detectDshowMic() {
|
|
12703
|
+
const env = process.env.MIC_DEVICE;
|
|
12704
|
+
if (env) return env.startsWith("audio=") ? env : `audio=${env}`;
|
|
12705
|
+
const r = spawnSync3("ffmpeg", ["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"], { encoding: "utf8", windowsHide: true });
|
|
12706
|
+
if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 install it (e.g. `winget install ffmpeg`) and put it on PATH to enable voice");
|
|
12707
|
+
const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
|
|
12708
|
+
const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
|
|
12709
|
+
if (!mic) throw new Error("no audio input device found (set MIC_DEVICE to the DirectShow device name)");
|
|
12710
|
+
log27.debug(`ffmpeg dshow mic: ${mic}`);
|
|
12711
|
+
return `audio=${mic}`;
|
|
12712
|
+
}
|
|
12363
12713
|
function detectedInputDevice() {
|
|
12364
12714
|
if (process.platform !== "darwin") return null;
|
|
12365
12715
|
let name = "";
|
|
12366
|
-
if (
|
|
12367
|
-
name = (
|
|
12716
|
+
if (spawnSync3("which", ["SwitchAudioSource"]).status === 0) {
|
|
12717
|
+
name = (spawnSync3("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
|
|
12368
12718
|
} else {
|
|
12369
|
-
const out =
|
|
12719
|
+
const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
|
|
12370
12720
|
let last = "";
|
|
12371
12721
|
for (const ln of out.split("\n")) {
|
|
12372
12722
|
const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
|
|
@@ -12385,21 +12735,21 @@ function detectedInputDevice() {
|
|
|
12385
12735
|
}
|
|
12386
12736
|
function resolveAecBinary() {
|
|
12387
12737
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12388
|
-
const src =
|
|
12389
|
-
const plist =
|
|
12738
|
+
const src = join7(nativeDir(), "mic-aec.swift");
|
|
12739
|
+
const plist = join7(nativeDir(), "Info.plist");
|
|
12390
12740
|
if (!existsSync4(src)) return null;
|
|
12391
|
-
const cacheDir =
|
|
12392
|
-
const bin =
|
|
12393
|
-
if (existsSync4(bin) &&
|
|
12394
|
-
if (
|
|
12741
|
+
const cacheDir = join7(homedir3(), ".agent", "cache");
|
|
12742
|
+
const bin = join7(cacheDir, "mic-aec");
|
|
12743
|
+
if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
|
|
12744
|
+
if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
|
|
12395
12745
|
mkdirSync5(cacheDir, { recursive: true });
|
|
12396
12746
|
log27.info("compiling AEC mic helper (first run)\u2026");
|
|
12397
|
-
const build =
|
|
12747
|
+
const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
12398
12748
|
if (build.status !== 0) {
|
|
12399
12749
|
log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
12400
12750
|
return null;
|
|
12401
12751
|
}
|
|
12402
|
-
const sign =
|
|
12752
|
+
const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
12403
12753
|
if (sign.status !== 0) {
|
|
12404
12754
|
log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
12405
12755
|
return null;
|
|
@@ -12409,14 +12759,14 @@ function resolveAecBinary() {
|
|
|
12409
12759
|
function openMicSettings() {
|
|
12410
12760
|
if (process.platform !== "darwin") return;
|
|
12411
12761
|
try {
|
|
12412
|
-
|
|
12762
|
+
spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
|
|
12413
12763
|
} catch {
|
|
12414
12764
|
}
|
|
12415
12765
|
}
|
|
12416
12766
|
function micPermissionStatus() {
|
|
12417
12767
|
const bin = resolveAecBinary();
|
|
12418
12768
|
if (!bin) return null;
|
|
12419
|
-
const r =
|
|
12769
|
+
const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
|
|
12420
12770
|
const out = (r.stdout ?? "").trim();
|
|
12421
12771
|
if (out === "authorized") return "authorized";
|
|
12422
12772
|
if (out === "denied" || out === "restricted") return "denied";
|
|
@@ -12426,7 +12776,7 @@ function micPermissionStatus() {
|
|
|
12426
12776
|
function aecUnavailableHint() {
|
|
12427
12777
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12428
12778
|
if (resolveAecBinary()) return null;
|
|
12429
|
-
if (
|
|
12779
|
+
if (spawnSync3("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
|
|
12430
12780
|
return null;
|
|
12431
12781
|
}
|
|
12432
12782
|
var NodeMicSource = class {
|
|
@@ -12442,12 +12792,13 @@ var NodeMicSource = class {
|
|
|
12442
12792
|
if (this.bin) {
|
|
12443
12793
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
12444
12794
|
} else {
|
|
12445
|
-
if (
|
|
12795
|
+
if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
12446
12796
|
log27.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
12797
|
+
const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
|
|
12447
12798
|
this.proc = spawn2(
|
|
12448
12799
|
"ffmpeg",
|
|
12449
|
-
["-loglevel", "error",
|
|
12450
|
-
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
12800
|
+
["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
12801
|
+
{ stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
|
|
12451
12802
|
);
|
|
12452
12803
|
this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
|
|
12453
12804
|
}
|
|
@@ -12834,7 +13185,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
12834
13185
|
// src/voice/diagSink.ts
|
|
12835
13186
|
init_logging();
|
|
12836
13187
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
12837
|
-
import { dirname as
|
|
13188
|
+
import { dirname as dirname5 } from "path";
|
|
12838
13189
|
var log28 = forComponent("VoiceDiag");
|
|
12839
13190
|
var JsonlDiagSink = class {
|
|
12840
13191
|
constructor(path) {
|
|
@@ -12859,7 +13210,7 @@ var JsonlDiagSink = class {
|
|
|
12859
13210
|
if (this.writing || this.disabled || !this.queue.length) return;
|
|
12860
13211
|
if (!this.dirReady) {
|
|
12861
13212
|
try {
|
|
12862
|
-
mkdirSync6(
|
|
13213
|
+
mkdirSync6(dirname5(this.path), { recursive: true });
|
|
12863
13214
|
this.dirReady = true;
|
|
12864
13215
|
} catch (e) {
|
|
12865
13216
|
return this.disable(e);
|
|
@@ -12891,13 +13242,13 @@ var JsonlDiagSink = class {
|
|
|
12891
13242
|
// cli/config.ts
|
|
12892
13243
|
import { homedir as homedir5 } from "os";
|
|
12893
13244
|
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
12894
|
-
import { join as
|
|
13245
|
+
import { join as join9 } from "path";
|
|
12895
13246
|
import { pathToFileURL } from "url";
|
|
12896
13247
|
|
|
12897
13248
|
// cli/permissions.ts
|
|
12898
13249
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
12899
13250
|
import { homedir as homedir4 } from "os";
|
|
12900
|
-
import { join as
|
|
13251
|
+
import { join as join8 } from "path";
|
|
12901
13252
|
var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
|
|
12902
13253
|
function parseOne(raw, decision) {
|
|
12903
13254
|
const m = RULE_RE.exec(raw.trim());
|
|
@@ -12919,13 +13270,13 @@ function parsePermRules(perms, explicit = false) {
|
|
|
12919
13270
|
function describeRule(r) {
|
|
12920
13271
|
return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
|
|
12921
13272
|
}
|
|
12922
|
-
var PERM_FILE = (cwd) =>
|
|
13273
|
+
var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
|
|
12923
13274
|
function loadPersistedRules(cwd) {
|
|
12924
13275
|
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
12925
13276
|
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
12926
13277
|
}
|
|
12927
13278
|
function loadClaudeSettings(cwd, home = homedir4()) {
|
|
12928
|
-
const files = [
|
|
13279
|
+
const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
|
|
12929
13280
|
let out = {};
|
|
12930
13281
|
for (const p of files) {
|
|
12931
13282
|
const perms = readJsonFile(p, null)?.permissions;
|
|
@@ -12938,7 +13289,7 @@ function persistRule(cwd, decision, ruleStr) {
|
|
|
12938
13289
|
const list = cur[decision] ??= [];
|
|
12939
13290
|
if (!list.includes(ruleStr)) list.push(ruleStr);
|
|
12940
13291
|
try {
|
|
12941
|
-
mkdirSync7(
|
|
13292
|
+
mkdirSync7(join8(cwd, ".agent"), { recursive: true });
|
|
12942
13293
|
writeFileSync4(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
|
|
12943
13294
|
} catch {
|
|
12944
13295
|
}
|
|
@@ -12952,7 +13303,7 @@ function mergePerms(a, b) {
|
|
|
12952
13303
|
}
|
|
12953
13304
|
return Object.keys(out).length ? out : void 0;
|
|
12954
13305
|
}
|
|
12955
|
-
var TRUST_FILE =
|
|
13306
|
+
var TRUST_FILE = join8(homedir4(), ".agent", "trusted.json");
|
|
12956
13307
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
12957
13308
|
const list = readJsonFile(file, []);
|
|
12958
13309
|
return Array.isArray(list) && list.includes(cwd);
|
|
@@ -12962,7 +13313,7 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
12962
13313
|
if (!Array.isArray(list)) list = [];
|
|
12963
13314
|
if (!list.includes(cwd)) list.push(cwd);
|
|
12964
13315
|
try {
|
|
12965
|
-
mkdirSync7(
|
|
13316
|
+
mkdirSync7(join8(file, ".."), { recursive: true });
|
|
12966
13317
|
writeFileSync4(file, JSON.stringify(list, null, 2) + "\n");
|
|
12967
13318
|
} catch {
|
|
12968
13319
|
}
|
|
@@ -12972,7 +13323,7 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
12972
13323
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
12973
13324
|
async function loadFrom(dir) {
|
|
12974
13325
|
for (const f of FILES) {
|
|
12975
|
-
const p =
|
|
13326
|
+
const p = join9(dir, ".agent", f);
|
|
12976
13327
|
if (!existsSync5(p)) continue;
|
|
12977
13328
|
try {
|
|
12978
13329
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
@@ -12985,7 +13336,7 @@ async function loadFrom(dir) {
|
|
|
12985
13336
|
return {};
|
|
12986
13337
|
}
|
|
12987
13338
|
function loadSettings(dir) {
|
|
12988
|
-
const p =
|
|
13339
|
+
const p = join9(dir, ".agent", "settings.json");
|
|
12989
13340
|
if (!existsSync5(p)) return {};
|
|
12990
13341
|
try {
|
|
12991
13342
|
const raw = JSON.parse(readFileSync7(p, "utf8"));
|
|
@@ -13032,7 +13383,8 @@ function applyOverlay(cfg, file) {
|
|
|
13032
13383
|
}
|
|
13033
13384
|
|
|
13034
13385
|
// cli/hooks-config.ts
|
|
13035
|
-
import { spawnSync as
|
|
13386
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
13387
|
+
init_platform();
|
|
13036
13388
|
var log29 = forComponent("hooks");
|
|
13037
13389
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
13038
13390
|
function ruleMatches(rule, toolName) {
|
|
@@ -13042,8 +13394,10 @@ function ruleMatches(rule, toolName) {
|
|
|
13042
13394
|
}
|
|
13043
13395
|
function runCmd(rule, env) {
|
|
13044
13396
|
try {
|
|
13045
|
-
const r =
|
|
13046
|
-
shell:
|
|
13397
|
+
const r = spawnSync4(rule.command, {
|
|
13398
|
+
shell: shellOption(),
|
|
13399
|
+
// /bin/sh; Windows: Git Bash when installed (hooks are written in sh)
|
|
13400
|
+
windowsHide: true,
|
|
13047
13401
|
encoding: "utf8",
|
|
13048
13402
|
timeout: rule.timeoutMs ?? 1e4,
|
|
13049
13403
|
env: { ...process.env, ...env }
|
|
@@ -13154,26 +13508,45 @@ function formatDiff(ops, opts = {}) {
|
|
|
13154
13508
|
}
|
|
13155
13509
|
|
|
13156
13510
|
// cli/session.ts
|
|
13157
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as
|
|
13511
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync7 } from "fs";
|
|
13158
13512
|
import { homedir as homedir6 } from "os";
|
|
13159
|
-
import { join as
|
|
13513
|
+
import { join as join10 } from "path";
|
|
13514
|
+
init_platform();
|
|
13160
13515
|
var log30 = forComponent("session");
|
|
13161
|
-
var globalDir = () =>
|
|
13516
|
+
var globalDir = () => join10(homedir6(), ".agent", "sessions");
|
|
13517
|
+
function indexLink(target, link2) {
|
|
13518
|
+
try {
|
|
13519
|
+
symlinkSync2(target, link2);
|
|
13520
|
+
} catch (e) {
|
|
13521
|
+
if (!IS_WIN) throw e;
|
|
13522
|
+
writeFileSync5(link2, JSON.stringify({ agentxSessionLink: target }));
|
|
13523
|
+
}
|
|
13524
|
+
}
|
|
13525
|
+
function readIndexLink(link2) {
|
|
13526
|
+
try {
|
|
13527
|
+
return readlinkSync(link2);
|
|
13528
|
+
} catch (e) {
|
|
13529
|
+
if (!IS_WIN) throw e;
|
|
13530
|
+
const target = JSON.parse(readFileSync8(link2, "utf8")).agentxSessionLink;
|
|
13531
|
+
if (typeof target !== "string") throw e;
|
|
13532
|
+
return target;
|
|
13533
|
+
}
|
|
13534
|
+
}
|
|
13162
13535
|
var SessionStore = class {
|
|
13163
13536
|
dir;
|
|
13164
13537
|
constructor(cwd) {
|
|
13165
|
-
this.dir =
|
|
13538
|
+
this.dir = join10(cwd, ".agent", "sessions");
|
|
13166
13539
|
}
|
|
13167
13540
|
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
13168
13541
|
newId(now4 = Date.now(), cwd) {
|
|
13169
13542
|
const d = new Date(now4);
|
|
13170
13543
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
13171
|
-
const slug2 = (cwd ?? process.cwd())
|
|
13544
|
+
const slug2 = pathTail(cwd ?? process.cwd())?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
13172
13545
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
13173
|
-
if (existsSync6(this.dir) && existsSync6(
|
|
13546
|
+
if (existsSync6(this.dir) && existsSync6(join10(this.dir, `${id}.json`))) {
|
|
13174
13547
|
for (let i = 2; i <= 99; i++) {
|
|
13175
13548
|
const c = `${id}-${i}`;
|
|
13176
|
-
if (!existsSync6(
|
|
13549
|
+
if (!existsSync6(join10(this.dir, `${c}.json`))) {
|
|
13177
13550
|
id = c;
|
|
13178
13551
|
break;
|
|
13179
13552
|
}
|
|
@@ -13188,15 +13561,15 @@ var SessionStore = class {
|
|
|
13188
13561
|
save(data) {
|
|
13189
13562
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
13190
13563
|
if (!existsSync6(this.dir)) mkdirSync8(this.dir, { recursive: true });
|
|
13191
|
-
const path =
|
|
13564
|
+
const path = join10(this.dir, `${data.meta.id}.json`);
|
|
13192
13565
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
13193
13566
|
writeFileSync5(tmp, JSON.stringify(data));
|
|
13194
13567
|
renameSync(tmp, path);
|
|
13195
13568
|
try {
|
|
13196
13569
|
const gd = globalDir();
|
|
13197
13570
|
if (!existsSync6(gd)) mkdirSync8(gd, { recursive: true });
|
|
13198
|
-
const link2 =
|
|
13199
|
-
if (!existsSync6(link2))
|
|
13571
|
+
const link2 = join10(gd, `${data.meta.id}.json`);
|
|
13572
|
+
if (!existsSync6(link2)) indexLink(path, link2);
|
|
13200
13573
|
} catch {
|
|
13201
13574
|
}
|
|
13202
13575
|
}
|
|
@@ -13205,7 +13578,7 @@ var SessionStore = class {
|
|
|
13205
13578
|
log30.debug(`rejecting unsafe session id: ${id}`);
|
|
13206
13579
|
return void 0;
|
|
13207
13580
|
}
|
|
13208
|
-
const path =
|
|
13581
|
+
const path = join10(this.dir, `${id}.json`);
|
|
13209
13582
|
if (!existsSync6(path)) return void 0;
|
|
13210
13583
|
try {
|
|
13211
13584
|
return JSON.parse(readFileSync8(path, "utf8"));
|
|
@@ -13221,7 +13594,7 @@ var SessionStore = class {
|
|
|
13221
13594
|
for (const f of readdirSync(this.dir)) {
|
|
13222
13595
|
if (!f.endsWith(".json")) continue;
|
|
13223
13596
|
try {
|
|
13224
|
-
metas.push(JSON.parse(readFileSync8(
|
|
13597
|
+
metas.push(JSON.parse(readFileSync8(join10(this.dir, f), "utf8")).meta);
|
|
13225
13598
|
} catch (e) {
|
|
13226
13599
|
log30.debug(`skipping unreadable session file ${f}`, e);
|
|
13227
13600
|
}
|
|
@@ -13236,7 +13609,7 @@ var SessionStore = class {
|
|
|
13236
13609
|
for (const f of readdirSync(this.dir)) {
|
|
13237
13610
|
if (!f.endsWith(".json")) continue;
|
|
13238
13611
|
try {
|
|
13239
|
-
const st =
|
|
13612
|
+
const st = statSync7(join10(this.dir, f));
|
|
13240
13613
|
out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
|
|
13241
13614
|
} catch {
|
|
13242
13615
|
}
|
|
@@ -13255,10 +13628,10 @@ var SessionStore = class {
|
|
|
13255
13628
|
function globalSessionLoad(idOrPrefix) {
|
|
13256
13629
|
const gd = globalDir();
|
|
13257
13630
|
if (!existsSync6(gd)) return void 0;
|
|
13258
|
-
const exact =
|
|
13631
|
+
const exact = join10(gd, `${idOrPrefix}.json`);
|
|
13259
13632
|
if (existsSync6(exact)) {
|
|
13260
13633
|
try {
|
|
13261
|
-
const target =
|
|
13634
|
+
const target = readIndexLink(exact);
|
|
13262
13635
|
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13263
13636
|
} catch {
|
|
13264
13637
|
return void 0;
|
|
@@ -13269,7 +13642,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
13269
13642
|
if (!f.endsWith(".json")) continue;
|
|
13270
13643
|
const base = f.slice(0, -5);
|
|
13271
13644
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
13272
|
-
const target =
|
|
13645
|
+
const target = readIndexLink(join10(gd, f));
|
|
13273
13646
|
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13274
13647
|
}
|
|
13275
13648
|
}
|
|
@@ -13284,10 +13657,10 @@ function globalSessionList() {
|
|
|
13284
13657
|
for (const f of readdirSync(gd)) {
|
|
13285
13658
|
if (!f.endsWith(".json")) continue;
|
|
13286
13659
|
try {
|
|
13287
|
-
const target =
|
|
13660
|
+
const target = readIndexLink(join10(gd, f));
|
|
13288
13661
|
if (!existsSync6(target)) {
|
|
13289
13662
|
try {
|
|
13290
|
-
unlinkSync(
|
|
13663
|
+
unlinkSync(join10(gd, f));
|
|
13291
13664
|
} catch {
|
|
13292
13665
|
}
|
|
13293
13666
|
continue;
|
|
@@ -13457,17 +13830,17 @@ ${formatDiff(ops)}`);
|
|
|
13457
13830
|
};
|
|
13458
13831
|
|
|
13459
13832
|
// cli/gitCheckpoints.ts
|
|
13460
|
-
import { execFile as
|
|
13833
|
+
import { execFile as execFile4 } from "child_process";
|
|
13461
13834
|
import { promisify } from "util";
|
|
13462
13835
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
13463
13836
|
import { homedir as homedir7 } from "os";
|
|
13464
|
-
import { join as
|
|
13837
|
+
import { join as join11, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
|
|
13465
13838
|
var log31 = forComponent("checkpoints");
|
|
13466
|
-
var exec = promisify(
|
|
13839
|
+
var exec = promisify(execFile4);
|
|
13467
13840
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
13468
13841
|
function isBroadRoot(p) {
|
|
13469
|
-
const abs =
|
|
13470
|
-
return abs ===
|
|
13842
|
+
const abs = resolve4(p);
|
|
13843
|
+
return abs === resolve4(homedir7()) || abs === parsePath(abs).root;
|
|
13471
13844
|
}
|
|
13472
13845
|
var ShadowRepo = class {
|
|
13473
13846
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -13504,8 +13877,8 @@ var ShadowRepo = class {
|
|
|
13504
13877
|
mkdirSync9(this.gitDir, { recursive: true });
|
|
13505
13878
|
const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
|
|
13506
13879
|
if (!valid) await this.run("init", "-q");
|
|
13507
|
-
mkdirSync9(
|
|
13508
|
-
writeFileSync6(
|
|
13880
|
+
mkdirSync9(join11(this.gitDir, "info"), { recursive: true });
|
|
13881
|
+
writeFileSync6(join11(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
13509
13882
|
this.ready = true;
|
|
13510
13883
|
} catch (e) {
|
|
13511
13884
|
log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
@@ -13614,13 +13987,13 @@ var GitCheckpoints = class {
|
|
|
13614
13987
|
}
|
|
13615
13988
|
/** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
|
|
13616
13989
|
roots() {
|
|
13617
|
-
const cwd =
|
|
13990
|
+
const cwd = resolve4(this.options.workTree);
|
|
13618
13991
|
const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
|
|
13619
13992
|
for (const d of this.options.addDirs ?? []) {
|
|
13620
|
-
const abs =
|
|
13993
|
+
const abs = resolve4(d);
|
|
13621
13994
|
if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
|
|
13622
13995
|
if (cwd.startsWith(abs + sep3)) continue;
|
|
13623
|
-
out.push({ workTree: abs, gitDir:
|
|
13996
|
+
out.push({ workTree: abs, gitDir: join11(abs, ".agent", "checkpoints.git") });
|
|
13624
13997
|
}
|
|
13625
13998
|
return out;
|
|
13626
13999
|
}
|
|
@@ -13707,7 +14080,7 @@ var GitCheckpoints = class {
|
|
|
13707
14080
|
preToolUse: async (call) => {
|
|
13708
14081
|
if (!this.snapshotted || !this.repos.length) return;
|
|
13709
14082
|
for (const rel of mutatedPaths(call)) {
|
|
13710
|
-
const abs =
|
|
14083
|
+
const abs = resolve4(this.options.workTree, rel);
|
|
13711
14084
|
if (this.forced.has(abs)) continue;
|
|
13712
14085
|
this.forced.add(abs);
|
|
13713
14086
|
const i = this.repoIndexFor(abs);
|
|
@@ -13763,7 +14136,7 @@ var GitCheckpointsOptions = class {
|
|
|
13763
14136
|
/** Real working tree to snapshot (the launch cwd). */
|
|
13764
14137
|
workTree = process.cwd();
|
|
13765
14138
|
/** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
|
|
13766
|
-
gitDir =
|
|
14139
|
+
gitDir = join11(process.cwd(), ".agent", "checkpoints.git");
|
|
13767
14140
|
/** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
|
|
13768
14141
|
addDirs = [];
|
|
13769
14142
|
/** Conversation id → per-session restore-point ref. */
|
|
@@ -13836,10 +14209,10 @@ function completePath(listDir, ref) {
|
|
|
13836
14209
|
|
|
13837
14210
|
// cli/lineEditor.ts
|
|
13838
14211
|
import { emitKeypressEvents } from "readline";
|
|
13839
|
-
import { spawnSync as
|
|
14212
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
13840
14213
|
import { writeFileSync as writeFileSync7, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
|
|
13841
14214
|
import { tmpdir as tmpdir2 } from "os";
|
|
13842
|
-
import { join as
|
|
14215
|
+
import { join as join12 } from "path";
|
|
13843
14216
|
|
|
13844
14217
|
// cli/bidi.ts
|
|
13845
14218
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -14336,6 +14709,7 @@ function releaseKeyInput() {
|
|
|
14336
14709
|
}
|
|
14337
14710
|
|
|
14338
14711
|
// cli/lineEditor.ts
|
|
14712
|
+
init_platform();
|
|
14339
14713
|
var cpWidth = (cp) => {
|
|
14340
14714
|
if (cp === 8205 || cp === 65039 || cp >= 768 && cp <= 879) return 0;
|
|
14341
14715
|
return cp >= 4352 && (cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 42191 && cp !== 12351 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 126976 && cp <= 129791 || cp >= 131072 && cp <= 262141) ? 2 : 1;
|
|
@@ -15024,14 +15398,15 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15024
15398
|
curRow = cursorRow;
|
|
15025
15399
|
}
|
|
15026
15400
|
function externalEdit(s) {
|
|
15027
|
-
const spec = process.env.VISUAL || process.env.EDITOR ||
|
|
15401
|
+
const spec = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
|
|
15028
15402
|
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
15029
|
-
const file =
|
|
15403
|
+
const file = join12(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
15030
15404
|
try {
|
|
15031
15405
|
writeFileSync7(file, s.buf);
|
|
15032
15406
|
process.stdin.setRawMode(false);
|
|
15033
15407
|
out.write("\x1B[?2004l");
|
|
15034
|
-
const
|
|
15408
|
+
const sp = resolveSpawn(cmd, [...cargs, file]);
|
|
15409
|
+
const r = spawnSync5(sp.command, sp.args, { stdio: "inherit", ...sp.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
|
|
15035
15410
|
if (r.status === 0) {
|
|
15036
15411
|
const text = readFileSync9(file, "utf8").replace(/\n$/, "");
|
|
15037
15412
|
s.reset();
|
|
@@ -15069,10 +15444,10 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15069
15444
|
render(s, promptOf(), maxVisible, opts.status);
|
|
15070
15445
|
};
|
|
15071
15446
|
process.on("SIGWINCH", onResize);
|
|
15072
|
-
return new Promise((
|
|
15447
|
+
return new Promise((resolve6) => {
|
|
15073
15448
|
activeAbort = () => {
|
|
15074
15449
|
finish();
|
|
15075
|
-
|
|
15450
|
+
resolve6(null);
|
|
15076
15451
|
};
|
|
15077
15452
|
const redraw = () => render(s, promptOf(), maxVisible, opts.status);
|
|
15078
15453
|
let lastStatus = opts.status?.() ?? "";
|
|
@@ -15152,20 +15527,20 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15152
15527
|
if (action === "submit") {
|
|
15153
15528
|
finish();
|
|
15154
15529
|
opts.onSubmit?.();
|
|
15155
|
-
return
|
|
15530
|
+
return resolve6(s.expand());
|
|
15156
15531
|
}
|
|
15157
15532
|
if (action === "eof") {
|
|
15158
15533
|
finish();
|
|
15159
|
-
return
|
|
15534
|
+
return resolve6(null);
|
|
15160
15535
|
}
|
|
15161
15536
|
if (action === "rewind") {
|
|
15162
15537
|
finish();
|
|
15163
|
-
return
|
|
15538
|
+
return resolve6(REWIND);
|
|
15164
15539
|
}
|
|
15165
15540
|
if (action === "cancel") {
|
|
15166
15541
|
if (s.buf.length === 0) {
|
|
15167
15542
|
finish();
|
|
15168
|
-
return
|
|
15543
|
+
return resolve6(null);
|
|
15169
15544
|
}
|
|
15170
15545
|
s.reset();
|
|
15171
15546
|
s.refresh();
|
|
@@ -15307,7 +15682,7 @@ function selectMenu(stream, opts) {
|
|
|
15307
15682
|
out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
|
|
15308
15683
|
prevLines++;
|
|
15309
15684
|
};
|
|
15310
|
-
return new Promise((
|
|
15685
|
+
return new Promise((resolve6) => {
|
|
15311
15686
|
const wasRaw = !!keyInput.isRaw;
|
|
15312
15687
|
menusOpen++;
|
|
15313
15688
|
emitKeypressEvents(keyInput);
|
|
@@ -15326,7 +15701,7 @@ function selectMenu(stream, opts) {
|
|
|
15326
15701
|
} catch {
|
|
15327
15702
|
}
|
|
15328
15703
|
}
|
|
15329
|
-
|
|
15704
|
+
resolve6(val);
|
|
15330
15705
|
};
|
|
15331
15706
|
const drillIn = () => {
|
|
15332
15707
|
const it = items[sel];
|
|
@@ -15447,19 +15822,19 @@ function windowAround(count, sel, max) {
|
|
|
15447
15822
|
var plainBuf = "";
|
|
15448
15823
|
var plainEnded = false;
|
|
15449
15824
|
function readPlainLine() {
|
|
15450
|
-
return new Promise((
|
|
15825
|
+
return new Promise((resolve6) => {
|
|
15451
15826
|
const emit = () => {
|
|
15452
15827
|
const nl = plainBuf.indexOf("\n");
|
|
15453
15828
|
if (nl >= 0) {
|
|
15454
15829
|
const line = plainBuf.slice(0, nl);
|
|
15455
15830
|
plainBuf = plainBuf.slice(nl + 1);
|
|
15456
|
-
|
|
15831
|
+
resolve6(line);
|
|
15457
15832
|
return true;
|
|
15458
15833
|
}
|
|
15459
15834
|
if (plainEnded) {
|
|
15460
15835
|
const rest = plainBuf;
|
|
15461
15836
|
plainBuf = "";
|
|
15462
|
-
|
|
15837
|
+
resolve6(rest.length ? rest : null);
|
|
15463
15838
|
return true;
|
|
15464
15839
|
}
|
|
15465
15840
|
return false;
|
|
@@ -15485,11 +15860,15 @@ function readPlainLine() {
|
|
|
15485
15860
|
});
|
|
15486
15861
|
}
|
|
15487
15862
|
|
|
15863
|
+
// cli/cli.ts
|
|
15864
|
+
init_platform();
|
|
15865
|
+
init_hostPath();
|
|
15866
|
+
|
|
15488
15867
|
// cli/osScheduler.ts
|
|
15489
|
-
import { spawnSync as
|
|
15868
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
15490
15869
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
15491
15870
|
import { homedir as homedir8 } from "os";
|
|
15492
|
-
import { join as
|
|
15871
|
+
import { join as join13 } from "path";
|
|
15493
15872
|
var log32 = forComponent("os-sched");
|
|
15494
15873
|
var OsScheduler = class {
|
|
15495
15874
|
options;
|
|
@@ -15497,13 +15876,13 @@ var OsScheduler = class {
|
|
|
15497
15876
|
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
15498
15877
|
}
|
|
15499
15878
|
get dir() {
|
|
15500
|
-
return
|
|
15879
|
+
return join13(this.options.home, ".agent", "sched");
|
|
15501
15880
|
}
|
|
15502
15881
|
label(id) {
|
|
15503
15882
|
return `cc.livx.agentx.sched-${id}`;
|
|
15504
15883
|
}
|
|
15505
15884
|
plistPath(id) {
|
|
15506
|
-
return
|
|
15885
|
+
return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
15507
15886
|
}
|
|
15508
15887
|
run(cmd, args, input) {
|
|
15509
15888
|
return this.options.exec(cmd, args, input);
|
|
@@ -15519,11 +15898,11 @@ var OsScheduler = class {
|
|
|
15519
15898
|
const script = this.writeScript(spec, oneOff);
|
|
15520
15899
|
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
15521
15900
|
const meta = { ...spec, created: Date.now(), mechanism };
|
|
15522
|
-
writeFileSync8(
|
|
15901
|
+
writeFileSync8(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
15523
15902
|
return mechanism;
|
|
15524
15903
|
}
|
|
15525
15904
|
cancel(id) {
|
|
15526
|
-
const meta = readJsonFile(
|
|
15905
|
+
const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
|
|
15527
15906
|
if (!meta) return false;
|
|
15528
15907
|
try {
|
|
15529
15908
|
if (this.options.platform === "darwin") {
|
|
@@ -15556,7 +15935,7 @@ var OsScheduler = class {
|
|
|
15556
15935
|
}
|
|
15557
15936
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
15558
15937
|
try {
|
|
15559
|
-
unlinkSync3(
|
|
15938
|
+
unlinkSync3(join13(this.dir, f));
|
|
15560
15939
|
} catch {
|
|
15561
15940
|
}
|
|
15562
15941
|
}
|
|
@@ -15564,19 +15943,19 @@ var OsScheduler = class {
|
|
|
15564
15943
|
}
|
|
15565
15944
|
list() {
|
|
15566
15945
|
if (!existsSync7(this.dir)) return [];
|
|
15567
|
-
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(
|
|
15946
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join13(this.dir, f), null)).filter(Boolean);
|
|
15568
15947
|
}
|
|
15569
15948
|
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
15570
15949
|
writeScript(spec, oneOff) {
|
|
15571
|
-
const p =
|
|
15950
|
+
const p = join13(this.dir, `${spec.id}.sh`);
|
|
15572
15951
|
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
15573
|
-
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(
|
|
15574
|
-
` : `rm -f ${q2(
|
|
15952
|
+
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
15953
|
+
` : `rm -f ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
15575
15954
|
` : "";
|
|
15576
15955
|
writeFileSync8(p, `#!/bin/sh
|
|
15577
15956
|
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
15578
15957
|
cd ${q2(spec.cwd)} || exit 1
|
|
15579
|
-
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(
|
|
15958
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join13(this.dir, `${spec.id}.log`))} 2>&1
|
|
15580
15959
|
${cleanup}`);
|
|
15581
15960
|
chmodSync(p, 493);
|
|
15582
15961
|
return p;
|
|
@@ -15613,7 +15992,7 @@ ${trigger}
|
|
|
15613
15992
|
<key>RunAtLoad</key><false/>
|
|
15614
15993
|
</dict></plist>
|
|
15615
15994
|
`;
|
|
15616
|
-
mkdirSync10(
|
|
15995
|
+
mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
15617
15996
|
writeFileSync8(this.plistPath(spec.id), plist);
|
|
15618
15997
|
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
15619
15998
|
return `launchd:${this.label(spec.id)}`;
|
|
@@ -15651,7 +16030,7 @@ var OsSchedulerOptions = class {
|
|
|
15651
16030
|
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
15652
16031
|
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
15653
16032
|
exec = (cmd, args, input) => {
|
|
15654
|
-
const r =
|
|
16033
|
+
const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
|
|
15655
16034
|
if (r.error) throw r.error;
|
|
15656
16035
|
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
15657
16036
|
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
@@ -15668,10 +16047,11 @@ import { createServer as createServer2, createConnection } from "net";
|
|
|
15668
16047
|
import { spawn as spawn3 } from "child_process";
|
|
15669
16048
|
import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
15670
16049
|
import { homedir as homedir9 } from "os";
|
|
15671
|
-
import { join as
|
|
16050
|
+
import { join as join14 } from "path";
|
|
16051
|
+
init_platform();
|
|
15672
16052
|
var log33 = forComponent("remote-trigger");
|
|
15673
|
-
var TRIGGER_DIR = () =>
|
|
15674
|
-
var sockPath = (sessionId, dir = TRIGGER_DIR()) =>
|
|
16053
|
+
var TRIGGER_DIR = () => join14(homedir9(), ".agent", "triggers");
|
|
16054
|
+
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join14(dir, `${sessionId}.sock`);
|
|
15675
16055
|
var TriggerServer = class {
|
|
15676
16056
|
constructor(onTrigger) {
|
|
15677
16057
|
this.onTrigger = onTrigger;
|
|
@@ -15764,6 +16144,15 @@ function triggerLive(sessionId, req, dir = TRIGGER_DIR(), timeoutMs = 3e3) {
|
|
|
15764
16144
|
});
|
|
15765
16145
|
});
|
|
15766
16146
|
}
|
|
16147
|
+
function headlessArgv(opts, text, id) {
|
|
16148
|
+
const script = `${opts.agentx} -p "$1" --resume "$2" --yes`;
|
|
16149
|
+
if (!IS_WIN) return ["/bin/sh", ["-c", script, "sh", text, id]];
|
|
16150
|
+
if (opts.agentxArgv?.length) {
|
|
16151
|
+
const r = resolveSpawn(opts.agentxArgv[0], [...opts.agentxArgv.slice(1), "-p", text, "--resume", id, "--yes"]);
|
|
16152
|
+
return [r.command, r.args];
|
|
16153
|
+
}
|
|
16154
|
+
return [defaultShell().bin, ["-c", script, "sh", text, id]];
|
|
16155
|
+
}
|
|
15767
16156
|
function makeRemoteTriggerTool(opts) {
|
|
15768
16157
|
const dir = opts.triggerDir ?? TRIGGER_DIR();
|
|
15769
16158
|
const doSpawn = opts.spawnFn ?? spawn3;
|
|
@@ -15790,9 +16179,10 @@ function makeRemoteTriggerTool(opts) {
|
|
|
15790
16179
|
if (live?.ok) return `Delivered to live session ${full} \u2014 it runs there; the user sees it in that terminal.`;
|
|
15791
16180
|
if (!target.meta.cwd || !existsSync8(target.meta.cwd)) return `Error: session ${full} has no reachable cwd (${target.meta.cwd ?? "unknown"}).`;
|
|
15792
16181
|
return new Promise((res) => {
|
|
15793
|
-
const child = doSpawn(
|
|
16182
|
+
const child = doSpawn(...headlessArgv(opts, text, full), {
|
|
15794
16183
|
cwd: target.meta.cwd,
|
|
15795
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16184
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
16185
|
+
...IS_WIN ? { windowsHide: true } : {}
|
|
15796
16186
|
});
|
|
15797
16187
|
let out = "", errOut = "";
|
|
15798
16188
|
child.stdout?.on("data", (d) => {
|
|
@@ -15857,7 +16247,7 @@ async function checkForUpdate(current) {
|
|
|
15857
16247
|
return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
|
|
15858
16248
|
}
|
|
15859
16249
|
function installUpdate() {
|
|
15860
|
-
const r =
|
|
16250
|
+
const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
|
|
15861
16251
|
return (r.status ?? 1) === 0;
|
|
15862
16252
|
}
|
|
15863
16253
|
var typeaheadOnLine = false;
|
|
@@ -16157,11 +16547,11 @@ function loadEnvFile(file) {
|
|
|
16157
16547
|
}
|
|
16158
16548
|
}
|
|
16159
16549
|
function loadInstallEnv() {
|
|
16160
|
-
let dir =
|
|
16161
|
-
for (let i = 0; i < 5 && !existsSync9(
|
|
16550
|
+
let dir = dirname6(import.meta.path);
|
|
16551
|
+
for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname6(dir);
|
|
16162
16552
|
for (const name of [".env", ".env.local"]) {
|
|
16163
|
-
loadEnvFile(
|
|
16164
|
-
loadEnvFile(
|
|
16553
|
+
loadEnvFile(join15(dir, name));
|
|
16554
|
+
loadEnvFile(join15(homedir10(), ".agent", name));
|
|
16165
16555
|
}
|
|
16166
16556
|
}
|
|
16167
16557
|
async function loadBodifySecrets() {
|
|
@@ -16195,6 +16585,31 @@ function apiKeysFromEnv() {
|
|
|
16195
16585
|
}
|
|
16196
16586
|
return keys;
|
|
16197
16587
|
}
|
|
16588
|
+
var KEYLESS_PROVIDERS = /* @__PURE__ */ new Set(["claude-code"]);
|
|
16589
|
+
function modelUsable(model, apiKeys) {
|
|
16590
|
+
const p = model.split("/")[0];
|
|
16591
|
+
return KEYLESS_PROVIDERS.has(p) || !!apiKeys[p];
|
|
16592
|
+
}
|
|
16593
|
+
function duplexModels(o) {
|
|
16594
|
+
const dflt = new DuplexAgentOptions(), notes = [];
|
|
16595
|
+
const cc = o.actModel.startsWith("claude-code/"), anthropic = !!o.apiKeys.anthropic;
|
|
16596
|
+
const why = (m) => `no ${m.split("/")[0].toUpperCase()}_API_KEY`;
|
|
16597
|
+
const asked = o.voiceModel ?? o.reflexModel;
|
|
16598
|
+
let reflexModel = asked ? resolveModelOrNewest(asked) : void 0;
|
|
16599
|
+
const reflex = reflexModel ?? dflt.reflexModel;
|
|
16600
|
+
if (!modelUsable(reflex, o.apiKeys)) {
|
|
16601
|
+
const to = anthropic ? "anthropic/claude-haiku-4-5" : cc ? "claude-code/haiku" : o.actModel.startsWith("cursor/") ? void 0 : o.actModel;
|
|
16602
|
+
if (to) reflexModel = to;
|
|
16603
|
+
notes.push(`voice model ${reflex} can't run here (${why(reflex)})${to ? ` \u2014 using ${to}` : ""}`);
|
|
16604
|
+
}
|
|
16605
|
+
let thinkModel = o.thinkModel === void 0 ? void 0 : o.thinkModel === false ? false : resolveModelOrNewest(String(o.thinkModel));
|
|
16606
|
+
const think = thinkModel === void 0 ? dflt.thinkModel : thinkModel;
|
|
16607
|
+
if (think !== false && !modelUsable(think, o.apiKeys)) {
|
|
16608
|
+
thinkModel = cc ? "claude-code/opus" : o.actModel;
|
|
16609
|
+
notes.push(`think model ${think} can't run here (${why(think)}) \u2014 using ${thinkModel}`);
|
|
16610
|
+
}
|
|
16611
|
+
return { ...reflexModel ? { reflexModel } : {}, ...thinkModel !== void 0 ? { thinkModel } : {}, notes };
|
|
16612
|
+
}
|
|
16198
16613
|
function makeHost(format = "text", opts) {
|
|
16199
16614
|
const streamJson = format === "stream-json";
|
|
16200
16615
|
const cleanStdout = format === "json";
|
|
@@ -16803,7 +17218,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
16803
17218
|
stream: args.stream,
|
|
16804
17219
|
host: makeHost(args.outputFormat, { stream: true }),
|
|
16805
17220
|
planMode: args.plan || cfg.planMode || false,
|
|
16806
|
-
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(
|
|
17221
|
+
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve5(args.cwd ?? process.cwd())) }) : void 0,
|
|
16807
17222
|
subagents: args.subagents || cfg.subagents || false,
|
|
16808
17223
|
maxSteps: args.maxSteps ?? cfg.maxSteps,
|
|
16809
17224
|
reasoning: args.reasoning ?? cfg.reasoning,
|
|
@@ -16885,16 +17300,23 @@ var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"
|
|
|
16885
17300
|
function mentionRefs(line) {
|
|
16886
17301
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
16887
17302
|
}
|
|
16888
|
-
var untilde = (p) => p.startsWith("~/") ?
|
|
17303
|
+
var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join15(homedir10(), p.slice(2)) : p;
|
|
17304
|
+
var PATH_LIKE = IS_WIN ? /^(\/|~[\\/]|\.\.?[\\/]|[A-Za-z]:[\\/]|\\\\)/ : /^(\/|~\/|\.\/|\.\.\/)/;
|
|
17305
|
+
var userPath = (cwd, p) => {
|
|
17306
|
+
const t = untilde(p);
|
|
17307
|
+
if (t !== p) return t;
|
|
17308
|
+
const host = IS_WIN && p.startsWith("/") ? toHostPath(p) : null;
|
|
17309
|
+
return host ? resolve5(host) : resolve5(cwd, p);
|
|
17310
|
+
};
|
|
16889
17311
|
function readImageParts(cwd, line) {
|
|
16890
17312
|
const refs = mentionRefs(line);
|
|
16891
17313
|
const parts = [];
|
|
16892
17314
|
for (const ref of refs) {
|
|
16893
17315
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
16894
17316
|
if (!mime) continue;
|
|
16895
|
-
const abs =
|
|
17317
|
+
const abs = userPath(cwd, ref);
|
|
16896
17318
|
try {
|
|
16897
|
-
|
|
17319
|
+
statSync8(abs);
|
|
16898
17320
|
} catch {
|
|
16899
17321
|
continue;
|
|
16900
17322
|
}
|
|
@@ -16906,16 +17328,16 @@ function pastePathClassifier(cwd) {
|
|
|
16906
17328
|
return (text) => {
|
|
16907
17329
|
let t = text.trim();
|
|
16908
17330
|
if (!t || t.includes("\n")) return null;
|
|
16909
|
-
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
16910
|
-
if (
|
|
16911
|
-
const abs =
|
|
17331
|
+
t = (IS_WIN ? t : t.replace(/\\ /g, " ")).replace(/^['"]|['"]$/g, "");
|
|
17332
|
+
if (!PATH_LIKE.test(t)) return null;
|
|
17333
|
+
const abs = userPath(cwd, t);
|
|
16912
17334
|
try {
|
|
16913
|
-
if (!
|
|
17335
|
+
if (!statSync8(abs).isFile()) return null;
|
|
16914
17336
|
} catch {
|
|
16915
17337
|
return null;
|
|
16916
17338
|
}
|
|
16917
17339
|
const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
|
|
16918
|
-
return { display: isImg ? "Image" : `File ${
|
|
17340
|
+
return { display: isImg ? "Image" : `File ${basename4(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
|
|
16919
17341
|
};
|
|
16920
17342
|
}
|
|
16921
17343
|
var mcpMentionResolver;
|
|
@@ -17039,7 +17461,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17039
17461
|
store.save(session);
|
|
17040
17462
|
} catch {
|
|
17041
17463
|
}
|
|
17042
|
-
return { ok: false };
|
|
17464
|
+
return { ok: false, error: e };
|
|
17043
17465
|
} finally {
|
|
17044
17466
|
spinner.stop();
|
|
17045
17467
|
spinner.turnStart = 0;
|
|
@@ -17090,7 +17512,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17090
17512
|
}
|
|
17091
17513
|
return { ok, res };
|
|
17092
17514
|
}
|
|
17093
|
-
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [
|
|
17515
|
+
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
|
|
17094
17516
|
function startSession(args, store, agent, cwd) {
|
|
17095
17517
|
if (args.resume || args.cont) {
|
|
17096
17518
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
@@ -17137,24 +17559,24 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
17137
17559
|
`;
|
|
17138
17560
|
function initInstructions(cwd) {
|
|
17139
17561
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17140
|
-
if (existsSync9(
|
|
17562
|
+
if (existsSync9(join15(cwd, f))) {
|
|
17141
17563
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
17142
17564
|
`));
|
|
17143
17565
|
return;
|
|
17144
17566
|
}
|
|
17145
17567
|
}
|
|
17146
|
-
const path =
|
|
17147
|
-
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}",
|
|
17568
|
+
const path = join15(cwd, "AGENTS.md");
|
|
17569
|
+
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
|
|
17148
17570
|
err(green(` created ${path}
|
|
17149
17571
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
17150
17572
|
}
|
|
17151
17573
|
function persistSetting(cwd, key, value) {
|
|
17152
|
-
const path =
|
|
17574
|
+
const path = join15(cwd, ".agent", "settings.json");
|
|
17153
17575
|
try {
|
|
17154
17576
|
const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
|
|
17155
17577
|
if (obj[key] === value) return;
|
|
17156
17578
|
obj[key] = value;
|
|
17157
|
-
mkdirSync12(
|
|
17579
|
+
mkdirSync12(dirname6(path), { recursive: true });
|
|
17158
17580
|
writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
|
|
17159
17581
|
} catch (e) {
|
|
17160
17582
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
@@ -17187,7 +17609,7 @@ function installCancelGuards(mounted) {
|
|
|
17187
17609
|
});
|
|
17188
17610
|
}
|
|
17189
17611
|
async function repl(args, ai, cfg, cwd) {
|
|
17190
|
-
const oauth = new McpOAuth({ storePath:
|
|
17612
|
+
const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
|
|
17191
17613
|
const mounted = await mountMcp(cfg, oauth);
|
|
17192
17614
|
let mcpToolNames = [];
|
|
17193
17615
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -17210,7 +17632,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17210
17632
|
},
|
|
17211
17633
|
tickMs: 15e3
|
|
17212
17634
|
});
|
|
17213
|
-
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${
|
|
17635
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve5(process.argv[1])}` : "agentx";
|
|
17214
17636
|
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
17215
17637
|
const osBackend = osSched.available() ? {
|
|
17216
17638
|
get sessionId() {
|
|
@@ -17233,7 +17655,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17233
17655
|
scheduleQueue.push(req.prompt);
|
|
17234
17656
|
}
|
|
17235
17657
|
});
|
|
17236
|
-
agent.options.tools = [...agent.options.tools ?? [], makeRemoteTriggerTool({ agentx: agentxInvocation(), selfId: () => session.meta.id })];
|
|
17658
|
+
agent.options.tools = [...agent.options.tools ?? [], makeRemoteTriggerTool({ agentx: agentxInvocation(), agentxArgv: process.argv[1] ? [process.execPath, resolve5(process.argv[1])] : ["agentx"], selfId: () => session.meta.id })];
|
|
17237
17659
|
const duplex = args.duplex;
|
|
17238
17660
|
let dx;
|
|
17239
17661
|
let voiceIO;
|
|
@@ -17438,6 +17860,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17438
17860
|
return `Rewound ${n} task checkpoint(s): restored ${r.restored} file(s), deleted ${r.deleted}.`;
|
|
17439
17861
|
}
|
|
17440
17862
|
};
|
|
17863
|
+
const { notes: tierNotes, ...tierModels } = duplexModels({ voiceModel: args.voiceModel, reflexModel: cfg.reflexModel, thinkModel: args.thinkModel ?? cfg.thinkModel, actModel: agent.options.model, apiKeys: { ...cfg.apiKeys, ...apiKeysFromEnv() } });
|
|
17864
|
+
for (const n of tierNotes) err(yellow(` \u26A0 ${n}
|
|
17865
|
+
`));
|
|
17441
17866
|
dx = new DuplexAgent({
|
|
17442
17867
|
ai,
|
|
17443
17868
|
...instinct ? { bargeIntent: (t) => instinct.bargeIntent(t) } : {},
|
|
@@ -17445,11 +17870,10 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17445
17870
|
fs: agent.options.fs,
|
|
17446
17871
|
memoryDir: agent.options.memoryDir,
|
|
17447
17872
|
memoryUserDir: agent.options.memoryUserDir,
|
|
17448
|
-
...
|
|
17873
|
+
...tierModels,
|
|
17449
17874
|
actModel: agent.options.model,
|
|
17450
17875
|
actOptions: workerOptions,
|
|
17451
17876
|
providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
|
|
17452
|
-
...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
|
|
17453
17877
|
host,
|
|
17454
17878
|
...args.voice ? { voiceStyle: "conversational", progressUpdates: true, askRelay: true, emotionTags: emotionsOn, spokenBudgetChars: 350 } : {},
|
|
17455
17879
|
// voice: progress asides + worker questions relayed through the conversation; emotion tags taught only with a TTS that speaks them
|
|
@@ -17463,7 +17887,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17463
17887
|
quickLook: {
|
|
17464
17888
|
branch: () => {
|
|
17465
17889
|
try {
|
|
17466
|
-
const head = readFileSync10(
|
|
17890
|
+
const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
|
|
17467
17891
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
17468
17892
|
} catch {
|
|
17469
17893
|
return "not a git repository";
|
|
@@ -17597,7 +18021,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17597
18021
|
const pendingImages = [];
|
|
17598
18022
|
const bangContext = [];
|
|
17599
18023
|
const grabClipboardAttachment = () => {
|
|
17600
|
-
const dir =
|
|
18024
|
+
const dir = join15(store.dir, `${session.meta.id}.images`);
|
|
17601
18025
|
try {
|
|
17602
18026
|
mkdirSync12(dir, { recursive: true });
|
|
17603
18027
|
} catch (e) {
|
|
@@ -17655,7 +18079,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17655
18079
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
17656
18080
|
`));
|
|
17657
18081
|
}
|
|
17658
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
18082
|
+
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join15(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
|
|
17659
18083
|
const cpHooks = checkpoints.hooks?.();
|
|
17660
18084
|
if (cpHooks) {
|
|
17661
18085
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -17707,11 +18131,11 @@ ${lines.join("\n")}
|
|
|
17707
18131
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
17708
18132
|
</system-reminder>`;
|
|
17709
18133
|
};
|
|
17710
|
-
const histPath =
|
|
18134
|
+
const histPath = join15(cwd, ".agent", "history");
|
|
17711
18135
|
const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
17712
18136
|
const remember2 = (line) => {
|
|
17713
18137
|
try {
|
|
17714
|
-
mkdirSync12(
|
|
18138
|
+
mkdirSync12(join15(cwd, ".agent"), { recursive: true });
|
|
17715
18139
|
appendFileSync(histPath, line + "\n");
|
|
17716
18140
|
} catch (e) {
|
|
17717
18141
|
log34.debug("history write failed", e);
|
|
@@ -18014,8 +18438,8 @@ ${task}`;
|
|
|
18014
18438
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
18015
18439
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
18016
18440
|
try {
|
|
18017
|
-
const { spawnSync:
|
|
18018
|
-
const r =
|
|
18441
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18442
|
+
const r = spawnSync8("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
18019
18443
|
if (r.error) err(text);
|
|
18020
18444
|
} finally {
|
|
18021
18445
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -18035,8 +18459,7 @@ ${task}`;
|
|
|
18035
18459
|
keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
|
|
18036
18460
|
process.env.BODIFY_API_KEY && process.env.BODIFY_APP_ID ? ok(`bodify secrets: ${process.env.BODIFY_APP_ID}`) : warn("bodify secrets: not configured (set BODIFY_API_KEY + BODIFY_APP_ID)");
|
|
18037
18461
|
{
|
|
18038
|
-
const
|
|
18039
|
-
const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
|
|
18462
|
+
const has = hasCommand;
|
|
18040
18463
|
if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
|
|
18041
18464
|
else {
|
|
18042
18465
|
ok("voice: keys present");
|
|
@@ -18463,12 +18886,13 @@ ${task}`;
|
|
|
18463
18886
|
return;
|
|
18464
18887
|
}
|
|
18465
18888
|
}
|
|
18466
|
-
const ed = process.env.VISUAL || process.env.EDITOR ||
|
|
18889
|
+
const ed = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
|
|
18467
18890
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
18468
18891
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
18469
18892
|
try {
|
|
18470
|
-
const { spawnSync:
|
|
18471
|
-
|
|
18893
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18894
|
+
const r = IS_WIN ? resolveSpawn(ed, [toHostPath(toVfsPath(idx)) ?? idx]) : { command: ed, args: [idx] };
|
|
18895
|
+
spawnSync8(r.command, r.args, { stdio: "inherit", ...r.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
|
|
18472
18896
|
} finally {
|
|
18473
18897
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
18474
18898
|
}
|
|
@@ -18818,10 +19242,10 @@ ${task}`;
|
|
|
18818
19242
|
return;
|
|
18819
19243
|
}
|
|
18820
19244
|
const md = exportMarkdown(session.meta, shown);
|
|
18821
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
18822
|
-
const path =
|
|
19245
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join15(".agent", "exports", `${session.meta.id}.md`);
|
|
19246
|
+
const path = resolve5(cwd, name);
|
|
18823
19247
|
try {
|
|
18824
|
-
mkdirSync12(
|
|
19248
|
+
mkdirSync12(dirname6(path), { recursive: true });
|
|
18825
19249
|
writeFileSync9(path, md);
|
|
18826
19250
|
err(green(` \u2713 exported \u2192 ${path}
|
|
18827
19251
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
@@ -18904,7 +19328,7 @@ ${task}`;
|
|
|
18904
19328
|
});
|
|
18905
19329
|
const listDir = (absDir) => {
|
|
18906
19330
|
try {
|
|
18907
|
-
return readdirSync4(
|
|
19331
|
+
return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
18908
19332
|
} catch (e) {
|
|
18909
19333
|
log34.debug("completion readdir failed", absDir, e);
|
|
18910
19334
|
return null;
|
|
@@ -19089,7 +19513,7 @@ ${out}
|
|
|
19089
19513
|
return false;
|
|
19090
19514
|
}
|
|
19091
19515
|
const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
|
|
19092
|
-
voiceDiag = new JsonlDiagSink(
|
|
19516
|
+
voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
|
|
19093
19517
|
voiceIO = new VoiceIO({
|
|
19094
19518
|
...fakeVoice ?? {},
|
|
19095
19519
|
onDiag: (ev) => voiceDiag?.push(ev),
|
|
@@ -19490,9 +19914,17 @@ ${out}
|
|
|
19490
19914
|
disposeClaudeCodeSessions();
|
|
19491
19915
|
await closeMcp(mounted);
|
|
19492
19916
|
}
|
|
19917
|
+
function duplexError(e, reflexModel) {
|
|
19918
|
+
if (!e) return "";
|
|
19919
|
+
const msg = String(e?.message ?? e).trim();
|
|
19920
|
+
const key = msg.match(/API key is required for ([\w-]+)/i);
|
|
19921
|
+
if (key) return `No API key for ${key[1]} (voice model ${reflexModel}) \u2014 set ${key[1].toUpperCase().replace(/-/g, "_")}_API_KEY in ~/.agent/.env, or choose another voice model (--voice-model).`;
|
|
19922
|
+
const code = e?.statusCode;
|
|
19923
|
+
return code && !msg.includes(String(code)) ? `${msg} (${code})` : msg;
|
|
19924
|
+
}
|
|
19493
19925
|
async function duplexServe(args, ai, cfg, cwd) {
|
|
19494
19926
|
const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
|
|
19495
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
19927
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19496
19928
|
installCancelGuards(mounted);
|
|
19497
19929
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
|
|
19498
19930
|
const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
|
|
@@ -19512,16 +19944,18 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19512
19944
|
};
|
|
19513
19945
|
let persist = () => {
|
|
19514
19946
|
};
|
|
19947
|
+
const { notes, ...tierModels } = duplexModels({ voiceModel: args.voiceModel, reflexModel: cfg.reflexModel, thinkModel: args.thinkModel ?? cfg.thinkModel, actModel: agent.options.model, apiKeys: { ...cfg.apiKeys, ...apiKeysFromEnv() } });
|
|
19948
|
+
for (const n of notes) err(yellow(` \u26A0 ${n}
|
|
19949
|
+
`));
|
|
19515
19950
|
const dx = new DuplexAgent({
|
|
19516
19951
|
ai,
|
|
19517
19952
|
fs: agent.options.fs,
|
|
19518
19953
|
memoryDir: agent.options.memoryDir,
|
|
19519
19954
|
memoryUserDir: agent.options.memoryUserDir,
|
|
19520
|
-
...
|
|
19955
|
+
...tierModels,
|
|
19521
19956
|
actModel: agent.options.model,
|
|
19522
19957
|
actOptions: { ...wo, planMode: false },
|
|
19523
19958
|
providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
|
|
19524
|
-
...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
|
|
19525
19959
|
host,
|
|
19526
19960
|
// --voice: the CLI voice register (progress asides, spoken budget). No emotion tags: the host's TTS may not speak them.
|
|
19527
19961
|
...args.voice ? { voiceStyle: "conversational", progressUpdates: true, spokenBudgetChars: 350 } : {},
|
|
@@ -19539,7 +19973,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19539
19973
|
log34.warn("duplex session save failed", e);
|
|
19540
19974
|
}
|
|
19541
19975
|
};
|
|
19542
|
-
out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel });
|
|
19976
|
+
out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel, ...notes.length ? { notes } : {} });
|
|
19543
19977
|
const done = new Promise((resolveDone) => {
|
|
19544
19978
|
const rl = createInterface({ input: process.stdin });
|
|
19545
19979
|
rl.on("line", (line) => {
|
|
@@ -19559,7 +19993,10 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19559
19993
|
if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
|
|
19560
19994
|
const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
|
|
19561
19995
|
const id = m.id ? String(m.id) : void 0;
|
|
19562
|
-
void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res
|
|
19996
|
+
void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res, error }) => {
|
|
19997
|
+
const why = (res?.finishReason ?? "error") === "error" ? duplexError(res?.error ?? error, dx.options.reflexModel) : "";
|
|
19998
|
+
out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
|
|
19999
|
+
}).catch((e) => {
|
|
19563
20000
|
log34.error("duplex turn failed", e);
|
|
19564
20001
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
19565
20002
|
});
|
|
@@ -19614,7 +20051,7 @@ async function main() {
|
|
|
19614
20051
|
let worktreeCleanup;
|
|
19615
20052
|
if (args.worktree) {
|
|
19616
20053
|
const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
19617
|
-
const session = enterWorktree2(args.worktree,
|
|
20054
|
+
const session = enterWorktree2(args.worktree, resolve5(args.cwd ?? process.cwd()));
|
|
19618
20055
|
err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
|
|
19619
20056
|
`));
|
|
19620
20057
|
worktreeCleanup = () => {
|
|
@@ -19624,8 +20061,8 @@ async function main() {
|
|
|
19624
20061
|
`));
|
|
19625
20062
|
};
|
|
19626
20063
|
}
|
|
19627
|
-
const cwd =
|
|
19628
|
-
const cfg = await loadConfig(cwd, args.mcpConfig &&
|
|
20064
|
+
const cwd = resolve5(args.cwd ?? process.cwd());
|
|
20065
|
+
const cfg = await loadConfig(cwd, args.mcpConfig && resolve5(args.mcpConfig));
|
|
19629
20066
|
if (!args.autoRoute && !args.model && (cfg.model === "auto" || !cfg.model && await jevAvailable())) {
|
|
19630
20067
|
args.autoRoute = true;
|
|
19631
20068
|
args.model = autoModels().standard;
|
|
@@ -19657,7 +20094,8 @@ async function main() {
|
|
|
19657
20094
|
loadInstallEnv();
|
|
19658
20095
|
await loadBodifySecrets();
|
|
19659
20096
|
const apiKeys = { ...cfg.apiKeys, ...apiKeysFromEnv() };
|
|
19660
|
-
|
|
20097
|
+
const keyless = String(args.model ?? cfg.model ?? "").startsWith("claude-code/");
|
|
20098
|
+
if (!Object.keys(apiKeys).length && !keyless) {
|
|
19661
20099
|
console.error(red("No provider key found. Set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY) in ~/.agent/.env, or set BODIFY_API_KEY + BODIFY_APP_ID to pull from Bodify."));
|
|
19662
20100
|
process.exit(1);
|
|
19663
20101
|
}
|
|
@@ -19674,7 +20112,7 @@ async function main() {
|
|
|
19674
20112
|
}
|
|
19675
20113
|
});
|
|
19676
20114
|
if (args.task) {
|
|
19677
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
20115
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19678
20116
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
|
|
19679
20117
|
const store = new SessionStore(cwd);
|
|
19680
20118
|
const session = startSession(args, store, agent, cwd);
|
|
@@ -19712,6 +20150,8 @@ export {
|
|
|
19712
20150
|
cacheMultipliers,
|
|
19713
20151
|
condenseReplay,
|
|
19714
20152
|
costOf,
|
|
20153
|
+
duplexError,
|
|
20154
|
+
duplexModels,
|
|
19715
20155
|
estimateTranscriptTokens,
|
|
19716
20156
|
expandMentions,
|
|
19717
20157
|
exportMarkdown,
|
|
@@ -19723,6 +20163,7 @@ export {
|
|
|
19723
20163
|
makeHost,
|
|
19724
20164
|
mcpMentionResolver,
|
|
19725
20165
|
mentionRefs,
|
|
20166
|
+
modelUsable,
|
|
19726
20167
|
parseArgs,
|
|
19727
20168
|
pastePathClassifier,
|
|
19728
20169
|
readImageParts,
|