@livx.cc/agentx 0.99.123 → 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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as AgentOptions, H as Hooks, w as RunResult, j as Decider, b as Agent, p as PermissionPolicy, G as Gender, L as LineKind, d as ChannelMode, x as SecondChannelOptions, S as SecondChannel } from './secondChannel-DES_VIeN.js';
2
- export { A as ADVISE_LIMIT, a as AdvisorSetup, C as CC_WRITE_TOOLS, e as ChatFragment, f as ChatUsage, g as Classification, h as Classifier, i as CompactResult, D as DEFAULT_MUTATING, k as Decision, M as ModelRouter, l as ModelRouterOptions, P as PLANNER_BLOCKED, m as PLANNER_REFUSAL, n as PLAN_FIRST, o as PermissionOptions, q as PermissionRule, r as PreToolUseDecision, R as ReasoningEffort, s as RecordingHooks, t as RecordingLifecycle, u as RouteDecision, v as RouterState, T as Tier, y as ToolUse, z as ToolUseMeta, W as WRITE_TOOLS, B as advisorFor, E as advisorPrompt, F as ccModeOptions, I as claudeCodeDisallowedTools, J as composeHooks, K as delegatePrompt, N as delegateTools, O as delegateWorkerFor, Q as estimateTokens, U as jevAvailable, V as jevClassifier, X as makeAdvisor, Y as meterWorkers, Z as pickTier, _ as planMode, $ as plannerBlocks, a0 as reasoningToChatFragment } from './secondChannel-DES_VIeN.js';
1
+ import { c as AgentOptions, H as Hooks, x as RunResult, k as Decider, b as Agent, q as PermissionPolicy, G as Gender, L as LineKind, d as ChannelMode, y as SecondChannelOptions, S as SecondChannel } from './secondChannel-GFT69ax4.js';
2
+ export { A as ADVISE_LIMIT, a as AdvisorSetup, C as CC_WRITE_TOOLS, e as ChatFragment, f as ChatUsage, g as Classification, h as Classifier, i as CompactResult, j as ContextFileSpec, D as DEFAULT_MUTATING, l as Decision, M as ModelRouter, m as ModelRouterOptions, P as PLANNER_BLOCKED, n as PLANNER_REFUSAL, o as PLAN_FIRST, p as PermissionOptions, r as PermissionRule, s as PreToolUseDecision, R as ReasoningEffort, t as RecordingHooks, u as RecordingLifecycle, v as RouteDecision, w as RouterState, T as Tier, z as ToolUse, B as ToolUseMeta, W as WRITE_TOOLS, E as advisorFor, F as advisorPrompt, I as ccModeOptions, J as claudeCodeDisallowedTools, K as composeHooks, N as delegatePrompt, O as delegateTools, Q as delegateWorkerFor, U as estimateTokens, V as jevAvailable, X as jevClassifier, Y as loadContextFiles, Z as loadInstructions, _ as makeAdvisor, $ as meterWorkers, a0 as pickTier, a1 as planMode, a2 as plannerBlocks, a3 as reasoningToChatFragment } from './secondChannel-GFT69ax4.js';
3
3
  export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, latestClaude, modelShortLabel, resolveModelAlias, resolveModelSwitch } from './models.js';
4
4
  import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
5
5
  export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
