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 <noreply@anthropic.com>
This commit is contained in:
parent
8333615fb4
commit
f733f0bcc3
4 changed files with 67 additions and 52 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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<string, string>();
|
||||
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 [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue