From f733f0bcc354178ee3a4d05fff723f22caacdf2b Mon Sep 17 00:00:00 2001 From: yushen Date: Wed, 4 Feb 2026 15:46:18 +0800 Subject: [PATCH] fix(agent): fix system prompt reload and tool casing issues - reloadSystemPrompt() now updates both agent and session prompt - reloadSystemPrompt() uses buildSkillsPrompt() consistently (not buildModelSkillsPrompt) - Extract rebuildSystemPrompt() to eliminate duplicated builder logic - Preserve original tool name casing in tooling summary (matching OpenClaw) - Fix report line count to use actual newline count Co-Authored-By: Claude Opus 4.5 --- src/agent/runner.ts | 75 +++++++++--------------- src/agent/system-prompt/builder.ts | 2 +- src/agent/system-prompt/sections.test.ts | 18 ++++++ src/agent/system-prompt/sections.ts | 24 ++++++-- 4 files changed, 67 insertions(+), 52 deletions(-) diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 9b072901..a5b4f3a0 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -588,36 +588,11 @@ export class Agent { return options.systemPrompt; } - const profile = this.profile?.getProfile(); - if (!profile && !options.profileId) { + if (!this.profile?.getProfile() && !options.profileId) { return undefined; } - const mode: SystemPromptMode = options.isSubagent ? "minimal" : "full"; - const skillsPrompt = this.skillManager?.buildSkillsPrompt(); - - const runtime = collectRuntimeInfo({ - agentName: this.profile?.getName(), - provider: this.resolvedProvider, - model: this.agent.state.model?.id, - }); - - return buildStructuredSystemPrompt({ - mode, - profile: profile - ? { - soul: profile.soul, - user: profile.user, - workspace: profile.workspace, - memory: profile.memory, - config: profile.config, - } - : undefined, - profileDir: this.profile?.getProfileDir(), - tools: toolNames, - skillsPrompt, - runtime, - }); + return this.rebuildSystemPrompt(toolNames); } /** @@ -631,36 +606,44 @@ export class Agent { this.profile.reloadProfile(); - // Rebuild using current tool names const toolNames = (this.agent.state.tools ?? []).map((t: { name: string }) => t.name); - const skillsPrompt = this.skillManager?.buildModelSkillsPrompt(); + const systemPrompt = this.rebuildSystemPrompt(toolNames); + + if (systemPrompt) { + this.agent.setSystemPrompt(systemPrompt); + this.session.setSystemPrompt(systemPrompt); + } + } + + /** + * Rebuild system prompt from current state. + * Shared by constructor (via buildFullSystemPrompt) and reloadSystemPrompt. + */ + private rebuildSystemPrompt(toolNames: string[]): string | undefined { + const profile = this.profile?.getProfile(); + if (!profile) return undefined; + + const skillsPrompt = this.skillManager?.buildSkillsPrompt(); const runtime = collectRuntimeInfo({ - agentName: this.profile.getName(), + agentName: this.profile?.getName(), provider: this.resolvedProvider, model: this.agent.state.model?.id, }); - const profile = this.profile.getProfile(); - const systemPrompt = buildStructuredSystemPrompt({ + return buildStructuredSystemPrompt({ mode: "full", - profile: profile - ? { - soul: profile.soul, - user: profile.user, - workspace: profile.workspace, - memory: profile.memory, - config: profile.config, - } - : undefined, - profileDir: this.profile.getProfileDir(), + profile: { + soul: profile.soul, + user: profile.user, + workspace: profile.workspace, + memory: profile.memory, + config: profile.config, + }, + profileDir: this.profile!.getProfileDir(), tools: toolNames, skillsPrompt, runtime, }); - - if (systemPrompt) { - this.agent.setSystemPrompt(systemPrompt); - } } } diff --git a/src/agent/system-prompt/builder.ts b/src/agent/system-prompt/builder.ts index 991234d7..b0f1f45f 100644 --- a/src/agent/system-prompt/builder.ts +++ b/src/agent/system-prompt/builder.ts @@ -79,7 +79,7 @@ export function buildSystemPromptWithReport(options: SystemPromptOptions): { reportSections.push({ name, chars: content.length, - lines: lines.length, + lines: included ? content.split("\n").length : 0, included, }); if (included) { diff --git a/src/agent/system-prompt/sections.test.ts b/src/agent/system-prompt/sections.test.ts index 7e8ebc6d..f98ccabd 100644 --- a/src/agent/system-prompt/sections.test.ts +++ b/src/agent/system-prompt/sections.test.ts @@ -120,6 +120,24 @@ describe("buildToolingSummary", () => { const result = buildToolingSummary(["read"], "minimal"); expect(result.join("\n")).toContain("## Tooling"); }); + + it("preserves original tool casing", () => { + const result = buildToolingSummary(["Read", "MyCustomTool", "EXEC"], "full"); + const text = result.join("\n"); + // Core tools: first-seen casing preserved + expect(text).toContain("- Read: Read file contents"); + expect(text).toContain("- EXEC: Run shell commands"); + // Unknown tools: original casing preserved + expect(text).toContain("- MyCustomTool"); + }); + + it("deduplicates tools by lowercase", () => { + const result = buildToolingSummary(["read", "Read", "READ"], "full"); + const text = result.join("\n"); + // Should appear only once (first occurrence) + const matches = text.match(/- read/gi); + expect(matches).toHaveLength(1); + }); }); describe("buildToolCallStyleSection", () => { diff --git a/src/agent/system-prompt/sections.ts b/src/agent/system-prompt/sections.ts index 7e033607..af1569d4 100644 --- a/src/agent/system-prompt/sections.ts +++ b/src/agent/system-prompt/sections.ts @@ -113,6 +113,7 @@ export function buildSafetySection(includeSafety: boolean): string[] { /** * Tooling summary — lists active tools with descriptions. * Included in full and minimal modes. + * Preserves original tool casing while deduplicating by lowercase. */ export function buildToolingSummary( tools: string[] | undefined, @@ -120,7 +121,18 @@ export function buildToolingSummary( ): string[] { if (mode === "none" || !tools || tools.length === 0) return []; - const toolSet = new Set(tools.map((t) => t.toLowerCase())); + // Preserve original casing: first occurrence wins per normalized name + const canonicalByNormalized = new Map(); + for (const name of tools) { + const normalized = name.toLowerCase(); + if (!canonicalByNormalized.has(normalized)) { + canonicalByNormalized.set(normalized, name); + } + } + const resolveToolName = (normalized: string) => + canonicalByNormalized.get(normalized) ?? normalized; + + const normalizedTools = new Set(canonicalByNormalized.keys()); // Build ordered tool lines const toolLines: string[] = []; @@ -128,17 +140,19 @@ export function buildToolingSummary( // Core tools in preferred order for (const tool of TOOL_ORDER) { - if (toolSet.has(tool) && !seen.has(tool)) { + if (normalizedTools.has(tool) && !seen.has(tool)) { seen.add(tool); + const displayName = resolveToolName(tool); const summary = CORE_TOOL_SUMMARIES[tool]; - toolLines.push(summary ? `- ${tool}: ${summary}` : `- ${tool}`); + toolLines.push(summary ? `- ${displayName}: ${summary}` : `- ${displayName}`); } } // External/unknown tools alphabetically - const extraTools = [...toolSet].filter((t) => !seen.has(t)).sort(); + const extraTools = [...normalizedTools].filter((t) => !seen.has(t)).sort(); for (const tool of extraTools) { - toolLines.push(`- ${tool}`); + const displayName = resolveToolName(tool); + toolLines.push(`- ${displayName}`); } return [