agent.libx.js 0.93.27 → 0.93.29

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
@@ -375,61 +375,65 @@ declare const webFetchTool: AgentTool;
375
375
  declare const webSearchTool: AgentTool;
376
376
 
377
377
  /**
378
- * Artifact blackboard — keep large tool/subagent outputs OUT of the main context, but queryable.
378
+ * Scratch — keep large tool/subagent outputs OUT of the main context, but queryable AS FILES.
379
379
  *
380
380
  * Pattern: many-tool / many-subagent engines bloat context with raw outputs (search results, big
381
381
  * file reads, subagent reports) that are mostly noise once a gist is taken. Here, a tool wrapped with
382
- * `withArtifactCapture` stashes any oversized result in an `ArtifactStore` and returns a compact stub
383
- * (id + preview) to context. When the caller later needs a buried detail, `Ask({question, over})`
384
- * peeks into the full artifact in a side-step a (cheap) model extracts just the answer, so the raw
385
- * blob never re-enters the caller's context. The caller (often a stronger model) synthesizes from the
386
- * tight answer Ask returns. Two-tier division of labour: cheap RETRIEVE/EXTRACT, strong SYNTHESIZE.
382
+ * `Scratch.capture` writes any oversized result to an ephemeral scratch filesystem (a real VFS) and
383
+ * returns a compact stub (path + preview) to context. To recover a buried detail later you do NOT
384
+ * re-bloat contextyou peek with the FILE tools already on hand:
385
+ * Grep/Glob/Read over the scratch dir returns matching lines, not the blob (a free, light peek).
386
+ * Ask({question, over?}) a CHEAP child agent over the scratch FS that greps/reads and returns
387
+ * just the answer; its reasoning tokens never touch the caller's context.
388
+ * Two-tier division of labour: cheap RETRIEVE/EXTRACT, strong caller SYNTHESIZE.
387
389
  *
388
- * Spike, not load-bearing yet segregated module (IoC via options), zero Agent-core changes.
390
+ * The scratch FS is injected (IoC): pass a MemFilesystem for ephemeral, or a disk-backed one under the
391
+ * session dir to persist across resume (CC's lesson: the filesystem is the durable store). Segregated
392
+ * module — zero Agent-core changes.
389
393
  */
390
394
 
