agent.libx.js 0.93.29 → 0.93.31
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/cli/cli.ts +100 -33
- package/dist/{Agent-kWrJvtZM.d.ts → Agent-uWtu_WFY.d.ts} +11 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +373 -108
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -3
- package/dist/index.js +86 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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-
|
|
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-
|
|
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';
|
|
@@ -314,7 +314,7 @@ declare const grepTool: AgentTool;
|
|
|
314
314
|
* Compact map of a VFS — code signatures and/or doc outlines. Edge-safe (pure IFilesystem walk).
|
|
315
315
|
* `mode`: "code" (default) = top-level signatures; "docs" = heading outlines; "all" = both.
|
|
316
316
|
*/
|
|
317
|
-
declare function repoIndex(fs: IFilesystem, glob?: string, mode?: 'code' | 'docs' | 'all'): Promise<string>;
|
|
317
|
+
declare function repoIndex(fs: IFilesystem, glob?: string, mode?: 'code' | 'docs' | 'all', signal?: AbortSignal): Promise<string>;
|
|
318
318
|
/** Compact map of the codebase or document workspace — orient in ONE call, not many. */
|
|
319
319
|
declare const repoMapTool: AgentTool;
|
|
320
320
|
/** Create or overwrite a file, creating parent directories as needed (mkdir -p). */
|
|
@@ -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). */
|
|
@@ -683,6 +693,11 @@ declare class DuplexAgentOptions {
|
|
|
683
693
|
actModel: string;
|
|
684
694
|
/** Premium reasoning model. Set to `false` to disable the Think tier entirely. */
|
|
685
695
|
thinkModel: string | false;
|
|
696
|
+
/** Per-worker providerOptions, derived from the worker's actual model at spawn time (IoC — keeps duplex
|
|
697
|
+
* provider-agnostic). Workers override the reflex/main model, so provider-specific options (e.g. cursor's
|
|
698
|
+
* cwd/cursorSession) must be recomputed for the worker's model, never inherited from the main template —
|
|
699
|
+
* leaking cursor options to an anthropic worker is a hard 400. Returns undefined → no providerOptions. */
|
|
700
|
+
providerOptionsFor?: (model: string) => Record<string, unknown> | undefined;
|
|
686
701
|
/** Escape hatches merged over the derived per-agent options. */
|
|
687
702
|
reflexOptions?: Partial<AgentOptions>;
|
|
688
703
|
actOptions?: Partial<AgentOptions>;
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,10 @@ var init_redact = __esm({
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
// src/tools.structured.ts
|
|
28
|
-
|
|
28
|
+
function ckAbort(signal) {
|
|
29
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
30
|
+
}
|
|
31
|
+
async function walkFiles(fs, dir, signal, out = []) {
|
|
29
32
|
let entries;
|
|
30
33
|
try {
|
|
31
34
|
entries = await fs.readDir(dir);
|
|
@@ -33,8 +36,9 @@ async function walkFiles(fs, dir, out = []) {
|
|
|
33
36
|
return out;
|
|
34
37
|
}
|
|
35
38
|
for (const name of entries.sort()) {
|
|
39
|
+
ckAbort(signal);
|
|
36
40
|
const p = dir === "/" ? `/${name}` : `${dir}/${name}`;
|
|
37
|
-
if (await fs.isDirectory(p)) await walkFiles(fs, p, out);
|
|
41
|
+
if (await fs.isDirectory(p)) await walkFiles(fs, p, signal, out);
|
|
38
42
|
else out.push(p);
|
|
39
43
|
}
|
|
40
44
|
return out;
|
|
@@ -91,13 +95,14 @@ function signaturesOf(content, cap = 40) {
|
|
|
91
95
|
}
|
|
92
96
|
return out;
|
|
93
97
|
}
|
|
94
|
-
async function repoIndex(fs, glob, mode = "code") {
|
|
98
|
+
async function repoIndex(fs, glob, mode = "code", signal) {
|
|
95
99
|
const scope = glob ? anchoredGlob(fs, String(glob)) : null;
|
|
96
100
|
const filter = mode === "code" ? isCode : mode === "docs" ? isDoc : (p) => isCode(p) || isDoc(p);
|
|
97
|
-
const files = (await walkFiles(fs, fsCwd(fs))).filter((p) => scope ? scope.test(p) : filter(p));
|
|
101
|
+
const files = (await walkFiles(fs, fsCwd(fs), signal)).filter((p) => scope ? scope.test(p) : filter(p));
|
|
98
102
|
const blocks = [];
|
|
99
103
|
let shown = 0;
|
|
100
104
|
for (const path of files) {
|
|
105
|
+
ckAbort(signal);
|
|
101
106
|
let content;
|
|
102
107
|
try {
|
|
103
108
|
content = await fs.readFile(path);
|
|
@@ -217,7 +222,7 @@ var init_tools_structured = __esm({
|
|
|
217
222
|
const include = pats.filter((p) => !p.startsWith("!")).map((p) => anchoredGlob(ctx.fs, p));
|
|
218
223
|
const exclude = pats.filter((p) => p.startsWith("!")).map((p) => anchoredGlob(ctx.fs, p.slice(1)));
|
|
219
224
|
const includes = include.length ? include : [anchoredGlob(ctx.fs, "**")];
|
|
220
|
-
const hits = (await walkFiles(ctx.fs, fsCwd(ctx.fs))).filter(
|
|
225
|
+
const hits = (await walkFiles(ctx.fs, fsCwd(ctx.fs), ctx.signal)).filter(
|
|
221
226
|
(p) => includes.some((re) => re.test(p)) && !exclude.some((re) => re.test(p))
|
|
222
227
|
);
|
|
223
228
|
return hits.length ? hits.join("\n") : "(no matches)";
|
|
@@ -244,11 +249,12 @@ var init_tools_structured = __esm({
|
|
|
244
249
|
throw new Error(`invalid regex: ${String(e)}`);
|
|
245
250
|
}
|
|
246
251
|
const scope = glob ? anchoredGlob(ctx.fs, String(glob)) : null;
|
|
247
|
-
const files = (await walkFiles(ctx.fs, fsCwd(ctx.fs))).filter((p) => !scope || scope.test(p));
|
|
252
|
+
const files = (await walkFiles(ctx.fs, fsCwd(ctx.fs), ctx.signal)).filter((p) => !scope || scope.test(p));
|
|
248
253
|
const ctxN = Math.max(0, Number(context ?? 0));
|
|
249
254
|
const out = [];
|
|
250
255
|
const matched = [];
|
|
251
256
|
for (const path of files) {
|
|
257
|
+
ckAbort(ctx.signal);
|
|
252
258
|
let content;
|
|
253
259
|
try {
|
|
254
260
|
content = await ctx.fs.readFile(path);
|
|
@@ -284,7 +290,7 @@ var init_tools_structured = __esm({
|
|
|
284
290
|
scope: { type: "string", enum: ["code", "docs", "all"], description: 'what to map: "code" (default), "docs", or "all"' }
|
|
285
291
|
}
|
|
286
292
|
},
|
|
287
|
-
run: ({ glob, scope }, ctx) => repoIndex(ctx.fs, glob, scope || "code")
|
|
293
|
+
run: ({ glob, scope }, ctx) => repoIndex(ctx.fs, glob, scope || "code", ctx.signal)
|
|
288
294
|
};
|
|
289
295
|
writeTool = {
|
|
290
296
|
name: "Write",
|
|
@@ -2682,6 +2688,14 @@ var AgentOptions = class {
|
|
|
2682
2688
|
/** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
|
|
2683
2689
|
* sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
|
|
2684
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;
|
|
2685
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). */
|
|
2686
2700
|
skillsDir;
|
|
2687
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). */
|
|
@@ -3004,6 +3018,7 @@ var Agent = class _Agent {
|
|
|
3004
3018
|
toolCallsTotal += toolCalls.length;
|
|
3005
3019
|
if (o.maxToolCalls && toolCallsTotal > o.maxToolCalls) return kill("max_tool_calls");
|
|
3006
3020
|
for (const tc of toolCalls) {
|
|
3021
|
+
if (o.signal?.aborted) return kill("aborted");
|
|
3007
3022
|
const raw = await this.dispatch(tc);
|
|
3008
3023
|
let content;
|
|
3009
3024
|
if (typeof raw === "string") {
|
|
@@ -3096,6 +3111,11 @@ var Agent = class _Agent {
|
|
|
3096
3111
|
this.ctx.emit = void 0;
|
|
3097
3112
|
}
|
|
3098
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
|
+
}
|
|
3099
3119
|
await hooks?.postToolUse?.(call, result, meta);
|
|
3100
3120
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: result, isError: threw });
|
|
3101
3121
|
if (images?.length) {
|
|
@@ -3149,6 +3169,15 @@ function estimateTokens(m) {
|
|
|
3149
3169
|
for (const x of m) chars += contentText(x.content).length + (x.tool_calls ? JSON.stringify(x.tool_calls).length : 0);
|
|
3150
3170
|
return Math.ceil(chars / 4);
|
|
3151
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
|
+
}
|
|
3152
3181
|
function stubOldToolResults(messages, keep) {
|
|
3153
3182
|
const meta = /* @__PURE__ */ new Map();
|
|
3154
3183
|
for (const msg of messages)
|
|
@@ -3626,6 +3655,34 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
3626
3655
|
captureAll(tools) {
|
|
3627
3656
|
return tools.map((t) => this.capture(t));
|
|
3628
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
|
+
}
|
|
3629
3686
|
};
|
|
3630
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.";
|
|
3631
3688
|
function makeAskTool(o) {
|
|
@@ -3759,10 +3816,18 @@ var DuplexAgentOptions = class {
|
|
|
3759
3816
|
ai;
|
|
3760
3817
|
/** The WORKER's filesystem (act + think). If omitted the worker keeps Agent's jailed-disk-at-cwd default. */
|
|
3761
3818
|
fs;
|
|
3762
|
-
|
|
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";
|
|
3763
3823
|
actModel = "anthropic/claude-sonnet-4-6";
|
|
3764
3824
|
/** Premium reasoning model. Set to `false` to disable the Think tier entirely. */
|
|
3765
3825
|
thinkModel = "anthropic/claude-opus-4-8";
|
|
3826
|
+
/** Per-worker providerOptions, derived from the worker's actual model at spawn time (IoC — keeps duplex
|
|
3827
|
+
* provider-agnostic). Workers override the reflex/main model, so provider-specific options (e.g. cursor's
|
|
3828
|
+
* cwd/cursorSession) must be recomputed for the worker's model, never inherited from the main template —
|
|
3829
|
+
* leaking cursor options to an anthropic worker is a hard 400. Returns undefined → no providerOptions. */
|
|
3830
|
+
providerOptionsFor;
|
|
3766
3831
|
/** Escape hatches merged over the derived per-agent options. */
|
|
3767
3832
|
reflexOptions;
|
|
3768
3833
|
actOptions;
|
|
@@ -3841,7 +3906,12 @@ var DuplexAgent = class {
|
|
|
3841
3906
|
const canSearch = workerToolNames.some((n) => /WebSearch/i.test(n));
|
|
3842
3907
|
const canFetch = workerToolNames.some((n) => /WebFetch/i.test(n));
|
|
3843
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)" : "";
|
|
3844
|
-
const
|
|
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 : "") + `
|
|
3845
3915
|
Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
3846
3916
|
const tools = [
|
|
3847
3917
|
...o.reflexOptions?.tools ?? [],
|
|
@@ -4025,6 +4095,9 @@ ${recent}` : brief) + verify;
|
|
|
4025
4095
|
model: tierModel,
|
|
4026
4096
|
...tier === "think" ? { reasoning: tierOpts?.reasoning ?? "high" } : {},
|
|
4027
4097
|
...tierOpts,
|
|
4098
|
+
// Recompute providerOptions for THIS worker's model (after tierOpts so it wins over any inherited
|
|
4099
|
+
// main-template value) — prevents cursor-only cwd/cursorSession leaking onto an anthropic worker.
|
|
4100
|
+
providerOptions: o.providerOptionsFor?.(tierModel),
|
|
4028
4101
|
...workerHost ? { host: workerHost } : {},
|
|
4029
4102
|
...hooks ? { hooks } : {},
|
|
4030
4103
|
signal: controller.signal
|
|
@@ -4273,8 +4346,10 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4273
4346
|
case "capabilities": {
|
|
4274
4347
|
const actTools = this.options.actOptions?.tools ?? [];
|
|
4275
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).` : "";
|
|
4276
4351
|
if (!names.length)
|
|
4277
|
-
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;
|
|
4278
4353
|
const hasFetch = names.some((n) => /WebFetch/i.test(n));
|
|
4279
4354
|
const hasBrowser = names.some((n) => /browser.*(navigate|click|page|type)/i.test(n));
|
|
4280
4355
|
const hasSearch = names.some((n) => /(^|_)WebSearch$|search/i.test(n) && !/WebFetch|browser/i.test(n));
|
|
@@ -4283,7 +4358,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4283
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.");
|
|
4284
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.');
|
|
4285
4360
|
const webNote = notes.length ? " NOTE: " + notes.join(" ") : "";
|
|
4286
|
-
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;
|
|
4287
4362
|
}
|
|
4288
4363
|
case "time":
|
|
4289
4364
|
return (/* @__PURE__ */ new Date()).toString();
|