mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 03:15:46 -06:00
fix(agents): clarify code mode contract for small models (#115275)
This commit is contained in:
@@ -245,24 +245,31 @@ describe("Code Mode catalog and model-visible surface", () => {
|
||||
execTool.description.lastIndexOf(nodesGuidance),
|
||||
);
|
||||
|
||||
expect(parameters.properties?.code?.description).toContain("no Python, shell");
|
||||
expect(parameters.properties?.code?.description).toContain(
|
||||
"`tools.search` takes a query string, not an object",
|
||||
);
|
||||
expect(parameters.properties?.command?.description).toContain("Not a shell command");
|
||||
expect(parameters.properties?.code?.description).toContain(
|
||||
"Select exact ids from `ALL_TOOLS` or `tools.search`",
|
||||
"a trailing expression is discarded and yields `null`",
|
||||
);
|
||||
expect(parameters.properties?.code?.description).toContain(
|
||||
"never put dependent calls in Promise.all",
|
||||
'tools.callValue("openclaw:core:read", { path: "notes.txt" })',
|
||||
);
|
||||
expect(parameters.properties?.code?.description).toContain("Use `callValue`, not `call`");
|
||||
expect(parameters.properties?.code?.description).toContain("return file.content");
|
||||
expect(parameters.properties?.code?.description).toContain(
|
||||
"return it first, then parse it in a later exec",
|
||||
);
|
||||
expect(parameters.properties?.code?.description).toContain(
|
||||
"exact ids from `ALL_TOOLS` or `tools.search(query)`",
|
||||
);
|
||||
expect(parameters.properties?.code?.description).toContain("`ALL_TOOLS`");
|
||||
expect(parameters.properties?.code?.description).toContain("Node built-in modules are not");
|
||||
expect(parameters.properties?.code?.description).toContain("`require`, `import`");
|
||||
expect(parameters.properties?.restartSafe?.description).toContain(
|
||||
"Leave unset for ordinary calls",
|
||||
);
|
||||
expect(parameters.properties?.language?.description).toContain(
|
||||
'Must be "javascript" or "typescript"',
|
||||
);
|
||||
expect(parameters).toMatchObject({ required: ["code"] });
|
||||
expect(parameters.properties).not.toHaveProperty("command");
|
||||
});
|
||||
|
||||
it("keeps code-mode exec guidance compact without advertising unavailable namespaces", () => {
|
||||
@@ -285,7 +292,7 @@ describe("Code Mode catalog and model-visible surface", () => {
|
||||
expect(execTool.description.length).toBeLessThan(2_400);
|
||||
expect(execTool.description).toContain("parallelize independent work only");
|
||||
expect(codeDescription).toEqual(expect.any(String));
|
||||
expect(String(codeDescription).length).toBeLessThan(320);
|
||||
expect(String(codeDescription).length).toBeLessThan(620);
|
||||
expect(codeDescription).not.toContain("MCP namespace globals");
|
||||
expect(codeDescription).not.toContain("`API` virtual declaration files");
|
||||
});
|
||||
|
||||
+7
-13
@@ -169,7 +169,7 @@ function createCodeModeExecDescription(
|
||||
nodesGuidance +
|
||||
skillsGuidance +
|
||||
' The `language` field accepts only "javascript" or "typescript"; do not pass "bash", "shell", or other values.' +
|
||||
" Both `code` and `command` contain JavaScript or TypeScript, never a shell command. " +
|
||||
" The `code` field contains JavaScript or TypeScript, never a shell command. " +
|
||||
"For shell or file operations, call the exact catalog tool from guest JavaScript; do not retry failed shell source." +
|
||||
(namespacePrompt ? `\n\n${namespacePrompt}` : "") +
|
||||
(catalogIndex ? `\n\n${catalogIndex}` : "")
|
||||
@@ -182,18 +182,12 @@ export function createCodeModeTools(ctx: CodeModeToolContext): AnyAgentTool[] {
|
||||
label: "exec",
|
||||
description: createCodeModeExecDescription(ctx),
|
||||
parameters: Type.Object({
|
||||
code: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"JavaScript or TypeScript for one complete workflow. Select exact ids from `ALL_TOOLS` or `tools.search`; never invent ids. `tools.search` takes a query string, not an object. Keep dependent calls in order; never put dependent calls in Promise.all. Return the final value. Node built-in modules are not available.",
|
||||
}),
|
||||
),
|
||||
command: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Alias for JavaScript or TypeScript code, provided for exec-compatible hook policies. Not a shell command.",
|
||||
}),
|
||||
),
|
||||
// `command` stays runtime-only for hook compatibility. Requiring the sole
|
||||
// model-facing field prevents schema-valid empty calls from constrained models.
|
||||
code: Type.String({
|
||||
description:
|
||||
'Required JS/TS; no Python, shell, `require`, `import`. Use explicit `return value`; a trailing expression is discarded and yields `null`. Use `callValue`, not `call`, for data; `call` wraps it under `.result`. Core text reads: `{kind:"text",content:string}`; use `.content`. Unknown format: return it first, then parse it in a later exec; never guess separators. Example: `const file=await tools.callValue("openclaw:core:read", { path: "notes.txt" }); if(file.kind!=="text") return file; return file.content;`. Use exact ids from `ALL_TOOLS` or `tools.search(query)`; never invent ids or parallelize dependent calls.',
|
||||
}),
|
||||
language: optionalStringEnum(["javascript", "typescript"] as const, {
|
||||
description:
|
||||
'Source language. Must be "javascript" or "typescript". Defaults to javascript.',
|
||||
|
||||
@@ -451,6 +451,21 @@ describe("buildAgentSystemPrompt", () => {
|
||||
expect(prompt).toContain("sessions_send");
|
||||
});
|
||||
|
||||
it("describes the actual Code Mode control surface", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
toolNames: ["exec", "wait"],
|
||||
codeModeActive: true,
|
||||
});
|
||||
|
||||
expect(prompt).toContain(
|
||||
"- exec: Run JavaScript/TypeScript Code Mode; call exact catalog tools from code, never shell/Python/imports",
|
||||
);
|
||||
expect(prompt).toContain("- wait: Resume a suspended Code Mode exec");
|
||||
expect(prompt).not.toContain("- exec: Run shell");
|
||||
expect(prompt).not.toContain("Use exec yieldMs");
|
||||
});
|
||||
|
||||
it("uses provider-neutral web_search prompt metadata", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
|
||||
@@ -837,10 +837,12 @@ export function buildAgentSystemPrompt(params: {
|
||||
grep: "Search file contents",
|
||||
find: "Find files by glob",
|
||||
ls: "List directories",
|
||||
exec:
|
||||
promptSurface === "cli_backend"
|
||||
exec: params.codeModeActive
|
||||
? "Run JavaScript/TypeScript Code Mode; call exact catalog tools from code, never shell/Python/imports"
|
||||
: promptSurface === "cli_backend"
|
||||
? "Run shell on connected node; sync; host=node"
|
||||
: "Run shell; pty for TTY CLIs",
|
||||
wait: "Resume a suspended Code Mode exec",
|
||||
process: "Control background exec",
|
||||
web_search: "Web search",
|
||||
web_fetch: "Fetch/extract URL",
|
||||
@@ -961,10 +963,11 @@ export function buildAgentSystemPrompt(params: {
|
||||
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
|
||||
}
|
||||
const toolSchemaDirectoryPrompt = params.toolSchemaDirectoryPrompt?.trim();
|
||||
const renderOpenClawToolWorkflowHints = shouldRenderOpenClawToolWorkflowHints({
|
||||
surface: promptSurface,
|
||||
hasToolList: toolLines.length > 0,
|
||||
});
|
||||
const renderOpenClawToolWorkflowHints =
|
||||
shouldRenderOpenClawToolWorkflowHints({
|
||||
surface: promptSurface,
|
||||
hasToolList: toolLines.length > 0,
|
||||
}) && params.codeModeActive !== true;
|
||||
|
||||
const hasGateway = availableTools.has("gateway");
|
||||
const hasOpenClaw = availableTools.has("openclaw");
|
||||
|
||||
Reference in New Issue
Block a user