391
- interface Artifact {
392
- id: string;
393
- tool: string;
394
- brief: string;
395
- content: string;
396
- bytes: number;
397
- }
398
- /** In-memory store of captured tool outputs. Ids are a monotonic `a1, a2, …` (test-friendly, no RNG). */
399
- declare class ArtifactStore {
400
- private items;
401
- private seq;
402
- put(tool: string, content: string, brief?: string): Artifact;
403
- get(id: string): Artifact | undefined;
404
- list(): Artifact[];
405
- get size(): number;
406
- /** Cheap keyword retrieval over brief+content — rank by how many query terms each artifact contains. */
407
- search(query: string, k?: number): Artifact[];
408
- }
409
- interface CaptureOptions {
395
+ /** Default scratch dir (a path in the injected scratch FS). */
396
+ declare const SCRATCH_DIR = "/scratch";
397
+ interface ScratchOptions {
410
398
  /** Min result length (chars) to capture; smaller results pass through untouched. Default 1500. */
411
399
  threshold?: number;
400
+ /** Dir within the scratch FS to write captures. Default SCRATCH_DIR. */
401
+ dir?: string;
412
402
  /** Chars of the captured output to preview in the stub. Default 320. */
413
403
  previewChars?: number;
414
404
  }
415
405
  /**
416
- * Wrap a tool so any oversized STRING result is stored as an artifact and replaced with a compact
417
- * stub. Non-string results (images/structured) and small results pass through unchanged.
406
+ * Owns an (injected) scratch filesystem + a capture counter. `capture(tool)` wraps a tool so its
407
+ * oversized string results spill to a scratch file and are replaced in context with a stub.
418
408
  */
419
- declare function withArtifactCapture(tool: AgentTool, store: ArtifactStore, opts?: CaptureOptions): AgentTool;
409
+ declare class Scratch {
410
+ fs: IFilesystem;
411
+ options: Required<ScratchOptions>;
412
+ private seq;
413
+ private dirReady?;
414
+ constructor(fs: IFilesystem, options?: ScratchOptions);
415
+ /** Number of captures so far. */
416
+ get count(): number;
417
+ /** Wrap a tool: oversized STRING results spill to a scratch file; everything else passes through. */
418
+ capture(tool: AgentTool): AgentTool;
419
+ /** Wrap many tools at once. */
420
+ captureAll(tools: AgentTool[]): AgentTool[];
421
+ }
420
422
  interface AskOptions {
421
- store: ArtifactStore;
423
+ /** The scratch filesystem to peek into (dedicated VFS holding only scratch files). */
424
+ fs: IFilesystem;
422
425
  ai: ChatLike;
423
- /** Model for the extraction step — intentionally a CHEAP model; the caller synthesizes. */
426
+ /** Model for the peek — intentionally a CHEAP model; the caller synthesizes. */
424
427
  model: string;
425
- /** Max chars of each artifact fed to the extractor (default 12000). */
426
- maxChars?: number;
428
+ /** Step budget for the peek child (default 6). */
429
+ maxSteps?: number;
430
+ /** Scratch dir to search when `over` is omitted. Default SCRATCH_DIR. */
431
+ dir?: string;
427
432
  }
428
433
  /**
429
- * `Ask` — peek into stored artifacts to answer a question, WITHOUT loading the raw data into the
430
- * caller's context. Pass `over` (artifact id(s)) for a targeted peek, or omit to auto-retrieve by
431
- * relevance. Returns only the extracted answer. Built for the two-tier split: a cheap model does the
432
- * retrieve+extract here; the (stronger) caller synthesizes the final answer from what comes back.
434
+ * `Ask` — peek into the scratch FS to answer a question WITHOUT loading raw data into the caller's
435
+ * context. Spawns a cheap child agent (Read/Grep/Glob over scratch) that greps/reads and returns just
436
+ * the answer. Pass `over` (a path) for a targeted peek, or omit to grep-discover.
433
437
  */
434
438
  declare function makeAskTool(o: AskOptions): AgentTool;
435
439
 
@@ -1134,4 +1138,4 @@ declare class CartesiaTTS {
1134
1138
  close(): void;
1135
1139
  }
1136
1140
 
1137
- export { Agent, type AgentDef, AgentOptions, AgentTool, type Artifact, ArtifactStore, type AskOptions, type Attempt, type AudioSink, type AudioSource, type AuthProvider, BodDbFilesystem, type CaptureOptions, CartesiaTTS, CartesiaTTSOptions, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, STT_SAMPLE_RATE, ScriptedHostBridge, type SkillInfo, SonioxSTT, SonioxSTTOptions, type SttLike, TTS_SAMPLE_RATE, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type TtsLike, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceEngine, VoiceEngineOptions, type VoiceState, type WebFetchOptions, type WebSearchOptions, type WorkerTier, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, compileSynthesizedTool, decodeDdgUrl, diskAgentOptions, expandCommand, expandTemplate, forComponent, fullAgentOptions, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchTool, mkdirp, multiEditTool, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, resolveAuth, rollbackTool, sandboxAgentOptions, slugify, tokenize, toolCall, topByRelevance, validateToolCode, webFetchTool, webSearchTool, withArtifactCapture, writeFact, writeTool };
1141
+ export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, type AudioSink, type AudioSource, type AuthProvider, BodDbFilesystem, CartesiaTTS, CartesiaTTSOptions, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, SCRATCH_DIR, STT_SAMPLE_RATE, Scratch, type ScratchOptions, ScriptedHostBridge, type SkillInfo, SonioxSTT, SonioxSTTOptions, type SttLike, TTS_SAMPLE_RATE, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type TtsLike, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, VoiceEngine, VoiceEngineOptions, type VoiceState, type WebFetchOptions, type WebSearchOptions, type WorkerTier, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, compileSynthesizedTool, decodeDdgUrl, diskAgentOptions, expandCommand, expandTemplate, forComponent, fullAgentOptions, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchTool, mkdirp, multiEditTool, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, resolveAuth, rollbackTool, sandboxAgentOptions, slugify, tokenize, toolCall, topByRelevance, validateToolCode, webFetchTool, webSearchTool, writeFact, writeTool };
package/dist/index.js CHANGED
@@ -1998,9 +1998,9 @@ async function loadCommands(fs, dir, opts = {}) {
1998
1998
  properties: { name: { type: "string" }, args: { type: "string", description: "arguments to fill the template" } }
1999
1999
  },
