From 8333615fb4e549dbbd103d3678d9503e946c8ec2 Mon Sep 17 00:00:00 2001 From: yushen Date: Wed, 4 Feb 2026 15:41:22 +0800 Subject: [PATCH] refactor(agent): integrate system prompt builder into profile, runner, and subagent ProfileManager.buildSystemPrompt() now delegates to the structured builder. Runner assembles prompt after tool resolution with safety, tooling summary, and runtime info. Subagent prompts use minimal mode with safety constitution. Co-Authored-By: Claude Opus 4.5 --- src/agent/profile/index.ts | 42 ++++------ src/agent/runner.ts | 117 ++++++++++++++++++++-------- src/agent/subagent/announce.test.ts | 3 +- src/agent/subagent/announce.ts | 36 +++------ 4 files changed, 111 insertions(+), 87 deletions(-) diff --git a/src/agent/profile/index.ts b/src/agent/profile/index.ts index b0ce0158..54e0b0f7 100644 --- a/src/agent/profile/index.ts +++ b/src/agent/profile/index.ts @@ -17,6 +17,7 @@ import { writeProfileFile, } from "./storage.js"; import { PROFILE_FILES } from "./types.js"; +import { buildSystemPrompt as buildPrompt } from "../system-prompt/index.js"; export { type AgentProfile, type CreateProfileOptions, type ProfileConfig, type ProfileManagerOptions } from "./types.js"; export { DEFAULT_TEMPLATES } from "./templates.js"; @@ -135,41 +136,24 @@ export class ProfileManager { return getProfileDir(this.profileId, { baseDir: this.baseDir }); } - /** 构建 system prompt */ + /** Build system prompt using the structured prompt builder */ buildSystemPrompt(): string { const profile = this.getProfile(); - console.log('[ProfileManager] buildSystemPrompt called, profile exists:', !!profile); if (!profile) { return ""; } - const parts: string[] = []; - - if (profile.soul) { - console.log('[ProfileManager] Adding soul, length:', profile.soul.length); - parts.push(profile.soul); - } - - if (profile.user) { - console.log('[ProfileManager] Adding user, content:', profile.user.substring(0, 100)); - parts.push(profile.user); - } else { - console.log('[ProfileManager] No user content in profile'); - } - - if (profile.workspace) { - parts.push(profile.workspace); - } - - if (profile.memory) { - parts.push(profile.memory); - } - - // 注入 profile 目录路径,让 Agent 知道文件在哪里 - const profileDir = this.getProfileDir(); - parts.push(`## Profile Directory\n\nYour profile files are located at: \`${profileDir}\`\n\nUse \`edit\` or \`write\` tools to update these files when needed.`); - - return parts.join("\n\n"); + return buildPrompt({ + mode: "full", + profile: { + soul: profile.soul, + user: profile.user, + workspace: profile.workspace, + memory: profile.memory, + config: profile.config, + }, + profileDir: this.getProfileDir(), + }); } /** 获取 tools 配置 */ diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 7acb3709..9b072901 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -28,6 +28,11 @@ import { markAuthProfileUsed, markAuthProfileGood, } from "./auth-profiles/index.js"; +import { + buildSystemPrompt as buildStructuredSystemPrompt, + collectRuntimeInfo, + type SystemPromptMode, +} from "./system-prompt/index.js"; import type { AuthProfileFailureReason } from "./auth-profiles/index.js"; // ============================================================ @@ -153,7 +158,6 @@ export class Agent { // Load Agent Profile (if profileId is specified) // Every Agent should have a Profile for memory, tools config, and other settings - let systemPrompt: string | undefined; if (options.profileId) { this.profile = new ProfileManager({ profileId: options.profileId, @@ -161,10 +165,6 @@ export class Agent { }); // Ensure profile directory exists (creates with default templates if new) this.profile.getOrCreateProfile(true); - systemPrompt = this.profile.buildSystemPrompt(); - } else if (options.systemPrompt) { - // Use provided systemPrompt directly (no profile - memory tools won't work) - systemPrompt = options.systemPrompt; } // Initialize SkillManager (enabled by default) @@ -174,17 +174,6 @@ export class Agent { profileBaseDir: options.profileBaseDir, config: options.skills, }); - - // Append skills prompt to system prompt - const skillsPrompt = this.skillManager.buildSkillsPrompt(); - if (skillsPrompt) { - systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillsPrompt}` : skillsPrompt; - } - } - - // Set the combined system prompt - if (systemPrompt) { - this.agent.setSystemPrompt(systemPrompt); } this.sessionId = options.sessionId ?? uuidv7(); @@ -249,7 +238,7 @@ export class Agent { compactionMode, // Token 模式参数 contextWindowTokens: this.contextWindowGuard.tokens, - systemPrompt, + // systemPrompt is set later via setSystemPrompt() after tools are resolved reserveTokens: options.reserveTokens, targetRatio: options.compactionTargetRatio, minKeepMessages: options.minKeepMessages, @@ -287,6 +276,14 @@ export class Agent { } this.agent.setTools(tools); + // Build the system prompt using the structured builder + const toolNames = tools.map((t: { name: string }) => t.name); + const systemPrompt = this.buildFullSystemPrompt(options, toolNames); + if (systemPrompt) { + this.agent.setSystemPrompt(systemPrompt); + this.session.setSystemPrompt(systemPrompt); + } + this.session.saveMeta({ provider: this.agent.state.model?.provider, model: this.agent.state.model?.id, @@ -578,35 +575,91 @@ export class Agent { this.profile?.updateStyle(style); } + /** + * Build the full system prompt using the structured builder. + * Combines profile content, tools, skills, and runtime info. + */ + private buildFullSystemPrompt( + options: AgentOptions, + toolNames: string[], + ): string | undefined { + // If a raw systemPrompt is provided directly, use it as-is (backward compat) + if (!options.profileId && options.systemPrompt) { + return options.systemPrompt; + } + + const profile = this.profile?.getProfile(); + if (!profile && !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, + }); + } + /** * Reload profile from disk and rebuild system prompt. * Call this after updating profile files to apply changes immediately. */ reloadSystemPrompt(): void { if (!this.profile) { - console.log('[Agent] reloadSystemPrompt: no profile'); return; } - // Reload profile from disk - console.log('[Agent] Reloading profile from disk...'); this.profile.reloadProfile(); - // Rebuild system prompt - let systemPrompt = this.profile.buildSystemPrompt(); - console.log('[Agent] Built system prompt, length:', systemPrompt?.length); + // Rebuild using current tool names + const toolNames = (this.agent.state.tools ?? []).map((t: { name: string }) => t.name); + const skillsPrompt = this.skillManager?.buildModelSkillsPrompt(); - // Re-add skills prompt if skills are enabled - if (this.skillManager) { - const skillsPrompt = this.skillManager.buildModelSkillsPrompt(); - if (skillsPrompt) { - systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillsPrompt}` : skillsPrompt; - } - } + const runtime = collectRuntimeInfo({ + agentName: this.profile.getName(), + provider: this.resolvedProvider, + model: this.agent.state.model?.id, + }); + + const profile = this.profile.getProfile(); + const systemPrompt = 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(), + tools: toolNames, + skillsPrompt, + runtime, + }); - // Apply new system prompt if (systemPrompt) { - console.log('[Agent] Applying system prompt, first 200 chars:', systemPrompt.substring(0, 200)); this.agent.setSystemPrompt(systemPrompt); } } diff --git a/src/agent/subagent/announce.test.ts b/src/agent/subagent/announce.test.ts index 8f1a7140..faf6ca35 100644 --- a/src/agent/subagent/announce.test.ts +++ b/src/agent/subagent/announce.test.ts @@ -10,11 +10,12 @@ describe("buildSubagentSystemPrompt", () => { task: "Analyze the auth module for security issues", }); - expect(prompt).toContain("You are a subagent spawned to complete a specific task"); + expect(prompt).toContain("## Subagent Rules"); expect(prompt).toContain("Analyze the auth module for security issues"); expect(prompt).toContain("parent-123"); expect(prompt).toContain("child-456"); expect(prompt).toContain("Do NOT spawn nested subagents"); + expect(prompt).toContain("## Safety"); }); it("includes label when provided", () => { diff --git a/src/agent/subagent/announce.ts b/src/agent/subagent/announce.ts index e5d06c51..7fa27d65 100644 --- a/src/agent/subagent/announce.ts +++ b/src/agent/subagent/announce.ts @@ -9,6 +9,7 @@ import { readEntries } from "../session/storage.js"; import { getHub } from "../../hub/hub-singleton.js"; +import { buildSystemPrompt } from "../system-prompt/index.js"; import type { SubagentAnnounceParams, SubagentRunOutcome, @@ -17,33 +18,18 @@ import type { /** * Build the system prompt injected into a subagent session. + * Uses the structured prompt builder with "minimal" mode. */ export function buildSubagentSystemPrompt(params: SubagentSystemPromptParams): string { - const { requesterSessionId, childSessionId, label, task } = params; - - const lines: string[] = [ - "You are a subagent spawned to complete a specific task.", - "", - "## Rules", - "- Stay focused on the assigned task below.", - "- Complete the task thoroughly and report your findings.", - "- Do NOT initiate side actions unrelated to the task.", - "- Do NOT attempt to communicate with the user directly.", - "- Do NOT spawn nested subagents.", - "- Your session is ephemeral and will be cleaned up after completion.", - "", - "## Context", - `Requester session: ${requesterSessionId}`, - `Child session: ${childSessionId}`, - ]; - - if (label) { - lines.push(`Label: "${label}"`); - } - - lines.push("", "## Task", task); - - return lines.join("\n"); + return buildSystemPrompt({ + mode: "minimal", + subagent: { + requesterSessionId: params.requesterSessionId, + childSessionId: params.childSessionId, + label: params.label, + task: params.task, + }, + }); } /**