Skip to content
Merged
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
68 changes: 68 additions & 0 deletions packages/core/src/planner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,36 @@ describe('Planner LLM-based plan generation', () => {
expect(createStep).toBeDefined();
});

it('preserves LLM-planned web_fetch actions and URL params', async () => {
const planner = createPlanner({ useLLM: true });
mockLLMGeneratePlan(planner, async () => ({
summary: '查阅官方文档',
steps: [
{
description: '抓取 API 文档',
action: 'web_fetch',
tool: 'web_fetch',
phase: '阶段1-分析',
params: defaultParams({ url: 'https://docs.example.com/api' }),
reasoning: '确认外部 API 行为',
needsCodeGeneration: false,
},
],
risks: [],
alternatives: [],
}));

const result = await planner.plan(createTask({ type: 'create' }), emptyContext(), []);

const webFetchStep = result.plan!.steps.find((s) => s.action === 'web_fetch');
expect(result.needsMoreContext).toBe(false);
expect(webFetchStep).toMatchObject({
action: 'web_fetch',
tool: 'web_fetch',
params: expect.objectContaining({ url: 'https://docs.example.com/api' }),
});
Comment on lines +198 to +206
});

it('injects project instructions into the planning prompt context', async () => {
const planner = createPlanner({ useLLM: true });
const calls: Array<{ context: string }> = [];
Expand Down Expand Up @@ -343,6 +373,44 @@ describe('Planner step ordering and dependencies', () => {
expect(searchStep!.dependencies).not.toContain(readStep!.stepId);
});

it('allows web_fetch to share the read-only planning dependency policy', async () => {
const planner = createPlanner({ useLLM: true });
mockLLMGeneratePlan(planner, async () => ({
summary: '查阅资料并分析代码',
steps: [
{
description: '抓取官方文档',
action: 'web_fetch',
tool: 'web_fetch',
phase: '阶段1-分析',
params: defaultParams({ url: 'https://docs.example.com/api' }),
reasoning: '确认外部 API 行为',
needsCodeGeneration: false,
},
{
description: '搜索本地调用',
action: 'search_code',
tool: 'search_code',
phase: '阶段1-分析',
params: defaultParams({ query: 'createClient', maxResults: 10 }),
reasoning: '定位本地调用方式',
needsCodeGeneration: false,
},
],
risks: [],
alternatives: [],
}));

const result = await planner.plan(createTask({ type: 'create' }), emptyContext(), []);

const steps = result.plan!.steps;
const webFetchStep = steps.find((s) => s.action === 'web_fetch');
const searchStep = steps.find((s) => s.action === 'search_code');
expect(webFetchStep).toBeDefined();
expect(searchStep).toBeDefined();
expect(searchStep!.dependencies).not.toContain(webFetchStep!.stepId);
});

it('generates correct dependencies for modify task (rule-based)', async () => {
const planner = createPlanner({ useLLM: false });
const result = await planner.plan(
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export class Planner {
}

private isReadOnlyPlanningAction(action: ExecutionStep['action']): boolean {
return ['read_file', 'search_code', 'list_directory', 'get_ast'].includes(action);
return ['read_file', 'search_code', 'list_directory', 'get_ast', 'web_fetch'].includes(action);
}

/**
Expand All @@ -355,6 +355,7 @@ export class Planner {
browser_click: 'browser_click',
browser_type: 'browser_type',
browser_screenshot: 'browser_screenshot',
web_fetch: 'web_fetch',
};

return actionMap[action] ?? 'read_file';
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,17 @@ describe('SecurityManager', () => {
expect(result.reasonCode).toBe('unknown_tool_requires_approval');
expect(result.riskLevel).toBe('medium');
});

it('asks for approval for web_fetch by default', () => {
const result = security.evaluate({
toolName: 'web_fetch',
args: { url: 'https://docs.example.com/api' },
projectRoot,
});
expect(result.decision).toBe('ask');
expect(result.reasonCode).toBe('unknown_tool_requires_approval');
expect(result.riskLevel).toBe('medium');
});
});

// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/types/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type ActionType =
| 'browser_type'
| 'browser_screenshot'
| 'get_page_structure'
| 'web_fetch'
| 'filesense_sync_and_summarize'
| 'filesense_query'
| 'filesense_navigate';
Expand Down
Loading