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, + }, + }); } /**