feat(FN-3758): align web fetch test coverage and documentation
Aligns web fetch tool test coverage across multiple test suites and updates the agents documentation, with a patch changeset for `@runfusion/fusion`. Fusion-Task-Id: FN-3758
This commit is contained in:
@@ -33,7 +33,7 @@ Mission → Milestone → Slice → Feature → Task
|
||||
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
|
||||
- **Skills tools** — `fn_skills_search`, `fn_skills_install`
|
||||
- **Insight tools** — `fn_insight_list`, `fn_insight_show`, `fn_insight_run_list`, `fn_insight_run_show`
|
||||
- **Other tools** — `fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`, `fn_research_retry`
|
||||
- **Other tools** — `fn_web_fetch`, `fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`, `fn_research_retry`
|
||||
<!-- END: tool-categories -->
|
||||
- **Dashboard** — Use `/fn` command to start/stop the dashboard
|
||||
|
||||
|
||||
@@ -18,6 +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_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_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_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_get` | triage, executor | Read one research run's structured findings/citations payload | `id` (string) |
|
||||
|
||||
@@ -364,6 +364,17 @@ Show a single insight-generation run by ID.
|
||||
|
||||
## Other Tools
|
||||
|
||||
### fn_web_fetch
|
||||
|
||||
Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pages. URL to fetch (http/https) Optional extraction hint for downstream summarization Timeout in milliseconds (default: 30000) Max bytes to return (default: 512000)
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `url` | string | ✓ | URL to fetch (http/https) |
|
||||
| `prompt` | string | — | Optional extraction hint for downstream summarization |
|
||||
| `timeoutMs` | number | — | Timeout in milliseconds (default: 30000) |
|
||||
| `maxBytes` | number | — | Max bytes to return (default: 512000) |
|
||||
|
||||
### fn_research_run
|
||||
|
||||
Start a bounded research run and optionally wait for findings.
|
||||
|
||||
@@ -29,6 +29,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
|
||||
| `fn_task_import_github_issue` | Import a specific GitHub issue as a Fusion task. Fetches the issue by number and creates a single task in the planning column with the issue title and body. |
|
||||
| `fn_task_browse_github_issues` | List open GitHub issues from a repository to browse before importing. Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue to import specific issues by number. |
|
||||
| `fn_task_plan` | Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task. |
|
||||
| `fn_web_fetch` | Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pages. URL to fetch (http/https) Optional extraction hint for downstream summarization Timeout in milliseconds (default: 30000) Max bytes to return (default: 512000) |
|
||||
| `fn_research_run` | Start a bounded research run and optionally wait for findings. |
|
||||
| `fn_research_list` | List recent research runs. |
|
||||
| `fn_research_get` | Get one research run and structured findings. |
|
||||
|
||||
43
packages/cli/src/__tests__/extension-web-fetch.test.ts
Normal file
43
packages/cli/src/__tests__/extension-web-fetch.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const fetchWebContentMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
fetchWebContent: fetchWebContentMock,
|
||||
}));
|
||||
|
||||
import kbExtension from "../extension.js";
|
||||
|
||||
describe("extension fn_web_fetch", () => {
|
||||
it("registers and executes fn_web_fetch", async () => {
|
||||
const tools = new Map<string, any>();
|
||||
const api = {
|
||||
registerTool(def: any) {
|
||||
tools.set(def.name, def);
|
||||
},
|
||||
registerCommand: vi.fn(),
|
||||
registerShortcut: vi.fn(),
|
||||
registerFlag: vi.fn(),
|
||||
on: vi.fn(),
|
||||
} as any;
|
||||
|
||||
fetchWebContentMock.mockResolvedValue({
|
||||
finalUrl: "https://example.com/final",
|
||||
status: 200,
|
||||
contentType: "text/plain",
|
||||
title: "Example",
|
||||
content: "hello world",
|
||||
truncated: false,
|
||||
bytesRead: 11,
|
||||
});
|
||||
|
||||
kbExtension(api);
|
||||
const tool = tools.get("fn_web_fetch");
|
||||
expect(tool).toBeTruthy();
|
||||
|
||||
const result = await tool.execute("id", { url: "https://example.com" }, undefined, undefined, { cwd: process.cwd() });
|
||||
expect(fetchWebContentMock).toHaveBeenCalledWith("https://example.com", { timeoutMs: undefined, maxBytes: undefined });
|
||||
expect(result.content[0].text).toContain("https://example.com/final");
|
||||
expect(result.details.status).toBe(200);
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
isGhAvailable,
|
||||
runGhJsonAsync,
|
||||
} from "@fusion/core/gh-cli";
|
||||
import { fetchWebContent } from "@fusion/engine";
|
||||
import { resolve, basename, extname, join } from "node:path";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
@@ -1363,6 +1364,54 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "fn_web_fetch",
|
||||
label: "fn: Web Fetch",
|
||||
description: "Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pages.",
|
||||
parameters: Type.Object({
|
||||
url: Type.String({ description: "URL to fetch (http/https)" }),
|
||||
prompt: Type.Optional(Type.String({ description: "Optional extraction hint for downstream summarization" })),
|
||||
timeoutMs: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 30000)" })),
|
||||
maxBytes: Type.Optional(Type.Number({ description: "Max bytes to return (default: 512000)" })),
|
||||
}),
|
||||
promptSnippet: "Fetch and extract readable text from a webpage URL",
|
||||
promptGuidelines: [
|
||||
"Use for lightweight GET requests where JS rendering is not required.",
|
||||
"For JS-rendered pages or complex browsing flows, use agent-browser skill instead.",
|
||||
],
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await fetchWebContent(params.url, {
|
||||
timeoutMs: params.timeoutMs,
|
||||
maxBytes: params.maxBytes,
|
||||
});
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: [
|
||||
`URL: ${result.finalUrl}`,
|
||||
`Status: ${result.status}`,
|
||||
`Content-Type: ${result.contentType}`,
|
||||
params.prompt ? `Prompt: ${params.prompt}` : undefined,
|
||||
result.title ? `Title: ${result.title}` : undefined,
|
||||
"",
|
||||
result.content,
|
||||
result.truncated ? "\n[truncated to maxBytes]" : "",
|
||||
].filter(Boolean).join("\n"),
|
||||
}],
|
||||
details: {
|
||||
finalUrl: result.finalUrl,
|
||||
status: result.status,
|
||||
contentType: result.contentType,
|
||||
title: result.title,
|
||||
truncated: result.truncated,
|
||||
bytesRead: result.bytesRead,
|
||||
promptSnippet: params.prompt ? params.prompt.slice(0, 200) : undefined,
|
||||
promptGuidelines: "Lightweight fetch only; for JS-rendered pages, use agent-browser skill.",
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// ── Research Tools ──────────────────────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
|
||||
Reference in New Issue
Block a user