From 980071c55735ee6a2541554d58468db929ede5b2 Mon Sep 17 00:00:00 2001 From: phantom5099 <1011668688@qq.com> Date: Tue, 9 Jun 2026 23:12:30 +0800 Subject: [PATCH 1/2] add plan subagent --- .../codingcode/src/client/direct/settings.ts | 5 ++- packages/codingcode/src/index.ts | 1 + .../codingcode/src/runtime/project-runtime.ts | 3 +- .../codingcode/src/server/routes/settings.ts | 21 ++++++++- packages/codingcode/src/subagent/registry.ts | 28 +++++++++++- .../test/prompts/system-prompt.test.ts | 17 +++++++ .../test/subagent/plan-profile.test.ts | 45 +++++++++++++++++++ .../codingcode/test/tools/websearch.test.ts | 10 ++--- 8 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 packages/codingcode/test/subagent/plan-profile.test.ts diff --git a/packages/codingcode/src/client/direct/settings.ts b/packages/codingcode/src/client/direct/settings.ts index cafde81..4b31684 100644 --- a/packages/codingcode/src/client/direct/settings.ts +++ b/packages/codingcode/src/client/direct/settings.ts @@ -22,6 +22,7 @@ import { } from '../../subagent/loader.js'; import { EXPLORE_PROFILE, + PLAN_PROFILE, setSubagentEnabledState, resolveSubagentEnabled, getProjectSubagentEnabledState, @@ -132,7 +133,7 @@ function agentsList(cwd: string): Array<{ projectDisabled?: boolean; }> { const custom = loadAgentProfiles(cwd); - return [EXPLORE_PROFILE, ...custom].map((a) => { + return [EXPLORE_PROFILE, PLAN_PROFILE, ...custom].map((a) => { const projectVal = getProjectAgentDisabledState(cwd, a.name); return { name: a.name, @@ -143,7 +144,7 @@ function agentsList(cwd: string): Array<{ maxSteps: a.maxSteps, model: a.model, disabled: resolveAgentDisabled(cwd, a.name), - source: a.name === EXPLORE_PROFILE.name ? ('builtin' as const) : ('project' as const), + source: a.name === EXPLORE_PROFILE.name || a.name === PLAN_PROFILE.name ? ('builtin' as const) : ('project' as const), hasProjectOverride: projectVal !== undefined, projectDisabled: projectVal, }; diff --git a/packages/codingcode/src/index.ts b/packages/codingcode/src/index.ts index dd99dbd..cc95e2c 100644 --- a/packages/codingcode/src/index.ts +++ b/packages/codingcode/src/index.ts @@ -65,6 +65,7 @@ export type { SystemPromptVariant, SystemPromptOptions } from './agent/prompt.js export { SubagentRegistry, EXPLORE_PROFILE, + PLAN_PROFILE, getSubagentEnabledState, setSubagentEnabledState, } from './subagent/registry.js'; diff --git a/packages/codingcode/src/runtime/project-runtime.ts b/packages/codingcode/src/runtime/project-runtime.ts index 1234f14..b686546 100644 --- a/packages/codingcode/src/runtime/project-runtime.ts +++ b/packages/codingcode/src/runtime/project-runtime.ts @@ -1,6 +1,6 @@ import { Effect } from 'effect'; import type { AgentProfile } from '../subagent/registry'; -import { EXPLORE_PROFILE } from '../subagent/registry'; +import { EXPLORE_PROFILE, PLAN_PROFILE } from '../subagent/registry'; import * as agentLoader from '../subagent/loader'; import type { ToolVisibilityPolicy } from '../tools/types'; import { HookService } from '../hooks/registry'; @@ -22,6 +22,7 @@ export class ProjectRuntimeService extends Effect.Service function buildProfiles(projectPath: string): AgentProfile[] { const profiles: AgentProfile[] = []; profiles.push(EXPLORE_PROFILE); + profiles.push(PLAN_PROFILE); for (const p of agentLoader.loadGlobalAgentProfiles()) { if (!profiles.find((existing) => existing.name === p.name)) { diff --git a/packages/codingcode/src/server/routes/settings.ts b/packages/codingcode/src/server/routes/settings.ts index c70a81b..fba1e72 100644 --- a/packages/codingcode/src/server/routes/settings.ts +++ b/packages/codingcode/src/server/routes/settings.ts @@ -30,6 +30,7 @@ import { } from '../../subagent/loader.js'; import { EXPLORE_PROFILE, + PLAN_PROFILE, resolveSubagentEnabled, getProjectSubagentEnabledState, setProjectSubagentEnabledState, @@ -155,6 +156,22 @@ function agentsList(cwd: string): Array<{ projectDisabled: exploreProjectVal, }); + // builtin: PLAN_PROFILE + const planProjectVal = getProjectAgentDisabledState(cwd, PLAN_PROFILE.name); + result.push({ + name: PLAN_PROFILE.name, + description: PLAN_PROFILE.description, + tools: PLAN_PROFILE.tools, + mcpServers: PLAN_PROFILE.mcpServers, + readonly: PLAN_PROFILE.readonly, + maxSteps: PLAN_PROFILE.maxSteps, + model: PLAN_PROFILE.model, + disabled: resolveAgentDisabled(cwd, PLAN_PROFILE.name), + source: 'builtin', + hasProjectOverride: planProjectVal !== undefined, + projectDisabled: planProjectVal, + }); + // global agents (not overridden by project) for (const a of globalCustom) { if (projectNames.has(a.name)) continue; @@ -296,7 +313,7 @@ settingsRouter.get('/agents', (c) => { if (isGlobalCwd(rawCwd)) { const custom = loadGlobalAgentProfiles(); return c.json( - [EXPLORE_PROFILE, ...custom].map((a) => ({ + [EXPLORE_PROFILE, PLAN_PROFILE, ...custom].map((a) => ({ name: a.name, description: a.description, tools: a.tools, @@ -305,7 +322,7 @@ settingsRouter.get('/agents', (c) => { maxSteps: a.maxSteps, model: a.model, disabled: getGlobalAgentDisabledState(a.name), - source: a.name === EXPLORE_PROFILE.name ? 'builtin' : 'global', + source: a.name === EXPLORE_PROFILE.name || a.name === PLAN_PROFILE.name ? 'builtin' : 'global', })) ); } diff --git a/packages/codingcode/src/subagent/registry.ts b/packages/codingcode/src/subagent/registry.ts index e42bc7f..737e90a 100644 --- a/packages/codingcode/src/subagent/registry.ts +++ b/packages/codingcode/src/subagent/registry.ts @@ -211,5 +211,31 @@ export const EXPLORE_PROFILE: AgentProfile = { 'You are a read-only code exploration agent. Your role is to help explore and understand codebases through reading files, searching for symbols, and analyzing code structure. You can only read; you cannot write or modify files.', tools: ['read_file', 'search_files', 'search_code', 'fetch_url', 'tool_search'], readonly: true, - maxSteps: 30, + maxSteps: 180, +}; + +export const PLAN_PROFILE: AgentProfile = { + name: 'plan', + description: + 'Read-only codebase research for planning. Analyzes project structure, patterns, and dependencies to inform implementation plans. No writes.', + systemPrompt: `You are a codebase research agent for planning. Your role is to analyze the codebase thoroughly before implementation begins. + +When researching for a plan: +1. Understand the project structure and conventions +2. Identify relevant files and existing patterns +3. Analyze dependencies and potential impacts +4. Assess complexity and risks +5. Check for existing implementations or similar patterns + +Output a structured analysis covering: +- **Current state assessment**: What exists today +- **Key files**: Files that need modification or creation +- **Dependencies and risks**: Technical debt, breaking changes, third-party concerns +- **Recommended approach**: Step-by-step implementation strategy +- **Implementation phases**: If the task is complex, break it into ordered phases + +You can ONLY read files, search code, run commands, and fetch URLs. You cannot write or modify any files.`, + tools: ['read_file', 'search_files', 'search_code', 'execute_command', 'fetch_url', 'tool_search'], + readonly: true, + maxSteps: 180, }; diff --git a/packages/codingcode/test/prompts/system-prompt.test.ts b/packages/codingcode/test/prompts/system-prompt.test.ts index 803537f..820756b 100644 --- a/packages/codingcode/test/prompts/system-prompt.test.ts +++ b/packages/codingcode/test/prompts/system-prompt.test.ts @@ -61,6 +61,23 @@ describe('buildSystemPrompt', () => { expect(prompt).toContain('Read-only code exploration.'); }); + it('includes plan subagent in available subagents when provided', () => { + const profiles = [ + { name: 'explore', description: 'Explore.', tools: ['read_file'], disabled: false }, + { name: 'plan', description: 'Codebase research for planning.', tools: ['read_file', 'search_code'], disabled: false }, + ]; + const prompt = buildSystemPrompt({ ...baseOpts, agentProfiles: profiles }); + expect(prompt).toContain('plan'); + expect(prompt).toContain('Codebase research for planning'); + expect(prompt).toContain('dispatch_agent'); + }); + + it('SYSTEM_NOTES guides using plan subagent for complex tasks', () => { + expect(SYSTEM_NOTES).toContain('plan'); + expect(SYSTEM_NOTES).toContain('dispatch_agent'); + expect(SYSTEM_NOTES).toContain('complex tasks'); + }); + it('omits available subagents section when no profiles are provided', () => { const prompt = buildSystemPrompt(baseOpts); expect(prompt).not.toContain('Available Subagents'); diff --git a/packages/codingcode/test/subagent/plan-profile.test.ts b/packages/codingcode/test/subagent/plan-profile.test.ts new file mode 100644 index 0000000..79d91e8 --- /dev/null +++ b/packages/codingcode/test/subagent/plan-profile.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { PLAN_PROFILE, EXPLORE_PROFILE } from '../../src/subagent/registry.js'; + +describe('PLAN_PROFILE', () => { + it('has name "plan"', () => { + expect(PLAN_PROFILE.name).toBe('plan'); + }); + + it('is readonly', () => { + expect(PLAN_PROFILE.readonly).toBe(true); + }); + + it('has maxSteps set to 180', () => { + expect(PLAN_PROFILE.maxSteps).toBe(180); + }); + + it('has a systemPrompt', () => { + expect(PLAN_PROFILE.systemPrompt).toBeTruthy(); + expect(PLAN_PROFILE.systemPrompt!.length).toBeGreaterThan(50); + }); + + it('only includes read-only tools', () => { + const writeTools = ['write_file', 'edit_file']; + for (const wt of writeTools) { + expect(PLAN_PROFILE.tools).not.toContain(wt); + } + }); + + it('includes read_file and search_code', () => { + expect(PLAN_PROFILE.tools).toContain('read_file'); + expect(PLAN_PROFILE.tools).toContain('search_code'); + }); + + it('includes execute_command for build checks', () => { + expect(PLAN_PROFILE.tools).toContain('execute_command'); + }); + + it('has a distinct name from explore', () => { + expect(PLAN_PROFILE.name).not.toBe(EXPLORE_PROFILE.name); + }); + + it('has description stating it is for planning', () => { + expect(PLAN_PROFILE.description.toLowerCase()).toContain('plan'); + }); +}); diff --git a/packages/codingcode/test/tools/websearch.test.ts b/packages/codingcode/test/tools/websearch.test.ts index f63e123..f49ee16 100644 --- a/packages/codingcode/test/tools/websearch.test.ts +++ b/packages/codingcode/test/tools/websearch.test.ts @@ -56,9 +56,9 @@ describe('parseBingHtml', () => { const results = parseBingHtml(html, 10); expect(results).toHaveLength(2); - expect(results[0].title).toBe('Example Title 1'); - expect(results[0].url).toBe('https://example.com/page1'); - expect(results[0].snippet).toBe('Snippet text 1'); + expect(results[0]!.title).toBe('Example Title 1'); + expect(results[0]!.url).toBe('https://example.com/page1'); + expect(results[0]!.snippet).toBe('Snippet text 1'); }); it('should respect maxResults limit', () => { @@ -87,8 +87,8 @@ describe('parseBingHtml', () => { `; const results = parseBingHtml(html, 10); - expect(results[0].title).toBe('Bold Title'); - expect(results[0].snippet).toBe('Text with emphasis'); + expect(results[0]!.title).toBe('Bold Title'); + expect(results[0]!.snippet).toBe('Text with emphasis'); }); }); From d6f9997b18cc11142d5ec49114ad4a1c2f164fdb Mon Sep 17 00:00:00 2001 From: phantom5099 <1011668688@qq.com> Date: Wed, 10 Jun 2026 00:19:37 +0800 Subject: [PATCH 2/2] Adjust the priority of the sub-agent --- packages/codingcode/src/agent/prompt.ts | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/codingcode/src/agent/prompt.ts b/packages/codingcode/src/agent/prompt.ts index 7745c34..e5322e7 100644 --- a/packages/codingcode/src/agent/prompt.ts +++ b/packages/codingcode/src/agent/prompt.ts @@ -73,13 +73,45 @@ export function buildSystemPrompt(opts: SystemPromptOptions): string { const enabledProfiles = opts.agentProfiles.filter((p) => !p.disabled); if (enabledProfiles.length > 0) { prompt += '\n\n## Available Subagents\n'; - prompt += `You can dispatch subagents using the dispatch_agent tool. Available profiles:\n`; + prompt += 'You can dispatch subagents using the dispatch_agent tool. Available profiles:\n'; for (const p of enabledProfiles) { prompt += `\n### ${p.name}\n${p.description}`; if (p.tools && p.tools.length > 0) { prompt += `\nTools: ${p.tools.join(', ')}`; } } + + prompt += ` + +### When to dispatch + +Dispatch a subagent when the task involves extensively reading files, searching across the codebase, or analyzing a whole module. A subagent runs in an independent context window — all of its tool calls (read_file, search_code, etc.) consume only the subagent\'s own context. Only the final result comes back to you. + +**Dispatch = protect your context window.** If you do the same work yourself, all raw content goes directly into your context. + +### When NOT to dispatch + +- The task needs only a small amount of information — do it yourself. +- You already know the exact file path and what to look for — use read_file / search_code directly. + +### Rules + +1. Once you dispatch a subagent, do **NOT** also perform the same searches yourself. +2. **Do NOT peek** — the subagent runs independently. Do not try to read its intermediate output, as that defeats the context protection. +3. When the subagent returns, relay its conclusion to the user concisely. + +### Example + +\`\`\` +User: "Find all API route definitions in this project." + +Thinking: This requires searching multiple directories broadly. If I grep and read files myself, all the raw output piles into my context. I should dispatch explore. + +dispatch_agent({ + agent: "explore", + prompt: "Search the entire project for API route definitions..." +}) +\`\`\``; } }