@@ -175,6 +175,8 @@ declare class JailOptions {
175
175
  deny: string[];
176
176
  /** Globs readable but not writable/deletable (the pinned constitution). */
177
177
  readonly: string[];
178
+ /** Appended to readonly-violation errors (e.g. "edit it via the kb tool instead"). */
179
+ readonlyHint?: string;
178
180
  /** If set, only paths matching these globs are accessible at all. */
179
181
  confineTo?: string[];
180
182
  /** Audit hook fired on every blocked operation (drives the circuit breaker). */
@@ -753,8 +755,12 @@ declare function expandCommand(fs: IFilesystem, cmd: CommandInfo, args: string):
753
755
  /** Scan `dir`(s) for `<dir>/<name>.md` command templates (first dir wins on duplicate names). */
754
756
  declare function scanCommands(fs: IFilesystem, dir: string | string[]): Promise<CommandInfo[]>;
755
757
  declare function loadCommands(fs: IFilesystem, dir: string | string[], opts?: {
758
+ /** @deprecated no-op — the catalog is full + task-independent (stable prompt prefix). */
756
759
  relevanceHint?: string;
760
+ /** @deprecated no-op — use `budgetBytes`. */
757
761
  max?: number;
762
+ /** Catalog byte budget (default 6000); over it descriptions trim, names never drop. */
763
+ budgetBytes?: number;
758
764
  sibling?: SiblingResolver;
759
765
  }): Promise<{
760
766
  commands: CommandInfo[];
@@ -779,8 +785,12 @@ interface SkillInfo {
779
785
  /** Scan `dir`(s) for `<dir>/<id>/SKILL.md` entries (first dir wins on duplicate ids). */
780
786
  declare function scanSkills(fs: IFilesystem, dir: string | string[]): Promise<SkillInfo[]>;
781
787
  declare function loadSkills(fs: IFilesystem, dir: string | string[], opts?: {
788
+ /** @deprecated no-op — the catalog is full + task-independent (stable prompt prefix). */
782
789
  relevanceHint?: string;
790
+ /** @deprecated no-op — use `budgetBytes`. */
783
791
  max?: number;
792
+ /** Catalog byte budget (default 6000); over it descriptions trim, names never drop. */
793
+ budgetBytes?: number;
784
794
  sibling?: SiblingResolver;
785
795
  }): Promise<{
786
796
  skills: SkillInfo[];
@@ -827,8 +837,12 @@ declare const VOICE_MEMORY_PROMPT: string;
827
837
  * memory mechanism therefore works in node, a browser tab, or on edge.
828
838
  */
829
839
  interface LoadMemoryOpts {
840
+ /** @deprecated no-op — the index is full + task-independent (stable prompt prefix). */
830
841
  relevanceHint?: string;
842
+ /** @deprecated no-op — use `budgetBytes`. */
831
843
  max?: number;
844
+ /** Index byte budget (default 6000); over it hooks trim, slugs never drop. */
845
+ budgetBytes?: number;
832
846
  /** Max Remember calls per session (default 25). Prevents runaway memory writes. */
833
847
  maxWritesPerSession?: number;
834
848
  /** Called after each successful memory write (auditing, UI indicators). */
@@ -901,14 +915,30 @@ interface ReflectOptions {
901
915
  }
902
916
  declare function reflectOnRun(o: ReflectOptions): Promise<string | null>;
903
917
 
918
+ /** One catalog row: a loadable thing (skill, command, memory) the model can pull by name. */
919
+ interface CatalogEntry {
920
+ name: string;
921
+ description?: string;
922
+ path?: string;
923
+ }
924
+ interface CatalogOptions {
925
+ /** Heading line (e.g. `## Skills …`). */
926
+ title: string;
927
+ /** Closing line (how to load an entry). */
928
+ footer?: string;
929
+ /** Max UTF-8 bytes for the whole block (default 6000). Names are never dropped, so a huge set may exceed it. */
930
+ budgetBytes?: number;
931
+ /** Prefix shown before each name (e.g. `/` for commands). */
932
+ namePrefix?: string;
933
+ }
934
+ declare const DEFAULT_CATALOG_BUDGET = 6000;
904
935
  /**
905
- * Walk the VFS directory tree from `/` down, collecting instruction files
906
- * (AGENT.md, AGENTS.md, CLAUDE.md, .claude/<name>) at each level.
907
- * Merges all found files general-first → specific-last (mirrors Claude Code's
908
- * hierarchical CLAUDE.md discovery). First matching filename per directory wins
909
- * (no duplicates from the same level). Edge-portable: works over any IFilesystem.
936
+ * Render a full, stable catalog: every entry as `- **name** — desc (\`path\`)`.
937
+ * Over `budgetBytes`, descriptions are trimmed evenly (one shared max length), then — if even
938
+ * empty descriptions don't fit — names only. Never drops a name; output depends only on the
939
+ * entries (no task hint), so the prompt prefix stays byte-identical across turns/tasks.
910
940
  */
911
- declare function loadInstructions(fs: IFilesystem, names?: string[]): Promise<string>;
941
+ declare function formatCatalog(entries: CatalogEntry[], opts: CatalogOptions): string;
912
942
 
913
943
  type OverlapVerdict = 'keep' | 'cut';
914
944
 
@@ -1852,4 +1882,4 @@ declare function exitWorktree(): {
1852
1882
  reason?: string;
1853
1883
  } | null;
1854
1884
 
1855
- export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, BodDbFilesystem, type CallMetric, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DEFAULT_MAX_DEPTH, DEFAULT_SKIP_DIRS, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, type FileScanOptions, type FillKind, type GateDrop, type GateTurn, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, type MainEvent, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, PROGRESS_TEMPLATES, PermissionPolicy, Progress, ProgressOptions, type ReflectOptions, RunResult, SCRATCH_DIR, type ScheduledJob, type ScheduledJobSnapshot, Scheduler, type SchedulerOptions, Scratch, type ScratchOptions, ScriptedHostBridge, type SiblingResolver, type SkillInfo, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type Trigger, type TriggerCron, type TriggerInterval, type TriggerOneOff, TurnGate, TurnGateOptions, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceCall, VoiceCallOptions, type WebFetchOptions, type WebSearchOptions, type WorkerTier, type WorktreeInfo, type WorktreeSession, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, cleanupWorktree, compileSynthesizedTool, cronMatches, decodeDdgUrl, diskAgentOptions, endsMidPhrase, enterWorktree, exitWorktree, expandCommand, expandEntry, expandTemplate, findGitRoot, forComponent, forgetFact, fullAgentOptions, getOrCreateWorktree, getWorktreeSession, globTool, grepTool, htmlToText, idfWeights, isClosing, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeGlobTool, makeGrepTool, makeScheduleTools, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchAnthropicTool, makeWebSearchTool, mkdirp, multiEditTool, nextCronAfter, parseCron, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, rollbackTool, sandboxAgentOptions, scanCommands, scanSkills, slugify, tokenize, toolCall, topByRelevance, validateToolCode, validateWorktreeSlug, webFetchTool, webSearchAnthropicTool, webSearchTool, worktreeBranchName, writeFact, writeTool };
1885
+ export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, BodDbFilesystem, type CallMetric, type CatalogEntry, type CatalogOptions, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_CATALOG_BUDGET, DEFAULT_DENY, DEFAULT_MAX_DEPTH, DEFAULT_SKIP_DIRS, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, type FileScanOptions, type FillKind, type GateDrop, type GateTurn, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, type MainEvent, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, PROGRESS_TEMPLATES, PermissionPolicy, Progress, ProgressOptions, type ReflectOptions, RunResult, SCRATCH_DIR, type ScheduledJob, type ScheduledJobSnapshot, Scheduler, type SchedulerOptions, Scratch, type ScratchOptions, ScriptedHostBridge, type SiblingResolver, type SkillInfo, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type Trigger, type TriggerCron, type TriggerInterval, type TriggerOneOff, TurnGate, TurnGateOptions, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceCall, VoiceCallOptions, type WebFetchOptions, type WebSearchOptions, type WorkerTier, type WorktreeInfo, type WorktreeSession, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, cleanupWorktree, compileSynthesizedTool, cronMatches, decodeDdgUrl, diskAgentOptions, endsMidPhrase, enterWorktree, exitWorktree, expandCommand, expandEntry, expandTemplate, findGitRoot, forComponent, forgetFact, formatCatalog, fullAgentOptions, getOrCreateWorktree, getWorktreeSession, globTool, grepTool, htmlToText, idfWeights, isClosing, lessonCapture, loadAgents, loadCommands, loadMemory, loadSkills, makeAskTool, makeGlobTool, makeGrepTool, makeScheduleTools, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchAnthropicTool, makeWebSearchTool, mkdirp, multiEditTool, nextCronAfter, parseCron, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, rollbackTool, sandboxAgentOptions, scanCommands, scanSkills, slugify, tokenize, toolCall, topByRelevance, validateToolCode, validateWorktreeSlug, webFetchTool, webSearchAnthropicTool, webSearchTool, worktreeBranchName, writeFact, writeTool };
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 bytes = 0;
123
+ let bytes2 = 0;
124
124
  try {
125
- bytes = Math.ceil(statSync2(path).size * 4 / 3);
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
  }
@@ -1634,14 +1634,14 @@ var init_tools = __esm({
1634
1634
  if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
1635
1635
  return imageRefResult(real, IMG_MIME[ext]);
1636
1636
  }
1637
- const bytes = await fs.readFileBytes(path);
1638
- const b64 = Buffer.from(bytes).toString("base64");
1637
+ const bytes2 = await fs.readFileBytes(path);
1638
+ const b64 = Buffer.from(bytes2).toString("base64");
1639
1639
  return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });
1640
1640
  }
1641
1641
  const raw = await ctx.fs.readFile(path);
1642
1642
  if (isBinary(raw)) {
1643
- const bytes = await byteSize(ctx.fs, path, raw);
1644
- return `[binary file: ${path} \u2014 ${bytes} bytes, not shown as text. Read renders png/jpg/jpeg/gif/webp images directly; otherwise attach it as @${path}.]`;
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}.]`;
1645
1645
  }
1646
1646
  ctx.readState.set(ctx.fs.resolvePath(path), raw);
1647
1647
  if (raw === "") return `[empty file: ${path} \u2014 0 bytes]`;
@@ -1972,8 +1972,8 @@ var init_tools_jobs = __esm({
1972
1972
  return [...this.jobs.values()].map((j) => this.info(j));
1973
1973
  }
1974
1974
  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 };
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 };
1977
1977
  }
1978
1978
  /** Wait (bounded) for a job to settle. Resolves the job's info either way; null for an unknown id. */
1979
1979
  async wait(id, timeoutMs) {
@@ -2407,7 +2407,8 @@ var init_JailedFilesystem = __esm({
2407
2407
  violate(op, abs, rule) {
2408
2408
  this.note(op, abs, rule);
2409
2409
  if (rule === "deny" || rule === "confine") throw new Error(`File not found: ${abs}`);
2410
- throw new Error(`Blocked by filesystem jail (${rule}): ${abs}`);
2410
+ const hint = rule === "readonly" && this.options.readonlyHint ? ` \u2014 ${this.options.readonlyHint}` : "";
2411
+ throw new Error(`Blocked by filesystem jail (${rule}): ${abs}${hint}`);
2411
2412
  }
2412
2413
  /** Fire the audit hook without throwing (for boolean probes that must still return false). */
2413
2414
  note(op, abs, rule) {
@@ -2532,6 +2533,8 @@ var init_JailedFilesystem = __esm({
2532
2533
  deny = [...DEFAULT_DENY];
2533
2534
  /** Globs readable but not writable/deletable (the pinned constitution). */
2534
2535
  readonly = [];
2536
+ /** Appended to readonly-violation errors (e.g. "edit it via the kb tool instead"). */
2537
+ readonlyHint;
2535
2538
  /** If set, only paths matching these globs are accessible at all. */
2536
2539
  confineTo;
2537
2540
  /** Audit hook fired on every blocked operation (drives the circuit breaker). */
@@ -3287,69 +3290,39 @@ ${prompt} [y/N] `);
3287
3290
  }