2000
2000
  async run({ name, args }, ctx) {
2001
- const slug = String(name ?? "").replace(/^\//, "");
2002
- const c = commands.find((x) => x.name === slug);
2003
- if (!c) return `Error: no command named '${slug}'. Available: ${commands.map((x) => x.name).join(", ")}`;
2001
+ const slug2 = String(name ?? "").replace(/^\//, "");
2002
+ const c = commands.find((x) => x.name === slug2);
2003
+ if (!c) return `Error: no command named '${slug2}'. Available: ${commands.map((x) => x.name).join(", ")}`;
2004
2004
  return expandCommand(ctx.fs, c, String(args ?? ""));
2005
2005
  }
2006
2006
  };
@@ -2035,9 +2035,9 @@ async function loadMemory(fs, dir, opts = {}) {
2035
2035
  const lines = md.split("\n");
2036
2036
  if (!header) header = lines.filter((l) => !/^\s*-\s*\[.+\]\(.+\.md\)/.test(l)).join("\n").trim();
2037
2037
  for (const l of lines.filter((l2) => /^\s*-\s*\[.+\]\(.+\.md\)/.test(l2))) {
2038
- const slug = l.match(/\]\(([^)]+)\.md\)/)?.[1];
2039
- if (slug && !seenSlugs.has(slug)) {
2040
- seenSlugs.add(slug);
2038
+ const slug2 = l.match(/\]\(([^)]+)\.md\)/)?.[1];
2039
+ if (slug2 && !seenSlugs.has(slug2)) {
2040
+ seenSlugs.add(slug2);
2041
2041
  allPointers.push(l);
2042
2042
  }
2043
2043
  }
@@ -2053,20 +2053,20 @@ function slugify(s, fallback = "note") {
2053
2053
  const base = String(s ?? "").trim().toLowerCase().replace(/\.md$/i, "").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
2054
2054
  return base || fallback;
2055
2055
  }
