@livx.cc/agentx 0.99.77 → 0.99.79
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 +690 -248
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +300 -50
- 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 +4 -4
package/dist/cli.js
CHANGED
|
@@ -359,13 +359,13 @@ function clauses(sentence) {
|
|
|
359
359
|
return sentence.split(/[,;:]\s*|\s+(?:so|but|and|then|because|however|therefore|thus|although|though)\s+/i).map((c) => c.trim()).filter(Boolean);
|
|
360
360
|
}
|
|
361
361
|
function endsOnAnnouncedAction(text) {
|
|
362
|
-
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
|
|
362
|
+
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.replace(/^[\s#>*_`-]+|[*_`]+/g, "").trim()).filter(Boolean);
|
|
363
363
|
const last = sentences[sentences.length - 1];
|
|
364
|
-
if (!last || NEXT_STEP.test(
|
|
364
|
+
if (!last || sentences.slice(-2).some((s) => NEXT_STEP.test(s)) || SIGN_OFF.test(last) || AWAITS_USER.test(last)) return false;
|
|
365
365
|
if (PROGRESSIVE.test(last)) return true;
|
|
366
366
|
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
367
367
|
}
|
|
368
|
-
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
368
|
+
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF, AWAITS_USER;
|
|
369
369
|
var init_llm = __esm({
|
|
370
370
|
"src/llm.ts"() {
|
|
371
371
|
"use strict";
|
|
@@ -383,6 +383,7 @@ var init_llm = __esm({
|
|
|
383
383
|
PROGRESSIVE = /^(?:also\s+|now\s+|then\s+)*(?:re-?)?(?:check|search|scan|read|verify|run|inspect|expand|look|examin|continu|proceed|try|fetch|load|open|test|build|grep|find|review|trac|dig|explor)\w*ing\b/i;
|
|
384
384
|
NEXT_STEP = /^next (?:step|run|up)\b/i;
|
|
385
385
|
SIGN_OFF = /^let me know\b/i;
|
|
386
|
+
AWAITS_USER = /^(?:do you )?(?:want|need) me to\b|\b(?:once|as soon as|after) you\b|\bwhen you(?:'re| are) ready\b/i;
|
|
386
387
|
}
|
|
387
388
|
});
|
|
388
389
|
|
|
@@ -402,6 +403,68 @@ var init_redact = __esm({
|
|
|
402
403
|
}
|
|
403
404
|
});
|
|
404
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
|
+
|
|
405
468
|
// src/tools.structured.ts
|
|
406
469
|
function ckAbort(signal) {
|
|
407
470
|
if (signal?.aborted) throw new Error("aborted");
|
|
@@ -444,7 +507,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
|
444
507
|
}
|
|
445
508
|
function scanRoots(fs, path, opts) {
|
|
446
509
|
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
447
|
-
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);
|
|
448
511
|
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
449
512
|
}
|
|
450
513
|
function scanNote(states) {
|
|
@@ -673,6 +736,7 @@ function parentDir(abs) {
|
|
|
673
736
|
return i <= 0 ? "/" : abs.slice(0, i);
|
|
674
737
|
}
|
|
675
738
|
async function mkdirp(fs, dir) {
|
|
739
|
+
dir = toVfsPath(dir);
|
|
676
740
|
if (dir === "/" || await fs.exists(dir)) return;
|
|
677
741
|
await mkdirp(fs, parentDir(dir));
|
|
678
742
|
if (!await fs.exists(dir)) await fs.createDir(dir);
|
|
@@ -733,6 +797,7 @@ var init_tools_structured = __esm({
|
|
|
733
797
|
"src/tools.structured.ts"() {
|
|
734
798
|
"use strict";
|
|
735
799
|
init_redact();
|
|
800
|
+
init_hostPath();
|
|
736
801
|
DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
737
802
|
DEFAULT_MAX_DEPTH = 24;
|
|
738
803
|
SCAN_PARAMS = {
|
|
@@ -2013,7 +2078,7 @@ __export(worktree_exports, {
|
|
|
2013
2078
|
});
|
|
2014
2079
|
import { spawnSync } from "child_process";
|
|
2015
2080
|
import { symlinkSync, mkdirSync, statSync as statSync4, readFileSync as readFileSync3, rmSync } from "fs";
|
|
2016
|
-
import { join as join2 } from "path";
|
|
2081
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
2017
2082
|
function validateWorktreeSlug(slug2) {
|
|
2018
2083
|
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
2019
2084
|
if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
|
|
@@ -2044,7 +2109,7 @@ function git(args, cwd) {
|
|
|
2044
2109
|
}
|
|
2045
2110
|
function findGitRoot(from) {
|
|
2046
2111
|
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
2047
|
-
return r.ok ? r.stdout : null;
|
|
2112
|
+
return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
|
|
2048
2113
|
}
|
|
2049
2114
|
function readWorktreeHead(worktreePath) {
|
|
2050
2115
|
try {
|
|
@@ -2098,7 +2163,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
|
2098
2163
|
continue;
|
|
2099
2164
|
}
|
|
2100
2165
|
try {
|
|
2101
|
-
symlinkSync(src, dst, "dir");
|
|
2166
|
+
symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
|
|
2102
2167
|
} catch (e) {
|
|
2103
2168
|
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
2104
2169
|
}
|
|
@@ -2177,20 +2242,58 @@ var NodeDiskFilesystem;
|
|
|
2177
2242
|
var init_NodeDiskFilesystem = __esm({
|
|
2178
2243
|
"src/NodeDiskFilesystem.ts"() {
|
|
2179
2244
|
"use strict";
|
|
2245
|
+
init_hostPath();
|
|
2180
2246
|
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
2181
2247
|
constructor(baseDir, opts = {}) {
|
|
2182
2248
|
this.baseDir = baseDir;
|
|
2183
2249
|
this.opts = { denySymlinks: true, ...opts };
|
|
2250
|
+
this.drives = this.win && (baseDir === "/" || baseDir === "\\");
|
|
2184
2251
|
}
|
|
2185
2252
|
baseDir;
|
|
2186
2253
|
cwd = "/";
|
|
2187
2254
|
opts;
|
|
2255
|
+
win = isWinPaths();
|
|
2256
|
+
/** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
|
|
2257
|
+
drives;
|
|
2188
2258
|
/** Ensure the root dir exists. */
|
|
2189
2259
|
async init() {
|
|
2260
|
+
if (this.drives) return;
|
|
2190
2261
|
await fsp.mkdir(this.baseDir, { recursive: true });
|
|
2191
2262
|
}
|
|
2192
2263
|
real(vpath) {
|
|
2193
|
-
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
|
+
}
|
|
2194
2297
|
}
|
|
2195
2298
|
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
2196
2299
|
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
@@ -2200,11 +2303,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2200
2303
|
static VERIFY_TTL_MS = 1e3;
|
|
2201
2304
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
2202
2305
|
async assertNoSymlink(real) {
|
|
2306
|
+
this.assertLongNames(real);
|
|
2203
2307
|
if (!this.opts.denySymlinks) return;
|
|
2204
|
-
const
|
|
2205
|
-
|
|
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;
|
|
2206
2311
|
const parts = rel.split(np.sep);
|
|
2207
|
-
let cur =
|
|
2312
|
+
let cur = base;
|
|
2208
2313
|
const now4 = Date.now();
|
|
2209
2314
|
for (let i = 0; i < parts.length; i++) {
|
|
2210
2315
|
cur = np.join(cur, parts[i]);
|
|
@@ -2224,13 +2329,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2224
2329
|
}
|
|
2225
2330
|
}
|
|
2226
2331
|
resolvePath(path, cwd) {
|
|
2227
|
-
return PathResolver.resolve(path, cwd || this.cwd);
|
|
2332
|
+
return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
|
|
2228
2333
|
}
|
|
2229
2334
|
getCwd() {
|
|
2230
2335
|
return this.cwd;
|
|
2231
2336
|
}
|
|
2232
2337
|
setCwd(path) {
|
|
2233
|
-
this.cwd = PathResolver.normalize(path);
|
|
2338
|
+
this.cwd = PathResolver.normalize(toVfsPath(path));
|
|
2234
2339
|
}
|
|
2235
2340
|
async readFile(path) {
|
|
2236
2341
|
const r = this.real(this.resolvePath(path));
|
|
@@ -2304,8 +2409,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2304
2409
|
}
|
|
2305
2410
|
}
|
|
2306
2411
|
async readDir(path) {
|
|
2307
|
-
const
|
|
2412
|
+
const v = this.resolvePath(path);
|
|
2413
|
+
if (this.isDriveRoot(v)) return this.listDrives();
|
|
2308
2414
|
try {
|
|
2415
|
+
const r = this.real(v);
|
|
2309
2416
|
await this.assertNoSymlink(r);
|
|
2310
2417
|
return await fsp.readdir(r);
|
|
2311
2418
|
} catch {
|
|
@@ -2325,8 +2432,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2325
2432
|
await fsp.mkdir(r);
|
|
2326
2433
|
}
|
|
2327
2434
|
async exists(path) {
|
|
2328
|
-
const
|
|
2435
|
+
const v = this.resolvePath(path);
|
|
2436
|
+
if (this.isDriveRoot(v)) return true;
|
|
2329
2437
|
try {
|
|
2438
|
+
const r = this.real(v);
|
|
2330
2439
|
await this.assertNoSymlink(r);
|
|
2331
2440
|
await fsp.stat(r);
|
|
2332
2441
|
return true;
|
|
@@ -2336,8 +2445,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2336
2445
|
}
|
|
2337
2446
|
async stat(path) {
|
|
2338
2447
|
let s;
|
|
2339
|
-
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 };
|
|
2340
2450
|
try {
|
|
2451
|
+
const r = this.real(v);
|
|
2341
2452
|
await this.assertNoSymlink(r);
|
|
2342
2453
|
s = await fsp.stat(r);
|
|
2343
2454
|
} catch {
|
|
@@ -2352,8 +2463,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2352
2463
|
};
|
|
2353
2464
|
}
|
|
2354
2465
|
async isDirectory(path) {
|
|
2355
|
-
const
|
|
2466
|
+
const v = this.resolvePath(path);
|
|
2467
|
+
if (this.isDriveRoot(v)) return true;
|
|
2356
2468
|
try {
|
|
2469
|
+
const r = this.real(v);
|
|
2357
2470
|
await this.assertNoSymlink(r);
|
|
2358
2471
|
return (await fsp.stat(r)).isDirectory();
|
|
2359
2472
|
} catch {
|
|
@@ -2361,14 +2474,20 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2361
2474
|
}
|
|
2362
2475
|
}
|
|
2363
2476
|
async isFile(path) {
|
|
2364
|
-
const r = this.real(this.resolvePath(path));
|
|
2365
2477
|
try {
|
|
2478
|
+
const r = this.real(this.resolvePath(path));
|
|
2366
2479
|
await this.assertNoSymlink(r);
|
|
2367
2480
|
return (await fsp.stat(r)).isFile();
|
|
2368
2481
|
} catch {
|
|
2369
2482
|
return false;
|
|
2370
2483
|
}
|
|
2371
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
|
+
}
|
|
2372
2491
|
};
|
|
2373
2492
|
}
|
|
2374
2493
|
});
|
|
@@ -2385,6 +2504,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2385
2504
|
"src/JailedFilesystem.ts"() {
|
|
2386
2505
|
"use strict";
|
|
2387
2506
|
init_tools_structured();
|
|
2507
|
+
init_hostPath();
|
|
2388
2508
|
JailedFilesystem = class {
|
|
2389
2509
|
constructor(inner, options) {
|
|
2390
2510
|
this.inner = inner;
|
|
@@ -2398,8 +2518,10 @@ var init_JailedFilesystem = __esm({
|
|
|
2398
2518
|
denyRe;
|
|
2399
2519
|
roRe;
|
|
2400
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. */
|
|
2401
2523
|
abs(path) {
|
|
2402
|
-
return this.inner.resolvePath(path, this.inner.getCwd());
|
|
2524
|
+
return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
|
|
2403
2525
|
}
|
|
2404
2526
|
/** Throw if reading `path` is not permitted (denied or out of confinement). */
|
|
2405
2527
|
guardRead(op, path) {
|
|
@@ -2484,7 +2606,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2484
2606
|
return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
|
|
2485
2607
|
}
|
|
2486
2608
|
resolvePath(path, cwd) {
|
|
2487
|
-
return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
|
|
2609
|
+
return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
|
|
2488
2610
|
}
|
|
2489
2611
|
getCwd() {
|
|
2490
2612
|
return this.inner.getCwd();
|
|
@@ -2550,6 +2672,159 @@ var init_JailedFilesystem = __esm({
|
|
|
2550
2672
|
}
|
|
2551
2673
|
});
|
|
2552
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
|
+
|
|
2553
2828
|
// src/shell.sandbox.ts
|
|
2554
2829
|
function writable(cwd, o, tmpDir) {
|
|
2555
2830
|
const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
|
|
@@ -2611,19 +2886,14 @@ __export(tools_shell_exports, {
|
|
|
2611
2886
|
makeShellJobTools: () => makeShellJobTools,
|
|
2612
2887
|
parsePsTime: () => parsePsTime,
|
|
2613
2888
|
psGroupCpuSeconds: () => psGroupCpuSeconds,
|
|
2614
|
-
sumTreeCpu: () => sumTreeCpu
|
|
2889
|
+
sumTreeCpu: () => sumTreeCpu,
|
|
2890
|
+
windowsShellNote: () => windowsShellNote
|
|
2615
2891
|
});
|
|
2616
2892
|
function killGroup(proc, signal) {
|
|
2617
|
-
|
|
2618
|
-
try {
|
|
2619
|
-
process.kill(-proc.pid, signal);
|
|
2620
|
-
return true;
|
|
2621
|
-
} catch {
|
|
2622
|
-
return false;
|
|
2623
|
-
}
|
|
2893
|
+
return killTree(proc?.pid, signal);
|
|
2624
2894
|
}
|
|
2625
2895
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
2626
|
-
if (!osSandbox) return
|
|
2896
|
+
if (!osSandbox) return shellArgv(defaultShell(), command);
|
|
2627
2897
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
2628
2898
|
const wrapper = await findSandboxWrapper();
|
|
2629
2899
|
const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
|
|
@@ -2651,9 +2921,17 @@ function parsePsTime(t) {
|
|
|
2651
2921
|
return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
|
|
2652
2922
|
}
|
|
2653
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
|
+
}
|
|
2654
2932
|
try {
|
|
2655
|
-
const { execFile:
|
|
2656
|
-
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))));
|
|
2657
2935
|
return sumTreeCpu(out, pgid);
|
|
2658
2936
|
} catch (e) {
|
|
2659
2937
|
log24.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
@@ -2704,7 +2982,7 @@ function makeRealShellTool(options) {
|
|
|
2704
2982
|
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2705
2983
|
},
|
|
2706
2984
|
get description() {
|
|
2707
|
-
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();
|
|
2708
2986
|
},
|
|
2709
2987
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2710
2988
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -2728,7 +3006,7 @@ function makeRealShellTool(options) {
|
|
|
2728
3006
|
return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
|
|
2729
3007
|
}
|
|
2730
3008
|
const spawn4 = options.spawn ?? await nodeSpawn();
|
|
2731
|
-
let argv =
|
|
3009
|
+
let argv = shellArgv(shellFor(options.shell), cmd);
|
|
2732
3010
|
if (options.osSandbox) {
|
|
2733
3011
|
try {
|
|
2734
3012
|
argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
|
|
@@ -2763,22 +3041,23 @@ function makeRealShellTool(options) {
|
|
|
2763
3041
|
}
|
|
2764
3042
|
};
|
|
2765
3043
|
try {
|
|
2766
|
-
return await new Promise((
|
|
3044
|
+
return await new Promise((resolve6) => {
|
|
2767
3045
|
let out = "";
|
|
2768
3046
|
let settled = false;
|
|
2769
3047
|
const finish = (s) => {
|
|
2770
3048
|
if (settled) return;
|
|
2771
3049
|
settled = true;
|
|
2772
|
-
|
|
3050
|
+
resolve6(s);
|
|
2773
3051
|
};
|
|
2774
3052
|
let proc;
|
|
2775
3053
|
try {
|
|
2776
|
-
|
|
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 });
|
|
2777
3056
|
} catch (e) {
|
|
2778
3057
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
2779
3058
|
}
|
|
2780
3059
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
2781
|
-
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
3060
|
+
else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2782
3061
|
const registry = options.registry;
|
|
2783
3062
|
if (registry?.adopt) {
|
|
2784
3063
|
onDeadline = () => {
|
|
@@ -2823,6 +3102,12 @@ function makeRealShellTool(options) {
|
|
|
2823
3102
|
}
|
|
2824
3103
|
};
|
|
2825
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
|
+
}
|
|
2826
3111
|
function handoffFor(timeoutMs, id, body, notifies) {
|
|
2827
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.");
|
|
2828
3113
|
return body ? `${head}
|
|
@@ -2877,7 +3162,7 @@ ${clean2(out) || "(no output yet)"}`;
|
|
|
2877
3162
|
}
|
|
2878
3163
|
];
|
|
2879
3164
|
}
|
|
2880
|
-
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;
|
|
2881
3166
|
var init_tools_shell = __esm({
|
|
2882
3167
|
"src/tools.shell.ts"() {
|
|
2883
3168
|
"use strict";
|
|
@@ -2886,9 +3171,11 @@ var init_tools_shell = __esm({
|
|
|
2886
3171
|
init_logging();
|
|
2887
3172
|
init_shell_sandbox();
|
|
2888
3173
|
init_tools_jobs();
|
|
3174
|
+
init_platform();
|
|
2889
3175
|
log24 = forComponent("shell");
|
|
2890
3176
|
clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2891
|
-
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 } : {} });
|
|
2892
3179
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
2893
3180
|
ShellJobRegistry = class {
|
|
2894
3181
|
constructor(cfg) {
|
|
@@ -2903,8 +3190,8 @@ var init_tools_shell = __esm({
|
|
|
2903
3190
|
const h = this.track(command, { idleMs: 0 });
|
|
2904
3191
|
try {
|
|
2905
3192
|
const spawn4 = this.cfg.spawn ?? await nodeSpawn();
|
|
2906
|
-
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) :
|
|
2907
|
-
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 }));
|
|
2908
3195
|
} catch (e) {
|
|
2909
3196
|
h.chunk(`failed to spawn: ${e?.message ?? e}`);
|
|
2910
3197
|
h.finish("error", void 0, e?.message ?? String(e));
|
|
@@ -3034,9 +3321,9 @@ var init_tools_shell = __esm({
|
|
|
3034
3321
|
|
|
3035
3322
|
// cli/cli.ts
|
|
3036
3323
|
import { createInterface } from "readline/promises";
|
|
3037
|
-
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";
|
|
3038
3325
|
import { homedir as homedir10 } from "os";
|
|
3039
|
-
import { spawnSync as
|
|
3326
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
3040
3327
|
|
|
3041
3328
|
// cli/clipboard.ts
|
|
3042
3329
|
import { execFileSync } from "child_process";
|
|
@@ -3067,6 +3354,15 @@ function grabClipboardImage(dir, id, run = execFileSync, platform2 = process.pla
|
|
|
3067
3354
|
}
|
|
3068
3355
|
return null;
|
|
3069
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
|
+
}
|
|
3070
3366
|
if (platform2 !== "darwin") return null;
|
|
3071
3367
|
const osa = (klass, out) => `set d to (the clipboard as \xABclass ${klass}\xBB)
|
|
3072
3368
|
set f to open for access POSIX file ${q(out)} with write permission
|
|
@@ -3091,10 +3387,13 @@ close access f`;
|
|
|
3091
3387
|
return null;
|
|
3092
3388
|
}
|
|
3093
3389
|
function copyTextToClipboard(text, platform2 = process.platform) {
|
|
3094
|
-
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
|
+
);
|
|
3095
3394
|
for (const [cmd, args] of candidates) {
|
|
3096
3395
|
try {
|
|
3097
|
-
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
3396
|
+
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"], ...platform2 === "win32" ? { windowsHide: true } : {} });
|
|
3098
3397
|
return true;
|
|
3099
3398
|
} catch {
|
|
3100
3399
|
}
|
|
@@ -3103,7 +3402,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
3103
3402
|
}
|
|
3104
3403
|
|
|
3105
3404
|
// cli/cli.ts
|
|
3106
|
-
import { join as
|
|
3405
|
+
import { join as join15, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
|
|
3107
3406
|
import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
|
|
3108
3407
|
|
|
3109
3408
|
// src/ccBridge.ts
|
|
@@ -4339,6 +4638,10 @@ var AgentOptions = class {
|
|
|
4339
4638
|
model = "anthropic/claude-sonnet-4-6";
|
|
4340
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.";
|
|
4341
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";
|
|
4342
4645
|
maxSteps = 25;
|
|
4343
4646
|
// --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
|
|
4344
4647
|
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
@@ -4934,8 +5237,9 @@ var Agent = class _Agent {
|
|
|
4934
5237
|
};
|
|
4935
5238
|
const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
|
|
4936
5239
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
4937
|
-
|
|
4938
|
-
|
|
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));
|
|
4939
5243
|
claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
4940
5244
|
}
|
|
4941
5245
|
if (this.ccServer) this.ccExec = hostExec;
|
|
@@ -9330,6 +9634,10 @@ function describeCall(call) {
|
|
|
9330
9634
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
9331
9635
|
return `${call.name}${hint}`;
|
|
9332
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
|
+
}
|
|
9333
9641
|
var DuplexAgentOptions = class {
|
|
9334
9642
|
/** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
|
|
9335
9643
|
ai;
|
|
@@ -9619,12 +9927,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9619
9927
|
timeoutMs: 3e4,
|
|
9620
9928
|
failEmptyTurns: false,
|
|
9621
9929
|
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
9930
|
+
...this.claudeCodeReflex(),
|
|
9622
9931
|
...o.reflexOptions,
|
|
9623
9932
|
tools,
|
|
9624
9933
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
9625
9934
|
hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
|
|
9626
9935
|
});
|
|
9627
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
|
+
}
|
|
9628
9950
|
/** Resolve memory tools + inject index into voice system prompt (once). */
|
|
9629
9951
|
async initMemory() {
|
|
9630
9952
|
if (!this.memoryReady) return;
|
|
@@ -9789,7 +10111,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9789
10111
|
await this.drainReflex();
|
|
9790
10112
|
this.scrubLeakFromTranscript();
|
|
9791
10113
|
this.scrubHeldText();
|
|
9792
|
-
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);
|
|
9793
10116
|
}
|
|
9794
10117
|
/** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
|
|
9795
10118
|
* Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
|
|
@@ -9895,6 +10218,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9895
10218
|
* hosts/tests key on it. */
|
|
9896
10219
|
static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
|
|
9897
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.";
|
|
9898
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?"];
|
|
9899
10223
|
lastFallback = "";
|
|
9900
10224
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
|
|
@@ -10553,14 +10877,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10553
10877
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
10554
10878
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
10555
10879
|
parkQuestion(askId, question) {
|
|
10556
|
-
return new Promise((
|
|
10880
|
+
return new Promise((resolve6) => {
|
|
10557
10881
|
let settled = false;
|
|
10558
10882
|
const finish = (answer) => {
|
|
10559
10883
|
if (settled) return;
|
|
10560
10884
|
settled = true;
|
|
10561
10885
|
clearTimeout(timer);
|
|
10562
10886
|
this.pendingAsks.delete(askId);
|
|
10563
|
-
|
|
10887
|
+
resolve6(answer);
|
|
10564
10888
|
};
|
|
10565
10889
|
const timer = setTimeout(() => {
|
|
10566
10890
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -11307,11 +11631,11 @@ function buildMcpCatalog(servers) {
|
|
|
11307
11631
|
}
|
|
11308
11632
|
return { specs, routes };
|
|
11309
11633
|
}
|
|
11310
|
-
function searchOverCatalog(servers, specs, routes,
|
|
11634
|
+
function searchOverCatalog(servers, specs, routes, resolve6, options) {
|
|
11311
11635
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
|
|
11312
11636
|
const r = routes.get(name);
|
|
11313
11637
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
11314
|
-
return
|
|
11638
|
+
return resolve6(r.server, r.rawName, args ?? {}, o);
|
|
11315
11639
|
}, options) : [];
|
|
11316
11640
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
11317
11641
|
}
|
|
@@ -11329,6 +11653,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
11329
11653
|
|
|
11330
11654
|
// src/mcp.client.ts
|
|
11331
11655
|
init_logging();
|
|
11656
|
+
init_platform();
|
|
11332
11657
|
import { spawn } from "child_process";
|
|
11333
11658
|
import { createHash } from "crypto";
|
|
11334
11659
|
var log23 = forComponent("mcp");
|
|
@@ -11356,7 +11681,9 @@ var StdioTransport = class {
|
|
|
11356
11681
|
pending = /* @__PURE__ */ new Map();
|
|
11357
11682
|
async start() {
|
|
11358
11683
|
const { command, args = [], env, cwd } = this.spec;
|
|
11359
|
-
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 } : {} });
|
|
11360
11687
|
this.proc = proc;
|
|
11361
11688
|
proc.stdout.setEncoding("utf8");
|
|
11362
11689
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -11413,7 +11740,7 @@ var StdioTransport = class {
|
|
|
11413
11740
|
const id = this.nextId++;
|
|
11414
11741
|
const timeoutMs = deadlineFor(method, this.spec, opts);
|
|
11415
11742
|
const { signal, onProgress } = opts;
|
|
11416
|
-
return new Promise((
|
|
11743
|
+
return new Promise((resolve6, reject) => {
|
|
11417
11744
|
if (signal?.aborted) return reject(abortError(signal));
|
|
11418
11745
|
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
11419
11746
|
this.pending.delete(id);
|
|
@@ -11428,7 +11755,7 @@ var StdioTransport = class {
|
|
|
11428
11755
|
};
|
|
11429
11756
|
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
11430
11757
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11431
|
-
this.pending.set(id, { resolve:
|
|
11758
|
+
this.pending.set(id, { resolve: resolve6, reject, timer, onProgress, done });
|
|
11432
11759
|
try {
|
|
11433
11760
|
this.write({ jsonrpc: "2.0", id, method, params: onProgress ? withProgressToken(params, id) : params });
|
|
11434
11761
|
} catch (e) {
|
|
@@ -11449,7 +11776,7 @@ var StdioTransport = class {
|
|
|
11449
11776
|
} catch (e) {
|
|
11450
11777
|
log23.debug("stdin end failed", e);
|
|
11451
11778
|
}
|
|
11452
|
-
this.proc?.kill();
|
|
11779
|
+
if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
|
|
11453
11780
|
}
|
|
11454
11781
|
};
|
|
11455
11782
|
var McpAuthError = class extends Error {
|
|
@@ -11612,12 +11939,12 @@ function buildTransport(cfg) {
|
|
|
11612
11939
|
}
|
|
11613
11940
|
function withTimeout(p, ms, label) {
|
|
11614
11941
|
if (!ms || ms <= 0) return p;
|
|
11615
|
-
return new Promise((
|
|
11942
|
+
return new Promise((resolve6, reject) => {
|
|
11616
11943
|
const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
|
|
11617
11944
|
timer.unref?.();
|
|
11618
11945
|
p.then((v) => {
|
|
11619
11946
|
clearTimeout(timer);
|
|
11620
|
-
|
|
11947
|
+
resolve6(v);
|
|
11621
11948
|
}, (e) => {
|
|
11622
11949
|
clearTimeout(timer);
|
|
11623
11950
|
reject(e);
|
|
@@ -11738,7 +12065,7 @@ var defaultPool = new McpPool();
|
|
|
11738
12065
|
import { createServer } from "http";
|
|
11739
12066
|
import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
|
|
11740
12067
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
11741
|
-
import { dirname as
|
|
12068
|
+
import { dirname as dirname3 } from "path";
|
|
11742
12069
|
var McpOAuthOptions = class {
|
|
11743
12070
|
/** Where to persist tokens. Mode 0600. */
|
|
11744
12071
|
storePath = ".agent/mcp-auth.json";
|
|
@@ -11771,7 +12098,7 @@ var McpOAuth = class {
|
|
|
11771
12098
|
}
|
|
11772
12099
|
}
|
|
11773
12100
|
save(store) {
|
|
11774
|
-
mkdirSync3(
|
|
12101
|
+
mkdirSync3(dirname3(this.options.storePath), { recursive: true });
|
|
11775
12102
|
writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
|
|
11776
12103
|
}
|
|
11777
12104
|
// -- ATTENDED: one-time authorization ----------------------------------------
|
|
@@ -11863,7 +12190,7 @@ var McpOAuth = class {
|
|
|
11863
12190
|
}
|
|
11864
12191
|
/** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
|
|
11865
12192
|
captureCode(authEndpoint, clientId, challenge, state) {
|
|
11866
|
-
return new Promise((
|
|
12193
|
+
return new Promise((resolve6, reject) => {
|
|
11867
12194
|
const server = createServer((req, res) => {
|
|
11868
12195
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
11869
12196
|
if (url.pathname !== "/callback") {
|
|
@@ -11877,7 +12204,7 @@ var McpOAuth = class {
|
|
|
11877
12204
|
clearTimeout(timer);
|
|
11878
12205
|
if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
|
|
11879
12206
|
if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
|
|
11880
|
-
|
|
12207
|
+
resolve6({ code, redirectUri });
|
|
11881
12208
|
});
|
|
11882
12209
|
let redirectUri = "";
|
|
11883
12210
|
const timer = setTimeout(() => {
|
|
@@ -11922,26 +12249,33 @@ function b64url(buf) {
|
|
|
11922
12249
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
11923
12250
|
}
|
|
11924
12251
|
function defaultOpenBrowser(url) {
|
|
11925
|
-
const
|
|
11926
|
-
const
|
|
11927
|
-
|
|
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());
|
|
11928
12256
|
}
|
|
11929
12257
|
|
|
11930
12258
|
// cli/core.ts
|
|
11931
12259
|
import { randomUUID } from "crypto";
|
|
11932
|
-
import { execFile as
|
|
11933
|
-
import { resolve as
|
|
12260
|
+
import { execFile as execFile3 } from "child_process";
|
|
12261
|
+
import { resolve as resolve3, basename as basename3, join as join6 } from "path";
|
|
11934
12262
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
11935
12263
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
11936
12264
|
init_tools_shell();
|
|
11937
12265
|
|
|
11938
12266
|
// src/tools.notify.ts
|
|
11939
12267
|
init_logging();
|
|
11940
|
-
import { execFile } from "child_process";
|
|
12268
|
+
import { execFile as execFile2 } from "child_process";
|
|
11941
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("; ");
|
|
11942
12276
|
function makeNotifyTool(opts = {}) {
|
|
11943
12277
|
const platform2 = opts.platform ?? process.platform;
|
|
11944
|
-
const run = opts.exec ??
|
|
12278
|
+
const run = opts.exec ?? execFile2;
|
|
11945
12279
|
return {
|
|
11946
12280
|
name: "PushNotification",
|
|
11947
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.",
|
|
@@ -11957,14 +12291,15 @@ function makeNotifyTool(opts = {}) {
|
|
|
11957
12291
|
const msg = String(message ?? "").slice(0, 256);
|
|
11958
12292
|
const head = String(title ?? "agentx").slice(0, 64);
|
|
11959
12293
|
if (!msg) return "Error: empty message";
|
|
11960
|
-
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;
|
|
11961
12295
|
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
11962
|
-
|
|
11963
|
-
|
|
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) => {
|
|
11964
12299
|
if (e) {
|
|
11965
12300
|
log25.debug("notification failed", e);
|
|
11966
|
-
|
|
11967
|
-
} else
|
|
12301
|
+
resolve6(`Notification failed: ${e.message}`);
|
|
12302
|
+
} else resolve6("Notification shown.");
|
|
11968
12303
|
});
|
|
11969
12304
|
});
|
|
11970
12305
|
}
|
|
@@ -12012,13 +12347,15 @@ function readJsonFile(path, fallback) {
|
|
|
12012
12347
|
}
|
|
12013
12348
|
|
|
12014
12349
|
// cli/core.ts
|
|
12350
|
+
init_hostPath();
|
|
12351
|
+
init_platform();
|
|
12015
12352
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
12016
12353
|
function autoWebTools() {
|
|
12017
12354
|
return ["WebFetch", "WebSearch"];
|
|
12018
12355
|
}
|
|
12019
12356
|
function pdfTextViaPoppler(path) {
|
|
12020
12357
|
return new Promise((res, rej) => {
|
|
12021
|
-
|
|
12358
|
+
execFile3("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
|
|
12022
12359
|
if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
|
|
12023
12360
|
else res(stdout);
|
|
12024
12361
|
});
|
|
@@ -12082,7 +12419,7 @@ function toClaudeCodeMcp(servers) {
|
|
|
12082
12419
|
function claudeCodeProviderOptions(model, cwd, mcpServers) {
|
|
12083
12420
|
if (!(model ?? "").startsWith("claude-code/")) return void 0;
|
|
12084
12421
|
const env = (() => {
|
|
12085
|
-
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"]);
|
|
12086
12423
|
return { ENABLE_CLAUDEAI_MCP_SERVERS: "false", ...base };
|
|
12087
12424
|
})();
|
|
12088
12425
|
const warm = process.env.AGENTX_CC_WARM === "0" ? {} : { claudeCodeSession: randomUUID() };
|
|
@@ -12096,7 +12433,7 @@ async function buildAgent(o) {
|
|
|
12096
12433
|
throw new Error(
|
|
12097
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."
|
|
12098
12435
|
);
|
|
12099
|
-
const cwd =
|
|
12436
|
+
const cwd = resolve3(o.cwd ?? process.cwd());
|
|
12100
12437
|
const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
|
|
12101
12438
|
await disk.init();
|
|
12102
12439
|
const jailedDisk = new JailedFilesystem(disk);
|
|
@@ -12110,21 +12447,22 @@ async function buildAgent(o) {
|
|
|
12110
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).`
|
|
12111
12448
|
);
|
|
12112
12449
|
let fs = jailedDisk;
|
|
12450
|
+
const vcwd = toVfsPath(cwd);
|
|
12113
12451
|
if (o.sandbox) {
|
|
12114
|
-
const mem = new MemFilesystem3();
|
|
12115
|
-
await mkdirp(mem,
|
|
12116
|
-
await hydrate(jailedDisk, mem,
|
|
12117
|
-
mem.setCwd(
|
|
12452
|
+
const mem = withHostPaths(new MemFilesystem3());
|
|
12453
|
+
await mkdirp(mem, vcwd);
|
|
12454
|
+
await hydrate(jailedDisk, mem, vcwd);
|
|
12455
|
+
mem.setCwd(vcwd);
|
|
12118
12456
|
fs = mem;
|
|
12119
12457
|
} else if (o.boddb) {
|
|
12120
|
-
const root =
|
|
12458
|
+
const root = resolve3(o.boddb);
|
|
12121
12459
|
mkdirSync4(root, { recursive: true });
|
|
12122
|
-
const db = new BodDB2({ path:
|
|
12123
|
-
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));
|
|
12124
12462
|
const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
|
|
12125
|
-
await mkdirp(bod,
|
|
12126
|
-
if (o.seed && firstRun) await hydrate(jailedDisk, bod,
|
|
12127
|
-
bod.setCwd(
|
|
12463
|
+
await mkdirp(bod, vcwd);
|
|
12464
|
+
if (o.seed && firstRun) await hydrate(jailedDisk, bod, vcwd);
|
|
12465
|
+
bod.setCwd(vcwd);
|
|
12128
12466
|
fs = bod;
|
|
12129
12467
|
}
|
|
12130
12468
|
let mountNote = "";
|
|
@@ -12133,8 +12471,8 @@ async function buildAgent(o) {
|
|
|
12133
12471
|
const mounts = [];
|
|
12134
12472
|
const notes = [];
|
|
12135
12473
|
for (const d of o.addDirs) {
|
|
12136
|
-
const abs =
|
|
12137
|
-
let name =
|
|
12474
|
+
const abs = resolve3(d);
|
|
12475
|
+
let name = basename3(abs) || "dir", prefix = `/${name}`, n = 2;
|
|
12138
12476
|
while (used.has(prefix)) prefix = `/${name}-${n++}`;
|
|
12139
12477
|
used.add(prefix);
|
|
12140
12478
|
const md = new NodeDiskFilesystem(abs);
|
|
@@ -12186,7 +12524,7 @@ Reference files in them by their mount path (the left side).`;
|
|
|
12186
12524
|
});
|
|
12187
12525
|
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
|
|
12188
12526
|
}
|
|
12189
|
-
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;
|
|
12190
12528
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
12191
12529
|
return agentRef.agent = new Agent({
|
|
12192
12530
|
ai: o.ai,
|
|
@@ -12211,12 +12549,13 @@ Reference files in them by their mount path (the left side).`;
|
|
|
12211
12549
|
const envNote = `Current date: ${now4.toLocaleDateString("en-CA")} ${now4.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })} (${Intl.DateTimeFormat().resolvedOptions().timeZone})
|
|
12212
12550
|
Platform: ${platformNames[platform()] ?? platform()} ${arch()} (${release()})
|
|
12213
12551
|
User: ${userInfo().username}
|
|
12214
|
-
Shell: ${process.env.SHELL ?? "unknown"}`;
|
|
12552
|
+
Shell: ${process.env.SHELL ?? (isWinPaths() ? defaultShell().label : "unknown")}`;
|
|
12215
12553
|
const cwdNote = o.boddb ? `Working directory: ${cwd}
|
|
12216
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}
|
|
12217
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}
|
|
12218
|
-
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
|
|
12219
|
-
|
|
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;
|
|
12220
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.";
|
|
12221
12560
|
const mcpNote = (() => {
|
|
12222
12561
|
const names = new Set(Object.keys(o.mcpServers ?? {}));
|
|
@@ -12299,10 +12638,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
12299
12638
|
|
|
12300
12639
|
// cli/voice.ts
|
|
12301
12640
|
init_logging();
|
|
12302
|
-
|
|
12303
|
-
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";
|
|
12304
12644
|
import { homedir as homedir3 } from "os";
|
|
12305
|
-
import { dirname as
|
|
12645
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
12306
12646
|
import { fileURLToPath } from "url";
|
|
12307
12647
|
var log27 = forComponent("VoiceIO");
|
|
12308
12648
|
var now3 = () => performance.now();
|
|
@@ -12345,10 +12685,10 @@ var Player = class {
|
|
|
12345
12685
|
this.proc = null;
|
|
12346
12686
|
}
|
|
12347
12687
|
};
|
|
12348
|
-
var nativeDir = () =>
|
|
12688
|
+
var nativeDir = () => join7(dirname4(fileURLToPath(import.meta.url)), "native");
|
|
12349
12689
|
function detectFfmpegMic() {
|
|
12350
12690
|
if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
|
|
12351
|
-
const r =
|
|
12691
|
+
const r = spawnSync3("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
|
|
12352
12692
|
if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
|
|
12353
12693
|
if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
|
|
12354
12694
|
const out = r.stderr ?? "";
|
|
@@ -12359,13 +12699,24 @@ function detectFfmpegMic() {
|
|
|
12359
12699
|
log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
12360
12700
|
return `:${mic.idx}`;
|
|
12361
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
|
+
}
|
|
12362
12713
|
function detectedInputDevice() {
|
|
12363
12714
|
if (process.platform !== "darwin") return null;
|
|
12364
12715
|
let name = "";
|
|
12365
|
-
if (
|
|
12366
|
-
name = (
|
|
12716
|
+
if (spawnSync3("which", ["SwitchAudioSource"]).status === 0) {
|
|
12717
|
+
name = (spawnSync3("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
|
|
12367
12718
|
} else {
|
|
12368
|
-
const out =
|
|
12719
|
+
const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
|
|
12369
12720
|
let last = "";
|
|
12370
12721
|
for (const ln of out.split("\n")) {
|
|
12371
12722
|
const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
|
|
@@ -12384,21 +12735,21 @@ function detectedInputDevice() {
|
|
|
12384
12735
|
}
|
|
12385
12736
|
function resolveAecBinary() {
|
|
12386
12737
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12387
|
-
const src =
|
|
12388
|
-
const plist =
|
|
12738
|
+
const src = join7(nativeDir(), "mic-aec.swift");
|
|
12739
|
+
const plist = join7(nativeDir(), "Info.plist");
|
|
12389
12740
|
if (!existsSync4(src)) return null;
|
|
12390
|
-
const cacheDir =
|
|
12391
|
-
const bin =
|
|
12392
|
-
if (existsSync4(bin) &&
|
|
12393
|
-
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;
|
|
12394
12745
|
mkdirSync5(cacheDir, { recursive: true });
|
|
12395
12746
|
log27.info("compiling AEC mic helper (first run)\u2026");
|
|
12396
|
-
const build =
|
|
12747
|
+
const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
12397
12748
|
if (build.status !== 0) {
|
|
12398
12749
|
log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
12399
12750
|
return null;
|
|
12400
12751
|
}
|
|
12401
|
-
const sign =
|
|
12752
|
+
const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
12402
12753
|
if (sign.status !== 0) {
|
|
12403
12754
|
log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
12404
12755
|
return null;
|
|
@@ -12408,14 +12759,14 @@ function resolveAecBinary() {
|
|
|
12408
12759
|
function openMicSettings() {
|
|
12409
12760
|
if (process.platform !== "darwin") return;
|
|
12410
12761
|
try {
|
|
12411
|
-
|
|
12762
|
+
spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
|
|
12412
12763
|
} catch {
|
|
12413
12764
|
}
|
|
12414
12765
|
}
|
|
12415
12766
|
function micPermissionStatus() {
|
|
12416
12767
|
const bin = resolveAecBinary();
|
|
12417
12768
|
if (!bin) return null;
|
|
12418
|
-
const r =
|
|
12769
|
+
const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
|
|
12419
12770
|
const out = (r.stdout ?? "").trim();
|
|
12420
12771
|
if (out === "authorized") return "authorized";
|
|
12421
12772
|
if (out === "denied" || out === "restricted") return "denied";
|
|
@@ -12425,7 +12776,7 @@ function micPermissionStatus() {
|
|
|
12425
12776
|
function aecUnavailableHint() {
|
|
12426
12777
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12427
12778
|
if (resolveAecBinary()) return null;
|
|
12428
|
-
if (
|
|
12779
|
+
if (spawnSync3("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
|
|
12429
12780
|
return null;
|
|
12430
12781
|
}
|
|
12431
12782
|
var NodeMicSource = class {
|
|
@@ -12441,12 +12792,13 @@ var NodeMicSource = class {
|
|
|
12441
12792
|
if (this.bin) {
|
|
12442
12793
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
12443
12794
|
} else {
|
|
12444
|
-
if (
|
|
12795
|
+
if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
12445
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()];
|
|
12446
12798
|
this.proc = spawn2(
|
|
12447
12799
|
"ffmpeg",
|
|
12448
|
-
["-loglevel", "error",
|
|
12449
|
-
{ 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 } : {} }
|
|
12450
12802
|
);
|
|
12451
12803
|
this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
|
|
12452
12804
|
}
|
|
@@ -12833,7 +13185,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
12833
13185
|
// src/voice/diagSink.ts
|
|
12834
13186
|
init_logging();
|
|
12835
13187
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
12836
|
-
import { dirname as
|
|
13188
|
+
import { dirname as dirname5 } from "path";
|
|
12837
13189
|
var log28 = forComponent("VoiceDiag");
|
|
12838
13190
|
var JsonlDiagSink = class {
|
|
12839
13191
|
constructor(path) {
|
|
@@ -12858,7 +13210,7 @@ var JsonlDiagSink = class {
|
|
|
12858
13210
|
if (this.writing || this.disabled || !this.queue.length) return;
|
|
12859
13211
|
if (!this.dirReady) {
|
|
12860
13212
|
try {
|
|
12861
|
-
mkdirSync6(
|
|
13213
|
+
mkdirSync6(dirname5(this.path), { recursive: true });
|
|
12862
13214
|
this.dirReady = true;
|
|
12863
13215
|
} catch (e) {
|
|
12864
13216
|
return this.disable(e);
|
|
@@ -12890,13 +13242,13 @@ var JsonlDiagSink = class {
|
|
|
12890
13242
|
// cli/config.ts
|
|
12891
13243
|
import { homedir as homedir5 } from "os";
|
|
12892
13244
|
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
12893
|
-
import { join as
|
|
13245
|
+
import { join as join9 } from "path";
|
|
12894
13246
|
import { pathToFileURL } from "url";
|
|
12895
13247
|
|
|
12896
13248
|
// cli/permissions.ts
|
|
12897
13249
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
12898
13250
|
import { homedir as homedir4 } from "os";
|
|
12899
|
-
import { join as
|
|
13251
|
+
import { join as join8 } from "path";
|
|
12900
13252
|
var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
|
|
12901
13253
|
function parseOne(raw, decision) {
|
|
12902
13254
|
const m = RULE_RE.exec(raw.trim());
|
|
@@ -12918,13 +13270,13 @@ function parsePermRules(perms, explicit = false) {
|
|
|
12918
13270
|
function describeRule(r) {
|
|
12919
13271
|
return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
|
|
12920
13272
|
}
|
|
12921
|
-
var PERM_FILE = (cwd) =>
|
|
13273
|
+
var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
|
|
12922
13274
|
function loadPersistedRules(cwd) {
|
|
12923
13275
|
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
12924
13276
|
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
12925
13277
|
}
|
|
12926
13278
|
function loadClaudeSettings(cwd, home = homedir4()) {
|
|
12927
|
-
const files = [
|
|
13279
|
+
const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
|
|
12928
13280
|
let out = {};
|
|
12929
13281
|
for (const p of files) {
|
|
12930
13282
|
const perms = readJsonFile(p, null)?.permissions;
|
|
@@ -12937,7 +13289,7 @@ function persistRule(cwd, decision, ruleStr) {
|
|
|
12937
13289
|
const list = cur[decision] ??= [];
|
|
12938
13290
|
if (!list.includes(ruleStr)) list.push(ruleStr);
|
|
12939
13291
|
try {
|
|
12940
|
-
mkdirSync7(
|
|
13292
|
+
mkdirSync7(join8(cwd, ".agent"), { recursive: true });
|
|
12941
13293
|
writeFileSync4(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
|
|
12942
13294
|
} catch {
|
|
12943
13295
|
}
|
|
@@ -12951,7 +13303,7 @@ function mergePerms(a, b) {
|
|
|
12951
13303
|
}
|
|
12952
13304
|
return Object.keys(out).length ? out : void 0;
|
|
12953
13305
|
}
|
|
12954
|
-
var TRUST_FILE =
|
|
13306
|
+
var TRUST_FILE = join8(homedir4(), ".agent", "trusted.json");
|
|
12955
13307
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
12956
13308
|
const list = readJsonFile(file, []);
|
|
12957
13309
|
return Array.isArray(list) && list.includes(cwd);
|
|
@@ -12961,7 +13313,7 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
12961
13313
|
if (!Array.isArray(list)) list = [];
|
|
12962
13314
|
if (!list.includes(cwd)) list.push(cwd);
|
|
12963
13315
|
try {
|
|
12964
|
-
mkdirSync7(
|
|
13316
|
+
mkdirSync7(join8(file, ".."), { recursive: true });
|
|
12965
13317
|
writeFileSync4(file, JSON.stringify(list, null, 2) + "\n");
|
|
12966
13318
|
} catch {
|
|
12967
13319
|
}
|
|
@@ -12971,7 +13323,7 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
12971
13323
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
12972
13324
|
async function loadFrom(dir) {
|
|
12973
13325
|
for (const f of FILES) {
|
|
12974
|
-
const p =
|
|
13326
|
+
const p = join9(dir, ".agent", f);
|
|
12975
13327
|
if (!existsSync5(p)) continue;
|
|
12976
13328
|
try {
|
|
12977
13329
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
@@ -12984,7 +13336,7 @@ async function loadFrom(dir) {
|
|
|
12984
13336
|
return {};
|
|
12985
13337
|
}
|
|
12986
13338
|
function loadSettings(dir) {
|
|
12987
|
-
const p =
|
|
13339
|
+
const p = join9(dir, ".agent", "settings.json");
|
|
12988
13340
|
if (!existsSync5(p)) return {};
|
|
12989
13341
|
try {
|
|
12990
13342
|
const raw = JSON.parse(readFileSync7(p, "utf8"));
|
|
@@ -13031,7 +13383,8 @@ function applyOverlay(cfg, file) {
|
|
|
13031
13383
|
}
|
|
13032
13384
|
|
|
13033
13385
|
// cli/hooks-config.ts
|
|
13034
|
-
import { spawnSync as
|
|
13386
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
13387
|
+
init_platform();
|
|
13035
13388
|
var log29 = forComponent("hooks");
|
|
13036
13389
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
13037
13390
|
function ruleMatches(rule, toolName) {
|
|
@@ -13041,8 +13394,10 @@ function ruleMatches(rule, toolName) {
|
|
|
13041
13394
|
}
|
|
13042
13395
|
function runCmd(rule, env) {
|
|
13043
13396
|
try {
|
|
13044
|
-
const r =
|
|
13045
|
-
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,
|
|
13046
13401
|
encoding: "utf8",
|
|
13047
13402
|
timeout: rule.timeoutMs ?? 1e4,
|
|
13048
13403
|
env: { ...process.env, ...env }
|
|
@@ -13153,26 +13508,45 @@ function formatDiff(ops, opts = {}) {
|
|
|
13153
13508
|
}
|
|
13154
13509
|
|
|
13155
13510
|
// cli/session.ts
|
|
13156
|
-
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";
|
|
13157
13512
|
import { homedir as homedir6 } from "os";
|
|
13158
|
-
import { join as
|
|
13513
|
+
import { join as join10 } from "path";
|
|
13514
|
+
init_platform();
|
|
13159
13515
|
var log30 = forComponent("session");
|
|
13160
|
-
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
|
+
}
|
|
13161
13535
|
var SessionStore = class {
|
|
13162
13536
|
dir;
|
|
13163
13537
|
constructor(cwd) {
|
|
13164
|
-
this.dir =
|
|
13538
|
+
this.dir = join10(cwd, ".agent", "sessions");
|
|
13165
13539
|
}
|
|
13166
13540
|
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
13167
13541
|
newId(now4 = Date.now(), cwd) {
|
|
13168
13542
|
const d = new Date(now4);
|
|
13169
13543
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
13170
|
-
const slug2 = (cwd ?? process.cwd())
|
|
13544
|
+
const slug2 = pathTail(cwd ?? process.cwd())?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
13171
13545
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
13172
|
-
if (existsSync6(this.dir) && existsSync6(
|
|
13546
|
+
if (existsSync6(this.dir) && existsSync6(join10(this.dir, `${id}.json`))) {
|
|
13173
13547
|
for (let i = 2; i <= 99; i++) {
|
|
13174
13548
|
const c = `${id}-${i}`;
|
|
13175
|
-
if (!existsSync6(
|
|
13549
|
+
if (!existsSync6(join10(this.dir, `${c}.json`))) {
|
|
13176
13550
|
id = c;
|
|
13177
13551
|
break;
|
|
13178
13552
|
}
|
|
@@ -13187,15 +13561,15 @@ var SessionStore = class {
|
|
|
13187
13561
|
save(data) {
|
|
13188
13562
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
13189
13563
|
if (!existsSync6(this.dir)) mkdirSync8(this.dir, { recursive: true });
|
|
13190
|
-
const path =
|
|
13564
|
+
const path = join10(this.dir, `${data.meta.id}.json`);
|
|
13191
13565
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
13192
13566
|
writeFileSync5(tmp, JSON.stringify(data));
|
|
13193
13567
|
renameSync(tmp, path);
|
|
13194
13568
|
try {
|
|
13195
13569
|
const gd = globalDir();
|
|
13196
13570
|
if (!existsSync6(gd)) mkdirSync8(gd, { recursive: true });
|
|
13197
|
-
const link2 =
|
|
13198
|
-
if (!existsSync6(link2))
|
|
13571
|
+
const link2 = join10(gd, `${data.meta.id}.json`);
|
|
13572
|
+
if (!existsSync6(link2)) indexLink(path, link2);
|
|
13199
13573
|
} catch {
|
|
13200
13574
|
}
|
|
13201
13575
|
}
|
|
@@ -13204,7 +13578,7 @@ var SessionStore = class {
|
|
|
13204
13578
|
log30.debug(`rejecting unsafe session id: ${id}`);
|
|
13205
13579
|
return void 0;
|
|
13206
13580
|
}
|
|
13207
|
-
const path =
|
|
13581
|
+
const path = join10(this.dir, `${id}.json`);
|
|
13208
13582
|
if (!existsSync6(path)) return void 0;
|
|
13209
13583
|
try {
|
|
13210
13584
|
return JSON.parse(readFileSync8(path, "utf8"));
|
|
@@ -13220,7 +13594,7 @@ var SessionStore = class {
|
|
|
13220
13594
|
for (const f of readdirSync(this.dir)) {
|
|
13221
13595
|
if (!f.endsWith(".json")) continue;
|
|
13222
13596
|
try {
|
|
13223
|
-
metas.push(JSON.parse(readFileSync8(
|
|
13597
|
+
metas.push(JSON.parse(readFileSync8(join10(this.dir, f), "utf8")).meta);
|
|
13224
13598
|
} catch (e) {
|
|
13225
13599
|
log30.debug(`skipping unreadable session file ${f}`, e);
|
|
13226
13600
|
}
|
|
@@ -13235,7 +13609,7 @@ var SessionStore = class {
|
|
|
13235
13609
|
for (const f of readdirSync(this.dir)) {
|
|
13236
13610
|
if (!f.endsWith(".json")) continue;
|
|
13237
13611
|
try {
|
|
13238
|
-
const st =
|
|
13612
|
+
const st = statSync7(join10(this.dir, f));
|
|
13239
13613
|
out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
|
|
13240
13614
|
} catch {
|
|
13241
13615
|
}
|
|
@@ -13254,10 +13628,10 @@ var SessionStore = class {
|
|
|
13254
13628
|
function globalSessionLoad(idOrPrefix) {
|
|
13255
13629
|
const gd = globalDir();
|
|
13256
13630
|
if (!existsSync6(gd)) return void 0;
|
|
13257
|
-
const exact =
|
|
13631
|
+
const exact = join10(gd, `${idOrPrefix}.json`);
|
|
13258
13632
|
if (existsSync6(exact)) {
|
|
13259
13633
|
try {
|
|
13260
|
-
const target =
|
|
13634
|
+
const target = readIndexLink(exact);
|
|
13261
13635
|
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13262
13636
|
} catch {
|
|
13263
13637
|
return void 0;
|
|
@@ -13268,7 +13642,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
13268
13642
|
if (!f.endsWith(".json")) continue;
|
|
13269
13643
|
const base = f.slice(0, -5);
|
|
13270
13644
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
13271
|
-
const target =
|
|
13645
|
+
const target = readIndexLink(join10(gd, f));
|
|
13272
13646
|
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13273
13647
|
}
|
|
13274
13648
|
}
|
|
@@ -13283,10 +13657,10 @@ function globalSessionList() {
|
|
|
13283
13657
|
for (const f of readdirSync(gd)) {
|
|
13284
13658
|
if (!f.endsWith(".json")) continue;
|
|
13285
13659
|
try {
|
|
13286
|
-
const target =
|
|
13660
|
+
const target = readIndexLink(join10(gd, f));
|
|
13287
13661
|
if (!existsSync6(target)) {
|
|
13288
13662
|
try {
|
|
13289
|
-
unlinkSync(
|
|
13663
|
+
unlinkSync(join10(gd, f));
|
|
13290
13664
|
} catch {
|
|
13291
13665
|
}
|
|
13292
13666
|
continue;
|
|
@@ -13456,17 +13830,17 @@ ${formatDiff(ops)}`);
|
|
|
13456
13830
|
};
|
|
13457
13831
|
|
|
13458
13832
|
// cli/gitCheckpoints.ts
|
|
13459
|
-
import { execFile as
|
|
13833
|
+
import { execFile as execFile4 } from "child_process";
|
|
13460
13834
|
import { promisify } from "util";
|
|
13461
13835
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
13462
13836
|
import { homedir as homedir7 } from "os";
|
|
13463
|
-
import { join as
|
|
13837
|
+
import { join as join11, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
|
|
13464
13838
|
var log31 = forComponent("checkpoints");
|
|
13465
|
-
var exec = promisify(
|
|
13839
|
+
var exec = promisify(execFile4);
|
|
13466
13840
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
13467
13841
|
function isBroadRoot(p) {
|
|
13468
|
-
const abs =
|
|
13469
|
-
return abs ===
|
|
13842
|
+
const abs = resolve4(p);
|
|
13843
|
+
return abs === resolve4(homedir7()) || abs === parsePath(abs).root;
|
|
13470
13844
|
}
|
|
13471
13845
|
var ShadowRepo = class {
|
|
13472
13846
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -13503,8 +13877,8 @@ var ShadowRepo = class {
|
|
|
13503
13877
|
mkdirSync9(this.gitDir, { recursive: true });
|
|
13504
13878
|
const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
|
|
13505
13879
|
if (!valid) await this.run("init", "-q");
|
|
13506
|
-
mkdirSync9(
|
|
13507
|
-
writeFileSync6(
|
|
13880
|
+
mkdirSync9(join11(this.gitDir, "info"), { recursive: true });
|
|
13881
|
+
writeFileSync6(join11(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
13508
13882
|
this.ready = true;
|
|
13509
13883
|
} catch (e) {
|
|
13510
13884
|
log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
@@ -13613,13 +13987,13 @@ var GitCheckpoints = class {
|
|
|
13613
13987
|
}
|
|
13614
13988
|
/** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
|
|
13615
13989
|
roots() {
|
|
13616
|
-
const cwd =
|
|
13990
|
+
const cwd = resolve4(this.options.workTree);
|
|
13617
13991
|
const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
|
|
13618
13992
|
for (const d of this.options.addDirs ?? []) {
|
|
13619
|
-
const abs =
|
|
13993
|
+
const abs = resolve4(d);
|
|
13620
13994
|
if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
|
|
13621
13995
|
if (cwd.startsWith(abs + sep3)) continue;
|
|
13622
|
-
out.push({ workTree: abs, gitDir:
|
|
13996
|
+
out.push({ workTree: abs, gitDir: join11(abs, ".agent", "checkpoints.git") });
|
|
13623
13997
|
}
|
|
13624
13998
|
return out;
|
|
13625
13999
|
}
|
|
@@ -13706,7 +14080,7 @@ var GitCheckpoints = class {
|
|
|
13706
14080
|
preToolUse: async (call) => {
|
|
13707
14081
|
if (!this.snapshotted || !this.repos.length) return;
|
|
13708
14082
|
for (const rel of mutatedPaths(call)) {
|
|
13709
|
-
const abs =
|
|
14083
|
+
const abs = resolve4(this.options.workTree, rel);
|
|
13710
14084
|
if (this.forced.has(abs)) continue;
|
|
13711
14085
|
this.forced.add(abs);
|
|
13712
14086
|
const i = this.repoIndexFor(abs);
|
|
@@ -13762,7 +14136,7 @@ var GitCheckpointsOptions = class {
|
|
|
13762
14136
|
/** Real working tree to snapshot (the launch cwd). */
|
|
13763
14137
|
workTree = process.cwd();
|
|
13764
14138
|
/** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
|
|
13765
|
-
gitDir =
|
|
14139
|
+
gitDir = join11(process.cwd(), ".agent", "checkpoints.git");
|
|
13766
14140
|
/** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
|
|
13767
14141
|
addDirs = [];
|
|
13768
14142
|
/** Conversation id → per-session restore-point ref. */
|
|
@@ -13835,10 +14209,10 @@ function completePath(listDir, ref) {
|
|
|
13835
14209
|
|
|
13836
14210
|
// cli/lineEditor.ts
|
|
13837
14211
|
import { emitKeypressEvents } from "readline";
|
|
13838
|
-
import { spawnSync as
|
|
14212
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
13839
14213
|
import { writeFileSync as writeFileSync7, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
|
|
13840
14214
|
import { tmpdir as tmpdir2 } from "os";
|
|
13841
|
-
import { join as
|
|
14215
|
+
import { join as join12 } from "path";
|
|
13842
14216
|
|
|
13843
14217
|
// cli/bidi.ts
|
|
13844
14218
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -14335,6 +14709,7 @@ function releaseKeyInput() {
|
|
|
14335
14709
|
}
|
|
14336
14710
|
|
|
14337
14711
|
// cli/lineEditor.ts
|
|
14712
|
+
init_platform();
|
|
14338
14713
|
var cpWidth = (cp) => {
|
|
14339
14714
|
if (cp === 8205 || cp === 65039 || cp >= 768 && cp <= 879) return 0;
|
|
14340
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;
|
|
@@ -15023,14 +15398,15 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15023
15398
|
curRow = cursorRow;
|
|
15024
15399
|
}
|
|
15025
15400
|
function externalEdit(s) {
|
|
15026
|
-
const spec = process.env.VISUAL || process.env.EDITOR ||
|
|
15401
|
+
const spec = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
|
|
15027
15402
|
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
15028
|
-
const file =
|
|
15403
|
+
const file = join12(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
15029
15404
|
try {
|
|
15030
15405
|
writeFileSync7(file, s.buf);
|
|
15031
15406
|
process.stdin.setRawMode(false);
|
|
15032
15407
|
out.write("\x1B[?2004l");
|
|
15033
|
-
const
|
|
15408
|
+
const sp = resolveSpawn(cmd, [...cargs, file]);
|
|
15409
|
+
const r = spawnSync5(sp.command, sp.args, { stdio: "inherit", ...sp.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
|
|
15034
15410
|
if (r.status === 0) {
|
|
15035
15411
|
const text = readFileSync9(file, "utf8").replace(/\n$/, "");
|
|
15036
15412
|
s.reset();
|
|
@@ -15068,10 +15444,10 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15068
15444
|
render(s, promptOf(), maxVisible, opts.status);
|
|
15069
15445
|
};
|
|
15070
15446
|
process.on("SIGWINCH", onResize);
|
|
15071
|
-
return new Promise((
|
|
15447
|
+
return new Promise((resolve6) => {
|
|
15072
15448
|
activeAbort = () => {
|
|
15073
15449
|
finish();
|
|
15074
|
-
|
|
15450
|
+
resolve6(null);
|
|
15075
15451
|
};
|
|
15076
15452
|
const redraw = () => render(s, promptOf(), maxVisible, opts.status);
|
|
15077
15453
|
let lastStatus = opts.status?.() ?? "";
|
|
@@ -15151,20 +15527,20 @@ function createLineEditor(out, input = keyInput) {
|
|
|
15151
15527
|
if (action === "submit") {
|
|
15152
15528
|
finish();
|
|
15153
15529
|
opts.onSubmit?.();
|
|
15154
|
-
return
|
|
15530
|
+
return resolve6(s.expand());
|
|
15155
15531
|
}
|
|
15156
15532
|
if (action === "eof") {
|
|
15157
15533
|
finish();
|
|
15158
|
-
return
|
|
15534
|
+
return resolve6(null);
|
|
15159
15535
|
}
|
|
15160
15536
|
if (action === "rewind") {
|
|
15161
15537
|
finish();
|
|
15162
|
-
return
|
|
15538
|
+
return resolve6(REWIND);
|
|
15163
15539
|
}
|
|
15164
15540
|
if (action === "cancel") {
|
|
15165
15541
|
if (s.buf.length === 0) {
|
|
15166
15542
|
finish();
|
|
15167
|
-
return
|
|
15543
|
+
return resolve6(null);
|
|
15168
15544
|
}
|
|
15169
15545
|
s.reset();
|
|
15170
15546
|
s.refresh();
|
|
@@ -15306,7 +15682,7 @@ function selectMenu(stream, opts) {
|
|
|
15306
15682
|
out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
|
|
15307
15683
|
prevLines++;
|
|
15308
15684
|
};
|
|
15309
|
-
return new Promise((
|
|
15685
|
+
return new Promise((resolve6) => {
|
|
15310
15686
|
const wasRaw = !!keyInput.isRaw;
|
|
15311
15687
|
menusOpen++;
|
|
15312
15688
|
emitKeypressEvents(keyInput);
|
|
@@ -15325,7 +15701,7 @@ function selectMenu(stream, opts) {
|
|
|
15325
15701
|
} catch {
|
|
15326
15702
|
}
|
|
15327
15703
|
}
|
|
15328
|
-
|
|
15704
|
+
resolve6(val);
|
|
15329
15705
|
};
|
|
15330
15706
|
const drillIn = () => {
|
|
15331
15707
|
const it = items[sel];
|
|
@@ -15446,19 +15822,19 @@ function windowAround(count, sel, max) {
|
|
|
15446
15822
|
var plainBuf = "";
|
|
15447
15823
|
var plainEnded = false;
|
|
15448
15824
|
function readPlainLine() {
|
|
15449
|
-
return new Promise((
|
|
15825
|
+
return new Promise((resolve6) => {
|
|
15450
15826
|
const emit = () => {
|
|
15451
15827
|
const nl = plainBuf.indexOf("\n");
|
|
15452
15828
|
if (nl >= 0) {
|
|
15453
15829
|
const line = plainBuf.slice(0, nl);
|
|
15454
15830
|
plainBuf = plainBuf.slice(nl + 1);
|
|
15455
|
-
|
|
15831
|
+
resolve6(line);
|
|
15456
15832
|
return true;
|
|
15457
15833
|
}
|
|
15458
15834
|
if (plainEnded) {
|
|
15459
15835
|
const rest = plainBuf;
|
|
15460
15836
|
plainBuf = "";
|
|
15461
|
-
|
|
15837
|
+
resolve6(rest.length ? rest : null);
|
|
15462
15838
|
return true;
|
|
15463
15839
|
}
|
|
15464
15840
|
return false;
|
|
@@ -15484,11 +15860,15 @@ function readPlainLine() {
|
|
|
15484
15860
|
});
|
|
15485
15861
|
}
|
|
15486
15862
|
|
|
15863
|
+
// cli/cli.ts
|
|
15864
|
+
init_platform();
|
|
15865
|
+
init_hostPath();
|
|
15866
|
+
|
|
15487
15867
|
// cli/osScheduler.ts
|
|
15488
|
-
import { spawnSync as
|
|
15868
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
15489
15869
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
15490
15870
|
import { homedir as homedir8 } from "os";
|
|
15491
|
-
import { join as
|
|
15871
|
+
import { join as join13 } from "path";
|
|
15492
15872
|
var log32 = forComponent("os-sched");
|
|
15493
15873
|
var OsScheduler = class {
|
|
15494
15874
|
options;
|
|
@@ -15496,13 +15876,13 @@ var OsScheduler = class {
|
|
|
15496
15876
|
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
15497
15877
|
}
|
|
15498
15878
|
get dir() {
|
|
15499
|
-
return
|
|
15879
|
+
return join13(this.options.home, ".agent", "sched");
|
|
15500
15880
|
}
|
|
15501
15881
|
label(id) {
|
|
15502
15882
|
return `cc.livx.agentx.sched-${id}`;
|
|
15503
15883
|
}
|
|
15504
15884
|
plistPath(id) {
|
|
15505
|
-
return
|
|
15885
|
+
return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
15506
15886
|
}
|
|
15507
15887
|
run(cmd, args, input) {
|
|
15508
15888
|
return this.options.exec(cmd, args, input);
|
|
@@ -15518,11 +15898,11 @@ var OsScheduler = class {
|
|
|
15518
15898
|
const script = this.writeScript(spec, oneOff);
|
|
15519
15899
|
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
15520
15900
|
const meta = { ...spec, created: Date.now(), mechanism };
|
|
15521
|
-
writeFileSync8(
|
|
15901
|
+
writeFileSync8(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
15522
15902
|
return mechanism;
|
|
15523
15903
|
}
|
|
15524
15904
|
cancel(id) {
|
|
15525
|
-
const meta = readJsonFile(
|
|
15905
|
+
const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
|
|
15526
15906
|
if (!meta) return false;
|
|
15527
15907
|
try {
|
|
15528
15908
|
if (this.options.platform === "darwin") {
|
|
@@ -15555,7 +15935,7 @@ var OsScheduler = class {
|
|
|
15555
15935
|
}
|
|
15556
15936
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
15557
15937
|
try {
|
|
15558
|
-
unlinkSync3(
|
|
15938
|
+
unlinkSync3(join13(this.dir, f));
|
|
15559
15939
|
} catch {
|
|
15560
15940
|
}
|
|
15561
15941
|
}
|
|
@@ -15563,19 +15943,19 @@ var OsScheduler = class {
|
|
|
15563
15943
|
}
|
|
15564
15944
|
list() {
|
|
15565
15945
|
if (!existsSync7(this.dir)) return [];
|
|
15566
|
-
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);
|
|
15567
15947
|
}
|
|
15568
15948
|
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
15569
15949
|
writeScript(spec, oneOff) {
|
|
15570
|
-
const p =
|
|
15950
|
+
const p = join13(this.dir, `${spec.id}.sh`);
|
|
15571
15951
|
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
15572
|
-
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(
|
|
15573
|
-
` : `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)}
|
|
15574
15954
|
` : "";
|
|
15575
15955
|
writeFileSync8(p, `#!/bin/sh
|
|
15576
15956
|
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
15577
15957
|
cd ${q2(spec.cwd)} || exit 1
|
|
15578
|
-
${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
|
|
15579
15959
|
${cleanup}`);
|
|
15580
15960
|
chmodSync(p, 493);
|
|
15581
15961
|
return p;
|
|
@@ -15612,7 +15992,7 @@ ${trigger}
|
|
|
15612
15992
|
<key>RunAtLoad</key><false/>
|
|
15613
15993
|
</dict></plist>
|
|
15614
15994
|
`;
|
|
15615
|
-
mkdirSync10(
|
|
15995
|
+
mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
15616
15996
|
writeFileSync8(this.plistPath(spec.id), plist);
|
|
15617
15997
|
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
15618
15998
|
return `launchd:${this.label(spec.id)}`;
|
|
@@ -15650,7 +16030,7 @@ var OsSchedulerOptions = class {
|
|
|
15650
16030
|
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
15651
16031
|
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
15652
16032
|
exec = (cmd, args, input) => {
|
|
15653
|
-
const r =
|
|
16033
|
+
const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
|
|
15654
16034
|
if (r.error) throw r.error;
|
|
15655
16035
|
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
15656
16036
|
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
@@ -15667,10 +16047,11 @@ import { createServer as createServer2, createConnection } from "net";
|
|
|
15667
16047
|
import { spawn as spawn3 } from "child_process";
|
|
15668
16048
|
import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
15669
16049
|
import { homedir as homedir9 } from "os";
|
|
15670
|
-
import { join as
|
|
16050
|
+
import { join as join14 } from "path";
|
|
16051
|
+
init_platform();
|
|
15671
16052
|
var log33 = forComponent("remote-trigger");
|
|
15672
|
-
var TRIGGER_DIR = () =>
|
|
15673
|
-
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`);
|
|
15674
16055
|
var TriggerServer = class {
|
|
15675
16056
|
constructor(onTrigger) {
|
|
15676
16057
|
this.onTrigger = onTrigger;
|
|
@@ -15763,6 +16144,15 @@ function triggerLive(sessionId, req, dir = TRIGGER_DIR(), timeoutMs = 3e3) {
|
|
|
15763
16144
|
});
|
|
15764
16145
|
});
|
|
15765
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
|
+
}
|
|
15766
16156
|
function makeRemoteTriggerTool(opts) {
|
|
15767
16157
|
const dir = opts.triggerDir ?? TRIGGER_DIR();
|
|
15768
16158
|
const doSpawn = opts.spawnFn ?? spawn3;
|
|
@@ -15789,9 +16179,10 @@ function makeRemoteTriggerTool(opts) {
|
|
|
15789
16179
|
if (live?.ok) return `Delivered to live session ${full} \u2014 it runs there; the user sees it in that terminal.`;
|
|
15790
16180
|
if (!target.meta.cwd || !existsSync8(target.meta.cwd)) return `Error: session ${full} has no reachable cwd (${target.meta.cwd ?? "unknown"}).`;
|
|
15791
16181
|
return new Promise((res) => {
|
|
15792
|
-
const child = doSpawn(
|
|
16182
|
+
const child = doSpawn(...headlessArgv(opts, text, full), {
|
|
15793
16183
|
cwd: target.meta.cwd,
|
|
15794
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16184
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
16185
|
+
...IS_WIN ? { windowsHide: true } : {}
|
|
15795
16186
|
});
|
|
15796
16187
|
let out = "", errOut = "";
|
|
15797
16188
|
child.stdout?.on("data", (d) => {
|
|
@@ -15856,7 +16247,7 @@ async function checkForUpdate(current) {
|
|
|
15856
16247
|
return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
|
|
15857
16248
|
}
|
|
15858
16249
|
function installUpdate() {
|
|
15859
|
-
const r =
|
|
16250
|
+
const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
|
|
15860
16251
|
return (r.status ?? 1) === 0;
|
|
15861
16252
|
}
|
|
15862
16253
|
var typeaheadOnLine = false;
|
|
@@ -16156,11 +16547,11 @@ function loadEnvFile(file) {
|
|
|
16156
16547
|
}
|
|
16157
16548
|
}
|
|
16158
16549
|
function loadInstallEnv() {
|
|
16159
|
-
let dir =
|
|
16160
|
-
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);
|
|
16161
16552
|
for (const name of [".env", ".env.local"]) {
|
|
16162
|
-
loadEnvFile(
|
|
16163
|
-
loadEnvFile(
|
|
16553
|
+
loadEnvFile(join15(dir, name));
|
|
16554
|
+
loadEnvFile(join15(homedir10(), ".agent", name));
|
|
16164
16555
|
}
|
|
16165
16556
|
}
|
|
16166
16557
|
async function loadBodifySecrets() {
|
|
@@ -16194,6 +16585,31 @@ function apiKeysFromEnv() {
|
|
|
16194
16585
|
}
|
|
16195
16586
|
return keys;
|
|
16196
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
|
+
}
|
|
16197
16613
|
function makeHost(format = "text", opts) {
|
|
16198
16614
|
const streamJson = format === "stream-json";
|
|
16199
16615
|
const cleanStdout = format === "json";
|
|
@@ -16802,7 +17218,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
16802
17218
|
stream: args.stream,
|
|
16803
17219
|
host: makeHost(args.outputFormat, { stream: true }),
|
|
16804
17220
|
planMode: args.plan || cfg.planMode || false,
|
|
16805
|
-
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,
|
|
16806
17222
|
subagents: args.subagents || cfg.subagents || false,
|
|
16807
17223
|
maxSteps: args.maxSteps ?? cfg.maxSteps,
|
|
16808
17224
|
reasoning: args.reasoning ?? cfg.reasoning,
|
|
@@ -16884,16 +17300,23 @@ var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"
|
|
|
16884
17300
|
function mentionRefs(line) {
|
|
16885
17301
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
16886
17302
|
}
|
|
16887
|
-
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
|
+
};
|
|
16888
17311
|
function readImageParts(cwd, line) {
|
|
16889
17312
|
const refs = mentionRefs(line);
|
|
16890
17313
|
const parts = [];
|
|
16891
17314
|
for (const ref of refs) {
|
|
16892
17315
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
16893
17316
|
if (!mime) continue;
|
|
16894
|
-
const abs =
|
|
17317
|
+
const abs = userPath(cwd, ref);
|
|
16895
17318
|
try {
|
|
16896
|
-
|
|
17319
|
+
statSync8(abs);
|
|
16897
17320
|
} catch {
|
|
16898
17321
|
continue;
|
|
16899
17322
|
}
|
|
@@ -16905,16 +17328,16 @@ function pastePathClassifier(cwd) {
|
|
|
16905
17328
|
return (text) => {
|
|
16906
17329
|
let t = text.trim();
|
|
16907
17330
|
if (!t || t.includes("\n")) return null;
|
|
16908
|
-
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
16909
|
-
if (
|
|
16910
|
-
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);
|
|
16911
17334
|
try {
|
|
16912
|
-
if (!
|
|
17335
|
+
if (!statSync8(abs).isFile()) return null;
|
|
16913
17336
|
} catch {
|
|
16914
17337
|
return null;
|
|
16915
17338
|
}
|
|
16916
17339
|
const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
|
|
16917
|
-
return { display: isImg ? "Image" : `File ${
|
|
17340
|
+
return { display: isImg ? "Image" : `File ${basename4(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
|
|
16918
17341
|
};
|
|
16919
17342
|
}
|
|
16920
17343
|
var mcpMentionResolver;
|
|
@@ -17038,7 +17461,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17038
17461
|
store.save(session);
|
|
17039
17462
|
} catch {
|
|
17040
17463
|
}
|
|
17041
|
-
return { ok: false };
|
|
17464
|
+
return { ok: false, error: e };
|
|
17042
17465
|
} finally {
|
|
17043
17466
|
spinner.stop();
|
|
17044
17467
|
spinner.turnStart = 0;
|
|
@@ -17089,7 +17512,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17089
17512
|
}
|
|
17090
17513
|
return { ok, res };
|
|
17091
17514
|
}
|
|
17092
|
-
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]);
|
|
17093
17516
|
function startSession(args, store, agent, cwd) {
|
|
17094
17517
|
if (args.resume || args.cont) {
|
|
17095
17518
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
@@ -17136,24 +17559,24 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
17136
17559
|
`;
|
|
17137
17560
|
function initInstructions(cwd) {
|
|
17138
17561
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17139
|
-
if (existsSync9(
|
|
17562
|
+
if (existsSync9(join15(cwd, f))) {
|
|
17140
17563
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
17141
17564
|
`));
|
|
17142
17565
|
return;
|
|
17143
17566
|
}
|
|
17144
17567
|
}
|
|
17145
|
-
const path =
|
|
17146
|
-
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}",
|
|
17568
|
+
const path = join15(cwd, "AGENTS.md");
|
|
17569
|
+
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
|
|
17147
17570
|
err(green(` created ${path}
|
|
17148
17571
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
17149
17572
|
}
|
|
17150
17573
|
function persistSetting(cwd, key, value) {
|
|
17151
|
-
const path =
|
|
17574
|
+
const path = join15(cwd, ".agent", "settings.json");
|
|
17152
17575
|
try {
|
|
17153
17576
|
const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
|
|
17154
17577
|
if (obj[key] === value) return;
|
|
17155
17578
|
obj[key] = value;
|
|
17156
|
-
mkdirSync12(
|
|
17579
|
+
mkdirSync12(dirname6(path), { recursive: true });
|
|
17157
17580
|
writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
|
|
17158
17581
|
} catch (e) {
|
|
17159
17582
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
@@ -17186,7 +17609,7 @@ function installCancelGuards(mounted) {
|
|
|
17186
17609
|
});
|
|
17187
17610
|
}
|
|
17188
17611
|
async function repl(args, ai, cfg, cwd) {
|
|
17189
|
-
const oauth = new McpOAuth({ storePath:
|
|
17612
|
+
const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
|
|
17190
17613
|
const mounted = await mountMcp(cfg, oauth);
|
|
17191
17614
|
let mcpToolNames = [];
|
|
17192
17615
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -17209,7 +17632,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17209
17632
|
},
|
|
17210
17633
|
tickMs: 15e3
|
|
17211
17634
|
});
|
|
17212
|
-
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${
|
|
17635
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve5(process.argv[1])}` : "agentx";
|
|
17213
17636
|
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
17214
17637
|
const osBackend = osSched.available() ? {
|
|
17215
17638
|
get sessionId() {
|
|
@@ -17232,7 +17655,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17232
17655
|
scheduleQueue.push(req.prompt);
|
|
17233
17656
|
}
|
|
17234
17657
|
});
|
|
17235
|
-
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 })];
|
|
17236
17659
|
const duplex = args.duplex;
|
|
17237
17660
|
let dx;
|
|
17238
17661
|
let voiceIO;
|
|
@@ -17437,6 +17860,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17437
17860
|
return `Rewound ${n} task checkpoint(s): restored ${r.restored} file(s), deleted ${r.deleted}.`;
|
|
17438
17861
|
}
|
|
17439
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
|
+
`));
|
|
17440
17866
|
dx = new DuplexAgent({
|
|
17441
17867
|
ai,
|
|
17442
17868
|
...instinct ? { bargeIntent: (t) => instinct.bargeIntent(t) } : {},
|
|
@@ -17444,11 +17870,10 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17444
17870
|
fs: agent.options.fs,
|
|
17445
17871
|
memoryDir: agent.options.memoryDir,
|
|
17446
17872
|
memoryUserDir: agent.options.memoryUserDir,
|
|
17447
|
-
...
|
|
17873
|
+
...tierModels,
|
|
17448
17874
|
actModel: agent.options.model,
|
|
17449
17875
|
actOptions: workerOptions,
|
|
17450
17876
|
providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
|
|
17451
|
-
...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
|
|
17452
17877
|
host,
|
|
17453
17878
|
...args.voice ? { voiceStyle: "conversational", progressUpdates: true, askRelay: true, emotionTags: emotionsOn, spokenBudgetChars: 350 } : {},
|
|
17454
17879
|
// voice: progress asides + worker questions relayed through the conversation; emotion tags taught only with a TTS that speaks them
|
|
@@ -17462,7 +17887,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17462
17887
|
quickLook: {
|
|
17463
17888
|
branch: () => {
|
|
17464
17889
|
try {
|
|
17465
|
-
const head = readFileSync10(
|
|
17890
|
+
const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
|
|
17466
17891
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
17467
17892
|
} catch {
|
|
17468
17893
|
return "not a git repository";
|
|
@@ -17596,7 +18021,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17596
18021
|
const pendingImages = [];
|
|
17597
18022
|
const bangContext = [];
|
|
17598
18023
|
const grabClipboardAttachment = () => {
|
|
17599
|
-
const dir =
|
|
18024
|
+
const dir = join15(store.dir, `${session.meta.id}.images`);
|
|
17600
18025
|
try {
|
|
17601
18026
|
mkdirSync12(dir, { recursive: true });
|
|
17602
18027
|
} catch (e) {
|
|
@@ -17654,7 +18079,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17654
18079
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
17655
18080
|
`));
|
|
17656
18081
|
}
|
|
17657
|
-
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 });
|
|
17658
18083
|
const cpHooks = checkpoints.hooks?.();
|
|
17659
18084
|
if (cpHooks) {
|
|
17660
18085
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -17706,11 +18131,11 @@ ${lines.join("\n")}
|
|
|
17706
18131
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
17707
18132
|
</system-reminder>`;
|
|
17708
18133
|
};
|
|
17709
|
-
const histPath =
|
|
18134
|
+
const histPath = join15(cwd, ".agent", "history");
|
|
17710
18135
|
const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
17711
18136
|
const remember2 = (line) => {
|
|
17712
18137
|
try {
|
|
17713
|
-
mkdirSync12(
|
|
18138
|
+
mkdirSync12(join15(cwd, ".agent"), { recursive: true });
|
|
17714
18139
|
appendFileSync(histPath, line + "\n");
|
|
17715
18140
|
} catch (e) {
|
|
17716
18141
|
log34.debug("history write failed", e);
|
|
@@ -18013,8 +18438,8 @@ ${task}`;
|
|
|
18013
18438
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
18014
18439
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
18015
18440
|
try {
|
|
18016
|
-
const { spawnSync:
|
|
18017
|
-
const r =
|
|
18441
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18442
|
+
const r = spawnSync8("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
18018
18443
|
if (r.error) err(text);
|
|
18019
18444
|
} finally {
|
|
18020
18445
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -18034,8 +18459,7 @@ ${task}`;
|
|
|
18034
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)");
|
|
18035
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)");
|
|
18036
18461
|
{
|
|
18037
|
-
const
|
|
18038
|
-
const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
|
|
18462
|
+
const has = hasCommand;
|
|
18039
18463
|
if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
|
|
18040
18464
|
else {
|
|
18041
18465
|
ok("voice: keys present");
|
|
@@ -18462,12 +18886,13 @@ ${task}`;
|
|
|
18462
18886
|
return;
|
|
18463
18887
|
}
|
|
18464
18888
|
}
|
|
18465
|
-
const ed = process.env.VISUAL || process.env.EDITOR ||
|
|
18889
|
+
const ed = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
|
|
18466
18890
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
18467
18891
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
18468
18892
|
try {
|
|
18469
|
-
const { spawnSync:
|
|
18470
|
-
|
|
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 } : {} });
|
|
18471
18896
|
} finally {
|
|
18472
18897
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
18473
18898
|
}
|
|
@@ -18817,10 +19242,10 @@ ${task}`;
|
|
|
18817
19242
|
return;
|
|
18818
19243
|
}
|
|
18819
19244
|
const md = exportMarkdown(session.meta, shown);
|
|
18820
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
18821
|
-
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);
|
|
18822
19247
|
try {
|
|
18823
|
-
mkdirSync12(
|
|
19248
|
+
mkdirSync12(dirname6(path), { recursive: true });
|
|
18824
19249
|
writeFileSync9(path, md);
|
|
18825
19250
|
err(green(` \u2713 exported \u2192 ${path}
|
|
18826
19251
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
@@ -18903,7 +19328,7 @@ ${task}`;
|
|
|
18903
19328
|
});
|
|
18904
19329
|
const listDir = (absDir) => {
|
|
18905
19330
|
try {
|
|
18906
|
-
return readdirSync4(
|
|
19331
|
+
return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
18907
19332
|
} catch (e) {
|
|
18908
19333
|
log34.debug("completion readdir failed", absDir, e);
|
|
18909
19334
|
return null;
|
|
@@ -19088,7 +19513,7 @@ ${out}
|
|
|
19088
19513
|
return false;
|
|
19089
19514
|
}
|
|
19090
19515
|
const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
|
|
19091
|
-
voiceDiag = new JsonlDiagSink(
|
|
19516
|
+
voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
|
|
19092
19517
|
voiceIO = new VoiceIO({
|
|
19093
19518
|
...fakeVoice ?? {},
|
|
19094
19519
|
onDiag: (ev) => voiceDiag?.push(ev),
|
|
@@ -19489,9 +19914,17 @@ ${out}
|
|
|
19489
19914
|
disposeClaudeCodeSessions();
|
|
19490
19915
|
await closeMcp(mounted);
|
|
19491
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
|
+
}
|
|
19492
19925
|
async function duplexServe(args, ai, cfg, cwd) {
|
|
19493
19926
|
const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
|
|
19494
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
19927
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19495
19928
|
installCancelGuards(mounted);
|
|
19496
19929
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
|
|
19497
19930
|
const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
|
|
@@ -19511,16 +19944,18 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19511
19944
|
};
|
|
19512
19945
|
let persist = () => {
|
|
19513
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
|
+
`));
|
|
19514
19950
|
const dx = new DuplexAgent({
|
|
19515
19951
|
ai,
|
|
19516
19952
|
fs: agent.options.fs,
|
|
19517
19953
|
memoryDir: agent.options.memoryDir,
|
|
19518
19954
|
memoryUserDir: agent.options.memoryUserDir,
|
|
19519
|
-
...
|
|
19955
|
+
...tierModels,
|
|
19520
19956
|
actModel: agent.options.model,
|
|
19521
19957
|
actOptions: { ...wo, planMode: false },
|
|
19522
19958
|
providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
|
|
19523
|
-
...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
|
|
19524
19959
|
host,
|
|
19525
19960
|
// --voice: the CLI voice register (progress asides, spoken budget). No emotion tags: the host's TTS may not speak them.
|
|
19526
19961
|
...args.voice ? { voiceStyle: "conversational", progressUpdates: true, spokenBudgetChars: 350 } : {},
|
|
@@ -19538,7 +19973,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19538
19973
|
log34.warn("duplex session save failed", e);
|
|
19539
19974
|
}
|
|
19540
19975
|
};
|
|
19541
|
-
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 } : {} });
|
|
19542
19977
|
const done = new Promise((resolveDone) => {
|
|
19543
19978
|
const rl = createInterface({ input: process.stdin });
|
|
19544
19979
|
rl.on("line", (line) => {
|
|
@@ -19558,7 +19993,10 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19558
19993
|
if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
|
|
19559
19994
|
const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
|
|
19560
19995
|
const id = m.id ? String(m.id) : void 0;
|
|
19561
|
-
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) => {
|
|
19562
20000
|
log34.error("duplex turn failed", e);
|
|
19563
20001
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
19564
20002
|
});
|
|
@@ -19613,7 +20051,7 @@ async function main() {
|
|
|
19613
20051
|
let worktreeCleanup;
|
|
19614
20052
|
if (args.worktree) {
|
|
19615
20053
|
const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
19616
|
-
const session = enterWorktree2(args.worktree,
|
|
20054
|
+
const session = enterWorktree2(args.worktree, resolve5(args.cwd ?? process.cwd()));
|
|
19617
20055
|
err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
|
|
19618
20056
|
`));
|
|
19619
20057
|
worktreeCleanup = () => {
|
|
@@ -19623,8 +20061,8 @@ async function main() {
|
|
|
19623
20061
|
`));
|
|
19624
20062
|
};
|
|
19625
20063
|
}
|
|
19626
|
-
const cwd =
|
|
19627
|
-
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));
|
|
19628
20066
|
if (!args.autoRoute && !args.model && (cfg.model === "auto" || !cfg.model && await jevAvailable())) {
|
|
19629
20067
|
args.autoRoute = true;
|
|
19630
20068
|
args.model = autoModels().standard;
|
|
@@ -19656,7 +20094,8 @@ async function main() {
|
|
|
19656
20094
|
loadInstallEnv();
|
|
19657
20095
|
await loadBodifySecrets();
|
|
19658
20096
|
const apiKeys = { ...cfg.apiKeys, ...apiKeysFromEnv() };
|
|
19659
|
-
|
|
20097
|
+
const keyless = String(args.model ?? cfg.model ?? "").startsWith("claude-code/");
|
|
20098
|
+
if (!Object.keys(apiKeys).length && !keyless) {
|
|
19660
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."));
|
|
19661
20100
|
process.exit(1);
|
|
19662
20101
|
}
|
|
@@ -19673,7 +20112,7 @@ async function main() {
|
|
|
19673
20112
|
}
|
|
19674
20113
|
});
|
|
19675
20114
|
if (args.task) {
|
|
19676
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
20115
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19677
20116
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
|
|
19678
20117
|
const store = new SessionStore(cwd);
|
|
19679
20118
|
const session = startSession(args, store, agent, cwd);
|
|
@@ -19711,6 +20150,8 @@ export {
|
|
|
19711
20150
|
cacheMultipliers,
|
|
19712
20151
|
condenseReplay,
|
|
19713
20152
|
costOf,
|
|
20153
|
+
duplexError,
|
|
20154
|
+
duplexModels,
|
|
19714
20155
|
estimateTranscriptTokens,
|
|
19715
20156
|
expandMentions,
|
|
19716
20157
|
exportMarkdown,
|
|
@@ -19722,6 +20163,7 @@ export {
|
|
|
19722
20163
|
makeHost,
|
|
19723
20164
|
mcpMentionResolver,
|
|
19724
20165
|
mentionRefs,
|
|
20166
|
+
modelUsable,
|
|
19725
20167
|
parseArgs,
|
|
19726
20168
|
pastePathClassifier,
|
|
19727
20169
|
readImageParts,
|