Compare commits

...

35 Commits

Author SHA1 Message Date
Christian Gunderman ef23e1c35f Another example. 2026-02-16 13:58:35 -08:00
Christian Gunderman 1a6334287b Strengthen delegation prompt. 2026-02-16 13:56:50 -08:00
Christian Gunderman 9a2bc40052 De-emphasize cost of subagents. 2026-02-16 13:06:01 -08:00
Christian Gunderman 8a075a1ed9 Agents enabled. 2026-02-16 12:37:40 -08:00
Christian Gunderman 02bf9db95c Enable generalist. 2026-02-16 12:19:18 -08:00
Christian Gunderman 8d5a152d32 Revert "Emphasize cost of a turn."
This reverts commit 4ad4b2adc9.
2026-02-16 11:36:38 -08:00
Christian Gunderman 99cb82dc1b Sub-agents for context mgmt. 2026-02-16 11:22:44 -08:00
Christian Gunderman 2d1d7bb2d6 Merge branch 'gundermanc/ranged-reads3' into gundermanc/sub-agents-context
# Conflicts:
#	packages/core/src/tools/edit.ts
2026-02-16 11:08:18 -08:00
Christian Gunderman 4ad4b2adc9 Emphasize cost of a turn. 2026-02-16 10:59:52 -08:00
Christian Gunderman 0aa04bae21 Read min. 2026-02-15 22:35:21 -08:00
Christian Gunderman 1f03760429 Another prompt. 2026-02-15 21:19:38 -08:00
Christian Gunderman fc13adaca9 Better prompt. 2026-02-15 14:32:20 -08:00
Christian Gunderman 9844066de3 Prompt changes. 2026-02-15 13:09:54 -08:00
Christian Gunderman e85e24fd20 Provide feedback on fuzzy matches. 2026-02-15 12:46:38 -08:00
Christian Gunderman c5d01784ff More edits improvements. 2026-02-14 15:35:54 -08:00
Christian Gunderman 74bd9342c3 Drop threshold to 5%. 2026-02-14 14:14:09 -08:00
Christian Gunderman 1a6c3ec9ef Add edit correction. 2026-02-14 13:29:03 -08:00
Christian Gunderman 1e4cdfd691 Make max matches optional. 2026-02-13 17:07:59 -08:00
Christian Gunderman 6d1bfa8da9 Turns for days. 2026-02-13 13:52:39 -08:00
Christian Gunderman e0f0b7b56e Push limits. 2026-02-13 12:07:11 -08:00
Christian Gunderman 56d4759d41 Fix subagent break. 2026-02-13 12:03:55 -08:00
Christian Gunderman 46a0538ffd Parallelize with generalist agent. 2026-02-13 11:17:35 -08:00
Christian Gunderman 952f9e1b5e Fix subagents.eval.ts: update prompt and assertion for linter test 2026-02-13 11:17:35 -08:00
Christian Gunderman f0f47df01e Add line. 2026-02-13 09:05:28 -08:00
Christian Gunderman 1ee6900190 Agent provided prompt suggestions. 2026-02-12 17:15:34 -08:00
Christian Gunderman 89e81f2202 Tweaks. 2026-02-12 08:00:27 -08:00
Christian Gunderman cb230cc989 Prompt tweaks. 2026-02-12 06:45:05 -08:00
Christian Gunderman 9d1220bbb0 Ranged file reads. 2026-02-11 23:14:59 -08:00
Christian Gunderman b005b33ca7 Fix eval. 2026-02-11 21:50:45 -08:00
Christian Gunderman 2b6676d7dc Important word. 2026-02-11 18:09:11 -08:00
Christian Gunderman ac179551a8 Revert prompt. 2026-02-11 17:59:34 -08:00
Christian Gunderman 0b5c652548 Fix test. 2026-02-11 17:26:20 -08:00
Christian Gunderman c1a954341c Simpler assert. 2026-02-11 17:14:57 -08:00
Christian Gunderman 80017bf986 Firmer language. 2026-02-11 16:25:24 -08:00
Christian Gunderman 19140f66d6 Use grep over large files. 2026-02-11 15:40:08 -08:00
20 changed files with 891 additions and 61 deletions
+270
View File
@@ -0,0 +1,270 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import { READ_FILE_TOOL_NAME, EDIT_TOOL_NAME } from '@google/gemini-cli-core';
describe('Frugal reads eval', () => {
/**
* Ensures that the agent is frugal in its use of context by relying
* primarily on ranged reads when the line number is known, and combining
* nearby ranges into a single contiguous read to save tool calls.
*/
evalTest('ALWAYS_PASSES', {
name: 'should use ranged read when nearby lines are targeted',
files: {
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
type: 'module',
}),
'eslint.config.mjs': `export default [
{
files: ["**/*.ts"],
rules: {
"no-var": "error"
}
}
];`,
'linter_mess.ts': (() => {
const lines = [];
for (let i = 0; i < 1000; i++) {
if (i === 500 || i === 510 || i === 520) {
lines.push(`var oldVar${i} = "needs fix";`);
} else {
lines.push(`const goodVar${i} = "clean";`);
}
}
return lines.join('\n');
})(),
},
prompt:
'Fix all linter errors in linter_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
assert: async (rig) => {
const logs = rig.readToolLogs();
// Check if the agent read the whole file
const readCalls = logs.filter(
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
);
const targetFileReads = readCalls.filter((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.file_path.includes('linter_mess.ts');
});
expect(
targetFileReads.length,
'Agent should have used read_file to check context',
).toBeGreaterThan(0);
// We expect a single contiguous range covering all errors since they are near each other.
// Some models re-verify or read more than once, so we allow up to 4.
expect(
targetFileReads.length,
'Agent should have been efficient with ranged reads for near errors',
).toEqual(1);
let totalLinesRead = 0;
const readRanges: { offset: number; limit: number }[] = [];
for (const call of targetFileReads) {
const args = JSON.parse(call.toolRequest.args);
expect(
args.limit,
'Agent read the entire file (missing limit) instead of using ranged read',
).toBeDefined();
const limit = args.limit;
const offset = args.offset ?? 0;
totalLinesRead += limit;
readRanges.push({ offset, limit });
expect(args.limit, 'Agent read too many lines at once').toBeLessThan(
1001,
);
}
// Ranged read shoud be frugal and just enough to satisfy the task at hand.
expect(
totalLinesRead,
'Agent read more of the file than expected',
).toBeLessThan(1000);
// Check that we read around the error lines
const errorLines = [500, 510, 520];
for (const line of errorLines) {
const covered = readRanges.some(
(range) => line >= range.offset && line < range.offset + range.limit,
);
expect(covered, `Agent should have read around line ${line}`).toBe(
true,
);
}
const editCalls = logs.filter(
(log) => log.toolRequest?.name === EDIT_TOOL_NAME,
);
const targetEditCalls = editCalls.filter((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.file_path.includes('linter_mess.ts');
});
expect(
targetEditCalls.length,
'Agent should have made replacement calls on the target file',
).toBeGreaterThanOrEqual(3);
},
});
/**
* Ensures the agent uses multiple ranged reads when the targets are far
* apart to avoid the need to read the whole file.
*/
evalTest('ALWAYS_PASSES', {
name: 'should use ranged read when targets are far apart',
files: {
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
type: 'module',
}),
'eslint.config.mjs': `export default [
{
files: ["**/*.ts"],
rules: {
"no-var": "error"
}
}
];`,
'far_mess.ts': (() => {
const lines = [];
for (let i = 0; i < 1000; i++) {
if (i === 100 || i === 900) {
lines.push(`var oldVar${i} = "needs fix";`);
} else {
lines.push(`const goodVar${i} = "clean";`);
}
}
return lines.join('\n');
})(),
},
prompt:
'Fix all linter errors in far_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
assert: async (rig) => {
const logs = rig.readToolLogs();
const readCalls = logs.filter(
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
);
const targetFileReads = readCalls.filter((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.file_path.includes('far_mess.ts');
});
// The agent should use ranged reads to be frugal with context tokens,
// even if it requires multiple calls for far-apart errors.
expect(
targetFileReads.length,
'Agent should have used read_file to check context',
).toBeGreaterThan(0);
// We allow multiple calls since the errors are far apart.
expect(
targetFileReads.length,
'Agent should have used separate reads for far apart errors',
).toBeLessThanOrEqual(4);
for (const call of targetFileReads) {
const args = JSON.parse(call.toolRequest.args);
expect(
args.limit,
'Agent should have used ranged read (limit) to save tokens',
).toBeDefined();
}
},
});
/**
* Validates that the agent reads the entire file if there are lots of matches
* (e.g.: 10), as it's more efficient than many small ranged reads.
*/
evalTest('ALWAYS_PASSES', {
name: 'should read the entire file when there are many matches',
files: {
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
type: 'module',
}),
'eslint.config.mjs': `export default [
{
files: ["**/*.ts"],
rules: {
"no-var": "error"
}
}
];`,
'many_mess.ts': (() => {
const lines = [];
for (let i = 0; i < 1000; i++) {
if (i % 100 === 0) {
lines.push(`var oldVar${i} = "needs fix";`);
} else {
lines.push(`const goodVar${i} = "clean";`);
}
}
return lines.join('\n');
})(),
},
prompt:
'Fix all linter errors in many_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
assert: async (rig) => {
const logs = rig.readToolLogs();
const readCalls = logs.filter(
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
);
const targetFileReads = readCalls.filter((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.file_path.includes('many_mess.ts');
});
expect(
targetFileReads.length,
'Agent should have used read_file to check context',
).toBeGreaterThan(0);
// In this case, we expect the agent to realize there are many scattered errors
// and just read the whole file to be efficient with tool calls.
const readEntireFile = targetFileReads.some((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.limit === undefined;
});
expect(
readEntireFile,
'Agent should have read the entire file because of the high number of scattered matches',
).toBe(true);
// Check that the agent actually fixed the errors
const editCalls = logs.filter(
(log) => log.toolRequest?.name === EDIT_TOOL_NAME,
);
const targetEditCalls = editCalls.filter((call) => {
const args = JSON.parse(call.toolRequest.args);
return args.file_path.includes('many_mess.ts');
});
expect(
targetEditCalls.length,
'Agent should have made replacement calls on the target file',
).toBeGreaterThanOrEqual(1);
},
});
});
+72 -10
View File
@@ -25,7 +25,7 @@ describe('Frugal Search', () => {
return args;
};
evalTest('USUALLY_PASSES', {
evalTest('ALWAYS_PASSES', {
name: 'should use targeted search with limit',
prompt: 'find me a sample usage of path.resolve() in the codebase',
files: {
@@ -128,17 +128,79 @@ describe('Frugal Search', () => {
grepParams.map((p) => p.total_max_matches),
)}`,
).toBe(true);
},
});
/**
* Ensure that the agent makes use of either grep or ranged reads in fulfilling this task.
* The task is specifically phrased to not evoke "view" or "search" specifically because
* the model implicitly understands that such tasks are searches. This covers the case of
* an unexpectedly large file benefitting from frugal approaches to viewing, like grep, or
* ranged reads.
*/
evalTest('ALWAYS_PASSES', {
name: 'should use grep or ranged read for large files',
prompt: 'What year was legacy_processor.ts written?',
files: {
'src/utils.ts': 'export const add = (a, b) => a + b;',
'src/types.ts': 'export type ID = string;',
'src/legacy_processor.ts': [
'// Copyright 2005 Legacy Systems Inc.',
...Array.from(
{ length: 5000 },
(_, i) =>
`// Legacy code block ${i} - strictly preserved for backward compatibility`,
),
].join('\\n'),
'README.md': '# Project documentation',
},
assert: async (rig) => {
const toolCalls = rig.readToolLogs();
const getParams = (call: any) => {
let args = call.toolRequest.args;
if (typeof args === 'string') {
try {
args = JSON.parse(args);
} catch (e) {
// Ignore parse errors
}
}
return args;
};
// Check for wasteful full file reads
const fullReads = toolCalls.filter((call) => {
if (call.toolRequest.name !== 'read_file') return false;
const args = getParams(call);
return (
args.file_path === 'src/legacy_processor.ts' &&
(args.limit === undefined || args.limit === null)
);
});
const hasMaxMatchesPerFileLimit = grepParams.some(
(p) =>
p.max_matches_per_file !== undefined && p.max_matches_per_file <= 5,
);
expect(
hasMaxMatchesPerFileLimit,
`Expected agent to use a small max_matches_per_file (<= 5) for a sample usage request. Actual values: ${JSON.stringify(
grepParams.map((p) => p.max_matches_per_file),
)}`,
).toBe(true);
fullReads.length,
'Agent should not attempt to read the entire large file at once',
).toBe(0);
// Check that it actually tried to find it using appropriate tools
const validAttempts = toolCalls.filter((call) => {
const args = getParams(call);
if (call.toolRequest.name === 'grep_search') {
return true;
}
if (
call.toolRequest.name === 'read_file' &&
args.file_path === 'src/legacy_processor.ts' &&
args.limit !== undefined
) {
return true;
}
return false;
});
expect(validAttempts.length).toBeGreaterThan(0);
},
});
});
+75
View File
@@ -50,4 +50,79 @@ describe('subagent eval test cases', () => {
await rig.expectToolCallSuccess(['docs-agent']);
},
});
evalTest('ALWAYS_PASSES', {
name: 'should fix linter errors in multiple projects using implicit parallelism',
prompt: 'Fix all linter errors.',
timeout: 600000,
files: {
'project-a/eslint.config.js': `
module.exports = [
{
files: ["**/*.js"],
rules: {
"no-var": "error"
}
}
];
`,
'project-a/index.js': 'var x = 1;',
'project-b/eslint.config.js': `
module.exports = [
{
files: ["**/*.js"],
rules: {
"no-console": "error"
}
}
];
`,
'project-b/main.js': 'console.log("hello");',
},
assert: async (rig) => {
const fileA = rig.readFile('project-a/index.js');
const fileB = rig.readFile('project-b/main.js');
if (fileA.includes('var x')) {
throw new Error(`project-a/index.js was not fixed. Content:\n${fileA}`);
}
// Check if console.log is present and NOT commented out or disabled.
const lines = fileB.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('console.log')) {
const isCommented = line.trim().startsWith('//');
const isDisabled =
(i > 0 && lines[i - 1].includes('eslint-disable')) ||
line.includes('eslint-disable-line');
if (!isCommented && !isDisabled) {
throw new Error(
`project-b/main.js was not fixed (console.log present without disable/comment). Content:\n${fileB}`,
);
}
}
}
// Assert that the agent delegated to a subagent for each project.
const toolLogs = rig.readToolLogs();
const subagentCalls = toolLogs.filter((log) => {
if (log.toolRequest.name === 'generalist') return true;
if (log.toolRequest.name === 'delegate_to_agent') {
try {
const args = JSON.parse(log.toolRequest.args);
return args.agent_name === 'generalist';
} catch {
return false;
}
}
return false;
});
if (subagentCalls.length < 2) {
throw new Error(
`Expected at least 2 generalist calls, but found ${subagentCalls.length}`,
);
}
},
});
});
+1 -1
View File
@@ -1535,7 +1535,7 @@ const SETTINGS_SCHEMA = {
label: 'Enable Agents',
category: 'Experimental',
requiresRestart: true,
default: false,
default: true,
description:
'Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents',
showInDialog: false,
@@ -10,6 +10,8 @@ import {
GREP_TOOL_NAME,
LS_TOOL_NAME,
READ_FILE_TOOL_NAME,
SHELL_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
} from '../tools/tool-names.js';
import {
DEFAULT_THINKING_MODE,
@@ -66,8 +68,8 @@ export const CodebaseInvestigatorAgent = (
name: 'codebase_investigator',
kind: 'local',
displayName: 'Codebase Investigator Agent',
description: `The specialized tool for codebase analysis, architectural mapping, and understanding system-wide dependencies.
Invoke this tool for tasks like vague requests, bug root-cause analysis, system refactoring, comprehensive feature implementation or to answer questions about the codebase that require investigation.
description: `The specialized tool for codebase analysis, architectural mapping, understanding system-wide dependencies, and VERIFYING fixes.
Invoke this tool for tasks like vague requests, bug root-cause analysis, system refactoring, comprehensive feature implementation or to answer questions about the codebase that require investigation or final verification.
It returns a structured report with key file paths, symbols, and actionable architectural insights.`,
inputConfig: {
inputSchema: {
@@ -114,12 +116,14 @@ export const CodebaseInvestigatorAgent = (
},
toolConfig: {
// Grant access only to read-only tools.
// Grant access to investigation tools.
tools: [
LS_TOOL_NAME,
READ_FILE_TOOL_NAME,
GLOB_TOOL_NAME,
GREP_TOOL_NAME,
SHELL_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
],
},
@@ -144,7 +148,8 @@ You operate in a non-interactive loop and must reason based on the information p
1. **DEEP ANALYSIS, NOT JUST FILE FINDING:** Your goal is to understand the *why* behind the code. Don't just list files; explain their purpose and the role of their key components. Your final report should empower another agent to make a correct and complete fix.
2. **SYSTEMATIC & CURIOUS EXPLORATION:** Start with high-value clues (like tracebacks or ticket numbers) and broaden your search as needed. Think like a senior engineer doing a code review. An initial file contains clues (imports, function calls, puzzling logic). **If you find something you don't understand, you MUST prioritize investigating it until it is clear.** Treat confusion as a signal to dig deeper.
3. **HOLISTIC & PRECISE:** Your goal is to find the complete and minimal set of locations that need to be understood or changed. Do not stop until you are confident you have considered the side effects of a potential fix (e.g., type errors, breaking changes to callers, opportunities for code reuse).
4. **Web Search:** You are allowed to use the \`web_fetch\` tool to research libraries, language features, or concepts you don't understand (e.g., "what does gettext.translation do with localedir=None?").
4. **Tool Usage:** You are allowed to use the \`run_shell_command\` tool to run linters, tests, or other diagnostic commands to gather information or verify that issues are resolved. Do NOT use it to perform implementation changes.
5. **Web Search:** You are allowed to use the \`web_fetch\` tool to research libraries, language features, or concepts you don't understand (e.g., "what does gettext.translation do with localedir=None?").
</RULES>
---
## Scratchpad Management
@@ -9,12 +9,18 @@ import { GeneralistAgent } from './generalist-agent.js';
import { makeFakeConfig } from '../test-utils/config.js';
import type { ToolRegistry } from '../tools/tool-registry.js';
import type { AgentRegistry } from './registry.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
describe('GeneralistAgent', () => {
it('should create a valid generalist agent definition', () => {
const config = makeFakeConfig();
vi.spyOn(config, 'getToolRegistry').mockReturnValue({
getAllToolNames: () => ['tool1', 'tool2', 'agent-tool'],
getAllTools: () => [
{ name: 'tool1' },
{ name: 'tool2' },
{ name: 'agent-tool' },
],
} as unknown as ToolRegistry);
vi.spyOn(config, 'getAgentRegistry').mockReturnValue({
getDirectoryContext: () => 'mock directory context',
@@ -34,4 +40,20 @@ describe('GeneralistAgent', () => {
// Ensure it's non-interactive
expect(agent.promptConfig.systemPrompt).toContain('non-interactive');
});
it('should use fully qualified names for MCP tools', () => {
const config = makeFakeConfig();
const mockMcpTool = Object.create(DiscoveredMCPTool.prototype);
mockMcpTool.getFullyQualifiedName = () => 'server__tool';
mockMcpTool.name = 'tool';
vi.spyOn(config, 'getToolRegistry').mockReturnValue({
getAllTools: () => [{ name: 'normal-tool' }, mockMcpTool],
} as unknown as ToolRegistry);
const agent = GeneralistAgent(config);
expect(agent.toolConfig?.tools).toContain('normal-tool');
expect(agent.toolConfig?.tools).toContain('server__tool');
});
});
+13 -4
View File
@@ -8,6 +8,7 @@ import { z } from 'zod';
import type { Config } from '../config/config.js';
import { getCoreSystemPrompt } from '../core/prompts.js';
import type { LocalAgentDefinition } from './types.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
const GeneralistAgentSchema = z.object({
response: z.string().describe('The final response from the agent.'),
@@ -23,9 +24,9 @@ export const GeneralistAgent = (
kind: 'local',
name: 'generalist',
displayName: 'Generalist Agent',
description:
"A general-purpose AI agent with access to all tools. Use it for complex tasks that don't fit into other specialized agents.",
experimental: true,
description: `A general-purpose AI agent with access to all tools.
- ALWAYS use it to break up and parallelize independent pieces of a larger task, when possible.
`,
inputConfig: {
inputSchema: {
type: 'object',
@@ -47,7 +48,15 @@ export const GeneralistAgent = (
model: 'inherit',
},
get toolConfig() {
const tools = config.getToolRegistry().getAllToolNames();
const tools = config
.getToolRegistry()
.getAllTools()
.map((tool) => {
if (tool instanceof DiscoveredMCPTool) {
return tool.getFullyQualifiedName();
}
return tool.name;
});
return {
tools,
};
+7 -5
View File
@@ -44,6 +44,7 @@ function makeMockedConfig(params?: Partial<ConfigParameters>): Config {
const config = makeFakeConfig(params);
vi.spyOn(config, 'getToolRegistry').mockReturnValue({
getAllToolNames: () => ['tool1', 'tool2'],
getAllTools: () => [{ name: 'tool1' }, { name: 'tool2' }],
} as unknown as ToolRegistry);
vi.spyOn(config, 'getAgentRegistry').mockReturnValue({
getDirectoryContext: () => 'mock directory context',
@@ -259,6 +260,7 @@ describe('AgentRegistry', () => {
overrides: {
codebase_investigator: { enabled: false },
cli_help: { enabled: false },
generalist: { enabled: false },
},
},
});
@@ -296,20 +298,20 @@ describe('AgentRegistry', () => {
expect(registry.getDefinition('cli_help')).toBeUndefined();
});
it('should NOT register generalist agent by default (because it is experimental)', async () => {
it('should register generalist agent by default', async () => {
const config = makeMockedConfig();
const registry = new TestableAgentRegistry(config);
await registry.initialize();
expect(registry.getDefinition('generalist')).toBeUndefined();
expect(registry.getDefinition('generalist')).toBeDefined();
});
it('should register generalist agent if explicitly enabled via override', async () => {
it('should NOT register generalist agent if explicitly disabled via override', async () => {
const config = makeMockedConfig({
agents: {
overrides: {
generalist: { enabled: true },
generalist: { enabled: false },
},
},
});
@@ -317,7 +319,7 @@ describe('AgentRegistry', () => {
await registry.initialize();
expect(registry.getDefinition('generalist')).toBeDefined();
expect(registry.getDefinition('generalist')).toBeUndefined();
});
it('should NOT register a non-experimental agent if enabled is false', async () => {
+2 -2
View File
@@ -43,7 +43,7 @@ export const DEFAULT_QUERY_STRING = 'Get Started!';
/**
* The default maximum number of conversational turns for an agent.
*/
export const DEFAULT_MAX_TURNS = 15;
export const DEFAULT_MAX_TURNS = 50;
/**
* The default maximum execution time for an agent in minutes.
@@ -200,7 +200,7 @@ export interface RunConfig {
maxTimeMinutes?: number;
/**
* The maximum number of conversational turns.
* If not specified, defaults to DEFAULT_MAX_TURNS (15).
* If not specified, defaults to DEFAULT_MAX_TURNS (50).
*/
maxTurns?: number;
}
+1
View File
@@ -1081,6 +1081,7 @@ describe('Server Config (config.ts)', () => {
overrides: {
codebase_investigator: { enabled: false },
cli_help: { enabled: false },
generalist: { enabled: false },
},
},
};
+17 -9
View File
@@ -751,7 +751,7 @@ export class Config {
this.model = params.model;
this.disableLoopDetection = params.disableLoopDetection ?? false;
this._activeModel = params.model;
this.enableAgents = params.enableAgents ?? false;
this.enableAgents = params.enableAgents ?? true;
this.agents = params.agents ?? {};
this.disableLLMCorrection = params.disableLLMCorrection ?? true;
this.planEnabled = params.plan ?? false;
@@ -2357,6 +2357,7 @@ export class Config {
const maybeRegister = (
toolClass: { name: string; Name?: string },
registerFn: () => void,
defaultEnabled = true,
) => {
const className = toolClass.name;
const toolName = toolClass.Name || className;
@@ -2364,7 +2365,7 @@ export class Config {
// On some platforms, the className can be minified to _ClassName.
const normalizedClassName = className.replace(/^_+/, '');
let isEnabled = true; // Enabled by default if coreTools is not set.
let isEnabled = defaultEnabled; // Use provided default if coreTools is not set.
if (coreTools) {
isEnabled = coreTools.some(
(tool) =>
@@ -2396,18 +2397,24 @@ export class Config {
errorString = String(error);
}
if (useRipgrep) {
maybeRegister(RipGrepTool, () =>
registry.registerTool(new RipGrepTool(this, this.messageBus)),
maybeRegister(
RipGrepTool,
() => registry.registerTool(new RipGrepTool(this, this.messageBus)),
false,
);
} else {
logRipgrepFallback(this, new RipgrepFallbackEvent(errorString));
maybeRegister(GrepTool, () =>
registry.registerTool(new GrepTool(this, this.messageBus)),
maybeRegister(
GrepTool,
() => registry.registerTool(new GrepTool(this, this.messageBus)),
false,
);
}
} else {
maybeRegister(GrepTool, () =>
registry.registerTool(new GrepTool(this, this.messageBus)),
maybeRegister(
GrepTool,
() => registry.registerTool(new GrepTool(this, this.messageBus)),
false,
);
}
@@ -2468,7 +2475,8 @@ export class Config {
if (
this.isAgentsEnabled() ||
agentsOverrides['codebase_investigator']?.enabled !== false ||
agentsOverrides['cli_help']?.enabled !== false
agentsOverrides['cli_help']?.enabled !== false ||
agentsOverrides['generalist']?.enabled !== false
) {
const definitions = this.agentRegistry.getAllDefinitions();
@@ -520,8 +520,10 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -650,8 +652,10 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -746,8 +750,10 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1311,8 +1317,10 @@ exports[`Core System Prompt (prompts.ts) > should include available_skills with
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1437,8 +1445,10 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1554,8 +1564,10 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1671,8 +1683,10 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1784,8 +1798,10 @@ exports[`Core System Prompt (prompts.ts) > should include planning phase suggest
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -1896,8 +1912,10 @@ exports[`Core System Prompt (prompts.ts) > should include sub-agents in XML for
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -2248,8 +2266,10 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -2361,8 +2381,10 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -2585,8 +2607,10 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
@@ -2698,8 +2722,10 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results by explicitly setting \`total_max_matches\` or \`max_matches_per_file\`, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
- Limit unnecessary context consumption from file reads by using grep_search (configured with \`max_matches_per_file\`) to search large files (> 1kb) or read_file with the desired offset and limit.
- If the file is small, prefer reading the whole thing over "scrolling" through it by reading ranges repeatedly.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
+2 -2
View File
@@ -95,7 +95,7 @@ describe('Core System Prompt (prompts.ts)', () => {
},
isInteractive: vi.fn().mockReturnValue(true),
isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
isAgentsEnabled: vi.fn().mockReturnValue(false),
isAgentsEnabled: vi.fn().mockReturnValue(true),
getPreviewFeatures: vi.fn().mockReturnValue(true),
getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO),
getActiveModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL),
@@ -375,7 +375,7 @@ describe('Core System Prompt (prompts.ts)', () => {
},
isInteractive: vi.fn().mockReturnValue(false),
isInteractiveShellEnabled: vi.fn().mockReturnValue(false),
isAgentsEnabled: vi.fn().mockReturnValue(false),
isAgentsEnabled: vi.fn().mockReturnValue(true),
getModel: vi.fn().mockReturnValue('auto'),
getActiveModel: vi.fn().mockReturnValue(PREVIEW_GEMINI_MODEL),
getPreviewFeatures: vi.fn().mockReturnValue(true),
+4 -4
View File
@@ -497,15 +497,15 @@ function workflowStepPlan(options: PrimaryWorkflowsOptions): string {
return `2. **Plan:** An approved plan is available for this task. Use this file as a guide for your implementation. You MUST read this file before proceeding. If you discover new requirements or need to change the approach, confirm with the user and update this plan file to reflect the updated design decisions or discovered requirements.`;
}
if (options.enableCodebaseInvestigator && options.enableWriteTodosTool) {
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. If 'codebase_investigator' was used, do not ignore the output of the agent, you must use it as the foundation of your plan. For complex tasks, break them down into smaller, manageable subtasks and use the \`${WRITE_TODOS_TOOL_NAME}\` tool to track your progress. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. If 'codebase_investigator' was used, do not ignore the output of the agent, you must use it as the foundation of your plan. For complex tasks, break them down into smaller, manageable subtasks and use the \`${WRITE_TODOS_TOOL_NAME}\` tool to track your progress. When these subtasks are independent, leverage the 'generalist' agent to execute them in parallel, increasing efficiency. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
}
if (options.enableCodebaseInvestigator) {
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. If 'codebase_investigator' was used, do not ignore the output of the agent, you must use it as the foundation of your plan. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. If 'codebase_investigator' was used, do not ignore the output of the agent, you must use it as the foundation of your plan. For tasks that can be broken down into independent sub-tasks, leverage the 'generalist' agent to parallelize their execution. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
}
if (options.enableWriteTodosTool) {
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. For complex tasks, break them down into smaller, manageable subtasks and use the \`${WRITE_TODOS_TOOL_NAME}\` tool to track your progress. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
return `2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. For complex tasks, break them down into smaller, manageable subtasks and use the \`${WRITE_TODOS_TOOL_NAME}\` tool to track your progress. When these subtasks are independent, leverage the 'generalist' agent to execute them in parallel, increasing efficiency. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.`;
}
return "2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.";
return "2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. If the user's request implies a change but does not explicitly state it, **YOU MUST ASK** for confirmation before modifying code. For tasks that can be broken down into independent sub-tasks, leverage the 'generalist' agent to parallelize their execution. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.";
}
function workflowVerifyStandardsSuffix(interactive: boolean): string {
+76 -2
View File
@@ -165,8 +165,82 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
Be strategic in your use of the available tools to minimize unnecessary context usage while still
providing the best answer that you can.
Consider the following when estimating the cost of your approach:
<estimating_context_usage>
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
- Unnecessary turns are generally more expensive than other types of wasted context.
- Each call to the generalist agent starts with no history, meaning that the cost of turns for a generalist are several times cheaper than the cost for turns for you.
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
</estimating_context_usage>
Use the following guidelines to optimize your search and read patterns.
<guidelines>
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to ${GREP_TOOL_NAME}, to enable you to skip using an extra turn reading the file.
- Prefer using tools like ${GREP_TOOL_NAME} to identify points of interest instead of reading lots of files individually.
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME}.
- ${READ_FILE_TOOL_NAME} fails if old_string is ambiguous, causing extra turns. Take care to read enough with ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME} to make the edit unambiguous.
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
- You can conserve your own context budget by delegating context intensive tasks to the \`generalist\` sub-agent.
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
</guidelines>
Keep in mind the following when delegating to the generalist sub-agent:
<subagent_delegation>
- A call to a sub-agent (like generalist) costs you 1 turn, but it saves you the token volume of all subsequent manual work.
- Though turns are very expensive, you can ultimately reduce your turn count, and your context usage by delegating proactively to the generalist agent.
- Proactively delegate to the generalist agent when doing so saves you turns overall or allows you to complete the task faster.
- Use the generalist agent to break up larger tasks into sub-tasks. Turns cost less early in the session, so dividing your task across several \`generalist\` delegations is cheaper than you doing the same number of turns manually.
- You can use the generalist to avoid polluting your context window with steps taken to find context.
</subagent_delegation>
<examples>
- **Searching:** utilize search tools like ${GREP_TOOL_NAME} and ${GLOB_TOOL_NAME} with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
- **Generalist delegation**: delegate tasks that require multiple turns to the generalist subagent.
</examples>
## Parallelism
You MUST ALWAYS utilize the generalist subagent to conserve context when doing repetitive tasks and parallelize independent bodies of work, even if you think you don't need to. This is very IMPORTANT to ensure you stay on track on repetitive tasks and/or complete tasks in a timely fashion.
Tasks that should be delegated include:
<should_delegate>
- Creating, refactoring, building, or updating independent projects or directories, one per generalist call.
- Refactor separate files by splitting them into batches, one batch for each generalist.
- Parallelizing finding the answer to multiple questions, one parallel generalist call per question.
- Repetitive changes across a codebase should be batched and delegated to a subagent.
</should_delegate>
<guidelines>
Try to delegate similarly sized pieces to the generalist. For example:
- Creating two projects -> each can be its own delegation.
- Repetitive code changes -> first, check the size of the work, and split it into batches of similar size.
- You can use up to 4 parallel subagents. Use as many as you can to complete the task faster.
</guidelines>
Always use parallel agents (via calls to \`generalist\`) or parallel tool calls when
able to do so safely.
<rules_for_parallelism>
Generally safe to parallelize and minimal risk of concurrency issues:
<what_you_can_parallelize>
- Edits to different files.
- Multi-step tasks that touch different folders, with generalist.
- Multi-step read-only investigations, with generalist.
- grep_search and ls to different directories.
</what_you_can_parallelize>
Not safe to parallelize. Will likely cause concurrency issues.
<what_you_cannot_parallelize>
- generalist tasks that involve making builds or edits to the same set of files.
</what_you_cannot_parallelize>
</rules_for_parallelism>
## Engineering Standards
- **Contextual Precedence:** Instructions found in ${formattedFilenames} files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
+95
View File
@@ -373,6 +373,101 @@ describe('EditTool', () => {
expect(result.occurrences).toBe(1);
});
it('should perform a fuzzy replacement when exact match fails but similarity is high', async () => {
const content =
'const myConfig = {\n enableFeature: true,\n retries: 3\n};';
// Typo: missing comma after true
const oldString =
'const myConfig = {\n enableFeature: true\n retries: 3\n};';
const newString =
'const myConfig = {\n enableFeature: false,\n retries: 5\n};';
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'config.ts',
instruction: 'update config',
old_string: oldString,
new_string: newString,
},
currentContent: content,
abortSignal,
});
expect(result.occurrences).toBe(1);
expect(result.newContent).toBe(newString);
});
it('should NOT perform a fuzzy replacement when similarity is below threshold', async () => {
const content =
'const myConfig = {\n enableFeature: true,\n retries: 3\n};';
// Completely different string
const oldString = 'function somethingElse() {\n return false;\n}';
const newString =
'const myConfig = {\n enableFeature: false,\n retries: 5\n};';
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'config.ts',
instruction: 'update config',
old_string: oldString,
new_string: newString,
},
currentContent: content,
abortSignal,
});
expect(result.occurrences).toBe(0);
expect(result.newContent).toBe(content);
});
it('should perform multiple fuzzy replacements if multiple valid matches are found', async () => {
const content = `
function doIt() {
console.log("hello");
}
function doIt() {
console.log("hello");
}
`;
// old_string uses single quotes, file uses double.
// This is a fuzzy match (quote difference).
const oldString = `
function doIt() {
console.log('hello');
}
`.trim();
const newString = `
function doIt() {
console.log("bye");
}
`.trim();
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'test.ts',
instruction: 'update',
old_string: oldString,
new_string: newString,
},
currentContent: content,
abortSignal,
});
expect(result.occurrences).toBe(2);
const expectedContent = `
function doIt() {
console.log("bye");
}
function doIt() {
console.log("bye");
}
`;
expect(result.newContent).toBe(expectedContent);
});
it('should NOT insert extra newlines when replacing a block preceded by a blank line (regression)', async () => {
const content = '\n function oldFunc() {\n // some code\n }';
const result = await calculateReplacement(mockConfig, {
+176
View File
@@ -47,6 +47,11 @@ import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
import { EDIT_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
import levenshtein from 'fast-levenshtein';
const ENABLE_FUZZY_MATCH_RECOVERY = true;
const FUZZY_MATCH_THRESHOLD = 0.1; // Allow up to 10% weighted difference
const WHITESPACE_PENALTY_FACTOR = 0.1; // Whitespace differences cost 10% of a character difference
interface ReplacementContext {
params: EditToolParams;
currentContent: string;
@@ -58,6 +63,8 @@ interface ReplacementResult {
occurrences: number;
finalOldString: string;
finalNewString: string;
strategy?: 'exact' | 'flexible' | 'regex' | 'fuzzy';
matchRanges?: Array<{ start: number; end: number }>;
}
export function applyReplacement(
@@ -301,6 +308,14 @@ export async function calculateReplacement(
return regexResult;
}
let fuzzyResult;
if (
ENABLE_FUZZY_MATCH_RECOVERY &&
(fuzzyResult = await calculateFuzzyReplacement(config, context))
) {
return fuzzyResult;
}
return {
newContent: currentContent,
occurrences: 0,
@@ -391,6 +406,8 @@ interface CalculatedEdit {
error?: { display: string; raw: string; type: ToolErrorType };
isNewFile: boolean;
originalLineEnding: '\r\n' | '\n';
strategy?: 'exact' | 'flexible' | 'regex' | 'fuzzy';
matchRanges?: Array<{ start: number; end: number }>;
}
class EditToolInvocation
@@ -516,6 +533,8 @@ class EditToolInvocation
isNewFile: false,
error: undefined,
originalLineEnding,
strategy: secondAttemptResult.strategy,
matchRanges: secondAttemptResult.matchRanges,
};
}
@@ -629,6 +648,8 @@ class EditToolInvocation
isNewFile: false,
error: undefined,
originalLineEnding,
strategy: replacementResult.strategy,
matchRanges: replacementResult.matchRanges,
};
}
@@ -855,6 +876,10 @@ class EditToolInvocation
? `Created new file: ${this.params.file_path} with provided content.`
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
];
const fuzzyFeedback = getFuzzyMatchFeedback(editData);
if (fuzzyFeedback) {
llmSuccessMessageParts.push(fuzzyFeedback);
}
if (this.params.modified_by_user) {
llmSuccessMessageParts.push(
`User modified the \`new_string\` content to be: ${this.params.new_string}.`,
@@ -1007,3 +1032,154 @@ export class EditTool
};
}
}
function stripWhitespace(str: string): string {
return str.replace(/\s/g, '');
}
function getFuzzyMatchFeedback(editData: CalculatedEdit): string | null {
if (
editData.strategy === 'fuzzy' &&
editData.matchRanges &&
editData.matchRanges.length > 0
) {
const ranges = editData.matchRanges
.map((r) => (r.start === r.end ? `${r.start}` : `${r.start}-${r.end}`))
.join(', ');
return `Applied fuzzy match at line${editData.matchRanges.length > 1 ? 's' : ''} ${ranges}.`;
}
return null;
}
async function calculateFuzzyReplacement(
config: Config,
context: ReplacementContext,
): Promise<ReplacementResult | null> {
const { currentContent, params } = context;
const { old_string, new_string } = params;
// Pre-check: Don't fuzzy match very short strings to avoid false positives
if (old_string.length < 10) {
return null;
}
const normalizedCode = currentContent.replace(/\r\n/g, '\n');
const normalizedSearch = old_string.replace(/\r\n/g, '\n');
const normalizedReplace = new_string.replace(/\r\n/g, '\n');
const sourceLines = normalizedCode.match(/.*(?:\n|$)/g)?.slice(0, -1) ?? [];
const searchLines = normalizedSearch
.match(/.*(?:\n|$)/g)
?.slice(0, -1)
.map((l) => l.trimEnd()); // Trim end of search lines to be more robust
if (!searchLines || searchLines.length === 0) {
return null;
}
const N = searchLines.length;
const candidates: Array<{ index: number; score: number }> = [];
const searchBlock = searchLines.join('\n');
// Sliding window
for (let i = 0; i <= sourceLines.length - N; i++) {
const windowLines = sourceLines.slice(i, i + N);
const windowText = windowLines.map((l) => l.trimEnd()).join('\n'); // Normalized join for comparison
// Length Heuristic Optimization
const lengthDiff = Math.abs(windowText.length - searchBlock.length);
if (
lengthDiff / searchBlock.length >
FUZZY_MATCH_THRESHOLD / WHITESPACE_PENALTY_FACTOR
) {
continue;
}
// Tiered Scoring
const d_raw = levenshtein.get(windowText, searchBlock);
const d_norm = levenshtein.get(
stripWhitespace(windowText),
stripWhitespace(searchBlock),
);
const weightedDist = d_norm + (d_raw - d_norm) * WHITESPACE_PENALTY_FACTOR;
const score = weightedDist / searchBlock.length;
if (score <= FUZZY_MATCH_THRESHOLD) {
candidates.push({ index: i, score });
}
}
if (candidates.length === 0) {
return null;
}
// Select best non-overlapping matches
// Sort by score ascending. If scores equal, prefer earlier index (stable sort).
candidates.sort((a, b) => a.score - b.score || a.index - b.index);
const selectedMatches: Array<{ index: number; score: number }> = [];
for (const candidate of candidates) {
// Check for overlap with already selected matches
// Two windows overlap if their start indices are within N lines of each other
// (Assuming window size N. Actually overlap is |i - j| < N)
const overlaps = selectedMatches.some(
(m) => Math.abs(m.index - candidate.index) < N,
);
if (!overlaps) {
selectedMatches.push(candidate);
}
}
// If we found matches, apply them
if (selectedMatches.length > 0) {
const event = new EditStrategyEvent('fuzzy');
logEditStrategy(config, event);
// Calculate match ranges before sorting for replacement
// Indices in selectedMatches are 0-based line indices
const matchRanges = selectedMatches
.map((m) => ({ start: m.index + 1, end: m.index + N }))
.sort((a, b) => a.start - b.start);
// Sort matches by index descending to apply replacements from bottom to top
// so that indices remain valid
selectedMatches.sort((a, b) => b.index - a.index);
const newLines = normalizedReplace.split('\n');
for (const match of selectedMatches) {
// If we want to preserve the indentation of the first line of the match:
const firstLineMatch = sourceLines[match.index];
const indentationMatch = firstLineMatch.match(/^([ \t]*)/);
const indentation = indentationMatch ? indentationMatch[1] : '';
const indentedReplaceLines = newLines.map(
(line) => `${indentation}${line}`,
);
let replacementText = indentedReplaceLines.join('\n');
// If the last line of the match had a newline, preserve it in the replacement
// to avoid merging with the next line or losing a blank line separator.
if (sourceLines[match.index + N - 1].endsWith('\n')) {
replacementText += '\n';
}
sourceLines.splice(match.index, N, replacementText);
}
let modifiedCode = sourceLines.join('');
modifiedCode = restoreTrailingNewline(currentContent, modifiedCode);
return {
newContent: modifiedCode,
occurrences: selectedMatches.length,
finalOldString: normalizedSearch,
finalNewString: normalizedReplace,
strategy: 'fuzzy',
matchRanges,
};
}
return null;
}
+2 -2
View File
@@ -235,8 +235,8 @@ describe('LSTool', () => {
expect(entries[0]).toBe('[DIR] x-dir');
expect(entries[1]).toBe('[DIR] y-dir');
expect(entries[2]).toBe('a-file.txt');
expect(entries[3]).toBe('b-file.txt');
expect(entries[2]).toBe('a-file.txt (8 bytes)');
expect(entries[3]).toBe('b-file.txt (8 bytes)');
});
it('should handle permission errors gracefully', async () => {
+6 -1
View File
@@ -241,7 +241,12 @@ class LSToolInvocation extends BaseToolInvocation<LSToolParams, ToolResult> {
// Create formatted content for LLM
const directoryContent = entries
.map((entry) => `${entry.isDirectory ? '[DIR] ' : ''}${entry.name}`)
.map((entry) => {
if (entry.isDirectory) {
return `[DIR] ${entry.name}`;
}
return `${entry.name} (${entry.size} bytes)`;
})
.join('\n');
let resultMessage = `Directory listing for ${resolvedDirPath}:\n${directoryContent}`;
+2 -2
View File
@@ -1492,8 +1492,8 @@
"enableAgents": {
"title": "Enable Agents",
"description": "Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents",
"markdownDescription": "Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
"default": false,
"markdownDescription": "Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `true`",
"default": true,
"type": "boolean"
},
"extensionManagement": {