fix(agents): clarify code mode contract for small models (#115275)

This commit is contained in:
Vincent Koc
2026-07-29 14:04:01 +08:00
committed by GitHub
parent 117e44cb80
commit 44aaa2f916
4 changed files with 46 additions and 27 deletions
+15 -8
View File
@@ -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
View File
@@ -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.',
+15
View File
@@ -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",
+9 -6
View File
@@ -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");