agent.libx.js 0.93.30 → 0.93.32

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 { a as AgentOptions, H as Hooks, h as RunResult, A as Agent } from './Agent-kWrJvtZM.js';
2
- export { C as ChatFragment, D as DEFAULT_MUTATING, b as Decision, P as PermissionOptions, c as PermissionPolicy, d as PermissionRule, e as PreToolUseDecision, R as ReasoningEffort, f as RecordingHooks, g as RecordingLifecycle, T as ToolUse, i as ToolUseMeta, j as composeHooks, p as planMode, r as reasoningToChatFragment } from './Agent-kWrJvtZM.js';
1
+ import { a as AgentOptions, H as Hooks, h as RunResult, A as Agent } from './Agent-uWtu_WFY.js';
2
+ export { C as ChatFragment, D as DEFAULT_MUTATING, b as Decision, P as PermissionOptions, c as PermissionPolicy, d as PermissionRule, e as PreToolUseDecision, R as ReasoningEffort, f as RecordingHooks, g as RecordingLifecycle, T as ToolUse, i as ToolUseMeta, j as composeHooks, p as planMode, r as reasoningToChatFragment } from './Agent-uWtu_WFY.js';
3
3
  import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
4
4
  export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
5
5
  import { BodDB } from '@bod.ee/db';
@@ -418,6 +418,16 @@ declare class Scratch {
418
418
  capture(tool: AgentTool): AgentTool;
419
419
  /** Wrap many tools at once. */
420
420
  captureAll(tools: AgentTool[]): AgentTool[];
421
+ /**
422
+ * Spill an oversized tool result to a scratch file and return PAGE 1 + a recoverable, paginated stub.
423
+ * Drop-in for `Agent.capToolResult`: the agent sees usable content immediately and knows how to get
424
+ * the rest (refine the query, Read the file in pages with offset/limit, or Ask to extract specifics).
425
+ * Lossless — unlike a plain crop, the full output stays available on the scratch FS.
426
+ */
427
+ spill(full: string, info: {
428
+ tool: string;
429
+ args: any;
430
+ }, pageBytes?: number): Promise<string>;
421
431
  }