3288
3291
  };
3289
3292
 
3290
- // src/relevance.ts
3291
- var STOP = /* @__PURE__ */ new Set([
3292
- "the",
3293
- "and",
3294
- "for",
3295
- "with",
3296
- "that",
3297
- "this",
3298
- "from",
3299
- "into",
3300
- "your",
3301
- "you",
3302
- "are",
3303
- "was",
3304
- "will",
3305
- "use",
3306
- "using",
3307
- "run",
3308
- "add",
3309
- "fix",
3310
- "make",
3311
- "file",
3312
- "files",
3313
- "code",
3314
- "please",
3315
- "need",
3316
- "want",
3317
- "should",
3318
- "all"
3319
- ]);
3320
- function tokenize(s) {
3321
- const out = /* @__PURE__ */ new Set();
3322
- for (const w of String(s ?? "").toLowerCase().match(/[a-z0-9]+/g) ?? []) {
3323
- if (w.length >= 3 && !STOP.has(w)) out.add(w);
3324
- }
3325
- return out;
3326
- }
3327
- function idfWeights(corpus) {
3328
- const N = corpus.length;
3329
- if (N === 0) return /* @__PURE__ */ new Map();
3330
- const df = /* @__PURE__ */ new Map();
3331
- for (const doc of corpus) for (const t of tokenize(doc)) df.set(t, (df.get(t) ?? 0) + 1);
3332
- const idf = /* @__PURE__ */ new Map();
3333
- for (const [t, n] of df) idf.set(t, Math.log((N + 1) / (n + 1)) + 1);
3334
- return idf;
3335
- }
3336
- function relevanceScore(text, queryTokens, idf) {
3337
- if (queryTokens.size === 0) return 0;
3338
- const t = tokenize(text);
3339
- let score = 0;
3340
- for (const q of queryTokens) if (t.has(q)) score += idf?.get(q) ?? 1;
3341
- return score;
3342
- }
3343
- function topByRelevance(items, query, text, k, corpus) {
3344
- if (!Number.isInteger(k) || k < 1) return { kept: items, rest: [] };
3345
- if (items.length <= k || !query.trim()) return { kept: items, rest: [] };
3346
- const q = tokenize(query);
3347
- if (q.size === 0) return { kept: items.slice(0, k), rest: items.slice(k) };
3348
- const idf = corpus?.length ? idfWeights(corpus) : void 0;
3349
- const scored = items.map((x, i) => ({ i, s: relevanceScore(text(x), q, idf) }));
3350
- scored.sort((a, b) => b.s - a.s || a.i - b.i);
3351
- const keep = new Set(scored.slice(0, k).map((e) => e.i));
3352
- 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(", ")}`);
3353
3326
  }
3354
3327
 
3355
3328
  // src/frontmatter.ts
@@ -3371,7 +3344,6 @@ function grabField(block, key) {
3371
3344
  }
3372
3345
 
3373
3346
  // src/commands.ts
3374
- var MAX_CATALOG = 25;
3375
3347
  function parseFrontmatter(md) {
3376
3348
  const { block, body } = splitFrontmatter(md);
3377
3349
  return { description: grabField(block, "description"), body };
@@ -3428,9 +3400,12 @@ async function scanCommands(fs, dir) {
3428
3400
  async function loadCommands(fs, dir, opts = {}) {
3429
3401
  const commands = await scanCommands(fs, dir);
3430
3402
  if (commands.length === 0) return { commands, catalog: "" };
3431
- const { kept, rest } = topByRelevance(commands, opts.relevanceHint ?? "", (c) => `${c.name} ${c.description}`, opts.max ?? MAX_CATALOG);
3432
- const catalog = "## Slash commands (reusable prompt templates)\n" + kept.map((c) => `- **/${c.name}** \u2014 ${c.description} (\`${c.path}\`)`).join("\n") + (rest.length ? `
3433
- - (${rest.length} more, names only \u2014 call \`SlashCommand\` to run): ${rest.map((c) => c.name).join(", ")}` : "") + "\nTo run one, call the `SlashCommand` tool with its name (and optional `args`); it returns the expanded template to act on.";
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
+ });
3434
3409
  const tool = {
3435
3410
  name: "SlashCommand",
3436
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.",
@@ -3458,7 +3433,6 @@ async function loadCommands(fs, dir, opts = {}) {
3458
3433
  }
3459
3434
 
3460
3435
  // src/skills.ts
3461
- var MAX_CATALOG2 = 25;
3462
3436
  function parseFrontmatter2(md) {
3463
3437
  const { block } = splitFrontmatter(md);
3464
3438
  const b = block || md.slice(0, 600);
@@ -3484,9 +3458,11 @@ async function scanSkills(fs, dir) {
3484
3458
  async function loadSkills(fs, dir, opts = {}) {
3485
3459
  const skills = await scanSkills(fs, dir);
3486
3460
  if (skills.length === 0) return { skills, catalog: "" };
3487
- const { kept, rest } = topByRelevance(skills, opts.relevanceHint ?? "", (s) => `${s.name} ${s.description}`, opts.max ?? MAX_CATALOG2);
3488
- const catalog = "## Skills (load one before acting on a matching task)\n" + kept.map((s) => `- **${s.name}** \u2014 ${s.description} (\`${s.path}\`)`).join("\n") + (rest.length ? `
3489
- - (${rest.length} more, names only \u2014 call \`Skill\` to load): ${rest.map((s) => s.name).join(", ")}` : "") + "\nWhen a task matches a skill, call the `Skill` tool with its name to load its full instructions.";
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
+ });
3490
3466
  const tool = {
3491
3467
  name: "Skill",
3492
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.",
@@ -3511,7 +3487,73 @@ async function loadSkills(fs, dir, opts = {}) {
3511
3487
 
3512
3488
  // src/memory.ts
3513
3489
  init_tools_structured();
3514
- var MAX_INDEX = 25;
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
3515
3557
  var MAX_INDEX_LINES = 200;
3516
3558
  var MAX_INDEX_BYTES = 25e3;
3517
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.';
@@ -3546,10 +3588,18 @@ async function loadMemory(fs, dir, opts = {}) {
3546
3588
  }
3547
3589
  }
3548
3590
  if (!hasContent) return { index: MEMORY_PROMPT, tools };
3549
- const { kept, rest } = topByRelevance(allPointers, opts.relevanceHint ?? "", (l) => l, opts.max ?? MAX_INDEX);
3550
- const restSlugs = rest.map((l) => l.match(/\]\(([^)]+)\.md\)/)?.[1] ?? l.match(/\[([^\]]+)\]/)?.[1] ?? "").filter(Boolean);
3551
- const index = MEMORY_PROMPT + "\n\n## Memory index (persistent context \u2014 recalled across sessions)\n" + (header ? header + "\n" : "") + kept.join("\n") + (restSlugs.length ? `
3552
- - (${restSlugs.length} more learnings, slugs only \u2014 call \`Recall\` if relevant): ${restSlugs.join(", ")}` : "");
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
+ });
3553
3603
  return { index, tools };
