opencode-swarm 7.71.3 → 7.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,7 @@ export { test_impact } from './test-impact';
93
93
  export { test_runner } from './test-runner';
94
94
  export { todo_extract } from './todo-extract';
95
95
  export { executeUpdateTaskStatus, type UpdateTaskStatusArgs, type UpdateTaskStatusResult, update_task_status, } from './update-task-status';
96
+ export { web_fetch } from './web-fetch';
96
97
  export { web_search } from './web-search';
97
98
  export { write_drift_evidence } from './write-drift-evidence';
98
99
  export { write_final_council_evidence } from './write-final-council-evidence';
@@ -83,6 +83,7 @@ export declare const TOOL_MANIFEST: {
83
83
  get_qa_gate_profile: () => ToolDefinition;
84
84
  set_qa_gates: () => ToolDefinition;
85
85
  web_search: () => ToolDefinition;
86
+ web_fetch: () => ToolDefinition;
86
87
  convene_general_council: () => ToolDefinition;
87
88
  write_final_council_evidence: () => ToolDefinition;
88
89
  skill_generate: () => ToolDefinition;
@@ -263,6 +263,10 @@ export declare const TOOL_METADATA: {
263
263
  description: string;
264
264
  agents: ("sme" | "architect" | "skill_improver")[];
265
265
  };
266
+ web_fetch: {
267
+ description: string;
268
+ agents: "architect"[];
269
+ };
266
270
  convene_general_council: {
267
271
  description: string;
268
272
  agents: "architect"[];
@@ -0,0 +1,86 @@
1
+ /**
2
+ * web_fetch tool — owned by the architect for MODE: DEEP_RESEARCH source
3
+ * reading (and available for any architect-driven research that needs full
4
+ * document text rather than search snippets).
5
+ *
6
+ * web_search returns titled snippets; web_fetch retrieves the readable text of
7
+ * a single URL so the architect can ground claims in primary sources. Results
8
+ * are stored as `crawl` evidence documents (same cache as web_search) and the
9
+ * returned `evidenceRef` can be cited in a research report.
10
+ *
11
+ * Config-gated on `council.general.enabled` — the same feature flag that opts a
12
+ * project into external network research. Unlike web_search it does NOT require
13
+ * a search API key, because it fetches arbitrary user/agent-supplied URLs
14
+ * directly rather than calling a search provider.
15
+ *
16
+ * Security (this is the first arbitrary-URL fetcher in the repo, so it carries
17
+ * its own SSRF + resource defenses — there is no provider host allowlist to
18
+ * lean on):
19
+ * - http/https schemes only; file:, ftp:, data:, etc. are rejected.
20
+ * - The host is DNS-resolved and every resolved address is checked against
21
+ * loopback / private / link-local / unique-local / CGNAT / metadata ranges;
22
+ * literal-IP hosts are checked directly. This blocks cloud metadata
23
+ * (169.254.169.254) and internal services.
24
+ * - The socket is then PINNED to the exact validated IP (host = resolved
25
+ * address) while the original hostname is kept for the Host header and TLS
26
+ * SNI/certificate identity. There is no second name resolution at connect
27
+ * time, so DNS rebinding cannot swap in a private/metadata address after the
28
+ * check (TOCTOU), and HTTPS verification still validates the hostname.
29
+ * - Redirects are followed manually (the underlying request never auto-follows)
30
+ * and every hop is re-validated AND re-pinned, so a public URL cannot 302
31
+ * into the metadata endpoint or an internal host.
32
+ * - The body is streamed and aborted once it exceeds `max_bytes` of DECODED
33
+ * output (so a gzip bomb is bounded by decompressed size, not the
34
+ * advisory Content-Length header).
35
+ * - An AbortController enforces `timeout_ms`; the signal is always cleared in
36
+ * a finally block.
37
+ *
38
+ * Never throws — returns a structured `success: true | false` JSON string.
39
+ */
40
+ import { lookup } from 'node:dns/promises';
41
+ import type { tool } from '@opencode-ai/plugin';
42
+ import { loadPluginConfig } from '../config/loader';
43
+ import { writeEvidenceDocuments } from '../evidence/documents';
44
+ interface HttpRequestArgs {
45
+ /** The URL to request. Used for path, Host header, and TLS SNI/identity. */
46
+ url: URL;
47
+ /**
48
+ * The pre-validated address to open the socket to. The connection is pinned
49
+ * to this exact IP so there is no second, unvalidated name resolution between
50
+ * the SSRF check and the connection (defeats DNS rebinding).
51
+ */
52
+ pinnedAddress: string;
53
+ signal: AbortSignal;
54
+ headers: Record<string, string>;
55
+ }
56
+ interface RawHttpResponse {
57
+ status: number;
58
+ headers: Record<string, string | undefined>;
59
+ /** Decoded (decompressed) body, streamed lazily so the byte cap bounds it. */
60
+ body: AsyncIterable<Uint8Array> | null;
61
+ /** Destroy the underlying socket/stream to close the connection early. */
62
+ cancel?: () => void;
63
+ }
64
+ /**
65
+ * Decide whether a single resolved IP address must be blocked because it points
66
+ * at a loopback, private, link-local, unique-local, CGNAT, or reserved target.
67
+ * Handles IPv4, IPv6, and IPv4-mapped IPv6 (::ffff:a.b.c.d).
68
+ */
69
+ export declare function isBlockedAddress(address: string): boolean;
70
+ /**
71
+ * Extract the document <title>, if present. Uses a forward indexOf scan so
72
+ * there is no regex backtracking on large inputs — a lazy
73
+ * `/<title[^>]*>([\s\S]*?)<\/title>/i` is O(n²) on unclosed title tags
74
+ * (same ReDoS class as the `stripSpans` fix).
75
+ */
76
+ export declare function extractTitle(html: string): string | undefined;
77
+ /** Strip HTML to readable plain text. Dependency-free; not a full reader. */
78
+ export declare function htmlToText(html: string): string;
79
+ export declare const web_fetch: ReturnType<typeof tool>;
80
+ export declare const _internals: {
81
+ httpRequest: (args: HttpRequestArgs) => Promise<RawHttpResponse>;
82
+ dnsLookup: typeof lookup;
83
+ loadPluginConfig: typeof loadPluginConfig;
84
+ writeEvidenceDocuments: typeof writeEvidenceDocuments;
85
+ };
86
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.71.3",
3
+ "version": "7.72.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -43,6 +43,7 @@
43
43
  ".opencode/skills/pre-phase-briefing",
44
44
  ".opencode/skills/council",
45
45
  ".opencode/skills/deep-dive",
46
+ ".opencode/skills/deep-research",
46
47
  ".opencode/skills/codebase-review-swarm",
47
48
  ".opencode/skills/design-docs",
48
49
  ".opencode/skills/swarm-pr-review",