Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ tmp/
# code graph skills
.venv
.codegraph
.zvec-grep/
.qwen/computer-use/installed.json
# Auto-generated computer-use marker can also appear under nested packages.
**/.qwen/computer-use/
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1984,6 +1984,24 @@ describe('mergeExcludeTools', () => {
expect(config.getPermissionsDeny()).toContain('tool_search');
});

it('should leave zvec_grep disabled by default', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
const settings: Settings = {};
const config = await loadCliConfig(settings, argv, undefined, []);
expect(config.isZvecGrepEnabled()).toBe(false);
});

it('should enable zvec_grep when tools.zvecGrep.enabled is true', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
const settings: Settings = {
tools: { zvecGrep: { enabled: true } },
};
const config = await loadCliConfig(settings, argv, undefined, []);
expect(config.isZvecGrepEnabled()).toBe(true);
});

it('should auto-disable tool_search for deepseek-v4 models', async () => {
process.argv = ['node', 'script.js', '--model', 'deepseek-v4-flash'];
const argv = await parseArguments();
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,8 @@ export async function loadCliConfig(
disabledSlashCommands.length > 0 ? disabledSlashCommands : undefined,
disabledSkillNamesProvider:
bareMode || safeMode ? undefined : disabledSkillNamesProvider,
zvecGrepEnabled:
bareMode || safeMode ? false : settings.tools?.zvecGrep?.enabled === true,
disabledTools: disabledTools.length > 0 ? disabledTools : undefined,
visibleTools: visibleTools.length > 0 ? visibleTools : undefined,
// New unified permissions (PermissionManager source of truth).
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,27 @@ const SETTINGS_SCHEMA = {
},
},
},
zvecGrep: {
type: 'object',
label: 'Zvec Grep',
category: 'Tools',
requiresRestart: true,
default: {},
description: 'Settings for the zvec-grep built-in search tool.',
showInDialog: false,
properties: {
enabled: {
type: 'boolean',
label: 'Enable Zvec Grep',
category: 'Tools',
requiresRestart: true,
default: false,
description:
'When enabled, registers the zvec_grep built-in tool. Disabled by default.',
showInDialog: false,
},
},
},
shell: {
type: 'object',
label: 'Shell',
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6051,6 +6051,34 @@ describe('setApprovalMode with folder trust', () => {
vi.clearAllMocks();
});

it('should not register zvec-grep tool by default', async () => {
const config = new Config({ ...baseParams, useRipgrep: false });
await config.initialize();

const calls = (ToolRegistry.prototype.registerFactory as Mock).mock.calls;
const zvecGrepRegistrations = calls.filter(
(call) => call[0] === ToolNames.ZVEC_GREP,
);

expect(zvecGrepRegistrations.length).toBe(0);
});

it('should register zvec-grep tool when enabled', async () => {
const config = new Config({
...baseParams,
useRipgrep: false,
zvecGrepEnabled: true,
});
await config.initialize();

const calls = (ToolRegistry.prototype.registerFactory as Mock).mock.calls;
const zvecGrepRegistrations = calls.filter(
(call) => call[0] === ToolNames.ZVEC_GREP,
);

expect(zvecGrepRegistrations.length).toBe(1);
});

it('should register grep tool when useRipgrep is true and it is available', async () => {
(canUseRipgrep as Mock).mockResolvedValue(true);
const config = new Config({ ...baseParams, useRipgrep: true });
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,7 @@ export interface ConfigParameters {
* Names returned must be lower-cased; consumers compare case-insensitively.
*/
disabledSkillNamesProvider?: () => ReadonlySet<string>;
zvecGrepEnabled?: boolean;
/**
* Tool names hidden from the registry at construction time. Unlike
* `permissions.deny` (which keeps the tool registered and rejects
Expand Down Expand Up @@ -1589,6 +1590,7 @@ export class Config {
private readonly disabledSkillNamesProvider:
| (() => ReadonlySet<string>)
| null;
private readonly zvecGrepEnabled: boolean;
// `disabledTools` is set at construction
// time but can be re-synced by the daemon mutation surface
// (`setWorkspaceToolEnabled` propagates through ACP) so a subsequent
Expand Down Expand Up @@ -1847,6 +1849,7 @@ export class Config {
...(params.disabledSlashCommands ?? []),
]);
this.disabledSkillNamesProvider = params.disabledSkillNamesProvider ?? null;
this.zvecGrepEnabled = params.zvecGrepEnabled ?? false;
this.disabledTools = new Set(params.disabledTools ?? []);
this.visibleTools = new Set(
(params.visibleTools ?? []).filter(
Expand Down Expand Up @@ -4149,6 +4152,10 @@ export class Config {
return this.disabledSkillNamesProvider?.() ?? EMPTY_DISABLED_SKILL_NAMES;
}

isZvecGrepEnabled(): boolean {
return this.zvecGrepEnabled;
}

/**
* Returns the read-only set of tool names hidden from this Config's
* ToolRegistry. Consulted by `ToolRegistry.registerTool` and
Expand Down Expand Up @@ -6355,6 +6362,13 @@ export class Config {
return new ReadFileTool(this);
});

if (this.isZvecGrepEnabled()) {
await registerLazy(ToolNames.ZVEC_GREP, async () => {
const { ZvecGrepTool } = await import('../tools/zvec-grep.js');
return new ZvecGrepTool(this);
});
}

// --- Grep / RipGrep (conditional) ---
if (this.getUseRipgrep()) {
let useRipgrep = false;
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/core/coreToolScheduler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12170,6 +12170,19 @@ describe('extractToolFilePaths', () => {
).toEqual(['packages/core', 'packages/core/**/*.ts']);
});

it('extracts zvec_grep paths and globs as path-shaped file filters', () => {
expect(
extractToolFilePaths('zvec_grep', {
operation: 'rg',
query: 'validate',
path: 'packages/core',
paths: ['src', 'include'],
glob: '**/*.{h,cc}',
exclude: ['thirdparty/**'],
}),
).toEqual(['packages/core', 'src', 'include', 'packages/core/**/*.{h,cc}']);
});

it('decodes file:// URIs for lsp via fileURLToPath', () => {
// Regression: LSP `filePath` is allowed to be a `file://` URI.
// Forwarding the URI as-is to the activation registry would never
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/core/coreToolScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ const FS_PATH_TOOL_NAMES: ReadonlySet<string> = new Set<string>([
ToolNames.EDIT,
ToolNames.WRITE_FILE,
ToolNames.GREP,
ToolNames.ZVEC_GREP,
ToolNames.GLOB,
ToolNames.LS,
ToolNames.LSP,
Expand Down Expand Up @@ -654,6 +655,27 @@ export function extractToolFilePaths(
return out;
}

case ToolNames.ZVEC_GREP: {
const pathField = obj['path'];
const pathsField = obj['paths'];
const globField = obj['glob'];
push(pathField);
if (Array.isArray(pathsField)) {
for (const item of pathsField) {
push(item);
}
}
if (typeof globField === 'string' && globField.length > 0) {
push(
joinSearchRootAndGlob(
typeof pathField === 'string' ? pathField : undefined,
globField,
),
);
}
return out;
}

case ToolNames.LS:
push(obj['path']);
return out;
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/subagents/builtin-agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { describe, it, expect } from 'vitest';
import { BuiltinAgentRegistry } from './builtin-agents.js';
import { ToolNames } from '../tools/tool-names.js';

describe('BuiltinAgentRegistry', () => {
describe('getBuiltinAgents', () => {
Expand Down Expand Up @@ -36,6 +37,12 @@ describe('BuiltinAgentRegistry', () => {
expect(generalAgent).toBeDefined();
expect(generalAgent?.description).toContain('General-purpose agent');
});

it('should allow Explore to use zvec-grep when the tool is enabled', () => {
const agent = BuiltinAgentRegistry.getBuiltinAgent('Explore');

expect(agent?.tools).toContain(ToolNames.ZVEC_GREP);
});
});

describe('getBuiltinAgent', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/subagents/builtin-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Notes:
tools: [
ToolNames.READ_FILE,
ToolNames.GREP,
ToolNames.ZVEC_GREP,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Explore agent unconditionally includes ZVEC_GREP in its tools array regardless of zvecGrepEnabled. When disabled (the default), transformToToolNames emits a debug warning on every Explore agent spawn.

Consider conditionally including it based on config.isZvecGrepEnabled().

— qwen3.7-max via Qwen Code /review

ToolNames.GLOB,
ToolNames.SHELL,
ToolNames.LS,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tools/tool-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const ToolNames = {
WRITE_FILE: 'write_file',
READ_FILE: 'read_file',
GREP: 'grep_search',
ZVEC_GREP: 'zvec_grep',
GLOB: 'glob',
SHELL: 'run_shell_command',
TODO_WRITE: 'todo_write',
Expand Down Expand Up @@ -75,6 +76,7 @@ export const ToolDisplayNames = {
WRITE_FILE: 'WriteFile',
READ_FILE: 'ReadFile',
GREP: 'Grep',
ZVEC_GREP: 'ZvecGrep',
GLOB: 'Glob',
SHELL: 'Shell',
TODO_WRITE: 'TodoList',
Expand Down
Loading
Loading