Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ const agenticBrowserToolNames = browserChatToolReferenceNames.filter(name => nam
export const COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION = 'When a tool reports that its output was saved to a temporary file because it was too large, ONLY use the `view` tool with a narrow `view_range` to inspect that file. NEVER read it with shell commands such as `cat`, `head`, `tail`, or `sed`, because their output may be offloaded again.';
const largeOutputToolInstructions: ToolInstructionLine = () => COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION;

/**
* Coordinates work across sessions that may use different worktrees for the
* same repository. Session-management server tools are contributed to every
* Agent Host session; `hasTool` only reflects client tools, so this is
* intentionally unconditional.
*/
export const COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION = 'Before beginning code changes or creating a session, use `get_current_session` to identify the current session, then use `list_sessions` to check other active sessions across the same project or repository—not only the same working directory—and exclude the current session URI. Compare each session\'s title, activity, branch, pull request, and changed files to identify plausible overlap. For plausible overlaps, use `get_session_context` to confirm the scope and `send_message` to agree on ownership or sequencing before editing shared files; otherwise continue independently without waiting. Prefer dividing work to avoid duplicate effort, conflicting edits, and unnecessary merge conflicts.';
const sessionCoordinationToolInstructions: ToolInstructionLine = () => COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION;

/**
* Front-end guidance for the integrated browser tools, ported from the Copilot
* extension's `defaultAgentInstructions`/per-model prompts. Emitted only when the
Expand All @@ -65,7 +74,7 @@ const browserToolInstructions: ToolInstructionLine = hasTool => {
/**
* The registered tool-instruction lines, in render order.
*/
const TOOL_INSTRUCTION_LINES: readonly ToolInstructionLine[] = [largeOutputToolInstructions, browserToolInstructions];
const TOOL_INSTRUCTION_LINES: readonly ToolInstructionLine[] = [largeOutputToolInstructions, sessionCoordinationToolInstructions, browserToolInstructions];

/** Tool-search guidance mirrored from the Copilot extension prompt. */
const toolSearchToolInstructions: ToolInstructionLine = hasTool =>
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/agentHost/node/shared/sessionServerTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const sessionServerToolDefinitions: ToolDefinition[] = [
{
name: SessionServerToolName.ListSessions,
title: 'List Sessions',
description: 'List sessions and their compact metadata (status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.',
description: 'List sessions and their compact metadata (title, status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Results include the calling session and do not mark it as current; use `get_current_session` to identify and exclude it when comparing work. Sessions from different worktrees may belong to the same repository; compare their titles and changed files as well as project and git/GitHub metadata to identify potentially overlapping work. Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.',
inputSchema: listSessionsInputSchema,
annotations: { readOnlyHint: true },
},
Expand All @@ -137,7 +137,7 @@ export const sessionServerToolDefinitions: ToolDefinition[] = [
{
name: SessionServerToolName.CreateSession,
title: 'Create Session',
description: 'Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.',
description: 'Create a session in a workspace and start it with an initial prompt. Before creating one, use `list_sessions` to check for potentially overlapping active work. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.',
inputSchema: createSessionInputSchema,
annotations: { readOnlyHint: false },
},
Expand Down
24 changes: 13 additions & 11 deletions src/vs/platform/agentHost/test/node/agentHostPromptRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { SchemaValues } from '../../common/agentHostSchema.js';
import type { ModelSelection } from '../../common/state/protocol/state.js';
import { AgentHostPromptRegistry, agentHostPromptRegistry, type IAgentHostPromptContext } from '../../node/copilot/prompts/promptRegistry.js';
import { COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS, COPILOT_AGENT_HOST_WORKSPACELESS_INSTRUCTIONS, COPILOT_AGENT_HOST_SYSTEM_MESSAGE } from '../../node/copilot/prompts/systemMessage.js';
import { COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION } from '../../node/copilot/prompts/toolInstructions.js';
import { COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION, COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION } from '../../node/copilot/prompts/toolInstructions.js';
import { BrowserChatToolReferenceName } from '../../../browserView/common/browserChatToolReferenceNames.js';
import { CLIENT_TOOL_SEARCH_REFERENCE_NAME } from '../../common/toolSearchConstants.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
Expand All @@ -35,6 +35,8 @@ suite('AgentHostPromptRegistry', () => {
ensureNoDisposablesAreLeakedInTestSuite();

const LARGE_OUTPUT_LINE = COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION;
const SESSION_COORDINATION_LINE = COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION;
const BASE_LINES = `${LARGE_OUTPUT_LINE}\n${SESSION_COORDINATION_LINE}`;

const withUniversalAgentHostInstructions = (config: SystemMessageConfig): SystemMessageConfig => {
const content = config.content ? `${config.content}\n\n${COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS}` : COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS;
Expand All @@ -45,7 +47,7 @@ suite('AgentHostPromptRegistry', () => {
...config,
sections: {
...config.sections,
tool_instructions: { action: 'append', content: `\n${LARGE_OUTPUT_LINE}` } satisfies SectionOverride,
tool_instructions: { action: 'append', content: `\n${BASE_LINES}` } satisfies SectionOverride,
},
content,
};
Expand Down Expand Up @@ -176,7 +178,7 @@ suite('AgentHostPromptRegistry', () => {
mode: 'customize',
sections: {
...COPILOT_AGENT_HOST_SYSTEM_MESSAGE.sections,
tool_instructions: { action: 'append', content: `\n${LARGE_OUTPUT_LINE}` },
tool_instructions: { action: 'append', content: `\n${BASE_LINES}` },
},
content: `${COPILOT_AGENT_HOST_WORKSPACELESS_INSTRUCTIONS}\n\n${COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS}`,
}
Expand Down Expand Up @@ -205,7 +207,7 @@ suite('AgentHostPromptRegistry', () => {
mode: 'customize',
sections: {
guidelines: { action: 'append', content: 'Be concise.' },
tool_instructions: { action: 'append', content: `\n${LARGE_OUTPUT_LINE}` },
tool_instructions: { action: 'append', content: `\n${BASE_LINES}` },
},
content: `${COPILOT_AGENT_HOST_WORKSPACELESS_INSTRUCTIONS}\n\n${COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS}`,
}
Expand Down Expand Up @@ -233,7 +235,7 @@ suite('AgentHostPromptRegistry', () => {
const BROWSER_LINE = 'Use the browser tools (openBrowserPage, readPage, etc.) when beneficial for front-end tasks, such as when visualizing or validating UI changes.';
const browserTools = [BrowserChatToolReferenceName.OpenBrowserPage, BrowserChatToolReferenceName.ReadPage];

test('layers the unconditional large-output instruction onto the default config', () => {
test('layers the unconditional host-wide instructions onto the default config', () => {
const registry = new AgentHostPromptRegistry();
assert.deepStrictEqual(registry.resolveSystemMessageConfig({ id: 'm' }, context({}, ['anyTool'])), withUniversalAgentHostInstructions(COPILOT_AGENT_HOST_SYSTEM_MESSAGE));
});
Expand All @@ -246,7 +248,7 @@ suite('AgentHostPromptRegistry', () => {
mode: 'customize',
sections: {
identity: COPILOT_AGENT_HOST_SYSTEM_MESSAGE.sections.identity,
tool_instructions: { action: 'append', content: `\n${LARGE_OUTPUT_LINE}\n${BROWSER_LINE}` },
tool_instructions: { action: 'append', content: `\n${BASE_LINES}\n${BROWSER_LINE}` },
},
})
);
Expand All @@ -262,11 +264,11 @@ suite('AgentHostPromptRegistry', () => {
});
assert.deepStrictEqual(
registry.resolveSystemMessageConfig({ id: 'claude-x' }, context({}, browserTools)),
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${LARGE_OUTPUT_LINE}\n${BROWSER_LINE}` } } })
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${BASE_LINES}\n${BROWSER_LINE}` } } })
);
});

test('composes the unconditional large-output instruction with a per-model override', () => {
test('composes the unconditional host-wide instructions with a per-model override', () => {
const registry = new AgentHostPromptRegistry();
registry.registerPrompt(class {
static readonly familyPrefixes = ['claude'];
Expand All @@ -276,7 +278,7 @@ suite('AgentHostPromptRegistry', () => {
});
assert.deepStrictEqual(
registry.resolveSystemMessageConfig({ id: 'claude-x' }, context({}, ['anyTool'])),
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${LARGE_OUTPUT_LINE}` } } })
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${BASE_LINES}` } } })
);
});
});
Expand All @@ -296,7 +298,7 @@ suite('AgentHostPromptRegistry', () => {
mode: 'customize',
sections: {
identity: COPILOT_AGENT_HOST_SYSTEM_MESSAGE.sections.identity,
tool_instructions: { action: 'append', content: `\n${LARGE_OUTPUT_LINE}\n${TOOL_SEARCH_LINE}` },
tool_instructions: { action: 'append', content: `\n${BASE_LINES}\n${TOOL_SEARCH_LINE}` },
},
})
);
Expand Down Expand Up @@ -328,7 +330,7 @@ suite('AgentHostPromptRegistry', () => {
});
assert.deepStrictEqual(
registry.resolveSystemMessageConfig({ id: 'claude-x' }, context({}, [CLIENT_TOOL_SEARCH_REFERENCE_NAME], false, true)),
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${LARGE_OUTPUT_LINE}\n${TOOL_SEARCH_LINE}` } } })
withUniversalAgentHostInstructions({ mode: 'customize', sections: { tool_instructions: { action: 'append', content: `\nAlways prefer ripgrep.\n${BASE_LINES}\n${TOOL_SEARCH_LINE}` } } })
);
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/agentHost/test/node/copilotAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js
import { AgentHostCompletions, IAgentHostCompletions } from '../../node/agentHostCompletions.js';
import { COPILOT_AGENT_HOST_SYSTEM_MESSAGE, CopilotAgent, CopilotSessionEntry, getCopilotManagedSettingsDiagnostics, rebaseUnder, REFRESH_DEBOUNCE_MS, resolveCopilotOtlpMetricsEndpoint } from '../../node/copilot/copilotAgent.js';
import { COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS } from '../../node/copilot/prompts/systemMessage.js';
import { COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION } from '../../node/copilot/prompts/toolInstructions.js';
import { COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION, COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION } from '../../node/copilot/prompts/toolInstructions.js';
import { NULL_CHECKPOINT_SERVICE } from '../../common/agentHostCheckpointService.js';
import { IAgentHostReviewService, NULL_REVIEW_SERVICE } from '../../common/agentHostReviewService.js';
import { getCopilotHomePath } from '../../common/copilotHome.js';
Expand Down Expand Up @@ -4389,7 +4389,7 @@ suite('CopilotAgent', () => {
...COPILOT_AGENT_HOST_SYSTEM_MESSAGE.sections,
tool_instructions: {
action: 'append',
content: `\n${COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION}`,
content: `\n${COPILOT_AGENT_HOST_LARGE_OUTPUT_TOOL_INSTRUCTION}\n${COPILOT_AGENT_HOST_SESSION_COORDINATION_TOOL_INSTRUCTION}`,
},
},
content: COPILOT_AGENT_HOST_FILE_LINK_INSTRUCTIONS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ Best practices:
* PARALLELIZE - make multiple independent search calls in ONE call.
</code_search_tools>

When a tool reports that its output was saved to a temporary file because it was too large, ONLY use the `view` tool with a narrow `view_range` to inspect that file. NEVER read it with shell commands such as `cat`, `head`, `tail`, or `sed`, because their output may be offloaded again.</tools>
When a tool reports that its output was saved to a temporary file because it was too large, ONLY use the `view` tool with a narrow `view_range` to inspect that file. NEVER read it with shell commands such as `cat`, `head`, `tail`, or `sed`, because their output may be offloaded again.
Before beginning code changes or creating a session, use `get_current_session` to identify the current session, then use `list_sessions` to check other active sessions across the same project or repository—not only the same working directory—and exclude the current session URI. Compare each session's title, activity, branch, pull request, and changed files to identify plausible overlap. For plausible overlaps, use `get_session_context` to confirm the scope and `send_message` to agree on ownership or sequencing before editing shared files; otherwise continue independently without waiting. Prefer dividing work to avoid duplicate effort, conflicting edits, and unnecessary merge conflicts.</tools>

<custom_instruction>${repository_instructions}</custom_instruction>

Expand Down Expand Up @@ -1137,7 +1138,7 @@ View pull request or code review comments that the user has not reviewed yet. Th
```

#### list_sessions
List sessions and their compact metadata (status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.
List sessions and their compact metadata (title, status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Results include the calling session and do not mark it as current; use `get_current_session` to identify and exclude it when comparing work. Sessions from different worktrees may belong to the same repository; compare their titles and changed files as well as project and git/GitHub metadata to identify potentially overlapping work. Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.
```json
{
"type": "object",
Expand Down Expand Up @@ -1202,7 +1203,7 @@ Get metadata and the open link for the session this conversation is running in.
```

#### create_session
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
Create a session in a workspace and start it with an initial prompt. Before creating one, use `list_sessions` to check for potentially overlapping active work. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
```json
{
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ Best practices:
* PARALLELIZE - make multiple independent search calls in ONE call.
</code_search_tools>

When a tool reports that its output was saved to a temporary file because it was too large, ONLY use the `view` tool with a narrow `view_range` to inspect that file. NEVER read it with shell commands such as `cat`, `head`, `tail`, or `sed`, because their output may be offloaded again.</tools>
When a tool reports that its output was saved to a temporary file because it was too large, ONLY use the `view` tool with a narrow `view_range` to inspect that file. NEVER read it with shell commands such as `cat`, `head`, `tail`, or `sed`, because their output may be offloaded again.
Before beginning code changes or creating a session, use `get_current_session` to identify the current session, then use `list_sessions` to check other active sessions across the same project or repository—not only the same working directory—and exclude the current session URI. Compare each session's title, activity, branch, pull request, and changed files to identify plausible overlap. For plausible overlaps, use `get_session_context` to confirm the scope and `send_message` to agree on ownership or sequencing before editing shared files; otherwise continue independently without waiting. Prefer dividing work to avoid duplicate effort, conflicting edits, and unnecessary merge conflicts.</tools>

<custom_instruction>${repository_instructions}</custom_instruction>

Expand Down Expand Up @@ -1137,7 +1138,7 @@ View pull request or code review comments that the user has not reviewed yet. Th
```

#### list_sessions
List sessions and their compact metadata (status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.
List sessions and their compact metadata (title, status, activity, working directory, project, worktree changes, git/GitHub info, timestamps). Results include the calling session and do not mark it as current; use `get_current_session` to identify and exclude it when comparing work. Sessions from different worktrees may belong to the same repository; compare their titles and changed files as well as project and git/GitHub metadata to identify potentially overlapping work. Pass `session` to fetch a single known session by URI. By default archived sessions are omitted. Optionally filter by `status`, `workspace`, `withChanges`, `unread`, `withPullRequest`, `includeArchived`, `createdAfter`, or `createdBefore`.
```json
{
"type": "object",
Expand Down Expand Up @@ -1202,7 +1203,7 @@ Get metadata and the open link for the session this conversation is running in.
```

#### create_session
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
Create a session in a workspace and start it with an initial prompt. Before creating one, use `list_sessions` to check for potentially overlapping active work. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
```json
{
"type": "object",
Expand Down
Loading
Loading