@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/cli.js
CHANGED
|
@@ -121,13 +121,13 @@ function elideImageParts(content, hit, stub) {
|
|
|
121
121
|
}
|
|
122
122
|
function mintImageRef(path, mime) {
|
|
123
123
|
const token = randomBytes(16).toString("hex");
|
|
124
|
-
let
|
|
124
|
+
let bytes2 = 0;
|
|
125
125
|
try {
|
|
126
|
-
|
|
126
|
+
bytes2 = Math.ceil(statSync3(path).size * 4 / 3);
|
|
127
127
|
} catch (e) {
|
|
128
128
|
log3.warn(`image ref stat failed, weighing as 0: ${path}`, e);
|
|
129
129
|
}
|
|
130
|
-
refRegistry.set(token, { mime, path, bytes });
|
|
130
|
+
refRegistry.set(token, { mime, path, bytes: bytes2 });
|
|
131
131
|
while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
|
|
132
132
|
return `${IMAGE_REF_SCHEME}${mime};${token};${path}`;
|
|
133
133
|
}
|
|
@@ -825,6 +825,7 @@ var init_tools_structured = __esm({
|
|
|
825
825
|
};
|
|
826
826
|
writeTool = {
|
|
827
827
|
name: "Write",
|
|
828
|
+
writes: true,
|
|
828
829
|
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).",
|
|
829
830
|
parameters: {
|
|
830
831
|
type: "object",
|
|
@@ -850,6 +851,7 @@ var init_tools_structured = __esm({
|
|
|
850
851
|
};
|
|
851
852
|
multiEditTool = {
|
|
852
853
|
name: "MultiEdit",
|
|
854
|
+
writes: true,
|
|
853
855
|
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.",
|
|
854
856
|
parameters: {
|
|
855
857
|
type: "object",
|
|
@@ -891,6 +893,7 @@ var init_tools_structured = __esm({
|
|
|
891
893
|
};
|
|
892
894
|
applyEditsTool = {
|
|
893
895
|
name: "ApplyEdits",
|
|
896
|
+
writes: true,
|
|
894
897
|
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.",
|
|
895
898
|
parameters: {
|
|
896
899
|
type: "object",
|
|
@@ -1584,6 +1587,7 @@ var init_tools = __esm({
|
|
|
1584
1587
|
};
|
|
1585
1588
|
bashTool = {
|
|
1586
1589
|
name: "bash",
|
|
1590
|
+
writes: true,
|
|
1587
1591
|
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`.",
|
|
1588
1592
|
parameters: {
|
|
1589
1593
|
type: "object",
|
|
@@ -1635,14 +1639,14 @@ var init_tools = __esm({
|
|
|
1635
1639
|
if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
|
|
1636
1640
|
return imageRefResult(real, IMG_MIME[ext]);
|
|
1637
1641
|
}
|
|
1638
|
-
const
|
|
1639
|
-
const b64 = Buffer.from(
|
|
1642
|
+
const bytes2 = await fs.readFileBytes(path);
|
|
1643
|
+
const b64 = Buffer.from(bytes2).toString("base64");
|
|
1640
1644
|
return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });
|
|
1641
1645
|
}
|
|
1642
1646
|
const raw = await ctx.fs.readFile(path);
|
|
1643
1647
|
if (isBinary(raw)) {
|
|
1644
|
-
const
|
|
1645
|
-
return `[binary file: ${path} \u2014 ${
|
|
1648
|
+
const bytes2 = await byteSize(ctx.fs, path, raw);
|
|
1649
|
+
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}.]`;
|
|
1646
1650
|
}
|
|
1647
1651
|
ctx.readState.set(ctx.fs.resolvePath(path), raw);
|
|
1648
1652
|
if (raw === "") return `[empty file: ${path} \u2014 0 bytes]`;
|
|
@@ -1661,6 +1665,7 @@ var init_tools = __esm({
|
|
|
1661
1665
|
};
|
|
1662
1666
|
editTool = {
|
|
1663
1667
|
name: "Edit",
|
|
1668
|
+
writes: true,
|
|
1664
1669
|
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.",
|
|
1665
1670
|
parameters: {
|
|
1666
1671
|
type: "object",
|
|
@@ -1972,8 +1977,8 @@ var init_tools_jobs = __esm({
|
|
|
1972
1977
|
return [...this.jobs.values()].map((j) => this.info(j));
|
|
1973
1978
|
}
|
|
1974
1979
|
info(j) {
|
|
1975
|
-
const { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes, holds, result } = j;
|
|
1976
|
-
return { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes, holds, result };
|
|
1980
|
+
const { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes: bytes2, holds, result } = j;
|
|
1981
|
+
return { id, kind, label, status, startedAt, lastActivityAt, endedAt, reason, bytes: bytes2, holds, result };
|
|
1977
1982
|
}
|
|
1978
1983
|
/** Wait (bounded) for a job to settle. Resolves the job's info either way; null for an unknown id. */
|
|
1979
1984
|
async wait(id, timeoutMs) {
|
|
@@ -2568,7 +2573,8 @@ var init_JailedFilesystem = __esm({
|
|
|
2568
2573
|
violate(op, abs, rule) {
|
|
2569
2574
|
this.note(op, abs, rule);
|
|
2570
2575
|
if (rule === "deny" || rule === "confine") throw new Error(`File not found: ${abs}`);
|
|
2571
|
-
|
|
2576
|
+
const hint = rule === "readonly" && this.options.readonlyHint ? ` \u2014 ${this.options.readonlyHint}` : "";
|
|
2577
|
+
throw new Error(`Blocked by filesystem jail (${rule}): ${abs}${hint}`);
|
|
2572
2578
|
}
|
|
2573
2579
|
/** Fire the audit hook without throwing (for boolean probes that must still return false). */
|
|
2574
2580
|
note(op, abs, rule) {
|
|
@@ -2693,6 +2699,8 @@ var init_JailedFilesystem = __esm({
|
|
|
2693
2699
|
deny = [...DEFAULT_DENY];
|
|
2694
2700
|
/** Globs readable but not writable/deletable (the pinned constitution). */
|
|
2695
2701
|
readonly = [];
|
|
2702
|
+
/** Appended to readonly-violation errors (e.g. "edit it via the kb tool instead"). */
|
|
2703
|
+
readonlyHint;
|
|
2696
2704
|
/** If set, only paths matching these globs are accessible at all. */
|
|
2697
2705
|
confineTo;
|
|
2698
2706
|
/** Audit hook fired on every blocked operation (drives the circuit breaker). */
|
|
@@ -2992,6 +3000,7 @@ function makeRealShellTool(options) {
|
|
|
2992
3000
|
};
|
|
2993
3001
|
return {
|
|
2994
3002
|
name: "Shell",
|
|
3003
|
+
writes: true,
|
|
2995
3004
|
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
2996
3005
|
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
2997
3006
|
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
@@ -3514,69 +3523,39 @@ function normalizeQuestion(input) {
|
|
|
3514
3523
|
return { question: q2.question, header: typeof q2.header === "string" ? q2.header : void 0, options, multiSelect: q2.multiSelect === true };
|
|
3515
3524
|
}
|
|
3516
3525
|
|
|
3517
|
-
// src/
|
|
3518
|
-
var
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
"
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
"
|
|
3529
|
-
"
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
"
|
|
3535
|
-
"
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
if (w.length >= 3 && !STOP.has(w)) out.add(w);
|
|
3551
|
-
}
|
|
3552
|
-
return out;
|
|
3553
|
-
}
|
|
3554
|
-
function idfWeights(corpus) {
|
|
3555
|
-
const N = corpus.length;
|
|
3556
|
-
if (N === 0) return /* @__PURE__ */ new Map();
|
|
3557
|
-
const df = /* @__PURE__ */ new Map();
|
|
3558
|
-
for (const doc of corpus) for (const t of tokenize(doc)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3559
|
-
const idf = /* @__PURE__ */ new Map();
|
|
3560
|
-
for (const [t, n] of df) idf.set(t, Math.log((N + 1) / (n + 1)) + 1);
|
|
3561
|
-
return idf;
|
|
3562
|
-
}
|
|
3563
|
-
function relevanceScore(text, queryTokens, idf) {
|
|
3564
|
-
if (queryTokens.size === 0) return 0;
|
|
3565
|
-
const t = tokenize(text);
|
|
3566
|
-
let score = 0;
|
|
3567
|
-
for (const q2 of queryTokens) if (t.has(q2)) score += idf?.get(q2) ?? 1;
|
|
3568
|
-
return score;
|
|
3569
|
-
}
|
|
3570
|
-
function topByRelevance(items, query, text, k, corpus) {
|
|
3571
|
-
if (!Number.isInteger(k) || k < 1) return { kept: items, rest: [] };
|
|
3572
|
-
if (items.length <= k || !query.trim()) return { kept: items, rest: [] };
|
|
3573
|
-
const q2 = tokenize(query);
|
|
3574
|
-
if (q2.size === 0) return { kept: items.slice(0, k), rest: items.slice(k) };
|
|
3575
|
-
const idf = corpus?.length ? idfWeights(corpus) : void 0;
|
|
3576
|
-
const scored = items.map((x, i) => ({ i, s: relevanceScore(text(x), q2, idf) }));
|
|
3577
|
-
scored.sort((a, b) => b.s - a.s || a.i - b.i);
|
|
3578
|
-
const keep = new Set(scored.slice(0, k).map((e) => e.i));
|
|
3579
|
-
return { kept: items.filter((_, i) => keep.has(i)), rest: items.filter((_, i) => !keep.has(i)) };
|
|
3526
|
+
// src/catalog.ts
|
|
3527
|
+
var DEFAULT_CATALOG_BUDGET = 6e3;
|
|
3528
|
+
var enc = new TextEncoder();
|
|
3529
|
+
var bytes = (s) => enc.encode(s).length;
|
|
3530
|
+
function clip(s, n) {
|
|
3531
|
+
const cps = [...s];
|
|
3532
|
+
if (cps.length <= n) return s;
|
|
3533
|
+
return n <= 1 ? "\u2026" : cps.slice(0, n - 1).join("").trimEnd() + "\u2026";
|
|
3534
|
+
}
|
|
3535
|
+
function formatCatalog(entries, opts) {
|
|
3536
|
+
const budget = opts.budgetBytes ?? DEFAULT_CATALOG_BUDGET;
|
|
3537
|
+
const pre = opts.namePrefix ?? "";
|
|
3538
|
+
const wrap = (body) => [opts.title, body, opts.footer].filter((x) => x !== void 0 && x !== "").join("\n");
|
|
3539
|
+
const render = (max) => wrap(entries.map((e) => {
|
|
3540
|
+
const d = (e.description ?? "").replace(/\s+/g, " ").trim();
|
|
3541
|
+
const desc = d && max > 0 ? ` \u2014 ${clip(d, max)}` : "";
|
|
3542
|
+
return `- **${pre}${e.name}**${desc}${e.path ? ` (\`${e.path}\`)` : ""}`;
|
|
3543
|
+
}).join("\n"));
|
|
3544
|
+
const longest = Math.max(0, ...entries.map((e) => [...e.description ?? ""].length));
|
|
3545
|
+
const full = render(longest);
|
|
3546
|
+
if (bytes(full) <= budget) return full;
|
|
3547
|
+
if (bytes(render(0)) <= budget) {
|
|
3548
|
+
let lo = 0, hi = longest;
|
|
3549
|
+
while (lo < hi) {
|
|
3550
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
3551
|
+
if (bytes(render(mid)) <= budget) lo = mid;
|
|
3552
|
+
else hi = mid - 1;
|
|
3553
|
+
}
|
|
3554
|
+
return render(lo);
|
|
3555
|
+
}
|
|
3556
|
+
const names = wrap(entries.map((e) => `- **${pre}${e.name}**`).join("\n"));
|
|
3557
|
+
if (bytes(names) <= budget) return names;
|
|
3558
|
+
return wrap(`- ${entries.map((e) => pre + e.name).join(", ")}`);
|
|
3580
3559
|
}
|
|
3581
3560
|
|
|
3582
3561
|
// src/frontmatter.ts
|
|
@@ -3598,7 +3577,6 @@ function grabField(block, key) {
|
|
|
3598
3577
|
}
|
|
3599
3578
|
|
|
3600
3579
|
// src/commands.ts
|
|
3601
|
-
var MAX_CATALOG = 25;
|
|
3602
3580
|
function parseFrontmatter(md) {
|
|
3603
3581
|
const { block, body } = splitFrontmatter(md);
|
|
3604
3582
|
return { description: grabField(block, "description"), body };
|
|
@@ -3655,9 +3633,12 @@ async function scanCommands(fs, dir) {
|
|
|
3655
3633
|
async function loadCommands(fs, dir, opts = {}) {
|
|
3656
3634
|
const commands = await scanCommands(fs, dir);
|
|
3657
3635
|
if (commands.length === 0) return { commands, catalog: "" };
|
|
3658
|
-
const
|
|
3659
|
-
|
|
3660
|
-
|
|
3636
|
+
const catalog = formatCatalog(commands, {
|
|
3637
|
+
title: "## Slash commands (reusable prompt templates)",
|
|
3638
|
+
footer: "To run one, call the `SlashCommand` tool with its name (and optional `args`); it returns the expanded template to act on.",
|
|
3639
|
+
namePrefix: "/",
|
|
3640
|
+
budgetBytes: opts.budgetBytes
|
|
3641
|
+
});
|
|
3661
3642
|
const tool = {
|
|
3662
3643
|
name: "SlashCommand",
|
|
3663
3644
|
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.",
|
|
@@ -3685,7 +3666,6 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
3685
3666
|
}
|
|
3686
3667
|
|
|
3687
3668
|
// src/skills.ts
|
|
3688
|
-
var MAX_CATALOG2 = 25;
|
|
3689
3669
|
function parseFrontmatter2(md) {
|
|
3690
3670
|
const { block } = splitFrontmatter(md);
|
|
3691
3671
|
const b = block || md.slice(0, 600);
|
|
@@ -3711,9 +3691,11 @@ async function scanSkills(fs, dir) {
|
|
|
3711
3691
|
async function loadSkills(fs, dir, opts = {}) {
|
|
3712
3692
|
const skills = await scanSkills(fs, dir);
|
|
3713
3693
|
if (skills.length === 0) return { skills, catalog: "" };
|
|
3714
|
-
const
|
|
3715
|
-
|
|
3716
|
-
|
|
3694
|
+
const catalog = formatCatalog(skills, {
|
|
3695
|
+
title: "## Skills (load one before acting on a matching task)",
|
|
3696
|
+
footer: "When a task matches a skill, call the `Skill` tool with its name to load its full instructions.",
|
|
3697
|
+
budgetBytes: opts.budgetBytes
|
|
3698
|
+
});
|
|
3717
3699
|
const tool = {
|
|
3718
3700
|
name: "Skill",
|
|
3719
3701
|
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.",
|
|
@@ -3738,7 +3720,73 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
3738
3720
|
|
|
3739
3721
|
// src/memory.ts
|
|
3740
3722
|
init_tools_structured();
|
|
3741
|
-
|
|
3723
|
+
|
|
3724
|
+
// src/relevance.ts
|
|
3725
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
3726
|
+
"the",
|
|
3727
|
+
"and",
|
|
3728
|
+
"for",
|
|
3729
|
+
"with",
|
|
3730
|
+
"that",
|
|
3731
|
+
"this",
|
|
3732
|
+
"from",
|
|
3733
|
+
"into",
|
|
3734
|
+
"your",
|
|
3735
|
+
"you",
|
|
3736
|
+
"are",
|
|
3737
|
+
"was",
|
|
3738
|
+
"will",
|
|
3739
|
+
"use",
|
|
3740
|
+
"using",
|
|
3741
|
+
"run",
|
|
3742
|
+
"add",
|
|
3743
|
+
"fix",
|
|
3744
|
+
"make",
|
|
3745
|
+
"file",
|
|
3746
|
+
"files",
|
|
3747
|
+
"code",
|
|
3748
|
+
"please",
|
|
3749
|
+
"need",
|
|
3750
|
+
"want",
|
|
3751
|
+
"should",
|
|
3752
|
+
"all"
|
|
3753
|
+
]);
|
|
3754
|
+
function tokenize(s) {
|
|
3755
|
+
const out = /* @__PURE__ */ new Set();
|
|
3756
|
+
for (const w of String(s ?? "").toLowerCase().match(/[a-z0-9]+/g) ?? []) {
|
|
3757
|
+
if (w.length >= 3 && !STOP.has(w)) out.add(w);
|
|
3758
|
+
}
|
|
3759
|
+
return out;
|
|
3760
|
+
}
|
|
3761
|
+
function idfWeights(corpus) {
|
|
3762
|
+
const N = corpus.length;
|
|
3763
|
+
if (N === 0) return /* @__PURE__ */ new Map();
|
|
3764
|
+
const df = /* @__PURE__ */ new Map();
|
|
3765
|
+
for (const doc of corpus) for (const t of tokenize(doc)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3766
|
+
const idf = /* @__PURE__ */ new Map();
|
|
3767
|
+
for (const [t, n] of df) idf.set(t, Math.log((N + 1) / (n + 1)) + 1);
|
|
3768
|
+
return idf;
|
|
3769
|
+
}
|
|
3770
|
+
function relevanceScore(text, queryTokens, idf) {
|
|
3771
|
+
if (queryTokens.size === 0) return 0;
|
|
3772
|
+
const t = tokenize(text);
|
|
3773
|
+
let score = 0;
|
|
3774
|
+
for (const q2 of queryTokens) if (t.has(q2)) score += idf?.get(q2) ?? 1;
|
|
3775
|
+
return score;
|
|
3776
|
+
}
|
|
3777
|
+
function topByRelevance(items, query, text, k, corpus) {
|
|
3778
|
+
if (!Number.isInteger(k) || k < 1) return { kept: items, rest: [] };
|
|
3779
|
+
if (items.length <= k || !query.trim()) return { kept: items, rest: [] };
|
|
3780
|
+
const q2 = tokenize(query);
|
|
3781
|
+
if (q2.size === 0) return { kept: items.slice(0, k), rest: items.slice(k) };
|
|
3782
|
+
const idf = corpus?.length ? idfWeights(corpus) : void 0;
|
|
3783
|
+
const scored = items.map((x, i) => ({ i, s: relevanceScore(text(x), q2, idf) }));
|
|
3784
|
+
scored.sort((a, b) => b.s - a.s || a.i - b.i);
|
|
3785
|
+
const keep = new Set(scored.slice(0, k).map((e) => e.i));
|
|
3786
|
+
return { kept: items.filter((_, i) => keep.has(i)), rest: items.filter((_, i) => !keep.has(i)) };
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
// src/memory.ts
|
|
3742
3790
|
var MAX_INDEX_LINES = 200;
|
|
3743
3791
|
var MAX_INDEX_BYTES = 25e3;
|
|
3744
3792
|
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.';
|
|
@@ -3773,10 +3821,18 @@ async function loadMemory(fs, dir, opts = {}) {
|
|
|
3773
3821
|
}
|
|
3774
3822
|
}
|
|
3775
3823
|
if (!hasContent) return { index: MEMORY_PROMPT, tools };
|
|
3776
|
-
const
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3824
|
+
const entries = allPointers.map((l) => {
|
|
3825
|
+
const m = l.match(/\]\(([^)]+)\.md\)/);
|
|
3826
|
+
const slug2 = m?.[1] ?? l;
|
|
3827
|
+
const title = m ? l.slice(l.indexOf("[") + 1, m.index).trim() : "";
|
|
3828
|
+
const hook = m ? l.slice(m.index + m[0].length).replace(/^\s*(?:[—:-]\s*)?/, "").trim() : "";
|
|
3829
|
+
return { name: slug2, description: title && title !== slug2 ? hook ? `${title} \u2014 ${hook}` : title : hook };
|
|
3830
|
+
});
|
|
3831
|
+
const index = MEMORY_PROMPT + "\n\n" + formatCatalog(entries, {
|
|
3832
|
+
title: "## Memory index (persistent context \u2014 recalled across sessions)" + (header ? "\n" + header : ""),
|
|
3833
|
+
footer: "Call `Recall` with a slug to load its full fact.",
|
|
3834
|
+
budgetBytes: opts.budgetBytes
|
|
3835
|
+
});
|
|
3780
3836
|
return { index, tools };
|
|
3781
3837
|
}
|
|
3782
3838
|
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
|
|
@@ -3967,8 +4023,8 @@ function memorySearchTool(fs, dirs) {
|
|
|
3967
4023
|
return `Error: invalid regex: ${e}`;
|
|
3968
4024
|
}
|
|
3969
4025
|
} else {
|
|
3970
|
-
const
|
|
3971
|
-
matcher = (l) => l.toLowerCase().includes(
|
|
4026
|
+
const lower2 = q2.toLowerCase();
|
|
4027
|
+
matcher = (l) => l.toLowerCase().includes(lower2);
|
|
3972
4028
|
}
|
|
3973
4029
|
const loaded = [];
|
|
3974
4030
|
for (const slug2 of slugs) {
|
|
@@ -4076,6 +4132,25 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
4076
4132
|
}
|
|
4077
4133
|
return "";
|
|
4078
4134
|
}
|
|
4135
|
+
async function loadContextFiles(fs, specs = []) {
|
|
4136
|
+
const sections = [];
|
|
4137
|
+
for (const { path, maxChars, label } of specs) {
|
|
4138
|
+
if (!await fs.exists(path)) continue;
|
|
4139
|
+
let md = (await fs.readFile(path)).trim();
|
|
4140
|
+
if (!md) continue;
|
|
4141
|
+
if (maxChars !== void 0) {
|
|
4142
|
+
const cps = [...md], n = Math.max(0, maxChars);
|
|
4143
|
+
if (cps.length > n) md = `${cps.slice(0, n).join("").trimEnd()}
|
|
4144
|
+
\u2026 (truncated \u2014 full file: ${path})`;
|
|
4145
|
+
}
|
|
4146
|
+
sections.push(`<!-- ${path} -->
|
|
4147
|
+
${label ? `### ${label}
|
|
4148
|
+
` : ""}${md}`);
|
|
4149
|
+
}
|
|
4150
|
+
return sections.length ? `## Context files
|
|
4151
|
+
|
|
4152
|
+
${sections.join("\n\n---\n\n")}` : "";
|
|
4153
|
+
}
|
|
4079
4154
|
|
|
4080
4155
|
// src/subagent.ts
|
|
4081
4156
|
init_tools();
|
|
@@ -4618,6 +4693,11 @@ init_logging();
|
|
|
4618
4693
|
var log8 = forComponent("modes");
|
|
4619
4694
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
4620
4695
|
var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
|
|
4696
|
+
var lower = (xs) => new Set([...xs].map((x) => x.toLowerCase()));
|
|
4697
|
+
var PLANNER_BLOCKED_LC = lower(PLANNER_BLOCKED);
|
|
4698
|
+
var WRITE_TOOLS_LC = lower(WRITE_TOOLS);
|
|
4699
|
+
var plannerBlocks = (t) => !!t.writes || PLANNER_BLOCKED_LC.has(t.name.toLowerCase());
|
|
4700
|
+
var isFileWrite = (t) => WRITE_TOOLS_LC.has(t.name.toLowerCase());
|
|
4621
4701
|
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.";
|
|
4622
4702
|
var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
|
|
4623
4703
|
var ADVISE_LIMIT = 3;
|
|
@@ -4640,7 +4720,7 @@ function advisorFor(worker, advisor) {
|
|
|
4640
4720
|
return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
|
|
4641
4721
|
}
|
|
4642
4722
|
function delegateTools(tools, planner) {
|
|
4643
|
-
return tools.map((t) => !
|
|
4723
|
+
return tools.map((t) => !plannerBlocks(t) ? t : {
|
|
4644
4724
|
...t,
|
|
4645
4725
|
run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
|
|
4646
4726
|
});
|
|
@@ -4710,7 +4790,7 @@ ${question}` }
|
|
|
4710
4790
|
}
|
|
4711
4791
|
}
|
|
4712
4792
|
};
|
|
4713
|
-
const gate = (tools) => tools.map((t) => !
|
|
4793
|
+
const gate = (tools) => tools.map((t) => !isFileWrite(t) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
|
|
4714
4794
|
return { tool, gate, used: () => used };
|
|
4715
4795
|
}
|
|
4716
4796
|
function meterWorkers(ai, main2) {
|
|
@@ -4753,6 +4833,13 @@ function meterWorkers(ai, main2) {
|
|
|
4753
4833
|
resolved = void 0;
|
|
4754
4834
|
} };
|
|
4755
4835
|
}
|
|
4836
|
+
var CC_WRITE_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
4837
|
+
function ccModeOptions(kind, hostHooks, used) {
|
|
4838
|
+
if (kind === "delegate") return { bridge: /* @__PURE__ */ new Set(["Task", "TaskBatch"]), disallow: [...CC_WRITE_TOOLS, "Bash", "Task", "Agent"] };
|
|
4839
|
+
if (kind !== "advisor") return { bridge: /* @__PURE__ */ new Set(), disallow: [] };
|
|
4840
|
+
const gate = { matcher: CC_WRITE_TOOLS.join("|"), hooks: [async () => used() ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: PLAN_FIRST.replace("`Advise`", "`mcp__agentx__Advise`") } }] };
|
|
4841
|
+
return { bridge: /* @__PURE__ */ new Set(["Advise"]), disallow: [], hooks: { ...hostHooks, PreToolUse: [...hostHooks?.PreToolUse ?? [], gate] } };
|
|
4842
|
+
}
|
|
4756
4843
|
|
|
4757
4844
|
// src/lint.ts
|
|
4758
4845
|
var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
@@ -4953,6 +5040,8 @@ var AgentOptions = class {
|
|
|
4953
5040
|
* Walks the VFS tree and merges all found files (general → specific, like Claude Code).
|
|
4954
5041
|
* `true` (default) = auto-discover standard names. `string[]` = custom names. `false` = skip. */
|
|
4955
5042
|
instructionFiles = true;
|
|
5043
|
+
/** Extra files injected right after the instructions (missing → skipped; over `maxChars` → head + pointer to the full file). */
|
|
5044
|
+
contextFiles;
|
|
4956
5045
|
/** Host interaction channel (human-in-the-loop). If set: adds the `AskUserQuestion` tool. */
|
|
4957
5046
|
host;
|
|
4958
5047
|
/** Add the `AskUserQuestion` tool when a host is present (default true). Set false for an agent that
|
|
@@ -5080,6 +5169,8 @@ var Agent = class _Agent {
|
|
|
5080
5169
|
meter;
|
|
5081
5170
|
// delegate/advisor: meters the second model's calls
|
|
5082
5171
|
modeModel;
|
|
5172
|
+
modeKind;
|
|
5173
|
+
adv;
|
|
5083
5174
|
lastTask = "";
|
|
5084
5175
|
// the current turn's text — what the advisor reads as the request
|
|
5085
5176
|
/** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
|
|
@@ -5184,12 +5275,15 @@ var Agent = class _Agent {
|
|
|
5184
5275
|
const o = this.options;
|
|
5185
5276
|
if (!o.model.startsWith("claude-code/")) return void 0;
|
|
5186
5277
|
const po = { steer: () => this.drainPendingSteers() };
|
|
5187
|
-
const
|
|
5278
|
+
const mode = ccModeOptions(this.modeKind, o.providerOptions?.hooks, () => this.adv?.used() ?? 0);
|
|
5279
|
+
const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name) || mode.bridge.has(t.name);
|
|
5188
5280
|
if (o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
|
|
5189
5281
|
this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
|
|
5190
5282
|
po.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
5191
5283
|
}
|
|
5192
|
-
const denyTools = claudeCodeDisallowedTools(o.permissions);
|
|
5284
|
+
const denyTools = [...claudeCodeDisallowedTools(o.permissions), ...mode.disallow];
|
|
5285
|
+
if (mode.hooks) po.hooks = mode.hooks;
|
|
5286
|
+
if (this.modeKind && o.providerOptions?.claudeCodeSession) po.claudeCodeSession = `${o.providerOptions.claudeCodeSession}:${this.modeKind}`;
|
|
5193
5287
|
if (denyTools.length) {
|
|
5194
5288
|
const prior = o.providerOptions?.disallowedTools ?? [];
|
|
5195
5289
|
po.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
|
|
@@ -5276,7 +5370,7 @@ var Agent = class _Agent {
|
|
|
5276
5370
|
* across turns (and the frozen prompt pairs well with prompt caching). Does NOT touch
|
|
5277
5371
|
* the transcript; `run`/`send` own that.
|
|
5278
5372
|
*/
|
|
5279
|
-
async prepare(
|
|
5373
|
+
async prepare() {
|
|
5280
5374
|
if (this.prepared) return this.systemPromptCache;
|
|
5281
5375
|
const o = this.options;
|
|
5282
5376
|
const fs = o.fs;
|
|
@@ -5287,14 +5381,19 @@ var Agent = class _Agent {
|
|
|
5287
5381
|
const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
|
|
5288
5382
|
this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
|
|
5289
5383
|
const ai = this.meter?.ai ?? o.ai;
|
|
5290
|
-
|
|
5384
|
+
this.modeKind = worker ? "delegate" : advisor ? "advisor" : void 0;
|
|
5385
|
+
const adv = this.adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
|
|
5291
5386
|
if (o.instructionFiles !== false) {
|
|
5292
5387
|
const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
|
|
5293
5388
|
const ins = await loadInstructions(fs, names);
|
|
5294
5389
|
if (ins) systemPrompt += "\n\n" + ins;
|
|
5295
5390
|
}
|
|
5391
|
+
if (o.contextFiles?.length) {
|
|
5392
|
+
const ctxFiles = await loadContextFiles(fs, o.contextFiles);
|
|
5393
|
+
if (ctxFiles) systemPrompt += "\n\n" + ctxFiles;
|
|
5394
|
+
}
|
|
5296
5395
|
if (o.memoryDir) {
|
|
5297
|
-
const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, {
|
|
5396
|
+
const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, { userDir: o.memoryUserDir });
|
|
5298
5397
|
if (index) systemPrompt += "\n\n" + index;
|
|
5299
5398
|
tools = [...tools, ...memTools];
|
|
5300
5399
|
}
|
|
@@ -5304,7 +5403,7 @@ var Agent = class _Agent {
|
|
|
5304
5403
|
const c = (await scanCommands(fs, commandsDir)).find((x) => x.name === name);
|
|
5305
5404
|
return c ? { path: c.path, stripFrontmatter: true } : void 0;
|
|
5306
5405
|
} : void 0;
|
|
5307
|
-
const { catalog, tool } = await loadSkills(fs, skillsDir, {
|
|
5406
|
+
const { catalog, tool } = await loadSkills(fs, skillsDir, { sibling });
|
|
5308
5407
|
if (catalog) systemPrompt += "\n\n" + catalog;
|
|
5309
5408
|
if (tool) tools = [...tools, tool];
|
|
5310
5409
|
}
|
|
@@ -5313,7 +5412,7 @@ var Agent = class _Agent {
|
|
|
5313
5412
|
const s = (await scanSkills(fs, skillsDir)).find((x) => x.name === name);
|
|
5314
5413
|
return s ? { path: s.path, stripFrontmatter: false } : void 0;
|
|
5315
5414
|
} : void 0;
|
|
5316
|
-
const { catalog, tool } = await loadCommands(fs, commandsDir, {
|
|
5415
|
+
const { catalog, tool } = await loadCommands(fs, commandsDir, { sibling });
|
|
5317
5416
|
if (catalog) systemPrompt += "\n\n" + catalog;
|
|
5318
5417
|
if (tool) tools = [...tools, tool];
|
|
5319
5418
|
}
|
|
@@ -5372,7 +5471,7 @@ var Agent = class _Agent {
|
|
|
5372
5471
|
this.invokedTools.clear();
|
|
5373
5472
|
this.fabricatedClaims.clear();
|
|
5374
5473
|
this.lastTask = contentText(task);
|
|
5375
|
-
const systemPrompt = await this.prepare(
|
|
5474
|
+
const systemPrompt = await this.prepare();
|
|
5376
5475
|
const startCtx = await this.fireSessionStart();
|
|
5377
5476
|
const userContent = await this.applyPromptSubmit(task);
|
|
5378
5477
|
this.transcript = [
|
|
@@ -5408,7 +5507,7 @@ var Agent = class _Agent {
|
|
|
5408
5507
|
async send(task) {
|
|
5409
5508
|
await this.ensureFs();
|
|
5410
5509
|
this.lastTask = contentText(task);
|
|
5411
|
-
const systemPrompt = await this.prepare(
|
|
5510
|
+
const systemPrompt = await this.prepare();
|
|
5412
5511
|
const startCtx = await this.fireSessionStart();
|
|
5413
5512
|
const userContent = await this.applyPromptSubmit(task);
|
|
5414
5513
|
const sys = systemPrompt + (startCtx ? "\n\n" + startCtx : "");
|
|
@@ -6232,9 +6331,9 @@ function capOversizedBodies(messages, target, keepRecentImages = 1) {
|
|
|
6232
6331
|
return out;
|
|
6233
6332
|
}
|
|
6234
6333
|
function elideBody(x, cap) {
|
|
6235
|
-
const note = (
|
|
6334
|
+
const note = (bytes2, imgs2) => `
|
|
6236
6335
|
|
|
6237
|
-
[${
|
|
6336
|
+
[${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` : ""}]`;
|
|
6238
6337
|
if (!Array.isArray(x.content)) {
|
|
6239
6338
|
const text = contentText(x.content);
|
|
6240
6339
|
if (text.length <= cap) return x;
|
|
@@ -7389,8 +7488,8 @@ function isTranscriptComplete(text) {
|
|
|
7389
7488
|
const lastWord = words4[words4.length - 1];
|
|
7390
7489
|
if (COMPLETE_ENDINGS.has(lastWord)) return true;
|
|
7391
7490
|
if (INCOMPLETE_ENDINGS.has(lastWord)) return false;
|
|
7392
|
-
const
|
|
7393
|
-
for (const marker of CONTINUATION_MARKERS) if (
|
|
7491
|
+
const lower2 = trimmed.toLowerCase();
|
|
7492
|
+
for (const marker of CONTINUATION_MARKERS) if (lower2.endsWith(marker)) return false;
|
|
7394
7493
|
if (words4.length <= 2) return false;
|
|
7395
7494
|
return true;
|
|
7396
7495
|
}
|
|
@@ -12393,11 +12492,11 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12393
12492
|
const list = id ? [this.tasks.get(String(id))].filter(Boolean) : [...this.tasks.values()];
|
|
12394
12493
|
if (!list.length) return id ? `No task '${id}'.` : "No background tasks.";
|
|
12395
12494
|
const cap = id ? 1500 : 300;
|
|
12396
|
-
const
|
|
12495
|
+
const clip2 = (x) => x.length > cap ? `${x.slice(0, cap)}\u2026 (truncated)` : x;
|
|
12397
12496
|
return list.map((t) => {
|
|
12398
12497
|
const head = `${t.id} (${t.label}): ${t.status}`;
|
|
12399
12498
|
const body = (t.status === "done" ? t.spokenText?.trim() || t.result?.trim() : t.status === "error" ? t.result?.trim() : "") ?? "";
|
|
12400
|
-
return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${
|
|
12499
|
+
return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${clip2(body)}` : head;
|
|
12401
12500
|
}).join("\n");
|
|
12402
12501
|
}
|
|
12403
12502
|
};
|
|
@@ -14986,6 +15085,7 @@ function readJsonFile(path, fallback) {
|
|
|
14986
15085
|
}
|
|
14987
15086
|
|
|
14988
15087
|
// cli/core.ts
|
|
15088
|
+
init_models();
|
|
14989
15089
|
init_hostPath();
|
|
14990
15090
|
init_platform();
|
|
14991
15091
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
@@ -15182,6 +15282,14 @@ Reference files in them by their mount path (the left side).`;
|
|
|
15182
15282
|
const po = providerOptionsFor(o.model, cwd, o.mcpServers, o.hostMcpServers);
|
|
15183
15283
|
return po ? { providerOptions: o.lean && isClaudeCode ? { ...po, settingSources: [] } : po } : {};
|
|
15184
15284
|
})(),
|
|
15285
|
+
// A child on ANOTHER provider (delegate mode's anthropic planner → claude-code/* worker, or back) gets that
|
|
15286
|
+
// runtime's own options (cwd, env, MCP) — the Agent default inherits only same-provider options, so a claude-code
|
|
15287
|
+
// worker under an anthropic planner ran with none. Same provider: the Agent default (parent's, minus its resume).
|
|
15288
|
+
providerOptionsFor: (m) => {
|
|
15289
|
+
const a = agentRef.agent, po = a?.options.providerOptions;
|
|
15290
|
+
if (modelProvider(m) === modelProvider(a?.options.model ?? "")) return po ? { ...po, resume: void 0, onClaudeCodeSession: void 0 } : void 0;
|
|
15291
|
+
return providerOptionsFor(m, cwd, o.mcpServers, o.hostMcpServers);
|
|
15292
|
+
},
|
|
15185
15293
|
...(() => {
|
|
15186
15294
|
const now4 = /* @__PURE__ */ new Date();
|
|
15187
15295
|
const platformNames = { darwin: "macOS", linux: "Linux", win32: "Windows" };
|
|
@@ -19732,7 +19840,7 @@ function makeHost(format = "text", opts) {
|
|
|
19732
19840
|
spinner.start();
|
|
19733
19841
|
return;
|
|
19734
19842
|
}
|
|
19735
|
-
const
|
|
19843
|
+
const clip2 = (s) => s.length > 200 ? s.slice(0, 200) + "\u2026" : s;
|
|
19736
19844
|
const terminal = a.status === "completed" || a.status === "error";
|
|
19737
19845
|
if (!terminal || !a.id || !activitySeen.has(a.id)) {
|
|
19738
19846
|
if (a.id) activitySeen.add(a.id);
|
|
@@ -19751,8 +19859,8 @@ function makeHost(format = "text", opts) {
|
|
|
19751
19859
|
}
|
|
19752
19860
|
const lines = text.split("\n");
|
|
19753
19861
|
const shown = lines.slice(0, previewLines());
|
|
19754
|
-
err(paint(" \u23BF " +
|
|
19755
|
-
for (const ln of shown.slice(1)) err(dim(" " +
|
|
19862
|
+
err(paint(" \u23BF " + clip2(shown[0])) + "\n");
|
|
19863
|
+
for (const ln of shown.slice(1)) err(dim(" " + clip2(ln)) + "\n");
|
|
19756
19864
|
const more = lines.length - shown.length;
|
|
19757
19865
|
if (more > 0 || a.truncated?.result) err(dim(` \u2026 (${more > 0 ? `+${more} more line${more > 1 ? "s" : ""}` : "truncated by backend"}${a.truncated?.result && more > 0 ? ", truncated" : ""} \xB7 ctrl+o expands)
|
|
19758
19866
|
`));
|
|
@@ -19988,7 +20096,7 @@ function formatTranscriptFull(messages, opts) {
|
|
|
19988
20096
|
if (!shown.length) return dim(" (empty transcript)\n");
|
|
19989
20097
|
const results = /* @__PURE__ */ new Map();
|
|
19990
20098
|
for (const m of shown) if (m.role === "tool" && m.tool_call_id) results.set(m.tool_call_id, m);
|
|
19991
|
-
const
|
|
20099
|
+
const clip2 = (s) => {
|
|
19992
20100
|
const lines = s.split("\n");
|
|
19993
20101
|
return lines.length > cap ? lines.slice(0, cap).join("\n") + `
|
|
19994
20102
|
\u2026 (+${lines.length - cap} more lines)` : s;
|
|
@@ -20009,7 +20117,7 @@ function formatTranscriptFull(messages, opts) {
|
|
|
20009
20117
|
}
|
|
20010
20118
|
out.push(cyan(" \u2699 ") + tc.function.name + dim(" " + summarizeCall(tc.function.name, args)) + "\n");
|
|
20011
20119
|
const r = results.get(tc.id);
|
|
20012
|
-
if (r) out.push(dim(" " +
|
|
20120
|
+
if (r) out.push(dim(" " + clip2(contentText(r.content).trimEnd()).replace(/\n/g, "\n ")) + "\n");
|
|
20013
20121
|
}
|
|
20014
20122
|
}
|
|
20015
20123
|
}
|