2056
- async function writeFact(fs, dir, slug, body, opts) {
2056
+ async function writeFact(fs, dir, slug2, body, opts) {
2057
2057
  await mkdirp(fs, dir);
2058
2058
  const content = opts?.type ? `---
2059
2059
  type: ${opts.type}
2060
2060
  ---
2061
2061
 
2062
2062
  ${body}` : body;
2063
- await fs.writeFile(`${dir}/${slug}.md`, content.endsWith("\n") ? content : content + "\n");
2063
+ await fs.writeFile(`${dir}/${slug2}.md`, content.endsWith("\n") ? content : content + "\n");
2064
2064
  const indexPath = `${dir}/MEMORY.md`;
2065
2065
  const idx = await fs.exists(indexPath) ? await fs.readFile(indexPath) : "# Memory Index\n";
2066
2066
  const summary = opts?.description || body.split("\n")[0].slice(0, 80);
2067
- const line = `- [${slug}](${slug}.md) \u2014 ${summary}`;
2067
+ const line = `- [${slug2}](${slug2}.md) \u2014 ${summary}`;
2068
2068
  const lines = idx.split("\n");
2069
- const at = lines.findIndex((l) => l.includes(`(${slug}.md)`));
2069
+ const at = lines.findIndex((l) => l.includes(`(${slug2}.md)`));
2070
2070
  if (at >= 0) {
2071
2071
  if (lines[at] !== line) {
2072
2072
  lines[at] = line;
@@ -2117,8 +2117,8 @@ async function listSlugs(fs, dir, prefix = "") {
2117
2117
  }
2118
2118
  return out;
2119
2119
  }
2120
- async function loadFact(fs, dir, slug) {
2121
- const path = `${dir}/${slug}.md`;
2120
+ async function loadFact(fs, dir, slug2) {
2121
+ const path = `${dir}/${slug2}.md`;
2122
2122
  try {
2123
2123
  const raw = await fs.readFile(path);
2124
2124
  const { body } = splitFrontmatter(raw);
@@ -2127,9 +2127,9 @@ async function loadFact(fs, dir, slug) {
2127
2127
  return null;
2128
2128
  }
2129
2129
  }
2130
- async function loadFactMulti(fs, dirs, slug) {
2130
+ async function loadFactMulti(fs, dirs, slug2) {
2131
2131
  for (const d of dirs) {
2132
- const r = await loadFact(fs, d, slug);
2132
+ const r = await loadFact(fs, d, slug2);
2133
2133
  if (r != null) return r;
2134
2134
  }
2135
2135
  return null;
@@ -2155,9 +2155,9 @@ function recallTool(fs, dirs) {
2155
2155
  pattern: { type: "string", description: 'glob pattern to match against slugs (e.g. "auth*", "*database*")' }
2156
2156
  }
2157
2157
  },
2158
- async run({ slug, slugs, pattern }, ctx) {
2158
+ async run({ slug: slug2, slugs, pattern }, ctx) {
2159
2159
  let targets = [];
2160
- if (slug) targets = [cleanSlug(slug)];
2160
+ if (slug2) targets = [cleanSlug(slug2)];
2161
2161
  else if (Array.isArray(slugs)) targets = slugs.map(cleanSlug).filter(Boolean);
2162
2162
  else if (pattern) {
2163
2163
  const escaped = String(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&");
@@ -2208,19 +2208,19 @@ function memorySearchTool(fs, dirs) {
2208
2208
  matcher = (l) => l.toLowerCase().includes(lower);
2209
2209
  }
2210
2210
  const loaded = [];
2211
- for (const slug of slugs) {
2212
- const body = await loadFactMulti(fs, dirs, slug);
2213
- if (body) loaded.push({ slug, body });
2211
+ for (const slug2 of slugs) {
2212
+ const body = await loadFactMulti(fs, dirs, slug2);
2213
+ if (body) loaded.push({ slug: slug2, body });
2214
2214
  }
2215
2215
  const idf = idfWeights(loaded.map((l) => l.body));
2216
2216
  const qTokens = tokenize(q);
2217
2217
  const hits = [];
2218
- for (const { slug, body } of loaded) {
2218
+ for (const { slug: slug2, body } of loaded) {
2219
2219
  const lines = body.split("\n");
2220
2220
  const matchLine = lines.find(matcher);
2221
2221
  if (!matchLine && !relevanceScore(body, qTokens, idf)) continue;
2222
2222
  const snippet = matchLine?.trim().slice(0, 120) || lines.find((l) => l.trim())?.trim().slice(0, 120) || "";
2223
- hits.push({ slug, snippet, score: relevanceScore(body, qTokens, idf) });
2223
+ hits.push({ slug: slug2, snippet, score: relevanceScore(body, qTokens, idf) });
2224
2224
  }
2225
2225
  if (!hits.length) return "(no matches)";
2226
2226
  hits.sort((a, b) => b.score - a.score);
@@ -2244,11 +2244,11 @@ function rememberTool(fs, dir, memOpts = {}) {
2244
2244
  description: { type: "string", description: "one-line summary for the memory index (\u226480 chars)" }
2245
2245
  }
2246
2246
  },
2247
- async run({ fact, slug, type, description }, ctx) {
2247
+ async run({ fact, slug: slug2, type, description }, ctx) {
2248
2248
  const body = String(fact ?? "").trim();
2249
2249
  if (!body) return `Error: nothing to remember (empty fact).`;
2250
2250
  if (++writes > maxWrites) return `Rate limit: too many memories this session (${maxWrites}). Only persist genuinely durable facts.`;
2251
- const name = slugify(slug || body.split("\n")[0]);
2251
+ const name = slugify(slug2 || body.split("\n")[0]);
2252
2252
  const isGlobal = (type === "user" || type === "feedback") && memOpts.userDir;
2253
2253
  const targetDir = isGlobal ? memOpts.userDir : dir;
2254
2254
  await writeFact(fs, targetDir, name, body, { type, description });
@@ -3568,100 +3568,100 @@ init_tools_structured();
3568
3568
  init_todo();
3569
3569
  init_tools_web();
3570
3570
 
3571
- // src/artifacts.ts
3571
+ // src/scratch.ts
3572
+ init_tools();
3573
+ init_tools_structured();
3572
3574
  init_logging();
3573
- var log5 = forComponent("artifacts");
3574
- var ArtifactStore = class {
3575
- items = /* @__PURE__ */ new Map();
3576
- seq = 0;
3577
- put(tool, content, brief) {
3578
- const id = "a" + ++this.seq;
3579
- const art = { id, tool, brief: brief || content.replace(/\s+/g, " ").trim().slice(0, 80), content, bytes: content.length };
3580
- this.items.set(id, art);
3581
- return art;
3582
- }
3583
- get(id) {
3584
- return this.items.get(id);
3585
- }
3586
- list() {
3587
- return [...this.items.values()];
3588
- }
3589
- get size() {
3590
- return this.items.size;
3591
- }
3592
- /** Cheap keyword retrieval over brief+content — rank by how many query terms each artifact contains. */
3593
- search(query, k = 3) {
3594
- const terms = [...new Set(query.toLowerCase().split(/\W+/).filter((t) => t.length > 2))];
3595
- if (!terms.length) return this.list().slice(-k);
3596
- return this.list().map((a) => {
3597
- const hay = (a.brief + " " + a.content).toLowerCase();
3598
- return { a, score: terms.reduce((s, t) => s + (hay.includes(t) ? 1 : 0), 0) };
3599
- }).filter((x) => x.score > 0).sort((x, y) => y.score - x.score).slice(0, k).map((x) => x.a);
3600
- }
3601
- };
3575
+ var log5 = forComponent("scratch");
3576
+ var SCRATCH_DIR = "/scratch";
3602
3577
  function shortArgs(args) {
3603
3578
  try {
3604
- const s = JSON.stringify(args);
3605
- return s.length > 60 ? s.slice(0, 57) + "\u2026" : s;
3579
+ const s = JSON.stringify(args ?? {});
3580
+ return s.length > 50 ? s.slice(0, 47) + "\u2026" : s;
3606
3581
  } catch {
3607
3582
  return "";
3608
3583
  }
3609
3584
  }
3610
- function withArtifactCapture(tool, store, opts = {}) {
3611
- const threshold = opts.threshold ?? 1500;
3612
- const previewChars = opts.previewChars ?? 320;
3613
- return {
3614
- ...tool,
3615
- run: async (args, ctx) => {
3616
- const raw = await tool.run(args, ctx);
3617
- if (typeof raw !== "string" || raw.length <= threshold) return raw;
3618
- const art = store.put(tool.name, raw, `${tool.name}(${shortArgs(args)})`);
3619
- const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
3620
- return `[artifact ${art.id} \xB7 ${tool.name} \xB7 ${art.bytes} bytes \u2014 full output stored out of context to keep it clean]
3585
+ var slug = (s) => s.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 32) || "out";
3586
+ var Scratch = class {
3587
+ constructor(fs, options = {}) {
3588
+ this.fs = fs;
3589
+ this.options = { threshold: 1500, dir: SCRATCH_DIR, previewChars: 320, ...options };
3590
+ }
3591
+ fs;
3592
+ options;
3593
+ seq = 0;
3594
+ dirReady;
3595
+ /** Number of captures so far. */
3596
+ get count() {
3597
+ return this.seq;
3598
+ }
3599
+ /** Wrap a tool: oversized STRING results spill to a scratch file; everything else passes through. */
3600
+ capture(tool) {
3601
+ const { threshold, dir, previewChars } = this.options;
3602
+ return {
3603
+ ...tool,
3604
+ run: async (args, ctx) => {
3605
+ const raw = await tool.run(args, ctx);
3606
+ if (typeof raw !== "string" || raw.length <= threshold) return raw;
3607
+ const id = "a" + ++this.seq;
3608
+ const path = `${dir}/${id}-${slug(tool.name)}.txt`;
3609
+ const header = `# ${tool.name}(${shortArgs(args)}) \u2014 ${raw.length} bytes
3610
+ `;
3611
+ try {
3612
+ await (this.dirReady ??= mkdirp(this.fs, dir));
3613
+ await this.fs.writeFile(path, header + raw);
3614
+ } catch (e) {
3615
+ log5.debug("scratch write failed; returning raw", e);
3616
+ return raw;
3617
+ }
3618
+ const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
3619
+ return `[scratch ${path} \xB7 ${tool.name} \xB7 ${raw.length} bytes \u2014 full output saved out of context to keep it clean]
3621
3620
  preview: ${preview}\u2026
3622
- To pull a specific detail from it, call Ask({ question: "\u2026", over: "${art.id}" }) \u2014 do NOT guess at what the preview cuts off.`;
3623
- }
3624
- };
3625
- }
3626
- var ASK_SYSTEM = "You are a retrieval-extraction step. Given one or more ARTIFACTS (raw outputs from earlier tools) and a QUESTION, find and return exactly the information that answers the question \u2014 quote values/facts verbatim from the artifacts. Do NOT add analysis, opinions, or anything not grounded in the artifacts. If the answer is not present, say so plainly. Be concise.";
3621
+ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026", over: "${path}" }). Do NOT guess at what the preview cuts off.`;
3622
+ }
3623
+ };
3624
+ }
3625
+ /** Wrap many tools at once. */
3626
+ captureAll(tools) {
3627
+ return tools.map((t) => this.capture(t));
3628
+ }
3629
+ };
3630
+ var ASK_PROMPT = "You are a retrieval-extraction step with Read, Grep and Glob over a scratch filesystem holding raw outputs from earlier tools. Find the information that answers the question and return it concisely, quoting values/facts verbatim. Do NOT add analysis or anything not grounded in the files. If the answer is not present, say so plainly.";
3627
3631
  function makeAskTool(o) {
3628
- const maxChars = o.maxChars ?? 12e3;
3632
+ const dir = o.dir ?? SCRATCH_DIR;
3629
3633
  return {
3630
3634
  name: "Ask",
3631
- description: 'Answer a question by peeking into stored artifacts \u2014 large earlier outputs (web search, big file reads, subagent reports) kept out of your context. Pass `over` with an artifact id like "a3" (or several, comma-separated) for a targeted lookup, or omit it to auto-find relevant artifacts. Returns only the extracted answer; the full data never enters your context.',
3635
+ description: "Answer a question by peeking into the scratch files \u2014 large earlier outputs (web search, big reads, subagent reports) kept out of your context. Pass `over` with a scratch path for a targeted lookup, or omit it to search the scratch dir. Returns only the extracted answer; the full data never enters your context.",
3632
3636
  parameters: {
3633
3637
  type: "object",
3634
3638
  required: ["question"],
3635
3639
  properties: {
3636
- question: { type: "string", description: "what you need from the artifact(s)" },
3637
- over: { type: "string", description: 'artifact id(s) to read, e.g. "a3" or "a1,a4"; omit to auto-retrieve' }
3640
+ question: { type: "string", description: "what you need from the scratch files" },
3641
+ over: { type: "string", description: 'scratch path to read, e.g. "/scratch/a3-websearch.txt"; omit to search' }
3638
3642
  }
3639
3643
  },
3640
3644
  async run({ question, over }) {
3641
3645
  const q = String(question ?? "").trim();
3642
3646
  if (!q) return "Error: empty question";
3643
- const arts = over ? String(over).split(/[,\s]+/).filter(Boolean).map((id) => o.store.get(id)).filter((a) => !!a) : o.store.search(q);
3644
- if (!arts.length) return over ? `Error: no artifact matching '${over}' (have: ${o.store.list().map((a) => a.id).join(", ") || "none"})` : "(no relevant artifacts found)";
3645
- const corpus = arts.map((a) => `### artifact ${a.id} (${a.tool})
3646
- ${a.content.slice(0, maxChars)}`).join("\n\n");
3647
+ const child = new Agent({
3648
+ ai: o.ai,
3649
+ model: o.model,
3650
+ fs: o.fs,
3651
+ tools: toolsByName(["Read", "Grep", "Glob"]),
3652
+ maxSteps: o.maxSteps ?? 6,
3653
+ systemPrompt: ASK_PROMPT
3654
+ });
3655
+ const hint = over ? `Start by reading: ${over}.` : `Grep/Glob ${dir} to find the relevant file(s) first.`;
3647
3656
  try {
3648
- const res = await o.ai.chat({
3649
- model: o.model,
3650
- messages: [
3651
- { role: "system", content: ASK_SYSTEM },
3652
- { role: "user", content: `ARTIFACTS:
3653
- ${corpus}
3654
-
3655
- QUESTION: ${q}` }
3656
- ]
3657
- });
3658
- const answer = (res?.content ?? "").trim();
3659
- return answer ? `${answer}
3657
+ const res = await child.run(`${hint}
3660
3658
 
3661
- (from ${arts.map((a) => a.id).join(", ")})` : "(no answer found in artifacts)";
3659
+ Question: ${q}`);
3660
+ const answer = (res.text ?? "").trim();
3661
+ return answer || "(no answer found in scratch)";
3662
3662
  } catch (e) {
3663
- log5.debug("Ask extraction failed", e);
3664
- return `Error querying artifacts: ${e?.message ?? e}`;
3663
+ log5.debug("Ask peek failed", e);
3664
+ return `Error querying scratch: ${e?.message ?? e}`;
3665
3665
  }
3666
3666
  }
3667
3667
  };
@@ -3724,15 +3724,15 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
3724
3724
  const m = text.match(/LESSON:\s*(.+)/i);
3725
3725
  const lesson = m?.[1]?.trim().slice(0, 200) ?? "";
3726
3726
  if (!lesson || /^none\b/i.test(lesson)) return null;
3727
- const slug = ("lesson-" + slugify(lesson)).slice(0, 56);
3727
+ const slug2 = ("lesson-" + slugify(lesson)).slice(0, 56);
3728
3728
  try {
3729
- await writeFact(o.fs, o.dir, slug, lesson);
3729
+ await writeFact(o.fs, o.dir, slug2, lesson);
3730
3730
  } catch (e) {
3731
3731
  log7.warn(`could not persist lesson: ${e?.message ?? e}`);
3732
3732
  return null;
3733
3733
  }
3734
- log7.debug(`reflection persisted ${slug}`);
3735
- return slug;
3734
+ log7.debug(`reflection persisted ${slug2}`);
3735
+ return slug2;
3736
3736
  }
3737
3737
  function digestRun(messages, maxChars) {
3738
3738
  const lines = [];
@@ -5233,7 +5233,6 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
5233
5233
  export {
5234
5234
  Agent,
5235
5235
  AgentOptions,
5236
- ArtifactStore,
5237
5236
  BodDbFilesystem,
5238
5237
  CartesiaTTS,
5239
5238
  CartesiaTTSOptions,
@@ -5257,8 +5256,10 @@ export {
5257
5256
  PermissionPolicy,
5258
5257
  RecordingHooks,
5259
5258
  RecordingLifecycle,
5259
+ SCRATCH_DIR,
5260
5260
  STT_SAMPLE_RATE,
5261
5261
  SandboxJobRegistry,
5262
+ Scratch,
5262
5263
  ScriptedHostBridge,
5263
5264
  SonioxSTT,
5264
5265
  SonioxSTTOptions,
@@ -5335,7 +5336,6 @@ export {
5335
5336
  validateToolCode,
5336
5337
  webFetchTool,
5337
5338
  webSearchTool,
5338
- withArtifactCapture,
5339
5339
  writeFact,
5340
5340
  writeTool
5341
5341
  };