diff --git a/package.json b/package.json index 71c8fb63fb..d696777fa7 100644 --- a/package.json +++ b/package.json @@ -3947,6 +3947,15 @@ "experimental" ] }, + "github.copilot.chat.planAgent.mermaid.enabled": { + "type": "boolean", + "default": false, + "scope": "resource", + "markdownDescription": "%github.copilot.config.planAgent.mermaid.enabled%", + "tags": [ + "experimental" + ] + }, "github.copilot.chat.implementAgent.model": { "type": "string", "default": "", diff --git a/package.nls.json b/package.nls.json index fca78b7144..d276faf420 100644 --- a/package.nls.json +++ b/package.nls.json @@ -326,6 +326,7 @@ "github.copilot.config.organizationCustomAgents.enabled": "When enabled, Copilot will load custom agents defined by your GitHub Organization.", "github.copilot.config.organizationInstructions.enabled": "When enabled, Copilot will load custom instructions defined by your GitHub Organization.", "github.copilot.config.planAgent.additionalTools": "Additional tools to enable for the Plan agent, on top of built-in tools. Use fully-qualified tool names (e.g., `github/issue_read`, `mcp_server/tool_name`).", + "github.copilot.config.planAgent.mermaid.enabled": "Allow the Plan agent to include Mermaid diagrams in plans when they materially clarify architecture, sequencing, or dependencies. When enabled, Plan mode may persist concise fenced `mermaid` blocks in the saved plan.", "github.copilot.config.implementAgent.model": "Override the language model used when starting implementation from the Plan agent's handoff. Use the format `Model Name (vendor)` (e.g., `GPT-5 (copilot)`). Leave empty to use the default model.", "github.copilot.config.askAgent.additionalTools": "Additional tools to enable for the Ask agent, on top of built-in read-only tools. Use fully-qualified tool names (e.g., `github/issue_read`, `mcp_server/tool_name`).", "github.copilot.config.askAgent.model": "Override the language model used by the Ask agent. Leave empty to use the default model.", diff --git a/src/extension/agents/vscode-node/planAgentProvider.ts b/src/extension/agents/vscode-node/planAgentProvider.ts index 4ef9f2fb01..f03ea0cfd5 100644 --- a/src/extension/agents/vscode-node/planAgentProvider.ts +++ b/src/extension/agents/vscode-node/planAgentProvider.ts @@ -12,6 +12,8 @@ import { ILogService } from '../../../platform/log/common/logService'; import { Disposable } from '../../../util/vs/base/common/lifecycle'; import { AgentConfig, AgentHandoff, buildAgentMarkdown, DEFAULT_READ_TOOLS } from './agentTypes'; +const MERMAID_RENDER_TOOL = 'vscode.mermaid-chat-features/renderMermaidDiagram'; + /** * Base Plan agent configuration - embedded from Plan.agent.md * This avoids runtime file loading and YAML parsing dependencies. @@ -61,6 +63,7 @@ export class PlanAgentProvider extends Disposable implements vscode.ChatCustomAg // these capture the model at render time. this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(ConfigKey.PlanAgentAdditionalTools.fullyQualifiedId) || + e.affectsConfiguration(ConfigKey.PlanAgentMermaidEnabled.fullyQualifiedId) || e.affectsConfiguration(ConfigKey.Deprecated.PlanAgentModel.fullyQualifiedId) || e.affectsConfiguration('chat.planAgent.defaultModel') || e.affectsConfiguration(ConfigKey.ImplementAgentModel.fullyQualifiedId)) { @@ -103,12 +106,26 @@ export class PlanAgentProvider extends Disposable implements vscode.ChatCustomAg return fileUri; } - static buildAgentBody(): string { + static buildAgentBody(planAgentMermaidEnabled = false): string { const discoverySection = `## 1. Discovery Run the *Explore* subagent to gather context, analogous existing features to use as implementation templates, and potential blockers or ambiguities. When the task spans multiple independent areas (e.g., frontend + backend, different features, separate repos), launch **2-3 *Explore* subagents in parallel** — one per area — to speed up discovery. Update the plan with your findings.`; + const baseRules = [ + '- STOP if you consider running file editing tools — plans are for others to execute. The only write tool you have is #tool:vscode/memory for persisting plans.', + '- Use #tool:vscode/askQuestions freely to clarify requirements — don\'t make large assumptions', + '- Present a well-researched plan with loose ends tied BEFORE implementation' + ]; + const mermaidRules = [ + `- When a Mermaid diagram would materially improve the plan, you may include a concise fenced \`mermaid\` block and you MUST use ${MERMAID_RENDER_TOOL} to render it in the same response.`, + '- Never inline Mermaid syntax as plain text. Mermaid content must stay inside fenced \`mermaid\` blocks only.' + ]; + const rulesSection = [...baseRules, ...(planAgentMermaidEnabled ? mermaidRules : [])].join('\n'); + const planStyleGuideRules = planAgentMermaidEnabled + ? `- NO code blocks except relevant Mermaid diagrams when they materially clarify architecture, sequencing, dependencies, or ownership in the plan. +- If you use Mermaid, keep the diagram concise, preserve the Mermaid source in the plan markdown so it survives persistence and handoff, and always call ${MERMAID_RENDER_TOOL} rather than leaving the syntax unrendered.` + : '- NO code blocks — describe changes, link to files and specific symbols/functions'; return `You are a PLANNING AGENT, pairing with the user to create a detailed, actionable plan. @@ -119,9 +136,7 @@ Your SOLE responsibility is planning. NEVER start implementation. **Current plan**: \`/memories/session/plan.md\` - update using #tool:vscode/memory . -- STOP if you consider running file editing tools — plans are for others to execute. The only write tool you have is #tool:vscode/memory for persisting plans. -- Use #tool:vscode/askQuestions freely to clarify requirements — don't make large assumptions -- Present a well-researched plan with loose ends tied BEFORE implementation +${rulesSection} @@ -189,7 +204,7 @@ Keep iterating until explicit approval or handoff. \`\`\` Rules: -- NO code blocks — describe changes, link to files and specific symbols/functions +${planStyleGuideRules} - NO blocking questions at the end — ask during workflow via #tool:vscode/askQuestions - The plan MUST be presented to the user, don't just mention the plan file. `; @@ -197,6 +212,7 @@ Rules: private buildCustomizedConfig(): AgentConfig { const additionalTools = this.configurationService.getConfig(ConfigKey.PlanAgentAdditionalTools); + const planAgentMermaidEnabled = this.configurationService.getConfig(ConfigKey.PlanAgentMermaidEnabled); const coreDefaultModel = this.configurationService.getNonExtensionConfig('chat.planAgent.defaultModel'); const modelOverride = coreDefaultModel || this.configurationService.getConfig(ConfigKey.Deprecated.PlanAgentModel); @@ -221,6 +237,9 @@ Rules: // Collect tools to add const toolsToAdd: string[] = [...additionalTools]; + if (planAgentMermaidEnabled) { + toolsToAdd.push(MERMAID_RENDER_TOOL); + } // Always include askQuestions tool (now provided by core) toolsToAdd.push('vscode/askQuestions'); @@ -235,7 +254,7 @@ Rules: ...BASE_PLAN_AGENT_CONFIG, tools, handoffs: [startImplementationHandoff, openInEditorHandoff, ...(BASE_PLAN_AGENT_CONFIG.handoffs ?? [])], - body: PlanAgentProvider.buildAgentBody(), + body: PlanAgentProvider.buildAgentBody(planAgentMermaidEnabled), ...(modelOverride ? { model: modelOverride } : {}), }; } diff --git a/src/extension/agents/vscode-node/test/planAgentProvider.spec.ts b/src/extension/agents/vscode-node/test/planAgentProvider.spec.ts index 1eaa729888..18a29887ff 100644 --- a/src/extension/agents/vscode-node/test/planAgentProvider.spec.ts +++ b/src/extension/agents/vscode-node/test/planAgentProvider.spec.ts @@ -82,12 +82,25 @@ suite('PlanAgentProvider', () => { assert.ok(content.includes('search')); assert.ok(content.includes('read')); assert.ok(content.includes('memory')); + assert.ok(!content.includes('vscode.mermaid-chat-features/renderMermaidDiagram')); // Should not have model override (not in base content) assert.ok(content.includes('name: Plan')); assert.ok(content.includes('description: Researches and outlines multi-step plans')); }); + test('includes Mermaid render tool when plan Mermaid setting is enabled', async () => { + await mockConfigurationService.setConfig(ConfigKey.PlanAgentMermaidEnabled, true); + + const provider = createProvider(); + const agents = await provider.provideCustomAgents({}, {} as any); + + assert.equal(agents.length, 1); + const content = await getAgentContent(agents[0]); + + assert.ok(content.includes('vscode.mermaid-chat-features/renderMermaidDiagram')); + }); + test('merges additionalTools setting with base tools', async () => { await mockConfigurationService.setConfig(ConfigKey.PlanAgentAdditionalTools, ['customTool1', 'customTool2']); @@ -199,6 +212,19 @@ suite('PlanAgentProvider', () => { assert.equal(eventFired, true); }); + test('fires onDidChangeCustomAgents when PlanAgentMermaidEnabled setting changes', async () => { + const provider = createProvider(); + + let eventFired = false; + provider.onDidChangeCustomAgents(() => { + eventFired = true; + }); + + await mockConfigurationService.setConfig(ConfigKey.PlanAgentMermaidEnabled, true); + + assert.equal(eventFired, true); + }); + test('fires onDidChangeCustomAgents when model setting changes', async () => { const provider = createProvider(); @@ -267,6 +293,31 @@ suite('PlanAgentProvider', () => { assert.ok(content.includes('Your SOLE responsibility is planning. NEVER start implementation.')); }); + test('keeps non-Mermaid code blocks disallowed by default', async () => { + const provider = createProvider(); + const agents = await provider.provideCustomAgents({}, {} as any); + + const content = await getAgentContent(agents[0]); + + assert.ok(content.includes('```markdown')); + assert.ok(content.includes('NO code blocks — describe changes, link to files and specific symbols/functions')); + assert.ok(!content.includes('NO code blocks except relevant Mermaid diagrams')); + }); + + test('documents Mermaid exception when plan Mermaid setting is enabled', async () => { + await mockConfigurationService.setConfig(ConfigKey.PlanAgentMermaidEnabled, true); + + const provider = createProvider(); + const agents = await provider.provideCustomAgents({}, {} as any); + + const content = await getAgentContent(agents[0]); + + assert.ok(content.includes('When a Mermaid diagram would materially improve the plan, you may include a concise fenced `mermaid` block and you MUST use vscode.mermaid-chat-features/renderMermaidDiagram to render it in the same response.')); + assert.ok(content.includes('Never inline Mermaid syntax as plain text. Mermaid content must stay inside fenced `mermaid` blocks only.')); + assert.ok(content.includes('NO code blocks except relevant Mermaid diagrams when they materially clarify architecture, sequencing, dependencies, or ownership in the plan.')); + assert.ok(content.includes('always call vscode.mermaid-chat-features/renderMermaidDiagram rather than leaving the syntax unrendered')); + }); + test('handles empty additionalTools array gracefully', async () => { await mockConfigurationService.setConfig(ConfigKey.PlanAgentAdditionalTools, []); diff --git a/src/extension/tools/vscode-node/switchAgentTool.ts b/src/extension/tools/vscode-node/switchAgentTool.ts index 9ab54f779b..bcdeb7383c 100644 --- a/src/extension/tools/vscode-node/switchAgentTool.ts +++ b/src/extension/tools/vscode-node/switchAgentTool.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { ConfigKey } from '../../../platform/configuration/common/configurationService'; import { CancellationToken } from '../../../util/vs/base/common/cancellation'; import { LanguageModelTextPart, LanguageModelToolResult, MarkdownString } from '../../../vscodeTypes'; import { PlanAgentProvider } from '../../agents/vscode-node/planAgentProvider'; @@ -26,7 +27,7 @@ export class SwitchAgentTool implements ICopilotTool { throw new Error(vscode.l10n.t('Only "Plan" agent is supported')); } - const planAgentBody = PlanAgentProvider.buildAgentBody(); + const planAgentBody = PlanAgentProvider.buildAgentBody(vscode.workspace.getConfiguration().get(ConfigKey.PlanAgentMermaidEnabled.fullyQualifiedId, false)); // Execute command to switch agent await vscode.commands.executeCommand('workbench.action.chat.toggleAgentMode', { diff --git a/src/platform/configuration/common/configurationService.ts b/src/platform/configuration/common/configurationService.ts index 1d4cfdaf7f..102c5db6c9 100644 --- a/src/platform/configuration/common/configurationService.ts +++ b/src/platform/configuration/common/configurationService.ts @@ -988,6 +988,8 @@ export namespace ConfigKey { /** Additional tools to enable for the Plan agent (additive to base tools) */ export const PlanAgentAdditionalTools = defineSetting('chat.planAgent.additionalTools', ConfigType.Simple, []); + /** Whether the Plan agent can include Mermaid diagrams in plans when relevant */ + export const PlanAgentMermaidEnabled = defineSetting('chat.planAgent.mermaid.enabled', ConfigType.Simple, false); /** Model override for Implement agent (empty = use default) */ export const ImplementAgentModel = defineSetting('chat.implementAgent.model', ConfigType.Simple, '');