3554
3604
  }
3555
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
@@ -3823,6 +3873,25 @@ ${sections.join("\n\n---\n\n")}`;
3823
3873
  }
3824
3874
  return "";
3825
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
+ }
3826
3895
 
3827
3896
  // src/subagent.ts
3828
3897
  init_tools();
@@ -4862,6 +4931,8 @@ var AgentOptions = class {
4862
4931
  * Walks the VFS tree and merges all found files (general → specific, like Claude Code).
4863
4932
  * `true` (default) = auto-discover standard names. `string[]` = custom names. `false` = skip. */
4864
4933
  instructionFiles = true;
4934
+ /** Extra files injected right after the instructions (missing → skipped; over `maxChars` → head + pointer to the full file). */
4935
+ contextFiles;
4865
4936
  /** Host interaction channel (human-in-the-loop). If set: adds the `AskUserQuestion` tool. */
4866
4937
  host;
4867
4938
  /** Add the `AskUserQuestion` tool when a host is present (default true). Set false for an agent that
@@ -5190,7 +5261,7 @@ var Agent = class _Agent {
5190
5261
  * across turns (and the frozen prompt pairs well with prompt caching). Does NOT touch
5191
5262
  * the transcript; `run`/`send` own that.
5192
5263
  */
5193
- async prepare(taskHint) {
5264
+ async prepare() {
5194
5265
  if (this.prepared) return this.systemPromptCache;
5195
5266
  const o = this.options;
5196
5267
  const fs = o.fs;
@@ -5208,8 +5279,12 @@ var Agent = class _Agent {
5208
5279
  const ins = await loadInstructions(fs, names);
5209
5280
  if (ins) systemPrompt += "\n\n" + ins;
5210
5281
  }
5282
+ if (o.contextFiles?.length) {
5283
+ const ctxFiles = await loadContextFiles(fs, o.contextFiles);
5284
+ if (ctxFiles) systemPrompt += "\n\n" + ctxFiles;
5285
+ }
5211
5286
  if (o.memoryDir) {
5212
- const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, { relevanceHint: taskHint, userDir: o.memoryUserDir });
5287
+ const { index, tools: memTools } = await loadMemory(fs, o.memoryDir, { userDir: o.memoryUserDir });
5213
5288
  if (index) systemPrompt += "\n\n" + index;
5214
5289
  tools = [...tools, ...memTools];
5215
5290
  }
@@ -5219,7 +5294,7 @@ var Agent = class _Agent {
5219
5294
  const c = (await scanCommands(fs, commandsDir)).find((x) => x.name === name);
5220
5295
  return c ? { path: c.path, stripFrontmatter: true } : void 0;
5221
5296
  } : void 0;
5222
- const { catalog, tool } = await loadSkills(fs, skillsDir, { relevanceHint: taskHint, sibling });
5297
+ const { catalog, tool } = await loadSkills(fs, skillsDir, { sibling });
5223
5298
  if (catalog) systemPrompt += "\n\n" + catalog;
5224
5299
  if (tool) tools = [...tools, tool];
5225
5300
  }
@@ -5228,7 +5303,7 @@ var Agent = class _Agent {
5228
5303
  const s = (await scanSkills(fs, skillsDir)).find((x) => x.name === name);
5229
5304
  return s ? { path: s.path, stripFrontmatter: false } : void 0;
5230
5305
  } : void 0;
5231
- const { catalog, tool } = await loadCommands(fs, commandsDir, { relevanceHint: taskHint, sibling });
5306
+ const { catalog, tool } = await loadCommands(fs, commandsDir, { sibling });
5232
5307
  if (catalog) systemPrompt += "\n\n" + catalog;
5233
5308
  if (tool) tools = [...tools, tool];
5234
5309
  }
@@ -5287,7 +5362,7 @@ var Agent = class _Agent {
5287
5362
  this.invokedTools.clear();
5288
5363
  this.fabricatedClaims.clear();
5289
5364
  this.lastTask = contentText(task);
5290
- const systemPrompt = await this.prepare(contentText(task));
5365
+ const systemPrompt = await this.prepare();
5291
5366
  const startCtx = await this.fireSessionStart();
5292
5367
  const userContent = await this.applyPromptSubmit(task);
5293
5368
  this.transcript = [
@@ -5323,7 +5398,7 @@ var Agent = class _Agent {
5323
5398
  async send(task) {
5324
5399
  await this.ensureFs();
5325
5400
  this.lastTask = contentText(task);
5326
- const systemPrompt = await this.prepare(contentText(task));
5401
+ const systemPrompt = await this.prepare();
5327
5402
  const startCtx = await this.fireSessionStart();
5328
5403
  const userContent = await this.applyPromptSubmit(task);
5329
5404
  const sys = systemPrompt + (startCtx ? "\n\n" + startCtx : "");
@@ -6147,9 +6222,9 @@ function capOversizedBodies(messages, target, keepRecentImages = 1) {
6147
6222
  return out;
6148
6223
  }
6149
6224
  function elideBody(x, cap) {
6150
- const note = (bytes, imgs2) => `
6225
+ const note = (bytes2, imgs2) => `
6151
6226
 
6152
- [${bytes} 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` : ""}]`;
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` : ""}]`;
6153
6228
  if (!Array.isArray(x.content)) {
6154
6229
  const text = contentText(x.content);
6155
6230
  if (text.length <= cap) return x;
@@ -12085,11 +12160,11 @@ Another agent just implemented the above. Independently check the CURRENT state
12085
12160
  const list = id ? [this.tasks.get(String(id))].filter(Boolean) : [...this.tasks.values()];
12086
12161
  if (!list.length) return id ? `No task '${id}'.` : "No background tasks.";
12087
12162
  const cap = id ? 1500 : 300;
12088
- const clip = (x) => x.length > cap ? `${x.slice(0, cap)}\u2026 (truncated)` : x;
12163
+ const clip2 = (x) => x.length > cap ? `${x.slice(0, cap)}\u2026 (truncated)` : x;
12089
12164
  return list.map((t) => {
12090
12165
  const head = `${t.id} (${t.label}): ${t.status}`;
12091
12166
  const body = (t.status === "done" ? t.spokenText?.trim() || t.result?.trim() : t.status === "error" ? t.result?.trim() : "") ?? "";
12092
- return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${clip(body)}` : head;
12167
+ return body ? `${head}. ${t.status === "error" ? "Error" : "Result"}: ${clip2(body)}` : head;
12093
12168
  }).join("\n");
12094
12169
  }
12095
12170
  };
@@ -13990,6 +14065,7 @@ export {
13990
14065
  CartesiaTTSOptions,
13991
14066
  CommandExecutor2 as CommandExecutor,
13992
14067
  ConsoleHostBridge,
14068
+ DEFAULT_CATALOG_BUDGET,
13993
14069
  DEFAULT_DENY,
13994
14070
  DEFAULT_MAX_DEPTH,
13995
14071
  DEFAULT_MUTATING,
@@ -14080,6 +14156,7 @@ export {
14080
14156
  findGitRoot,
14081
14157
  forComponent,
14082
14158
  forgetFact,
14159
+ formatCatalog,
14083
14160
  formatJobNotice,
14084
14161
  fullAgentOptions,
14085
14162
  getOrCreateWorktree,
@@ -14098,6 +14175,7 @@ export {
14098
14175
  lessonCapture,
14099
14176
  loadAgents,
14100
14177
  loadCommands,
14178
+ loadContextFiles,
14101
14179
  loadInstructions,
14102
14180
  loadMemory,
14103
14181
  loadSkills,