@livx.cc/agentx 0.99.122 → 0.99.124
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/README.md +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +218 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +42 -12
- package/dist/index.js +210 -105
- package/dist/index.js.map +1 -1
- package/dist/{mcp-CUo-9LUe.d.ts → mcp-DZhcLur2.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{secondChannel-BddfVzTp.d.ts → secondChannel-GFT69ax4.d.ts} +47 -2
- package/dist/{tools-DZHZIyxh.d.ts → tools-DwfeF8VQ.d.ts} +3 -0
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js +1 -0
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -120,13 +120,13 @@ function elideImageParts(content, hit, stub) {
|
|
|
120
120
|
}
|
|
121
121
|
function mintImageRef(path, mime) {
|
|
122
122
|
const token = randomBytes(16).toString("hex");
|
|
123
|
-
let
|
|
123
|
+
let bytes2 = 0;
|
|
124
124
|
try {
|
|
125
|
-
|
|
125
|
+
bytes2 = Math.ceil(statSync2(path).size * 4 / 3);
|
|
126
126
|
} catch (e) {
|
|
127
127
|
log3.warn(`image ref stat failed, weighing as 0: ${path}`, e);
|
|
128
128
|
}
|
|
129
|
-
refRegistry.set(token, { mime, path, bytes });
|
|
129
|
+
refRegistry.set(token, { mime, path, bytes: bytes2 });
|
|
130
130
|
while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
|
|
131
131
|
return `${IMAGE_REF_SCHEME}${mime};${token};${path}`;
|
|
132
132
|
}
|
|
@@ -799,6 +799,7 @@ var init_tools_structured = __esm({
|
|
|
799
799
|
};
|
|
800
800
|
writeTool = {
|
|
801
801
|
name: "Write",
|
|
802
|
+
writes: true,
|
|
802
803
|
description: "Create or overwrite a file with the given contents, creating parent directories as needed. Use for new files instead of `bash echo >`. To replace an EXISTING file you must Read it first \u2014 overwriting an un-Read file is refused (it would destroy unseen content).",
|
|
803
804
|
parameters: {
|
|
804
805
|
type: "object",
|
|
@@ -824,6 +825,7 @@ var init_tools_structured = __esm({
|
|
|
824
825
|
};
|
|
825
826
|
multiEditTool = {
|
|
826
827
|
name: "MultiEdit",
|
|
828
|
+
writes: true,
|
|
827
829
|
description: "Apply several exact-substring replacements to one file in order, in a single call. Requires a prior Read. Each `old_string` must be unique at the time it is applied. All-or-nothing: if any edit fails, none are written.",
|
|
828
830
|
parameters: {
|
|
829
831
|
type: "object",
|
|
@@ -865,6 +867,7 @@ var init_tools_structured = __esm({
|
|
|
865
867
|
};
|
|
866
868
|
applyEditsTool = {
|
|
867
869
|
name: "ApplyEdits",
|
|
870
|
+
writes: true,
|
|
868
871
|
description: "Apply edits across one or MORE files in a single call \u2014 for cross-file refactors (rename/extract/move). edits=[{path, old_string?, new_string}]. WITH old_string: replace that exact substring (must be UNIQUE in the file \u2014 add surrounding context; read fresh + verified, no prior Read needed). WITHOUT old_string: write new_string as the whole file (creates it + parent dirs; but to OVERWRITE a file that already exists you must Read it first, else use old_string to edit part of it). Locate sites first with Grep (its output shows the exact text). Atomic: validated across all files before any write.",
|
|
869
872
|
parameters: {
|
|
870
873
|
type: "object",
|
|
@@ -1579,6 +1582,7 @@ var init_tools = __esm({
|
|
|
1579
1582
|
};
|
|
1580
1583
|
bashTool = {
|
|
1581
1584
|
name: "bash",
|
|
1585
|
+
writes: true,
|
|
1582
1586
|
description: "Run a shell command. Supports ls, cat, grep, find, head, tail, echo, mkdir, rm, mv, cp, wc, pipes (|), redirects (>, >>), and chaining (&&, ||, ;). Best for: running tests/builds, file operations (mkdir/mv/rm), and piped workflows. For searching file contents, prefer `Grep` (structured results, no re-parse). For finding files by name, prefer `Glob`.",
|
|
1583
1587
|
parameters: {
|
|
1584
1588
|
type: "object",
|
|
@@ -1630,14 +1634,14 @@ var init_tools = __esm({
|
|
|
1630
1634
|
if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
|
|
1631
1635
|
return imageRefResult(real, IMG_MIME[ext]);
|
|
1632
1636
|
}
|
|
1633
|
-
const
|
|
1634
|
-
const b64 = Buffer.from(
|
|
1637
|
+
const bytes2 = await fs.readFileBytes(path);
|
|
1638
|
+
const b64 = Buffer.from(bytes2).toString("base64");
|
|
1635
1639
|
return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });
|
|
1636
1640
|
}
|
|
1637
1641
|
const raw = await ctx.fs.readFile(path);
|
|
1638
1642
|
if (isBinary(raw)) {
|
|
1639
|
-
const
|
|
1640
|
-
return `[binary file: ${path} \u2014 ${
|
|
1643
|
+
const bytes2 = await byteSize(ctx.fs, path, raw);
|
|
1644
|
+
return `[binary file: ${path} \u2014 ${bytes2} bytes, not shown as text. Read renders png/jpg/jpeg/gif/webp images directly; otherwise attach it as @${path}.]`;
|
|
1641
1645
|
}
|
|
1642
1646
|
ctx.readState.set(ctx.fs.resolvePath(path), raw);
|
|
1643
1647
|
if (raw === "") return `[empty file: ${path} \u2014 0 bytes]`;
|
|
@@ -1656,6 +1660,7 @@ var init_tools = __esm({
|
|
|
1656
1660
|
};
|
|
1657
1661
|
editTool = {
|
|
1658
1662
|
name: "Edit",
|
|
1663
|
+
writes: true,
|
|
1659
1664
|
description: "Replace an exact substring in a file. Requires a prior Read of the same file. `old_string` must occur exactly once \u2014 include surrounding context to disambiguate.",
|
|
1660
1665
|
parameters: {
|
|
1661
1666
|
type: "object",
|
|
@@ -1967,8 +1972,8 @@ var init_tools_jobs = __esm({
|
|
|
1967
1972
|
return [...this.jobs.values()].map((j) => this.info(j));
|
|
1968
1973
|
}
|
|
1969
1974
|
info(j) {
|
|
1970
|
-
const { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes, holds, result } = j;
|
|
1971
|
-
return { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes, holds, result };
|
|
1975
|
+
const { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes: bytes2, holds, result } = j;
|
|
1976
|
+
return { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes: bytes2, holds, result };
|
|
1972
1977
|
}
|
|
1973
1978
|
/** Wait (bounded) for a job to settle. Resolves the job's info either way; null for an unknown id. */
|
|
1974
1979
|
async wait(id, timeoutMs) {
|
|
@@ -2402,7 +2407,8 @@ var init_JailedFilesystem = __esm({
|
|
|
2402
2407
|
violate(op, abs, rule) {
|
|
2403
2408
|
this.note(op, abs, rule);
|
|
2404
2409
|
if (rule === "deny" || rule === "confine") throw new Error(`File not found: ${abs}`);
|
|
2405
|
-
|
|
2410
|
+
const hint = rule === "readonly" && this.options.readonlyHint ? ` \u2014 ${this.options.readonlyHint}` : "";
|
|
2411
|
+
throw new Error(`Blocked by filesystem jail (${rule}): ${abs}${hint}`);
|
|
2406
2412
|
}
|
|
2407
2413
|
/** Fire the audit hook without throwing (for boolean probes that must still return false). */
|
|
2408
2414
|
note(op, abs, rule) {
|
|
@@ -2527,6 +2533,8 @@ var init_JailedFilesystem = __esm({
|
|
|
2527
2533
|
deny = [...DEFAULT_DENY];
|
|
2528
2534
|
/** Globs readable but not writable/deletable (the pinned constitution). */
|
|
2529
2535
|
readonly = [];
|
|
2536
|
+
/** Appended to readonly-violation errors (e.g. "edit it via the kb tool instead"). */
|
|
2537
|
+
readonlyHint;
|
|
2530
2538
|
/** If set, only paths matching these globs are accessible at all. */
|
|
2531
2539
|
confineTo;
|
|
2532
2540
|
/** Audit hook fired on every blocked operation (drives the circuit breaker). */
|
|
@@ -2792,6 +2800,7 @@ function makeRealShellTool(options) {
|
|
|
2792
2800
|
};
|
|
2793
2801
|
return {
|
|
2794
2802
|
name: "Shell",
|
|
2803
|
+
writes: true,
|
|
2795
2804
|
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
2796
2805
|
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
2797
2806
|
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
@@ -3281,69 +3290,39 @@ ${prompt} [y/N] `);
|
|
|
3281
3290
|
}
|
|
3282
3291
|
};
|
|
3283
3292
|
|
|
3284
|
-
// src/
|
|
3285
|
-
var
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
"
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
"
|
|
3296
|
-
"
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
"
|
|
3302
|
-
"
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
if (w.length >= 3 && !STOP.has(w)) out.add(w);
|
|
3318
|
-
}
|
|
3319
|
-
return out;
|
|
3320
|
-
}
|
|
3321
|
-
function idfWeights(corpus) {
|
|
3322
|
-
const N = corpus.length;
|
|
3323
|
-
if (N === 0) return /* @__PURE__ */ new Map();
|
|
3324
|
-
const df = /* @__PURE__ */ new Map();
|
|
3325
|
-
for (const doc of corpus) for (const t of tokenize(doc)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3326
|
-
const idf = /* @__PURE__ */ new Map();
|
|
3327
|
-
for (const [t, n] of df) idf.set(t, Math.log((N + 1) / (n + 1)) + 1);
|
|
3328
|
-
return idf;
|
|
3329
|
-
}
|
|
3330
|
-
function relevanceScore(text, queryTokens, idf) {
|
|
3331
|
-
if (queryTokens.size === 0) return 0;
|
|
3332
|
-
const t = tokenize(text);
|
|
3333
|
-
let score = 0;
|
|
3334
|
-
for (const q of queryTokens) if (t.has(q)) score += idf?.get(q) ?? 1;
|
|
3335
|
-
return score;
|
|
3336
|
-
}
|
|
3337
|
-
function topByRelevance(items, query, text, k, corpus) {
|
|
3338
|
-
if (!Number.isInteger(k) || k < 1) return { kept: items, rest: [] };
|
|
3339
|
-
if (items.length <= k || !query.trim()) return { kept: items, rest: [] };
|
|
3340
|
-
const q = tokenize(query);
|
|
3341
|
-
if (q.size === 0) return { kept: items.slice(0, k), rest: items.slice(k) };
|
|
3342
|
-
const idf = corpus?.length ? idfWeights(corpus) : void 0;
|
|
3343
|
-
const scored = items.map((x, i) => ({ i, s: relevanceScore(text(x), q, idf) }));
|
|
3344
|
-
scored.sort((a, b) => b.s - a.s || a.i - b.i);
|
|
3345
|
-
const keep = new Set(scored.slice(0, k).map((e) => e.i));
|
|
3346
|
-
return { kept: items.filter((_, i) => keep.has(i)), rest: items.filter((_, i) => !keep.has(i)) };
|
|
3293
|
+
// src/catalog.ts
|
|
3294
|
+
var DEFAULT_CATALOG_BUDGET = 6e3;
|
|
3295
|
+
var enc = new TextEncoder();
|
|
3296
|
+
var bytes = (s) => enc.encode(s).length;
|
|
3297
|
+
function clip(s, n) {
|
|
3298
|
+
const cps = [...s];
|
|
3299
|
+
if (cps.length <= n) return s;
|
|
3300
|
+
return n <= 1 ? "\u2026" : cps.slice(0, n - 1).join("").trimEnd() + "\u2026";
|
|
3301
|
+
}
|
|
3302
|
+
function formatCatalog(entries, opts) {
|
|
3303
|
+
const budget = opts.budgetBytes ?? DEFAULT_CATALOG_BUDGET;
|
|
3304
|
+
const pre = opts.namePrefix ?? "";
|
|
3305
|
+
const wrap = (body) => [opts.title, body, opts.footer].filter((x) => x !== void 0 && x !== "").join("\n");
|
|
3306
|
+
const render = (max) => wrap(entries.map((e) => {
|
|
3307
|
+
const d = (e.description ?? "").replace(/\s+/g, " ").trim();
|
|
3308
|
+
const desc = d && max > 0 ? ` \u2014 ${clip(d, max)}` : "";
|
|
3309
|
+
return `- **${pre}${e.name}**${desc}${e.path ? ` (\`${e.path}\`)` : ""}`;
|
|
3310
|
+
}).join("\n"));
|
|
3311
|
+
const longest = Math.max(0, ...entries.map((e) => [...e.description ?? ""].length));
|
|
3312
|
+
const full = render(longest);
|
|
3313
|
+
if (bytes(full) <= budget) return full;
|
|
3314
|
+
if (bytes(render(0)) <= budget) {
|
|
3315
|
+
let lo = 0, hi = longest;
|
|
3316
|
+
while (lo < hi) {
|
|
3317
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
3318
|
+
if (bytes(render(mid)) <= budget) lo = mid;
|
|
3319
|
+
else hi = mid - 1;
|
|
3320
|
+
}
|
|
3321
|
+
return render(lo);
|
|
3322
|
+
}
|
|
3323
|
+
const names = wrap(entries.map((e) => `- **${pre}${e.name}**`).join("\n"));
|
|
3324
|
+
if (bytes(names) <= budget) return names;
|
|
3325
|
+
return wrap(`- ${entries.map((e) => pre + e.name).join(", ")}`);
|
|
3347
3326
|
}
|
|
3348
3327
|
|
|
3349
3328
|
// src/frontmatter.ts
|
|
@@ -3365,7 +3344,6 @@ function grabField(block, key) {
|
|
|
3365
3344
|
}
|
|
3366
3345
|
|
|
3367
3346
|
// src/commands.ts
|
|
3368
|
-
var MAX_CATALOG = 25;
|
|
3369
3347
|
function parseFrontmatter(md) {
|
|
3370
3348
|
const { block, body } = splitFrontmatter(md);
|
|
3371
3349
|
return { description: grabField(block, "description"), body };
|
|
@@ -3422,9 +3400,12 @@ async function scanCommands(fs, dir) {
|
|
|
3422
3400
|
async function loadCommands(fs, dir, opts = {}) {
|
|
3423
3401
|
const commands = await scanCommands(fs, dir);
|
|
3424
3402
|
if (commands.length === 0) return { commands, catalog: "" };
|
|
3425
|
-
const
|
|
3426
|
-
|
|
3427
|
-
|
|
3403
|
+
const catalog = formatCatalog(commands, {
|
|
3404
|
+
title: "## Slash commands (reusable prompt templates)",
|
|
3405
|
+
footer: "To run one, call the `SlashCommand` tool with its name (and optional `args`); it returns the expanded template to act on.",
|
|
3406
|
+
namePrefix: "/",
|
|
3407
|
+
budgetBytes: opts.budgetBytes
|
|
3408
|
+
});
|
|
3428
3409
|
const tool = {
|
|
3429
3410
|
name: "SlashCommand",
|
|
3430
3411
|
description: "Run a slash command (or skill) by name \u2014 returns its expanded prompt template/instructions (with your `args` substituted/appended). Resolves both the command and skill namespaces. Use when a task matches a listed command or skill.",
|
|
@@ -3452,7 +3433,6 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
3452
3433
|
}
|
|
3453
3434
|
|
|
3454
3435
|
// src/skills.ts
|
|
3455
|
-
var MAX_CATALOG2 = 25;
|
|
3456
3436
|
function parseFrontmatter2(md) {
|
|
3457
3437
|
const { block } = splitFrontmatter(md);
|
|
3458
3438
|
const b = block || md.slice(0, 600);
|
|
@@ -3478,9 +3458,11 @@ async function scanSkills(fs, dir) {
|
|
|
3478
3458
|
async function loadSkills(fs, dir, opts = {}) {
|
|
3479
3459
|
const skills = await scanSkills(fs, dir);
|
|
3480
3460
|
if (skills.length === 0) return { skills, catalog: "" };
|
|
3481
|
-
const
|
|
3482
|
-
|
|
3483
|
-
|
|
3461
|
+
const catalog = formatCatalog(skills, {
|
|
3462
|
+
title: "## Skills (load one before acting on a matching task)",
|
|
3463
|
+
footer: "When a task matches a skill, call the `Skill` tool with its name to load its full instructions.",
|
|
3464
|
+
budgetBytes: opts.budgetBytes
|
|
3465
|
+
});
|
|
3484
3466
|
const tool = {
|
|
3485
3467
|
name: "Skill",
|
|
3486
3468
|
description: "Load a skill (or slash command) by name \u2014 returns its full instructions (SKILL.md / expanded template), with any `args` substituted/appended. Resolves both the skill and command namespaces. Use when a task matches a listed skill or command.",
|
|
@@ -3505,7 +3487,73 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
3505
3487
|
|
|
3506
3488
|
// src/memory.ts
|
|
3507
3489
|
init_tools_structured();
|
|
3508
|
-
|
|
3490
|
+
|
|
3491
|
+
// src/relevance.ts
|
|
3492
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
3493
|
+
"the",
|
|
3494
|
+
"and",
|
|
3495
|
+
"for",
|
|
3496
|
+
"with",
|
|
3497
|
+
"that",
|
|
3498
|
+
"this",
|
|
3499
|
+
"from",
|
|
3500
|
+
"into",
|
|
3501
|
+
"your",
|
|
3502
|
+
"you",
|
|
3503
|
+
"are",
|
|
3504
|
+
"was",
|
|
3505
|
+
"will",
|
|
3506
|
+
"use",
|
|
3507
|
+
"using",
|
|
3508
|
+
"run",
|
|
3509
|
+
"add",
|
|
3510
|
+
"fix",
|
|
3511
|
+
"make",
|
|
3512
|
+
"file",
|
|
3513
|
+
"files",
|
|
3514
|
+
"code",
|
|
3515
|
+
"please",
|
|
3516
|
+
"need",
|
|
3517
|
+
"want",
|
|
3518
|
+
"should",
|
|
3519
|
+
"all"
|
|
3520
|
+
]);
|
|
3521
|
+
function tokenize(s) {
|
|
3522
|
+
const out = /* @__PURE__ */ new Set();
|
|
3523
|
+
for (const w of String(s ?? "").toLowerCase().match(/[a-z0-9]+/g) ?? []) {
|
|
3524
|
+
if (w.length >= 3 && !STOP.has(w)) out.add(w);
|
|
3525
|
+
}
|
|
3526
|
+
return out;
|
|
3527
|
+
}
|
|
3528
|
+
function idfWeights(corpus) {
|
|
3529
|
+
const N = corpus.length;
|
|
3530
|
+
if (N === 0) return /* @__PURE__ */ new Map();
|
|
3531
|
+
const df = /* @__PURE__ */ new Map();
|
|
3532
|
+
for (const doc of corpus) for (const t of tokenize(doc)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3533
|
+
const idf = /* @__PURE__ */ new Map();
|
|
3534
|
+
for (const [t, n] of df) idf.set(t, Math.log((N + 1) / (n + 1)) + 1);
|
|
3535
|
+
return idf;
|
|
3536
|
+
}
|
|
3537
|
+
function relevanceScore(text, queryTokens, idf) {
|
|
3538
|
+
if (queryTokens.size === 0) return 0;
|
|
3539
|
+
const t = tokenize(text);
|
|
3540
|
+
let score = 0;
|
|
3541
|
+
for (const q of queryTokens) if (t.has(q)) score += idf?.get(q) ?? 1;
|
|
3542
|
+
return score;
|
|
3543
|
+
}
|
|
3544
|
+
function topByRelevance(items, query, text, k, corpus) {
|
|
3545
|
+
if (!Number.isInteger(k) || k < 1) return { kept: items, rest: [] };
|
|
3546
|
+
if (items.length <= k || !query.trim()) return { kept: items, rest: [] };
|
|
3547
|
+
const q = tokenize(query);
|
|
3548
|
+
if (q.size === 0) return { kept: items.slice(0, k), rest: items.slice(k) };
|
|
3549
|
+
const idf = corpus?.length ? idfWeights(corpus) : void 0;
|
|
3550
|
+
const scored = items.map((x, i) => ({ i, s: relevanceScore(text(x), q, idf) }));
|
|
3551
|
+
scored.sort((a, b) => b.s - a.s || a.i - b.i);
|
|
3552
|
+
const keep = new Set(scored.slice(0, k).map((e) => e.i));
|
|
3553
|
+
return { kept: items.filter((_, i) => keep.has(i)), rest: items.filter((_, i) => !keep.has(i)) };
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
// src/memory.ts
|
|
3509
3557
|
var MAX_INDEX_LINES = 200;
|
|
3510
3558
|
var MAX_INDEX_BYTES = 25e3;
|
|
3511
3559
|
var MEMORY_PROMPT = '## Memory guidelines\n**When to Remember:** user corrections ("no, do X instead"), preferences ("I prefer Y"), project constraints, recurring gotchas, role/context ("I\'m a data scientist"), workflow patterns confirmed by the user. Capture the WHY, not just the what.\n**When NOT to Remember:** task-specific scratch, transient state, things derivable from code/git, conversation filler, anything already in instruction files. If removing the memory wouldn\'t change future behavior, don\'t save it.\n**Types** (pass to Remember \u2014 this determines where the memory is stored):\n- `user` \u2014 role, preferences, knowledge level, collaboration style (GLOBAL \u2014 follows you across projects)\n- `feedback` \u2014 corrections, what to avoid/repeat, with WHY (GLOBAL)\n- `project` \u2014 ongoing work, goals, constraints, deadlines (LOCAL to this project; convert relative dates to absolute)\n- `reference` \u2014 pointers to external resources, URLs, vendor docs (LOCAL to this project)\n**Before acting on a recalled memory:** verify it\'s still true \u2014 a memory naming a file/function/flag is a claim from when it was written. Grep/read to confirm before recommending. Stale memory \u2192 update via Remember (same slug overwrites).\n**Forget:** when the user asks to remove/delete/forget a memory, call `Forget` with its slug \u2014 it deletes the fact and its index line. Never "remove" by re-Remembering a note that says it was removed: that keeps the memory.\n**Dedup:** before writing, check if a similar memory exists (Recall or MemorySearch). Update the existing one rather than creating duplicates.\nCall `Recall` with a slug (or multiple slugs/a pattern) to load full bodies when relevant.\nCall `MemorySearch` with a query to find memories by content when you don\'t know the slug.\nCall `Remember` to persist a durable fact for future sessions.';
|
|
@@ -3540,10 +3588,18 @@ async function loadMemory(fs, dir, opts = {}) {
|
|
|
3540
3588
|
}
|
|
3541
3589
|
}
|
|
3542
3590
|
if (!hasContent) return { index: MEMORY_PROMPT, tools };
|
|
3543
|
-
const
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3591
|
+
const entries = allPointers.map((l) => {
|
|
3592
|
+
const m = l.match(/\]\(([^)]+)\.md\)/);
|
|
3593
|
+
const slug2 = m?.[1] ?? l;
|
|
3594
|
+
const title = m ? l.slice(l.indexOf("[") + 1, m.index).trim() : "";
|
|
3595
|
+
const hook = m ? l.slice(m.index + m[0].length).replace(/^\s*(?:[—:-]\s*)?/, "").trim() : "";
|
|
3596
|
+
return { name: slug2, description: title && title !== slug2 ? hook ? `${title} \u2014 ${hook}` : title : hook };
|
|
3597
|
+
});
|
|
3598
|
+
const index = MEMORY_PROMPT + "\n\n" + formatCatalog(entries, {
|
|
3599
|
+
title: "## Memory index (persistent context \u2014 recalled across sessions)" + (header ? "\n" + header : ""),
|
|
3600
|
+
footer: "Call `Recall` with a slug to load its full fact.",
|
|
3601
|
+
budgetBytes: opts.budgetBytes
|
|
3602
|
+
});
|
|
3547
3603
|
return { index, tools };
|
|
3548
3604
|
}
|
|
3549
3605
|
var TERM_STOPWORDS = new Set(`the and for but not you your yours our ours his her hers its their they them this that these those
|
|
@@ -3708,8 +3764,8 @@ function memorySearchTool(fs, dirs) {
|
|
|
3708
3764
|
return `Error: invalid regex: ${e}`;
|
|
3709
3765
|
}
|
|
3710
3766
|
} else {
|
|
3711
|
-
const
|
|
3712
|
-
matcher = (l) => l.toLowerCase().includes(
|
|
3767
|
+
const lower2 = q.toLowerCase();
|
|
3768
|
+
matcher = (l) => l.toLowerCase().includes(lower2);
|
|
3713
3769
|
}
|
|
3714
3770
|
const loaded = [];
|
|
3715
3771
|
for (const slug2 of slugs) {
|
|
@@ -3817,6 +3873,25 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
3817
3873
|
}
|
|
3818
3874
|
return "";
|
|
3819
3875
|
}
|
|
3876
|
+
async function loadContextFiles(fs, specs = []) {
|
|
3877
|
+
const sections = [];
|
|
3878
|
+
for (const { path, maxChars, label } of specs) {
|
|
3879
|
+
if (!await fs.exists(path)) continue;
|
|
3880
|
+
let md = (await fs.readFile(path)).trim();
|
|
3881
|
+
if (!md) continue;
|
|
3882
|
+
if (maxChars !== void 0) {
|
|
3883
|
+
const cps = [...md], n = Math.max(0, maxChars);
|
|
3884
|
+
if (cps.length > n) md = `${cps.slice(0, n).join("").trimEnd()}
|
|
3885
|
+
\u2026 (truncated \u2014 full file: ${path})`;
|
|
3886
|
+
}
|
|
3887
|
+
sections.push(`<!-- ${path} -->
|
|
3888
|
+
${label ? `### ${label}
|
|
3889
|
+
` : ""}${md}`);
|
|
3890
|
+
}
|
|
3891
|
+
return sections.length ? `## Context files
|
|
3892
|
+
|
|
3893
|
+
${sections.join("\n\n---\n\n")}` : "";
|
|
3894
|
+
}
|
|
3820
3895
|
|
|
3821
3896
|
// src/subagent.ts
|
|
3822
3897
|
init_tools();
|
|
@@ -4509,6 +4584,11 @@ init_logging();
|
|
|
4509
4584
|
var log8 = forComponent("modes");
|
|
4510
4585
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
4511
4586
|
var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
|
|
4587
|
+
var lower = (xs) => new Set([...xs].map((x) => x.toLowerCase()));
|
|
4588
|
+
var PLANNER_BLOCKED_LC = lower(PLANNER_BLOCKED);
|
|
4589
|
+
var WRITE_TOOLS_LC = lower(WRITE_TOOLS);
|
|
4590
|
+
var plannerBlocks = (t) => !!t.writes || PLANNER_BLOCKED_LC.has(t.name.toLowerCase());
|
|
4591
|
+
var isFileWrite = (t) => WRITE_TOOLS_LC.has(t.name.toLowerCase());
|
|
4512
4592
|
var PLANNER_REFUSAL = "Refused: you are the planner and do not write files. Delegate the change with Task/TaskBatch (model set to the worker) and give the worker a full spec.";
|
|
4513
4593
|
var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
|
|
4514
4594
|
var ADVISE_LIMIT = 3;
|
|
@@ -4531,7 +4611,7 @@ function advisorFor(worker, advisor) {
|
|
|
4531
4611
|
return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
|
|
4532
4612
|
}
|
|
4533
4613
|
function delegateTools(tools, planner) {
|
|
4534
|
-
return tools.map((t) => !
|
|
4614
|
+
return tools.map((t) => !plannerBlocks(t) ? t : {
|
|
4535
4615
|
...t,
|
|
4536
4616
|
run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
|
|
4537
4617
|
});
|
|
@@ -4601,7 +4681,7 @@ ${question}` }
|
|
|
4601
4681
|
}
|
|
4602
4682
|
}
|
|
4603
4683
|
};
|
|
4604
|
-
const gate = (tools) => tools.map((t) => !
|
|
4684
|
+
const gate = (tools) => tools.map((t) => !isFileWrite(t) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
|
|
4605
4685
|
return { tool, gate, used: () => used };
|
|
4606
4686
|
}
|
|
4607
4687
|
function meterWorkers(ai, main) {
|
|
@@ -4644,6 +4724,13 @@ function meterWorkers(ai, main) {
|
|
|
4644
4724
|
resolved = void 0;
|
|
4645
4725
|
} };
|
|
4646
4726
|
}
|
|
4727
|
+
var CC_WRITE_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
4728
|
+
function ccModeOptions(kind, hostHooks, used) {
|
|
4729
|
+
if (kind === "delegate") return { bridge: /* @__PURE__ */ new Set(["Task", "TaskBatch"]), disallow: [...CC_WRITE_TOOLS, "Bash", "Task", "Agent"] };
|
|
4730
|
+
if (kind !== "advisor") return { bridge: /* @__PURE__ */ new Set(), disallow: [] };
|
|
4731
|
+
const gate = { matcher: CC_WRITE_TOOLS.join("|"), hooks: [async () => used() ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: PLAN_FIRST.replace("`Advise`", "`mcp__agentx__Advise`") } }] };
|
|
4732
|
+
return { bridge: /* @__PURE__ */ new Set(["Advise"]), disallow: [], hooks: { ...hostHooks, PreToolUse: [...hostHooks?.PreToolUse ?? [], gate] } };
|
|
4733
|
+
}
|
|
4647
4734
|
|
|
4648
4735
|
// src/lint.ts
|
|
4649
4736
|
var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
@@ -4844,6 +4931,8 @@ var AgentOptions = class {
|
|
|
4844
4931
|
* Walks the VFS tree and merges all found files (general → specific, like Claude Code).
|
|
4845
4932
|
* `true` (default) = auto-discover standard names. `string[]` = custom names. `false` = skip. */
|
|
4846
4933
|
instructionFiles = true;
|
|
4934
|
+
/** Extra files injected right after the instructions (missing → skipped; over `maxChars` → head + pointer to the full file). */
|
|
4935
|
+
contextFiles;
|
|
4847
4936
|
/** Host interaction channel (human-in-the-loop). If set: adds the `AskUserQuestion` tool. */
|
|
4848
4937
|
host;
|
|
4849
4938
|
/** Add the `AskUserQuestion` tool when a host is present (default true). Set false for an agent that
|
|
@@ -4971,6 +5060,8 @@ var Agent = class _Agent {
|
|
|
4971
5060
|
meter;
|
|
4972
5061
|
// delegate/advisor: meters the second model's calls
|
|
4973
5062
|
modeModel;
|
|
5063
|
+
modeKind;
|
|
5064
|
+
adv;
|
|
4974
5065
|
lastTask = "";
|
|
4975
5066
|
// the current turn's text — what the advisor reads as the request
|
|
4976
5067
|
/** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
|
|
@@ -5075,12 +5166,15 @@ var Agent = class _Agent {
|
|
|
5075
5166
|
const o = this.options;
|
|
5076
5167
|
if (!o.model.startsWith("claude-code/")) return void 0;
|
|
5077
5168
|
const po = { steer: () => this.drainPendingSteers() };
|
|
5078
|
-
const
|
|
5169
|
+
const mode = ccModeOptions(this.modeKind, o.providerOptions?.hooks, () => this.adv?.used() ?? 0);
|
|
5170
|
+
const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name) || mode.bridge.has(t.name);
|
|
5079
5171
|
if (o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
|
|
5080
5172
|
this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
|
|
5081
5173
|
po.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
5082
5174
|
}
|
|
5083
|
-
const denyTools = claudeCodeDisallowedTools(o.permissions);
|
|
5175
|
+
const denyTools = [...claudeCodeDisallowedTools(o.permissions), ...mode.disallow];
|
|
5176
|
+
if (mode.hooks) po.hooks = mode.hooks;
|
|
5177
|
+
if (this.modeKind && o.providerOptions?.claudeCodeSession) po.claudeCodeSession = `${o.providerOptions.claudeCodeSession}:${this.modeKind}`;
|
|
5084
5178
|
if (denyTools.length) {
|
|
5085
5179
|
const prior = o.providerOptions?.disallowedTools ?? [];
|
|
5086
5180
|
po.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
|
|
@@ -5167,7 +5261,7 @@ var Agent = class _Agent {
|
|
|
5167
5261
|
* across turns (and the frozen prompt pairs well with prompt caching). Does NOT touch
|
|
5168
5262
|
* the transcript; `run`/`send` own that.
|
|
5169
5263
|
*/
|
|
5170
|
-
async prepare(
|
|
5264
|
+
async prepare() {
|
|
5171
5265
|
if (this.prepared) return this.systemPromptCache;
|
|
5172
5266
|
const o = this.options;
|
|
5173
5267
|
const fs = o.fs;
|
|
@@ -5178,14 +5272,19 @@ var Agent = class _Agent {
|
|
|
5178
5272
|
const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
|
|
5179
5273
|
this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
|
|
5180
5274
|
const ai = this.meter?.ai ?? o.ai;
|
|
5181
|
-
|
|
5275
|
+
this.modeKind = worker ? "delegate" : advisor ? "advisor" : void 0;
|
|
5276
|
+
const adv = this.adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
|
|
5182
5277
|
if (o.instructionFiles !== false) {
|
|
5183
5278
|
const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
|
|
5184
5279
|
const ins = await loadInstructions(fs, names);
|
|
5185
5280
|
if (ins) systemPrompt += "\n\n" + ins;
|
|
5186
5281
|
}
|
|
5282
|
+
if (o.contextFiles?.length) {
|
|
5283
|
+
const ctxFiles = await loadContextFiles(fs, o.contextFiles);
|
|
5284
|
+
if (ctxFiles) systemPrompt += "\n\n" + ctxFiles;
|
|
5285
|
+
}
|
|
5187
5286
|
if (o.memoryDir) {
|
|
5188
|
-
const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, {
|
|
5287
|
+
const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, { userDir: o.memoryUserDir });
|
|
5189
5288
|
if (index) systemPrompt += "\n\n" + index;
|
|
5190
5289
|
tools = [...tools, ...memTools];
|
|
5191
5290
|
}
|
|
@@ -5195,7 +5294,7 @@ var Agent = class _Agent {
|
|
|
5195
5294
|
const c = (await scanCommands(fs, commandsDir)).find((x) => x.name === name);
|
|
5196
5295
|
return c ? { path: c.path, stripFrontmatter: true } : void 0;
|
|
5197
5296
|
} : void 0;
|
|
5198
|
-
const { catalog, tool } = await loadSkills(fs, skillsDir, {
|
|
5297
|
+
const { catalog, tool } = await loadSkills(fs, skillsDir, { sibling });
|
|
5199
5298
|
if (catalog) systemPrompt += "\n\n" + catalog;
|
|
5200
5299
|
if (tool) tools = [...tools, tool];
|
|
5201
5300
|
}
|
|
@@ -5204,7 +5303,7 @@ var Agent = class _Agent {
|
|
|
5204
5303
|
const s = (await scanSkills(fs, skillsDir)).find((x) => x.name === name);
|
|
5205
5304
|
return s ? { path: s.path, stripFrontmatter: false } : void 0;
|
|
5206
5305
|
} : void 0;
|
|
5207
|
-
const { catalog, tool } = await loadCommands(fs, commandsDir, {
|
|
5306
|
+
const { catalog, tool } = await loadCommands(fs, commandsDir, { sibling });
|
|
5208
5307
|
if (catalog) systemPrompt += "\n\n" + catalog;
|
|
5209
5308
|
if (tool) tools = [...tools, tool];
|
|
5210
5309
|
}
|
|
@@ -5263,7 +5362,7 @@ var Agent = class _Agent {
|
|
|
5263
5362
|
this.invokedTools.clear();
|
|
5264
5363
|
this.fabricatedClaims.clear();
|
|
5265
5364
|
this.lastTask = contentText(task);
|
|
5266
|
-
const systemPrompt = await this.prepare(
|
|
5365
|
+
const systemPrompt = await this.prepare();
|
|
5267
5366
|
const startCtx = await this.fireSessionStart();
|
|
5268
5367
|
const userContent = await this.applyPromptSubmit(task);
|
|
5269
5368
|
this.transcript = [
|
|
@@ -5299,7 +5398,7 @@ var Agent = class _Agent {
|
|
|
5299
5398
|
async send(task) {
|
|
5300
5399
|
await this.ensureFs();
|
|
5301
5400
|
this.lastTask = contentText(task);
|
|
5302
|
-
const systemPrompt = await this.prepare(
|
|
5401
|
+
const systemPrompt = await this.prepare();
|
|
5303
5402
|
const startCtx = await this.fireSessionStart();
|
|
5304
5403
|
const userContent = await this.applyPromptSubmit(task);
|
|
5305
5404
|
const sys = systemPrompt + (startCtx ? "\n\n" + startCtx : "");
|
|
@@ -6123,9 +6222,9 @@ function capOversizedBodies(messages, target, keepRecentImages = 1) {
|
|
|
6123
6222
|
return out;
|
|
6124
6223
|
}
|
|
6125
6224
|
function elideBody(x, cap) {
|
|
6126
|
-
const note = (
|
|
6225
|
+
const note = (bytes2, imgs2) => `
|
|
6127
6226
|
|
|
6128
|
-
[${
|
|
6227
|
+
[${bytes2} bytes elided by /compact${x.role === "tool" ? " \u2014 re-run the tool to view" : ""}${imgs2 ? `; ${imgs2} image part(s) KEPT in full \u2014 they still cost context` : ""}]`;
|
|
6129
6228
|
if (!Array.isArray(x.content)) {
|
|
6130
6229
|
const text = contentText(x.content);
|
|
6131
6230
|
if (text.length <= cap) return x;
|
|
@@ -7365,8 +7464,8 @@ function isTranscriptComplete(text) {
|
|
|
7365
7464
|
const lastWord = words3[words3.length - 1];
|
|
7366
7465
|
if (COMPLETE_ENDINGS.has(lastWord)) return true;
|
|
7367
7466
|
if (INCOMPLETE_ENDINGS.has(lastWord)) return false;
|
|
7368
|
-
const
|
|
7369
|
-
for (const marker of CONTINUATION_MARKERS) if (
|
|
7467
|
+
const lower2 = trimmed.toLowerCase();
|
|
7468
|
+
for (const marker of CONTINUATION_MARKERS) if (lower2.endsWith(marker)) return false;
|
|
7370
7469
|
if (words3.length <= 2) return false;
|
|
7371
7470
|
return true;
|
|
7372
7471
|
}
|
|
@@ -12061,11 +12160,11 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12061
12160
|
const list = id ? [this.tasks.get(String(id))].filter(Boolean) : [...this.tasks.values()];
|
|
12062
12161
|
if (!list.length) return id ? `No task '${id}'.` : "No background tasks.";
|
|
12063
12162
|
const cap = id ? 1500 : 300;
|
|
12064
|
-
const
|
|
12163
|
+
const clip2 = (x) => x.length > cap ? `${x.slice(0, cap)}\u2026 (truncated)` : x;
|
|
12065
12164
|
return list.map((t) => {
|
|
12066
12165
|
const head = `${t.id} (${t.label}): ${t.status}`;
|
|
12067
12166
|
const body = (t.status === "done" ? t.spokenText?.trim() || t.result?.trim() : t.status === "error" ? t.result?.trim() : "") ?? "";
|
|
12068
|
-
return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${
|
|
12167
|
+
return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${clip2(body)}` : head;
|
|
12069
12168
|
}).join("\n");
|
|
12070
12169
|
}
|
|
12071
12170
|
};
|
|
@@ -13961,10 +14060,12 @@ export {
|
|
|
13961
14060
|
Agent,
|
|
13962
14061
|
AgentOptions,
|
|
13963
14062
|
BodDbFilesystem,
|
|
14063
|
+
CC_WRITE_TOOLS,
|
|
13964
14064
|
CartesiaTTS,
|
|
13965
14065
|
CartesiaTTSOptions,
|
|
13966
14066
|
CommandExecutor2 as CommandExecutor,
|
|
13967
14067
|
ConsoleHostBridge,
|
|
14068
|
+
DEFAULT_CATALOG_BUDGET,
|
|
13968
14069
|
DEFAULT_DENY,
|
|
13969
14070
|
DEFAULT_MAX_DEPTH,
|
|
13970
14071
|
DEFAULT_MUTATING,
|
|
@@ -14022,6 +14123,7 @@ export {
|
|
|
14022
14123
|
askUserQuestionTool,
|
|
14023
14124
|
bashTool,
|
|
14024
14125
|
buildMcpCatalog,
|
|
14126
|
+
ccModeOptions,
|
|
14025
14127
|
checkpointTool,
|
|
14026
14128
|
checkpointTools,
|
|
14027
14129
|
claudeCodeDisallowedTools,
|
|
@@ -14054,6 +14156,7 @@ export {
|
|
|
14054
14156
|
findGitRoot,
|
|
14055
14157
|
forComponent,
|
|
14056
14158
|
forgetFact,
|
|
14159
|
+
formatCatalog,
|
|
14057
14160
|
formatJobNotice,
|
|
14058
14161
|
fullAgentOptions,
|
|
14059
14162
|
getOrCreateWorktree,
|
|
@@ -14072,6 +14175,7 @@ export {
|
|
|
14072
14175
|
lessonCapture,
|
|
14073
14176
|
loadAgents,
|
|
14074
14177
|
loadCommands,
|
|
14178
|
+
loadContextFiles,
|
|
14075
14179
|
loadInstructions,
|
|
14076
14180
|
loadMemory,
|
|
14077
14181
|
loadSkills,
|
|
@@ -14107,6 +14211,7 @@ export {
|
|
|
14107
14211
|
parseImageRef,
|
|
14108
14212
|
pickTier,
|
|
14109
14213
|
planMode,
|
|
14214
|
+
plannerBlocks,
|
|
14110
14215
|
raceAttempts,
|
|
14111
14216
|
readTool,
|
|
14112
14217
|
reasoningToChatFragment,
|