422
432
  interface AskOptions {
423
433
  /** The scratch filesystem to peek into (dedicated VFS holding only scratch files). */
package/dist/index.js CHANGED
@@ -2688,6 +2688,14 @@ var AgentOptions = class {
2688
2688
  /** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
2689
2689
  * sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
2690
2690
  maxContextTokens = 0;
2691
+ /** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
2692
+ * a marker telling the model it was cropped (refine the query, or page further). Guards against one
2693
+ * Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
2694
+ maxToolResultBytes = 6e4;
2695
+ /** Hook to handle an oversized tool result instead of the default lossy crop: receives the FULL output
2696
+ * and returns the (cropped) string to put in context — e.g. spill to scratch and return a recoverable,
2697
+ * paginated stub. Called only when a result exceeds `maxToolResultBytes`. */
2698
+ capToolResult;
2691
2699
  /** VFS dir(s) of skills (`<dir>/<id>/SKILL.md`). If set: inject a catalog + add the `Skill` tool. Multiple dirs are merged (first wins on name collisions). */
2692
2700
  skillsDir;
2693
2701
  /** VFS dir(s) of slash-command templates (`<dir>/<name>.md`). If set: inject a catalog + add the `SlashCommand` tool. Multiple dirs are merged (first wins). */
@@ -3103,6 +3111,11 @@ var Agent = class _Agent {
3103
3111
  this.ctx.emit = void 0;
3104
3112
  }
3105
3113
  if (!threw) result = await this.maybeAutoTest(tc.function.name, result);
3114
+ const cap = this.options.maxToolResultBytes ?? 0;
3115
+ if (!threw && cap > 0 && result.length > cap) {
3116
+ const info = { tool: tc.function.name, args };
3117
+ result = this.options.capToolResult ? await this.options.capToolResult(result, info) : cropResult(result, cap);
3118
+ }
3106
3119
  await hooks?.postToolUse?.(call, result, meta);
3107
3120
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: result, isError: threw });
3108
3121
  if (images?.length) {
@@ -3156,6 +3169,15 @@ function estimateTokens(m) {
3156
3169
  for (const x of m) chars += contentText(x.content).length + (x.tool_calls ? JSON.stringify(x.tool_calls).length : 0);
3157
3170
  return Math.ceil(chars / 4);
3158
3171
  }
3172
+ function cropResult(result, cap) {
3173
+ const head = result.slice(0, cap);
3174
+ const nl = head.lastIndexOf("\n");
3175
+ const page = nl > cap * 0.5 ? head.slice(0, nl) : head;
3176
+ const omitted = result.length - page.length;
3177
+ return `${page}
3178
+
3179
+ [output cropped \u2014 showing ${page.length} of ${result.length} bytes; ${omitted} omitted. This is page 1. Refine your query/command to narrow it, or call the tool again with a tighter scope to see more.]`;
3180
+ }
3159
3181
  function stubOldToolResults(messages, keep) {
3160
3182
  const meta = /* @__PURE__ */ new Map();
3161
3183
  for (const msg of messages)
@@ -3633,6 +3655,34 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
3633
3655
  captureAll(tools) {
3634
3656
  return tools.map((t) => this.capture(t));
3635
3657
  }
3658
+ /**
3659
+ * Spill an oversized tool result to a scratch file and return PAGE 1 + a recoverable, paginated stub.
3660
+ * Drop-in for `Agent.capToolResult`: the agent sees usable content immediately and knows how to get
3661
+ * the rest (refine the query, Read the file in pages with offset/limit, or Ask to extract specifics).
3662
+ * Lossless — unlike a plain crop, the full output stays available on the scratch FS.
3663
+ */
3664
+ async spill(full, info, pageBytes = 8e3) {
3665
+ const { dir } = this.options;
3666
+ const id = "a" + ++this.seq;
3667
+ const path = `${dir}/${id}-${slug(info.tool)}.txt`;
3668
+ const header = `# ${info.tool}(${shortArgs(info.args)}) \u2014 ${full.length} bytes
3669
+ `;
3670
+ try {
3671
+ await (this.dirReady ??= mkdirp(this.fs, dir));
3672
+ await this.fs.writeFile(path, header + full);
3673
+ } catch (e) {
3674
+ log5.debug("scratch spill failed; cropping lossy", e);
3675
+ return full.slice(0, pageBytes) + `
3676
+
3677
+ [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
3678
+ }
3679
+ const head = full.slice(0, pageBytes);
3680
+ const nl = head.lastIndexOf("\n");
3681
+ const page = nl > pageBytes * 0.5 ? head.slice(0, nl) : head;
3682
+ return `${page}
3683
+
3684
+ [output cropped \u2014 page 1 (${page.length} of ${full.length} bytes). Full output saved to ${path}. To see more: refine your query/command to narrow it, or Read ${path} with offset/limit to page through it, or Ask({ question: "\u2026", over: "${path}" }) to extract specifics.]`;
3685
+ }
3636
3686
  };
3637
3687
  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.";
3638
3688
  function makeAskTool(o) {
@@ -3766,7 +3816,10 @@ var DuplexAgentOptions = class {
3766
3816
  ai;
3767
3817
  /** The WORKER's filesystem (act + think). If omitted the worker keeps Agent's jailed-disk-at-cwd default. */
3768
3818
  fs;
3769
- reflexModel = "groq/openai/gpt-oss-20b";
3819
+ // The reflex IS the voice. 120b (not 20b) for channel discipline + instruction-following: the 20b
3820
+ // mislabels gpt-oss harmony channels under load, leaking raw analysis into the spoken `final` channel
3821
+ // (and misfiring Hold). 120b is the same price tier (~$0.15/$0.60) — the quality/cost trade is free.
3822
+ reflexModel = "groq/openai/gpt-oss-120b";
3770
3823
  actModel = "anthropic/claude-sonnet-4-6";
3771
3824
  /** Premium reasoning model. Set to `false` to disable the Think tier entirely. */
3772
3825
  thinkModel = "anthropic/claude-opus-4-8";
@@ -3853,7 +3906,12 @@ var DuplexAgent = class {
3853
3906
  const canSearch = workerToolNames.some((n) => /WebSearch/i.test(n));
3854
3907
  const canFetch = workerToolNames.some((n) => /WebFetch/i.test(n));
3855
3908
  const workerWeb = canSearch ? `, and it CAN search the web and read web pages \u2014 so when the user gives you something specific to look up ("search for X", "find me\u2026", "what's the latest on\u2026"), route it to Act. But a bare capability QUESTION like "can you search the web?" just gets a short spoken "yes, I can" \u2014 do NOT dispatch and NEVER invent a query the user did not give you` : canFetch ? ", and it can fetch a specific web page URL (but cannot search the web)" : "";
3856
- const prompt = VOICE_SYSTEM_PROMPT.replace("{{MEMORY_SLOT}}", memSlot).replace("{{THINK_SLOT}}", thinkSlot).replace("{{WORKER_WEB}}", workerWeb) + (o.voiceStyle === "conversational" ? "\n" + VOICE_STYLE_CONVERSATIONAL : "") + `
3909
+ const mcpNames = [
3910
+ ...Object.keys(o.actOptions?.providerOptions?.mcpServers ?? {}),
3911
+ ...new Set(workerToolNames.filter((n) => n.startsWith("mcp__")).map((n) => n.slice(5).split("__")[0]))
3912
+ ];
3913
+ const workerMcp = mcpNames.length ? `, and it can use these MCP servers: ${[...new Set(mcpNames)].join(", ")}` + (mcpNames.some((n) => /browser/i.test(n)) ? ' \u2014 including driving a REAL browser (open tabs, navigate, click, screenshot), so answer "yes" if asked whether you can control/drive a browser and route an actual browse to Act' : "") : "";
3914
+ const prompt = VOICE_SYSTEM_PROMPT.replace("{{MEMORY_SLOT}}", memSlot).replace("{{THINK_SLOT}}", thinkSlot).replace("{{WORKER_WEB}}", workerWeb + workerMcp) + (o.voiceStyle === "conversational" ? "\n" + VOICE_STYLE_CONVERSATIONAL : "") + `
3857
3915
  Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
3858
3916
  const tools = [
3859
3917
  ...o.reflexOptions?.tools ?? [],
@@ -4288,8 +4346,10 @@ Another agent just implemented the above. Independently check the CURRENT state
4288
4346
  case "capabilities": {
4289
4347
  const actTools = this.options.actOptions?.tools ?? [];
4290
4348
  const names = actTools.map((t) => t.name);
4349
+ const mcpServers = Object.keys(this.options.actOptions?.providerOptions?.mcpServers ?? {});
4350
+ const mcpNote = mcpServers.length ? ` Plus MCP servers your worker can use: ${mcpServers.join(", ")} (e.g. browser-bridge \u2192 drive a real browser: open tabs, navigate, click, screenshot).` : "";
4291
4351
  if (!names.length)
4292
- return "Your worker uses Act's default local toolset (reading/editing files, running shell commands). No extra tools (e.g. web/internet) are configured; if a request is not a basic file or shell operation, assume you can't do it and say so.";
4352
+ return "Your worker uses Act's default local toolset (reading/editing files, running shell commands). No extra tools (e.g. web/internet) are configured; if a request is not a basic file or shell operation, assume you can't do it and say so." + mcpNote;
4293
4353
  const hasFetch = names.some((n) => /WebFetch/i.test(n));
4294
4354
  const hasBrowser = names.some((n) => /browser.*(navigate|click|page|type)/i.test(n));
4295
4355
  const hasSearch = names.some((n) => /(^|_)WebSearch$|search/i.test(n) && !/WebFetch|browser/i.test(n));
@@ -4298,7 +4358,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4298
4358
  if (hasBrowser) notes.push("The browser tools drive a real browser: you CAN open a site and, if needed, navigate to a search engine and search there \u2014 but it is manual and takes a moment, not an instant lookup.");
4299
4359
  else if (!hasSearch && hasFetch) notes.push('You have no general web-search tool, so for an instant "search the web" you can only fetch a URL they provide.');
4300
4360
  const webNote = notes.length ? " NOTE: " + notes.join(" ") : "";
4301
- return `Tools your background worker (Act) can actually use: ${names.join(", ")}. Read each name literally and match the request to a SPECIFIC tool; if none fits, you do NOT have that ability \u2014 say so honestly.` + webNote;
4361
+ return `Tools your background worker (Act) can actually use: ${names.join(", ")}. Read each name literally and match the request to a SPECIFIC tool; if none fits, you do NOT have that ability \u2014 say so honestly.` + webNote + mcpNote;
4302
4362
  }
4303
4363
  case "time":
4304
4364
  return (/* @__PURE__ */ new Date()).toString();