feat(FN-3803): add universal web fetch to reviewer, merger, and triage agen
The merge introduces the universal web fetch tool across reviewer/merger/triage agents (FN-3803, 4 commits) with docs and regression test, extracts the roadmap as a plugin scaffold in `plugins/fusion-plugin-reports` (FN-3166, FN-3165) while removing dashboard roadmap backend and associated tests, ad Fusion-Task-Id: FN-3803
This commit is contained in:
5
.changeset/fn-3803-universal-web-fetch.md
Normal file
5
.changeset/fn-3803-universal-web-fetch.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Make `fn_web_fetch` universally available to all agent roles (reviewer, merger, triage now included).
|
||||||
@@ -258,6 +258,8 @@ The extension has no skills — tool descriptions give the LLM everything it nee
|
|||||||
|
|
||||||
Use `fn_web_fetch` for lightweight URL reads from agent/chat sessions. It performs an HTTP GET, follows redirects, extracts readable text (including HTML→text and JSON pretty-print), and returns bounded content.
|
Use `fn_web_fetch` for lightweight URL reads from agent/chat sessions. It performs an HTTP GET, follows redirects, extracts readable text (including HTML→text and JSON pretty-print), and returns bounded content.
|
||||||
|
|
||||||
|
`fn_web_fetch` is a universal baseline capability and is available by default across all agent roles/surfaces (executor, step-session, reviewer, merger, triage, and heartbeat, including engineer/custom direct-report paths routed through heartbeat).
|
||||||
|
|
||||||
- Default limits: `timeoutMs=30000` and `maxBytes=512000` (500 KB)
|
- Default limits: `timeoutMs=30000` and `maxBytes=512000` (500 KB)
|
||||||
- Security: blocks private/loopback/link-local hosts (including DNS-resolved private addresses) unless explicitly overridden in internal/test contexts
|
- Security: blocks private/loopback/link-local hosts (including DNS-resolved private addresses) unless explicitly overridden in internal/test contexts
|
||||||
- Scope: read-only fetch (no JS rendering, no auth flows, no POST/cookie workflows)
|
- Scope: read-only fetch (no JS rendering, no auth flows, no POST/cookie workflows)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ These tools are **not** part of the user-invokable extension surface. They are i
|
|||||||
| `fn_memory_search` | triage, executor, heartbeat | Search project memory plus per-agent layered memory snippets | `query` (string), `limit?` (number) |
|
| `fn_memory_search` | triage, executor, heartbeat | Search project memory plus per-agent layered memory snippets | `query` (string), `limit?` (number) |
|
||||||
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window (including bounded per-agent layered paths) | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window (including bounded per-agent layered paths) | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
||||||
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append memory notes with explicit scope: `scope="agent"` for private operating context, `scope="project"` for workspace-wide durable knowledge | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append memory notes with explicit scope: `scope="agent"` for private operating context, `scope="project"` for workspace-wide durable knowledge | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
||||||
| `fn_web_fetch` | executor, step-session, heartbeat | Lightweight HTTP fetch with HTML→text extraction, timeout/size caps, and SSRF guard (no JS rendering) | `url` (string), `prompt?` (string), `timeoutMs?` (number), `maxBytes?` (number) |
|
| `fn_web_fetch` | executor, step-session, reviewer, merger, triage, heartbeat | Lightweight HTTP fetch with HTML→text extraction, timeout/size caps, and SSRF guard (no JS rendering) | `url` (string), `prompt?` (string), `timeoutMs?` (number), `maxBytes?` (number) |
|
||||||
| `fn_research_run` | triage, executor | Start a bounded research run (optionally wait for completion) and return structured findings metadata | `query` (string), `wait_for_completion?` (boolean), `max_wait_ms?` (number) |
|
| `fn_research_run` | triage, executor | Start a bounded research run (optionally wait for completion) and return structured findings metadata | `query` (string), `wait_for_completion?` (boolean), `max_wait_ms?` (number) |
|
||||||
| `fn_research_list` | triage, executor | List recent research runs with status/summary metadata | `status?` (`pending` \| `running` \| `completed` \| `failed` \| `cancelled`), `limit?` (number) |
|
| `fn_research_list` | triage, executor | List recent research runs with status/summary metadata | `status?` (`pending` \| `running` \| `completed` \| `failed` \| `cancelled`), `limit?` (number) |
|
||||||
| `fn_research_get` | triage, executor | Read one research run's structured findings/citations payload | `id` (string) |
|
| `fn_research_get` | triage, executor | Read one research run's structured findings/citations payload | `id` (string) |
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ describe("reviewStep — spec review type", () => {
|
|||||||
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||||
expect(opts.systemPrompt).toContain("## Project Memory");
|
expect(opts.systemPrompt).toContain("## Project Memory");
|
||||||
expect(opts.systemPrompt).toContain("Do not update memory during review");
|
expect(opts.systemPrompt).toContain("Do not update memory during review");
|
||||||
expect(opts.customTools?.map((tool: any) => tool.name)).toEqual(["fn_memory_search", "fn_memory_get"]);
|
expect(opts.customTools?.map((tool: any) => tool.name)).toEqual(["fn_web_fetch", "fn_memory_search", "fn_memory_get"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("omits reviewer memory tools and instructions when memory is disabled", async () => {
|
it("omits reviewer memory tools and instructions when memory is disabled", async () => {
|
||||||
@@ -217,7 +217,7 @@ describe("reviewStep — spec review type", () => {
|
|||||||
|
|
||||||
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||||
expect(opts.systemPrompt).not.toContain("## Project Memory");
|
expect(opts.systemPrompt).not.toContain("## Project Memory");
|
||||||
expect(opts.customTools).toBeUndefined();
|
expect(opts.customTools?.map((tool: any) => tool.name)).toEqual(["fn_web_fetch"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds review request with spec-specific instructions", async () => {
|
it("builds review request with spec-specific instructions", async () => {
|
||||||
|
|||||||
36
packages/engine/src/__tests__/web-fetch-universal.test.ts
Normal file
36
packages/engine/src/__tests__/web-fetch-universal.test.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
function readSource(file: string): string {
|
||||||
|
return readFileSync(join(import.meta.dirname, "..", file), "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("fn_web_fetch universal registration", () => {
|
||||||
|
it("executor registers fn_web_fetch", () => {
|
||||||
|
expect(readSource("executor.ts")).toContain("createWebFetchTool()");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("step-session executor registers fn_web_fetch", () => {
|
||||||
|
expect(readSource("step-session-executor.ts")).toContain("createWebFetchTool()");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reviewer registers fn_web_fetch", () => {
|
||||||
|
expect(readSource("reviewer.ts")).toContain("customTools: [createWebFetchTool()");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merger registers fn_web_fetch", () => {
|
||||||
|
expect(readSource("merger.ts")).toContain("customTools: [reportBuildFailureTool, createWebFetchTool()]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("triage registers fn_web_fetch", () => {
|
||||||
|
expect(readSource("triage.ts")).toContain("createWebFetchTool(),");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("heartbeat registers fn_web_fetch for task and no-task branches", () => {
|
||||||
|
const source = readSource("agent-heartbeat.ts");
|
||||||
|
expect(source).toContain("heartbeatTools.push(createWebFetchTool())");
|
||||||
|
expect(source).toContain("if (taskId)");
|
||||||
|
expect(source).toContain("else {");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -66,6 +66,7 @@ import { resolveAgentInstructions, buildSystemPromptWithInstructions } from "./a
|
|||||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { createRunAuditor, generateSyntheticRunId, type EngineRunContext } from "./run-audit.js";
|
import { createRunAuditor, generateSyntheticRunId, type EngineRunContext } from "./run-audit.js";
|
||||||
|
import { createWebFetchTool } from "./agent-tools.js";
|
||||||
|
|
||||||
/** Conflict type classification for merge conflict resolution */
|
/** Conflict type classification for merge conflict resolution */
|
||||||
export type ConflictType =
|
export type ConflictType =
|
||||||
@@ -6443,7 +6444,7 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
|||||||
cwd: rootDir,
|
cwd: rootDir,
|
||||||
systemPrompt: mergerSystemPrompt,
|
systemPrompt: mergerSystemPrompt,
|
||||||
tools: "coding",
|
tools: "coding",
|
||||||
customTools: [reportBuildFailureTool],
|
customTools: [reportBuildFailureTool, createWebFetchTool()],
|
||||||
onText: agentLogger.onText,
|
onText: agentLogger.onText,
|
||||||
onThinking: agentLogger.onThinking,
|
onThinking: agentLogger.onThinking,
|
||||||
onToolStart: agentLogger.onToolStart,
|
onToolStart: agentLogger.onToolStart,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
buildPluginPromptSection,
|
buildPluginPromptSection,
|
||||||
} from "./agent-instructions.js";
|
} from "./agent-instructions.js";
|
||||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||||
import { createMemoryGetTool, createMemorySearchTool } from "./agent-tools.js";
|
import { createMemoryGetTool, createMemorySearchTool, createWebFetchTool } from "./agent-tools.js";
|
||||||
|
|
||||||
export const REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
export const REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ export async function reviewStep(
|
|||||||
cwd,
|
cwd,
|
||||||
systemPrompt: reviewerSystemPromptFinal,
|
systemPrompt: reviewerSystemPromptFinal,
|
||||||
tools: "readonly",
|
tools: "readonly",
|
||||||
customTools: memoryTools,
|
customTools: [createWebFetchTool(), ...(memoryTools ?? [])],
|
||||||
onText: agentLogger ? agentLogger.onText : (delta) => options.onText?.(delta),
|
onText: agentLogger ? agentLogger.onText : (delta) => options.onText?.(delta),
|
||||||
onThinking: agentLogger?.onThinking,
|
onThinking: agentLogger?.onThinking,
|
||||||
onToolStart: agentLogger?.onToolStart,
|
onToolStart: agentLogger?.onToolStart,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
createListAgentsTool,
|
createListAgentsTool,
|
||||||
createMemoryTools,
|
createMemoryTools,
|
||||||
createResearchTools,
|
createResearchTools,
|
||||||
|
createWebFetchTool,
|
||||||
createTaskDocumentReadTool,
|
createTaskDocumentReadTool,
|
||||||
createTaskDocumentWriteTool,
|
createTaskDocumentWriteTool,
|
||||||
} from "./agent-tools.js";
|
} from "./agent-tools.js";
|
||||||
@@ -966,6 +967,7 @@ export class TriageProcessor {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined),
|
: undefined),
|
||||||
|
createWebFetchTool(),
|
||||||
// Agent delegation tools — discover and delegate work to other agents.
|
// Agent delegation tools — discover and delegate work to other agents.
|
||||||
...(this.options.agentStore ? [
|
...(this.options.agentStore ? [
|
||||||
createListAgentsTool(this.options.agentStore),
|
createListAgentsTool(this.options.agentStore),
|
||||||
|
|||||||
Reference in New Issue
Block a user