opencode-swarm 7.71.3 → 7.72.1
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.
- package/.opencode/skills/deep-research/SKILL.md +172 -0
- package/dist/cli/index.js +176 -36
- package/dist/commands/deep-research.d.ts +18 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/bundled-skills.d.ts +1 -1
- package/dist/index.js +905 -128
- package/dist/services/diagnose-service.d.ts +1 -0
- package/dist/services/warning-buffer.d.ts +10 -7
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/manifest.d.ts +1 -0
- package/dist/tools/tool-metadata.d.ts +4 -0
- package/dist/tools/web-fetch.d.ts +86 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ var package_default;
|
|
|
69
69
|
var init_package = __esm(() => {
|
|
70
70
|
package_default = {
|
|
71
71
|
name: "opencode-swarm",
|
|
72
|
-
version: "7.
|
|
72
|
+
version: "7.72.1",
|
|
73
73
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
74
74
|
main: "dist/index.js",
|
|
75
75
|
types: "dist/index.d.ts",
|
|
@@ -112,6 +112,7 @@ var init_package = __esm(() => {
|
|
|
112
112
|
".opencode/skills/pre-phase-briefing",
|
|
113
113
|
".opencode/skills/council",
|
|
114
114
|
".opencode/skills/deep-dive",
|
|
115
|
+
".opencode/skills/deep-research",
|
|
115
116
|
".opencode/skills/codebase-review-swarm",
|
|
116
117
|
".opencode/skills/design-docs",
|
|
117
118
|
".opencode/skills/swarm-pr-review",
|
|
@@ -589,6 +590,10 @@ var init_tool_metadata = __esm(() => {
|
|
|
589
590
|
description: "External web search (Tavily or Brave) for architect-driven council research, SME domain research, and skill-improver research. Returns titled results with snippets, URLs, normalized query metadata, temporal intent, freshness, and removed stale years. Config-gated on council.general.enabled in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project .opencode/opencode-swarm.json overrides. Requires a search API key. Used by the architect in MODE: COUNCIL to gather a RESEARCH CONTEXT before dispatching council agents and by SME for opt-in external skill/source evaluation.",
|
|
590
591
|
agents: ["architect", "sme", "skill_improver"]
|
|
591
592
|
},
|
|
593
|
+
web_fetch: {
|
|
594
|
+
description: "Fetch the readable text of a single http(s) URL (architect-only). Returns decoded page text, document title, final URL after redirects, and an evidence reference. Reads primary sources that web_search only surfaces as snippets. Config-gated on council.general.enabled. Blocks private/loopback/link-local/metadata addresses (re-validated and re-pinned across redirects); enforces timeout and body size cap.",
|
|
595
|
+
agents: ["architect"]
|
|
596
|
+
},
|
|
592
597
|
convene_general_council: {
|
|
593
598
|
description: "Synthesize responses from a multi-model General Council. Accepts parallel member responses (Round 1, optionally Round 2), detects disagreements, and returns consensus points, persisting disagreements, and a structured synthesis. Architect-only. Config-gated on council.general.enabled in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project .opencode/opencode-swarm.json overrides.",
|
|
594
599
|
agents: ["architect"]
|
|
@@ -16948,6 +16953,12 @@ function addDeferredWarning(warning) {
|
|
|
16948
16953
|
deferredWarnings.push(warning);
|
|
16949
16954
|
}
|
|
16950
16955
|
}
|
|
16956
|
+
function getDeferredWarnings() {
|
|
16957
|
+
return [...deferredWarnings];
|
|
16958
|
+
}
|
|
16959
|
+
function clearDeferredWarnings() {
|
|
16960
|
+
deferredWarnings.length = 0;
|
|
16961
|
+
}
|
|
16951
16962
|
var deferredWarnings, MAX_DEFERRED_WARNINGS = 50;
|
|
16952
16963
|
var init_warning_buffer = __esm(() => {
|
|
16953
16964
|
deferredWarnings = [];
|
|
@@ -17101,6 +17112,7 @@ var init_bundled_skills = __esm(() => {
|
|
|
17101
17112
|
"pre-phase-briefing",
|
|
17102
17113
|
"council",
|
|
17103
17114
|
"deep-dive",
|
|
17115
|
+
"deep-research",
|
|
17104
17116
|
"codebase-review-swarm",
|
|
17105
17117
|
"design-docs",
|
|
17106
17118
|
"swarm-pr-review",
|
|
@@ -69353,6 +69365,119 @@ var init_deep_dive = __esm(() => {
|
|
|
69353
69365
|
]);
|
|
69354
69366
|
});
|
|
69355
69367
|
|
|
69368
|
+
// src/commands/deep-research.ts
|
|
69369
|
+
function sanitizeQuestion2(raw) {
|
|
69370
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
69371
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
69372
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
69373
|
+
if (normalized.length <= MAX_QUESTION_LEN2)
|
|
69374
|
+
return normalized;
|
|
69375
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN2)}…`;
|
|
69376
|
+
}
|
|
69377
|
+
function isBoundedInteger(raw, min, max) {
|
|
69378
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
69379
|
+
return false;
|
|
69380
|
+
const n = Number(raw);
|
|
69381
|
+
return Number.isInteger(n) && n >= min && n <= max;
|
|
69382
|
+
}
|
|
69383
|
+
function parseArgs4(args2) {
|
|
69384
|
+
const result = {
|
|
69385
|
+
depth: DEFAULT_DEPTH,
|
|
69386
|
+
maxResearchers: DEFAULT_MAX_RESEARCHERS,
|
|
69387
|
+
rounds: DEFAULT_ROUNDS,
|
|
69388
|
+
output: "report",
|
|
69389
|
+
rest: []
|
|
69390
|
+
};
|
|
69391
|
+
let i2 = 0;
|
|
69392
|
+
while (i2 < args2.length) {
|
|
69393
|
+
const token = args2[i2];
|
|
69394
|
+
if (token === "--depth") {
|
|
69395
|
+
if (i2 + 1 >= args2.length)
|
|
69396
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69397
|
+
const value = args2[++i2];
|
|
69398
|
+
if (!DEPTHS.has(value)) {
|
|
69399
|
+
return {
|
|
69400
|
+
...result,
|
|
69401
|
+
error: `Invalid depth "${value}". Must be one of: standard, exhaustive.`
|
|
69402
|
+
};
|
|
69403
|
+
}
|
|
69404
|
+
result.depth = value;
|
|
69405
|
+
} else if (token === "--max-researchers") {
|
|
69406
|
+
if (i2 + 1 >= args2.length)
|
|
69407
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69408
|
+
const value = args2[++i2];
|
|
69409
|
+
if (!isBoundedInteger(value, 1, 6)) {
|
|
69410
|
+
return {
|
|
69411
|
+
...result,
|
|
69412
|
+
error: `Invalid --max-researchers value "${value}". Must be an integer between 1 and 6.`
|
|
69413
|
+
};
|
|
69414
|
+
}
|
|
69415
|
+
result.maxResearchers = Number(value);
|
|
69416
|
+
result.maxResearchersExplicit = true;
|
|
69417
|
+
} else if (token === "--rounds") {
|
|
69418
|
+
if (i2 + 1 >= args2.length)
|
|
69419
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69420
|
+
const value = args2[++i2];
|
|
69421
|
+
if (!isBoundedInteger(value, 1, 4)) {
|
|
69422
|
+
return {
|
|
69423
|
+
...result,
|
|
69424
|
+
error: `Invalid --rounds value "${value}". Must be an integer between 1 and 4.`
|
|
69425
|
+
};
|
|
69426
|
+
}
|
|
69427
|
+
result.rounds = Number(value);
|
|
69428
|
+
result.roundsExplicit = true;
|
|
69429
|
+
} else if (token === "--brief") {
|
|
69430
|
+
result.output = "brief";
|
|
69431
|
+
} else if (token.startsWith("--")) {
|
|
69432
|
+
return { ...result, error: `Unknown flag "${token}"` };
|
|
69433
|
+
} else {
|
|
69434
|
+
result.rest.push(token);
|
|
69435
|
+
}
|
|
69436
|
+
i2++;
|
|
69437
|
+
}
|
|
69438
|
+
return result;
|
|
69439
|
+
}
|
|
69440
|
+
async function handleDeepResearchCommand(_directory, args2) {
|
|
69441
|
+
const parsed = parseArgs4(args2);
|
|
69442
|
+
if (parsed.error) {
|
|
69443
|
+
return `Error: ${parsed.error}
|
|
69444
|
+
|
|
69445
|
+
${USAGE4}`;
|
|
69446
|
+
}
|
|
69447
|
+
const question = sanitizeQuestion2(parsed.rest.join(" "));
|
|
69448
|
+
if (!question) {
|
|
69449
|
+
return USAGE4;
|
|
69450
|
+
}
|
|
69451
|
+
if (parsed.depth === "exhaustive") {
|
|
69452
|
+
if (!parsed.maxResearchersExplicit)
|
|
69453
|
+
parsed.maxResearchers = EXHAUSTIVE_DEFAULT_MAX_RESEARCHERS;
|
|
69454
|
+
if (!parsed.roundsExplicit)
|
|
69455
|
+
parsed.rounds = EXHAUSTIVE_DEFAULT_ROUNDS;
|
|
69456
|
+
}
|
|
69457
|
+
return `[MODE: DEEP_RESEARCH depth=${parsed.depth} max_researchers=${parsed.maxResearchers} rounds=${parsed.rounds} output=${parsed.output}] ${question}`;
|
|
69458
|
+
}
|
|
69459
|
+
var MAX_QUESTION_LEN2 = 2000, DEPTHS, DEFAULT_DEPTH = "standard", DEFAULT_MAX_RESEARCHERS = 3, EXHAUSTIVE_DEFAULT_MAX_RESEARCHERS = 5, DEFAULT_ROUNDS = 2, EXHAUSTIVE_DEFAULT_ROUNDS = 3, USAGE4 = `Usage: /swarm deep-research <question> [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]
|
|
69460
|
+
|
|
69461
|
+
Run a multi-source, fact-checked deep research pass and synthesize a cited report.
|
|
69462
|
+
|
|
69463
|
+
Examples:
|
|
69464
|
+
/swarm deep-research "What are the tradeoffs of WASM vs native plugins?"
|
|
69465
|
+
/swarm deep research "current state of deep research agents" --depth exhaustive
|
|
69466
|
+
/swarm deep-research "is Tavily or Brave better for our use case" --rounds 3 --brief
|
|
69467
|
+
|
|
69468
|
+
Flags:
|
|
69469
|
+
--depth <name> standard (focused) or exhaustive (broader fan-out)
|
|
69470
|
+
--max-researchers <N> parallel synthesis workers per round, 1..6
|
|
69471
|
+
--rounds <N> max iterative research rounds, 1..4
|
|
69472
|
+
--brief emit a short brief instead of a full cited report
|
|
69473
|
+
|
|
69474
|
+
Requires council.general.enabled: true and a configured search API key (Tavily or Brave)
|
|
69475
|
+
in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project
|
|
69476
|
+
.opencode/opencode-swarm.json overrides.`;
|
|
69477
|
+
var init_deep_research = __esm(() => {
|
|
69478
|
+
DEPTHS = new Set(["standard", "exhaustive"]);
|
|
69479
|
+
});
|
|
69480
|
+
|
|
69356
69481
|
// src/commands/design-docs.ts
|
|
69357
69482
|
function sanitizeDescription(raw) {
|
|
69358
69483
|
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
@@ -69370,7 +69495,7 @@ function cleanFlagValue(raw) {
|
|
|
69370
69495
|
return null;
|
|
69371
69496
|
return raw;
|
|
69372
69497
|
}
|
|
69373
|
-
function
|
|
69498
|
+
function parseArgs5(args2) {
|
|
69374
69499
|
const result = {
|
|
69375
69500
|
out: "docs",
|
|
69376
69501
|
lang: "auto",
|
|
@@ -69425,30 +69550,30 @@ function parseArgs4(args2) {
|
|
|
69425
69550
|
return result;
|
|
69426
69551
|
}
|
|
69427
69552
|
async function handleDesignDocsCommand(directory, args2) {
|
|
69428
|
-
const parsed =
|
|
69553
|
+
const parsed = parseArgs5(args2);
|
|
69429
69554
|
if (parsed.error) {
|
|
69430
69555
|
return `Error: ${parsed.error}
|
|
69431
69556
|
|
|
69432
|
-
${
|
|
69557
|
+
${USAGE5}`;
|
|
69433
69558
|
}
|
|
69434
69559
|
try {
|
|
69435
69560
|
const { config: config3 } = loadPluginConfigWithMeta(directory);
|
|
69436
69561
|
if (config3.design_docs?.enabled !== true) {
|
|
69437
69562
|
return "Error: design docs are disabled. Set `design_docs.enabled: true` in " + `opencode-swarm.json to enable the docs_design agent and this command.
|
|
69438
69563
|
|
|
69439
|
-
` +
|
|
69564
|
+
` + USAGE5;
|
|
69440
69565
|
}
|
|
69441
69566
|
} catch (configErr) {
|
|
69442
69567
|
console.warn(`[design-docs] Could not read opencode-swarm.json (${String(configErr)}). ` + "Falling through — the architect will abort if docs_design is not registered.");
|
|
69443
69568
|
}
|
|
69444
69569
|
const description = sanitizeDescription(parsed.rest.join(" "));
|
|
69445
69570
|
if (!description && !parsed.update) {
|
|
69446
|
-
return
|
|
69571
|
+
return USAGE5;
|
|
69447
69572
|
}
|
|
69448
69573
|
const header = `[MODE: DESIGN_DOCS out=${parsed.out} lang=${parsed.lang} update=${parsed.update}] ${description}`;
|
|
69449
69574
|
return header.trimEnd();
|
|
69450
69575
|
}
|
|
69451
|
-
var MAX_DESC_LEN = 2000,
|
|
69576
|
+
var MAX_DESC_LEN = 2000, USAGE5 = `Usage: /swarm design-docs <description> [--out <dir>] [--lang <name>] [--update]
|
|
69452
69577
|
|
|
69453
69578
|
Generate or sync language-agnostic design docs for the project under build:
|
|
69454
69579
|
<out>/domain.md, <out>/technical-spec.md, <out>/behavior-spec.md,
|
|
@@ -70663,11 +70788,11 @@ async function getDiagnoseData(directory) {
|
|
|
70663
70788
|
detail: "No snapshots yet (snapshots written on next session start)"
|
|
70664
70789
|
});
|
|
70665
70790
|
}
|
|
70666
|
-
if (
|
|
70791
|
+
if (getDeferredWarnings().length > 0) {
|
|
70667
70792
|
checks5.push({
|
|
70668
70793
|
name: "Deferred Warnings",
|
|
70669
70794
|
status: "⚠️",
|
|
70670
|
-
detail: `${
|
|
70795
|
+
detail: `${getDeferredWarnings().length} warning(s) deferred from init (run with verbose logs for details)`
|
|
70671
70796
|
});
|
|
70672
70797
|
}
|
|
70673
70798
|
const cachePaths = getPluginCachePaths();
|
|
@@ -70706,7 +70831,8 @@ async function getDiagnoseData(directory) {
|
|
|
70706
70831
|
checks: checks5,
|
|
70707
70832
|
passCount,
|
|
70708
70833
|
totalCount,
|
|
70709
|
-
allPassed
|
|
70834
|
+
allPassed,
|
|
70835
|
+
deferredWarnings: getDeferredWarnings()
|
|
70710
70836
|
};
|
|
70711
70837
|
}
|
|
70712
70838
|
function formatDiagnoseMarkdown(diagnose) {
|
|
@@ -70717,11 +70843,11 @@ function formatDiagnoseMarkdown(diagnose) {
|
|
|
70717
70843
|
"",
|
|
70718
70844
|
`**Result**: ${diagnose.allPassed ? "✅ All checks passed" : `⚠️ ${diagnose.passCount}/${diagnose.totalCount} checks passed`}`
|
|
70719
70845
|
];
|
|
70720
|
-
if (deferredWarnings.length > 0) {
|
|
70846
|
+
if (diagnose.deferredWarnings.length > 0) {
|
|
70721
70847
|
lines.push("");
|
|
70722
70848
|
lines.push("## Deferred Warnings");
|
|
70723
70849
|
lines.push("");
|
|
70724
|
-
for (const warning of deferredWarnings) {
|
|
70850
|
+
for (const warning of diagnose.deferredWarnings) {
|
|
70725
70851
|
lines.push(`- ${warning}`);
|
|
70726
70852
|
}
|
|
70727
70853
|
}
|
|
@@ -75204,7 +75330,7 @@ function validateAndSanitizeUrl2(rawUrl) {
|
|
|
75204
75330
|
return { error: "Invalid URL format" };
|
|
75205
75331
|
}
|
|
75206
75332
|
}
|
|
75207
|
-
function
|
|
75333
|
+
function parseArgs6(args2) {
|
|
75208
75334
|
const out2 = {
|
|
75209
75335
|
plan: false,
|
|
75210
75336
|
trace: false,
|
|
@@ -75278,24 +75404,24 @@ function detectGitRemote2() {
|
|
|
75278
75404
|
}
|
|
75279
75405
|
}
|
|
75280
75406
|
function handleIssueCommand(_directory, args2) {
|
|
75281
|
-
const parsed =
|
|
75407
|
+
const parsed = parseArgs6(args2);
|
|
75282
75408
|
const rawInput = parsed.rest.join(" ").trim();
|
|
75283
75409
|
if (!rawInput) {
|
|
75284
|
-
return
|
|
75410
|
+
return USAGE6;
|
|
75285
75411
|
}
|
|
75286
75412
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
75287
75413
|
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl2(rawInput) : rawInput);
|
|
75288
75414
|
if (!issueInfo) {
|
|
75289
75415
|
return `Error: Could not parse issue reference from "${rawInput}"
|
|
75290
75416
|
|
|
75291
|
-
${
|
|
75417
|
+
${USAGE6}`;
|
|
75292
75418
|
}
|
|
75293
75419
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
75294
75420
|
const result = validateAndSanitizeUrl2(issueUrl);
|
|
75295
75421
|
if ("error" in result) {
|
|
75296
75422
|
return `Error: ${result.error}
|
|
75297
75423
|
|
|
75298
|
-
${
|
|
75424
|
+
${USAGE6}`;
|
|
75299
75425
|
}
|
|
75300
75426
|
const flags2 = [];
|
|
75301
75427
|
if (parsed.plan)
|
|
@@ -75307,10 +75433,10 @@ ${USAGE5}`;
|
|
|
75307
75433
|
const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
|
|
75308
75434
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
75309
75435
|
}
|
|
75310
|
-
var MAX_URL_LEN2 = 2048,
|
|
75436
|
+
var MAX_URL_LEN2 = 2048, USAGE6;
|
|
75311
75437
|
var init_issue = __esm(() => {
|
|
75312
75438
|
init_pr_ref();
|
|
75313
|
-
|
|
75439
|
+
USAGE6 = [
|
|
75314
75440
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
75315
75441
|
"",
|
|
75316
75442
|
"Ingest a GitHub issue into the swarm workflow.",
|
|
@@ -80579,7 +80705,7 @@ var init_pr_monitor_status = __esm(() => {
|
|
|
80579
80705
|
});
|
|
80580
80706
|
|
|
80581
80707
|
// src/commands/pr-review.ts
|
|
80582
|
-
function
|
|
80708
|
+
function parseArgs7(args2) {
|
|
80583
80709
|
const out2 = { council: false, rest: [] };
|
|
80584
80710
|
for (const token of args2) {
|
|
80585
80711
|
if (token === "--council") {
|
|
@@ -80598,29 +80724,29 @@ function parseArgs6(args2) {
|
|
|
80598
80724
|
return out2;
|
|
80599
80725
|
}
|
|
80600
80726
|
function handlePrReviewCommand(directory, args2) {
|
|
80601
|
-
const parsed =
|
|
80727
|
+
const parsed = parseArgs7(args2);
|
|
80602
80728
|
if (parsed.unknownFlag) {
|
|
80603
80729
|
return `Error: Unknown flag "${parsed.unknownFlag}"
|
|
80604
80730
|
|
|
80605
|
-
${
|
|
80731
|
+
${USAGE7}`;
|
|
80606
80732
|
}
|
|
80607
80733
|
const resolved = resolvePrCommandInput(parsed.rest, directory);
|
|
80608
80734
|
if (resolved === null) {
|
|
80609
|
-
return
|
|
80735
|
+
return USAGE7;
|
|
80610
80736
|
}
|
|
80611
80737
|
if ("error" in resolved) {
|
|
80612
80738
|
return `Error: ${resolved.error}
|
|
80613
80739
|
|
|
80614
|
-
${
|
|
80740
|
+
${USAGE7}`;
|
|
80615
80741
|
}
|
|
80616
80742
|
const councilFlag = parsed.council ? "council=true" : "council=false";
|
|
80617
80743
|
const signal = `[MODE: PR_REVIEW pr="${resolved.prUrl}" ${councilFlag}]`;
|
|
80618
80744
|
return resolved.instructions ? `${signal} ${resolved.instructions}` : signal;
|
|
80619
80745
|
}
|
|
80620
|
-
var
|
|
80746
|
+
var USAGE7;
|
|
80621
80747
|
var init_pr_review = __esm(() => {
|
|
80622
80748
|
init_pr_ref();
|
|
80623
|
-
|
|
80749
|
+
USAGE7 = [
|
|
80624
80750
|
"Usage: /swarm pr-review <url|owner/repo#N|N> [--council] [instructions...]",
|
|
80625
80751
|
"",
|
|
80626
80752
|
"Run a full swarm PR review on a GitHub pull request.",
|
|
@@ -87896,7 +88022,7 @@ var init_rollback = __esm(() => {
|
|
|
87896
88022
|
});
|
|
87897
88023
|
|
|
87898
88024
|
// src/commands/sdd.ts
|
|
87899
|
-
function
|
|
88025
|
+
function parseArgs8(args2) {
|
|
87900
88026
|
const parsed = { json: false, dryRun: false };
|
|
87901
88027
|
for (let i2 = 0;i2 < args2.length; i2++) {
|
|
87902
88028
|
const token = args2[i2];
|
|
@@ -87927,11 +88053,11 @@ function formatList(items) {
|
|
|
87927
88053
|
`) : "- none";
|
|
87928
88054
|
}
|
|
87929
88055
|
async function handleSddStatusCommand(directory, args2) {
|
|
87930
|
-
const parsed =
|
|
88056
|
+
const parsed = parseArgs8(args2);
|
|
87931
88057
|
if (parsed.error)
|
|
87932
88058
|
return `Error: ${parsed.error}
|
|
87933
88059
|
|
|
87934
|
-
${
|
|
88060
|
+
${USAGE8}`;
|
|
87935
88061
|
const status = loadSddStatusSync(directory);
|
|
87936
88062
|
if (parsed.json)
|
|
87937
88063
|
return JSON.stringify(status, null, 2);
|
|
@@ -87965,11 +88091,11 @@ ${formatList([
|
|
|
87965
88091
|
`);
|
|
87966
88092
|
}
|
|
87967
88093
|
async function handleSddValidateCommand(directory, args2) {
|
|
87968
|
-
const parsed =
|
|
88094
|
+
const parsed = parseArgs8(args2);
|
|
87969
88095
|
if (parsed.error)
|
|
87970
88096
|
return `Error: ${parsed.error}
|
|
87971
88097
|
|
|
87972
|
-
${
|
|
88098
|
+
${USAGE8}`;
|
|
87973
88099
|
const status = loadSddStatusSync(directory);
|
|
87974
88100
|
const projection = buildOpenSpecProjectionSync(directory, {
|
|
87975
88101
|
changeId: parsed.changeId
|
|
@@ -88007,11 +88133,11 @@ ${formatList(result.warnings)}` : ""
|
|
|
88007
88133
|
`);
|
|
88008
88134
|
}
|
|
88009
88135
|
async function handleSddProjectCommand(directory, args2) {
|
|
88010
|
-
const parsed =
|
|
88136
|
+
const parsed = parseArgs8(args2);
|
|
88011
88137
|
if (parsed.error)
|
|
88012
88138
|
return `Error: ${parsed.error}
|
|
88013
88139
|
|
|
88014
|
-
${
|
|
88140
|
+
${USAGE8}`;
|
|
88015
88141
|
const result = writeProjectedSpecSync(directory, {
|
|
88016
88142
|
changeId: parsed.changeId,
|
|
88017
88143
|
dryRun: parsed.dryRun
|
|
@@ -88031,7 +88157,7 @@ ${USAGE7}`;
|
|
|
88031
88157
|
return [
|
|
88032
88158
|
"SDD projection failed: no valid OpenSpec-compatible projection could be built.",
|
|
88033
88159
|
"",
|
|
88034
|
-
|
|
88160
|
+
USAGE8
|
|
88035
88161
|
].join(`
|
|
88036
88162
|
`);
|
|
88037
88163
|
}
|
|
@@ -88048,9 +88174,9 @@ ${formatList(result.projection.warnings)}` : ""
|
|
|
88048
88174
|
`);
|
|
88049
88175
|
}
|
|
88050
88176
|
async function handleSddCommand(_directory, _args) {
|
|
88051
|
-
return
|
|
88177
|
+
return USAGE8;
|
|
88052
88178
|
}
|
|
88053
|
-
var
|
|
88179
|
+
var USAGE8 = `Usage:
|
|
88054
88180
|
/swarm sdd status [--json]
|
|
88055
88181
|
/swarm sdd validate [--json] [--change <id>]
|
|
88056
88182
|
/swarm sdd project [--dry-run] [--json] [--change <id>]
|
|
@@ -89556,6 +89682,7 @@ __export(exports_commands, {
|
|
|
89556
89682
|
handleEvidenceCommand: () => handleEvidenceCommand,
|
|
89557
89683
|
handleDoctorCommand: () => handleDoctorCommand,
|
|
89558
89684
|
handleDiagnoseCommand: () => handleDiagnoseCommand,
|
|
89685
|
+
handleDeepResearchCommand: () => handleDeepResearchCommand,
|
|
89559
89686
|
handleDeepDiveCommand: () => handleDeepDiveCommand,
|
|
89560
89687
|
handleDarkMatterCommand: () => handleDarkMatterCommand,
|
|
89561
89688
|
handleCurateCommand: () => handleCurateCommand,
|
|
@@ -89834,6 +89961,7 @@ var init_commands = __esm(() => {
|
|
|
89834
89961
|
init_curate();
|
|
89835
89962
|
init_dark_matter();
|
|
89836
89963
|
init_deep_dive();
|
|
89964
|
+
init_deep_research();
|
|
89837
89965
|
init_diagnose();
|
|
89838
89966
|
init_doctor();
|
|
89839
89967
|
init_evidence();
|
|
@@ -90066,6 +90194,7 @@ var init_registry = __esm(() => {
|
|
|
90066
90194
|
init_curate();
|
|
90067
90195
|
init_dark_matter();
|
|
90068
90196
|
init_deep_dive();
|
|
90197
|
+
init_deep_research();
|
|
90069
90198
|
init_design_docs();
|
|
90070
90199
|
init_diagnose();
|
|
90071
90200
|
init_doctor();
|
|
@@ -90461,6 +90590,20 @@ Subcommands:
|
|
|
90461
90590
|
category: "agent",
|
|
90462
90591
|
aliasOf: "deep-dive"
|
|
90463
90592
|
},
|
|
90593
|
+
"deep-research": {
|
|
90594
|
+
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleDeepResearchCommand),
|
|
90595
|
+
description: "Launch a multi-source, fact-checked deep research pass and synthesize a cited report [question]",
|
|
90596
|
+
args: "<question> [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]",
|
|
90597
|
+
details: "Runs the orchestrator-worker deep-research protocol: the architect decomposes the question into subtopics, gathers evidence with web_search and web_fetch across up to N iterative rounds, dispatches parallel sme synthesis workers, verifies every claim against cited sources with dual reviewers, challenges high-stakes claims with the critic, and presents a cited report in chat. Read-only — does not mutate source code, delegate to coder, or call declare_scope. Requires council.general.enabled and a search API key.",
|
|
90598
|
+
category: "agent"
|
|
90599
|
+
},
|
|
90600
|
+
"deep research": {
|
|
90601
|
+
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleDeepResearchCommand),
|
|
90602
|
+
description: "Alias for /swarm deep-research — launch a cited deep research pass",
|
|
90603
|
+
args: "<question> [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]",
|
|
90604
|
+
category: "agent",
|
|
90605
|
+
aliasOf: "deep-research"
|
|
90606
|
+
},
|
|
90464
90607
|
"codebase-review": {
|
|
90465
90608
|
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleCodebaseReviewCommand),
|
|
90466
90609
|
description: "Launch codebase-review-swarm for a quote-grounded full-repo or large-subsystem audit",
|
|
@@ -91957,6 +92100,22 @@ HARD CONSTRAINTS (apply regardless of skill load success):
|
|
|
91957
92100
|
- Explorers generate candidate findings only — reviewers verify or reject
|
|
91958
92101
|
- Critics challenge only HIGH/CRITICAL findings — do NOT waste cycles on lower severity
|
|
91959
92102
|
|
|
92103
|
+
### MODE: DEEP_RESEARCH
|
|
92104
|
+
Activates when: architect receives \`[MODE: DEEP_RESEARCH depth=X max_researchers=N rounds=N output=report|brief] <question>\` signal from the deep-research command handler.
|
|
92105
|
+
|
|
92106
|
+
Purpose: Orchestrator-worker deep research over external sources. Decompose the question into subtopics, gather evidence with \`web_search\` and \`web_fetch\` across up to \`rounds\` iterative rounds (re-planning gaps between rounds), dispatch parallel sme synthesis workers, verify every claim against cited sources with 2 reviewers, challenge high-stakes claims with the critic, and present a cited report in chat. This mode does NOT mutate source code, does NOT delegate to coder, and does NOT call declare_scope.
|
|
92107
|
+
|
|
92108
|
+
ACTION: Load skill file:.opencode/skills/deep-research/SKILL.md immediately and follow its protocol.
|
|
92109
|
+
|
|
92110
|
+
HARD CONSTRAINTS (apply regardless of skill load success):
|
|
92111
|
+
- Do NOT delegate to coder
|
|
92112
|
+
- Do NOT call declare_scope
|
|
92113
|
+
- Do NOT mutate source code or write any files outside .swarm/
|
|
92114
|
+
- You (architect) own \`web_search\` and \`web_fetch\`; sme workers receive gathered evidence in their dispatch message — do NOT expect sme to fetch
|
|
92115
|
+
- Every claim in the final report MUST cite a source from the gathered evidence; reviewers verify claim↔citation before a claim is reported
|
|
92116
|
+
- Critics challenge only high-stakes / contested claims — do NOT waste cycles on well-supported ones
|
|
92117
|
+
- If council.general.enabled is false or no search API key is configured, surface that and STOP — do not produce ungrounded research
|
|
92118
|
+
|
|
91960
92119
|
### MODE: CODEBASE_REVIEW
|
|
91961
92120
|
Activates when: architect receives \`[MODE: CODEBASE_REVIEW mode=X output=X update_main=X allow_dirty=X tracks="..." continue_run="..."] scope="..."\` signal from the codebase-review command handler.
|
|
91962
92121
|
|
|
@@ -101144,7 +101303,7 @@ var init_design_doc_drift = __esm(() => {
|
|
|
101144
101303
|
var exports_project_context = {};
|
|
101145
101304
|
__export(exports_project_context, {
|
|
101146
101305
|
buildProjectContext: () => buildProjectContext,
|
|
101147
|
-
_internals: () =>
|
|
101306
|
+
_internals: () => _internals104,
|
|
101148
101307
|
LANG_BACKEND_DETECTION_TIMEOUT_MS: () => LANG_BACKEND_DETECTION_TIMEOUT_MS
|
|
101149
101308
|
});
|
|
101150
101309
|
import * as fs136 from "node:fs";
|
|
@@ -101228,7 +101387,7 @@ function selectLintCommand(backend, directory) {
|
|
|
101228
101387
|
return null;
|
|
101229
101388
|
}
|
|
101230
101389
|
async function buildProjectContext(directory) {
|
|
101231
|
-
const backend = await
|
|
101390
|
+
const backend = await _internals104.pickBackend(directory);
|
|
101232
101391
|
if (!backend)
|
|
101233
101392
|
return null;
|
|
101234
101393
|
const ctx = emptyProjectContext();
|
|
@@ -101267,17 +101426,17 @@ async function buildProjectContext(directory) {
|
|
|
101267
101426
|
if (backend.prompts.reviewerChecklist.length > 0) {
|
|
101268
101427
|
ctx.REVIEWER_CHECKLIST = bulletList(backend.prompts.reviewerChecklist);
|
|
101269
101428
|
}
|
|
101270
|
-
const profiles =
|
|
101429
|
+
const profiles = _internals104.pickedProfiles(directory);
|
|
101271
101430
|
if (profiles.length > 1) {
|
|
101272
101431
|
ctx.PROJECT_CONTEXT_SECONDARY_LANGUAGES = profiles.slice(1).map((p) => p.id).join(", ");
|
|
101273
101432
|
}
|
|
101274
101433
|
return ctx;
|
|
101275
101434
|
}
|
|
101276
|
-
var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300,
|
|
101435
|
+
var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300, _internals104;
|
|
101277
101436
|
var init_project_context = __esm(() => {
|
|
101278
101437
|
init_dispatch();
|
|
101279
101438
|
init_framework_detector();
|
|
101280
|
-
|
|
101439
|
+
_internals104 = {
|
|
101281
101440
|
pickBackend,
|
|
101282
101441
|
pickedProfiles
|
|
101283
101442
|
};
|
|
@@ -140503,6 +140662,696 @@ var update_task_status = createSwarmTool({
|
|
|
140503
140662
|
}
|
|
140504
140663
|
});
|
|
140505
140664
|
|
|
140665
|
+
// src/tools/web-fetch.ts
|
|
140666
|
+
init_zod();
|
|
140667
|
+
init_loader();
|
|
140668
|
+
import { lookup } from "node:dns/promises";
|
|
140669
|
+
import * as http from "node:http";
|
|
140670
|
+
import * as https from "node:https";
|
|
140671
|
+
import { isIP } from "node:net";
|
|
140672
|
+
import { Readable } from "node:stream";
|
|
140673
|
+
import * as zlib from "node:zlib";
|
|
140674
|
+
|
|
140675
|
+
// src/evidence/documents.ts
|
|
140676
|
+
init_utils2();
|
|
140677
|
+
init_redaction();
|
|
140678
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
140679
|
+
import { appendFile as appendFile17, mkdir as mkdir31 } from "node:fs/promises";
|
|
140680
|
+
import * as path188 from "node:path";
|
|
140681
|
+
var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
|
|
140682
|
+
var MAX_EVIDENCE_TEXT_LENGTH = 4000;
|
|
140683
|
+
async function writeEvidenceDocuments(directory, inputs, now = () => new Date) {
|
|
140684
|
+
const filePath = validateSwarmPath(directory, EVIDENCE_CACHE_FILE);
|
|
140685
|
+
const capturedAt = now().toISOString();
|
|
140686
|
+
const records = inputs.map((input) => createEvidenceDocumentRecord(input, capturedAt)).filter((record3) => record3 !== null);
|
|
140687
|
+
if (records.length > 0) {
|
|
140688
|
+
await mkdir31(path188.dirname(filePath), { recursive: true });
|
|
140689
|
+
await appendFile17(filePath, `${records.map((record3) => JSON.stringify(record3)).join(`
|
|
140690
|
+
`)}
|
|
140691
|
+
`, "utf-8");
|
|
140692
|
+
}
|
|
140693
|
+
return {
|
|
140694
|
+
path: ".swarm/evidence-cache/documents.jsonl",
|
|
140695
|
+
records,
|
|
140696
|
+
refs: records.map((record3) => record3.ref)
|
|
140697
|
+
};
|
|
140698
|
+
}
|
|
140699
|
+
function createEvidenceDocumentRecord(input, defaultCapturedAt) {
|
|
140700
|
+
const text = normalizeEvidenceText(input.text ?? input.snippet ?? "");
|
|
140701
|
+
if (!text)
|
|
140702
|
+
return null;
|
|
140703
|
+
const capturedAt = input.capturedAt ?? defaultCapturedAt;
|
|
140704
|
+
const base = {
|
|
140705
|
+
sourceType: input.sourceType,
|
|
140706
|
+
query: normalizeOptional(input.query),
|
|
140707
|
+
title: normalizeOptional(input.title),
|
|
140708
|
+
url: normalizeOptional(input.url),
|
|
140709
|
+
text
|
|
140710
|
+
};
|
|
140711
|
+
const id = createEvidenceDocumentId(base);
|
|
140712
|
+
return {
|
|
140713
|
+
id,
|
|
140714
|
+
ref: `evidence-cache:${id}`,
|
|
140715
|
+
...base,
|
|
140716
|
+
capturedAt,
|
|
140717
|
+
createdBy: normalizeOptional(input.createdBy),
|
|
140718
|
+
metadata: input.metadata ?? {}
|
|
140719
|
+
};
|
|
140720
|
+
}
|
|
140721
|
+
function createEvidenceDocumentId(input) {
|
|
140722
|
+
const hash4 = createHash16("sha256").update([
|
|
140723
|
+
input.sourceType,
|
|
140724
|
+
input.query ?? "",
|
|
140725
|
+
input.title ?? "",
|
|
140726
|
+
input.url ?? "",
|
|
140727
|
+
input.text
|
|
140728
|
+
].join(`
|
|
140729
|
+
`)).digest("hex");
|
|
140730
|
+
return `evd_${hash4.slice(0, 16)}`;
|
|
140731
|
+
}
|
|
140732
|
+
function normalizeEvidenceText(text) {
|
|
140733
|
+
const normalized = redactSecrets(text.replace(/\s+/g, " ").trim());
|
|
140734
|
+
return truncateEvidenceText(normalized, MAX_EVIDENCE_TEXT_LENGTH);
|
|
140735
|
+
}
|
|
140736
|
+
function truncateEvidenceText(text, maxLength) {
|
|
140737
|
+
if (text.length <= maxLength)
|
|
140738
|
+
return text;
|
|
140739
|
+
const truncated = text.slice(0, maxLength);
|
|
140740
|
+
const lastPlaceholderStart = truncated.lastIndexOf("[REDACTED:");
|
|
140741
|
+
const lastPlaceholderEnd = truncated.lastIndexOf("]");
|
|
140742
|
+
if (lastPlaceholderStart > lastPlaceholderEnd) {
|
|
140743
|
+
return truncated.slice(0, lastPlaceholderStart).trimEnd();
|
|
140744
|
+
}
|
|
140745
|
+
return truncated;
|
|
140746
|
+
}
|
|
140747
|
+
function normalizeOptional(value) {
|
|
140748
|
+
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
140749
|
+
return normalized ? redactSecrets(normalized) : undefined;
|
|
140750
|
+
}
|
|
140751
|
+
|
|
140752
|
+
// src/tools/web-fetch.ts
|
|
140753
|
+
init_create_tool();
|
|
140754
|
+
init_resolve_working_directory();
|
|
140755
|
+
var DEFAULT_MAX_BYTES = 1e6;
|
|
140756
|
+
var MAX_BYTES_HARD_CAP = 5000000;
|
|
140757
|
+
var DEFAULT_TIMEOUT_MS4 = 15000;
|
|
140758
|
+
var MAX_TIMEOUT_MS2 = 30000;
|
|
140759
|
+
var MAX_REDIRECTS = 5;
|
|
140760
|
+
var MAX_TEXT_LENGTH2 = 50000;
|
|
140761
|
+
var ArgsSchema6 = exports_external.object({
|
|
140762
|
+
url: exports_external.string().min(1).max(2048),
|
|
140763
|
+
max_bytes: exports_external.number().int().min(1024).max(MAX_BYTES_HARD_CAP).optional(),
|
|
140764
|
+
timeout_ms: exports_external.number().int().min(1000).max(MAX_TIMEOUT_MS2).optional(),
|
|
140765
|
+
working_directory: exports_external.string().optional()
|
|
140766
|
+
});
|
|
140767
|
+
function isBlockedAddress(address) {
|
|
140768
|
+
const family = isIP(address);
|
|
140769
|
+
if (family === 4)
|
|
140770
|
+
return isBlockedIPv4(address);
|
|
140771
|
+
if (family === 6)
|
|
140772
|
+
return isBlockedIPv6(address);
|
|
140773
|
+
return true;
|
|
140774
|
+
}
|
|
140775
|
+
function isBlockedIPv4(address) {
|
|
140776
|
+
const parts2 = address.split(".");
|
|
140777
|
+
if (parts2.length !== 4)
|
|
140778
|
+
return true;
|
|
140779
|
+
const octets = parts2.map((p) => Number(p));
|
|
140780
|
+
if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255))
|
|
140781
|
+
return true;
|
|
140782
|
+
const [a, b] = octets;
|
|
140783
|
+
if (a === 0)
|
|
140784
|
+
return true;
|
|
140785
|
+
if (a === 10)
|
|
140786
|
+
return true;
|
|
140787
|
+
if (a === 127)
|
|
140788
|
+
return true;
|
|
140789
|
+
if (a === 169 && b === 254)
|
|
140790
|
+
return true;
|
|
140791
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
140792
|
+
return true;
|
|
140793
|
+
if (a === 192 && b === 168)
|
|
140794
|
+
return true;
|
|
140795
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
140796
|
+
return true;
|
|
140797
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
140798
|
+
return true;
|
|
140799
|
+
if (a === 192 && b === 0 && octets[2] === 0)
|
|
140800
|
+
return true;
|
|
140801
|
+
if (a === 192 && b === 0 && octets[2] === 2)
|
|
140802
|
+
return true;
|
|
140803
|
+
if (a === 198 && b === 51 && octets[2] === 100)
|
|
140804
|
+
return true;
|
|
140805
|
+
if (a === 203 && b === 0 && octets[2] === 113)
|
|
140806
|
+
return true;
|
|
140807
|
+
if (a === 192 && b === 88 && octets[2] === 99)
|
|
140808
|
+
return true;
|
|
140809
|
+
if (a >= 224)
|
|
140810
|
+
return true;
|
|
140811
|
+
return false;
|
|
140812
|
+
}
|
|
140813
|
+
function expandIPv6(input) {
|
|
140814
|
+
let s = input.toLowerCase().split("%")[0];
|
|
140815
|
+
if (!s)
|
|
140816
|
+
return null;
|
|
140817
|
+
if (s.includes(".")) {
|
|
140818
|
+
const colon = s.lastIndexOf(":");
|
|
140819
|
+
if (colon === -1)
|
|
140820
|
+
return null;
|
|
140821
|
+
const v4 = s.slice(colon + 1).split(".");
|
|
140822
|
+
if (v4.length !== 4)
|
|
140823
|
+
return null;
|
|
140824
|
+
const o = v4.map((p) => Number(p));
|
|
140825
|
+
if (o.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
|
|
140826
|
+
return null;
|
|
140827
|
+
const h1 = (o[0] << 8 | o[1]).toString(16);
|
|
140828
|
+
const h2 = (o[2] << 8 | o[3]).toString(16);
|
|
140829
|
+
s = `${s.slice(0, colon + 1)}${h1}:${h2}`;
|
|
140830
|
+
}
|
|
140831
|
+
const halves = s.split("::");
|
|
140832
|
+
if (halves.length > 2)
|
|
140833
|
+
return null;
|
|
140834
|
+
const parseGroups = (part) => {
|
|
140835
|
+
if (part === "")
|
|
140836
|
+
return [];
|
|
140837
|
+
const groups = part.split(":");
|
|
140838
|
+
const out2 = [];
|
|
140839
|
+
for (const g of groups) {
|
|
140840
|
+
if (!/^[0-9a-f]{1,4}$/.test(g))
|
|
140841
|
+
return null;
|
|
140842
|
+
out2.push(Number.parseInt(g, 16));
|
|
140843
|
+
}
|
|
140844
|
+
return out2;
|
|
140845
|
+
};
|
|
140846
|
+
const head = parseGroups(halves[0]);
|
|
140847
|
+
if (head === null)
|
|
140848
|
+
return null;
|
|
140849
|
+
if (halves.length === 2) {
|
|
140850
|
+
const tail = parseGroups(halves[1]);
|
|
140851
|
+
if (tail === null)
|
|
140852
|
+
return null;
|
|
140853
|
+
const missing = 8 - head.length - tail.length;
|
|
140854
|
+
if (missing < 1)
|
|
140855
|
+
return null;
|
|
140856
|
+
return [...head, ...new Array(missing).fill(0), ...tail];
|
|
140857
|
+
}
|
|
140858
|
+
return head.length === 8 ? head : null;
|
|
140859
|
+
}
|
|
140860
|
+
function isBlockedIPv6(raw) {
|
|
140861
|
+
const h = expandIPv6(raw);
|
|
140862
|
+
if (!h)
|
|
140863
|
+
return true;
|
|
140864
|
+
if (h.every((x) => x === 0))
|
|
140865
|
+
return true;
|
|
140866
|
+
if (h.slice(0, 7).every((x) => x === 0) && h[7] === 1)
|
|
140867
|
+
return true;
|
|
140868
|
+
const mappedV4 = h.slice(0, 5).every((x) => x === 0) && h[5] === 65535;
|
|
140869
|
+
const compatV4 = h.slice(0, 6).every((x) => x === 0) && !(h[6] === 0 && h[7] <= 1);
|
|
140870
|
+
if (mappedV4 || compatV4) {
|
|
140871
|
+
const v4 = `${h[6] >> 8}.${h[6] & 255}.${h[7] >> 8}.${h[7] & 255}`;
|
|
140872
|
+
return isBlockedIPv4(v4);
|
|
140873
|
+
}
|
|
140874
|
+
const first = h[0];
|
|
140875
|
+
if (first >= 64512 && first <= 65023)
|
|
140876
|
+
return true;
|
|
140877
|
+
if (first >= 65152 && first <= 65215)
|
|
140878
|
+
return true;
|
|
140879
|
+
if (first >= 65280)
|
|
140880
|
+
return true;
|
|
140881
|
+
return false;
|
|
140882
|
+
}
|
|
140883
|
+
async function validateFetchUrl(candidate, dnsLookup) {
|
|
140884
|
+
let url3;
|
|
140885
|
+
try {
|
|
140886
|
+
url3 = new URL(candidate);
|
|
140887
|
+
} catch {
|
|
140888
|
+
return {
|
|
140889
|
+
ok: false,
|
|
140890
|
+
reason: "invalid_url",
|
|
140891
|
+
message: `Not a valid URL: ${candidate}`
|
|
140892
|
+
};
|
|
140893
|
+
}
|
|
140894
|
+
if (url3.protocol !== "http:" && url3.protocol !== "https:") {
|
|
140895
|
+
return {
|
|
140896
|
+
ok: false,
|
|
140897
|
+
reason: "blocked_scheme",
|
|
140898
|
+
message: `Only http and https URLs are allowed (got "${url3.protocol}").`
|
|
140899
|
+
};
|
|
140900
|
+
}
|
|
140901
|
+
const host = url3.hostname.replace(/^\[|\]$/g, "");
|
|
140902
|
+
if (isIP(host)) {
|
|
140903
|
+
if (isBlockedAddress(host)) {
|
|
140904
|
+
return {
|
|
140905
|
+
ok: false,
|
|
140906
|
+
reason: "blocked_host",
|
|
140907
|
+
message: `Refusing to fetch a private, loopback, or reserved address: ${host}`
|
|
140908
|
+
};
|
|
140909
|
+
}
|
|
140910
|
+
return { ok: true, url: url3, address: host };
|
|
140911
|
+
}
|
|
140912
|
+
let resolved;
|
|
140913
|
+
try {
|
|
140914
|
+
resolved = await dnsLookup(host, { all: true });
|
|
140915
|
+
} catch (err3) {
|
|
140916
|
+
return {
|
|
140917
|
+
ok: false,
|
|
140918
|
+
reason: "dns_failure",
|
|
140919
|
+
message: `Could not resolve host "${host}": ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
140920
|
+
};
|
|
140921
|
+
}
|
|
140922
|
+
if (resolved.length === 0) {
|
|
140923
|
+
return {
|
|
140924
|
+
ok: false,
|
|
140925
|
+
reason: "dns_failure",
|
|
140926
|
+
message: `Host "${host}" resolved to no addresses.`
|
|
140927
|
+
};
|
|
140928
|
+
}
|
|
140929
|
+
for (const { address } of resolved) {
|
|
140930
|
+
if (isBlockedAddress(address)) {
|
|
140931
|
+
return {
|
|
140932
|
+
ok: false,
|
|
140933
|
+
reason: "blocked_host",
|
|
140934
|
+
message: `Host "${host}" resolves to a private, loopback, or reserved address (${address}).`
|
|
140935
|
+
};
|
|
140936
|
+
}
|
|
140937
|
+
}
|
|
140938
|
+
return { ok: true, url: url3, address: resolved[0].address };
|
|
140939
|
+
}
|
|
140940
|
+
function isAllowedContentType(contentType) {
|
|
140941
|
+
if (!contentType)
|
|
140942
|
+
return true;
|
|
140943
|
+
const type = contentType.split(";")[0].trim().toLowerCase();
|
|
140944
|
+
if (type.startsWith("text/"))
|
|
140945
|
+
return true;
|
|
140946
|
+
if (type === "application/json" || type === "application/xml")
|
|
140947
|
+
return true;
|
|
140948
|
+
if (type === "application/xhtml+xml" || type.endsWith("+json") || type.endsWith("+xml")) {
|
|
140949
|
+
return true;
|
|
140950
|
+
}
|
|
140951
|
+
return false;
|
|
140952
|
+
}
|
|
140953
|
+
function extractTitle(html) {
|
|
140954
|
+
const lower = html.toLowerCase();
|
|
140955
|
+
const start2 = lower.indexOf("<title");
|
|
140956
|
+
if (start2 === -1)
|
|
140957
|
+
return;
|
|
140958
|
+
const tagEnd = lower.indexOf(">", start2);
|
|
140959
|
+
if (tagEnd === -1)
|
|
140960
|
+
return;
|
|
140961
|
+
const contentStart = tagEnd + 1;
|
|
140962
|
+
const end = lower.indexOf("</title", contentStart);
|
|
140963
|
+
if (end === -1)
|
|
140964
|
+
return;
|
|
140965
|
+
const content = html.slice(contentStart, end);
|
|
140966
|
+
const title = decodeEntities(content.replace(/\s+/g, " ").trim());
|
|
140967
|
+
return title || undefined;
|
|
140968
|
+
}
|
|
140969
|
+
var NAMED_ENTITIES = {
|
|
140970
|
+
amp: "&",
|
|
140971
|
+
lt: "<",
|
|
140972
|
+
gt: ">",
|
|
140973
|
+
quot: '"',
|
|
140974
|
+
apos: "'",
|
|
140975
|
+
nbsp: " ",
|
|
140976
|
+
"#39": "'"
|
|
140977
|
+
};
|
|
140978
|
+
function decodeEntities(text) {
|
|
140979
|
+
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, body2) => {
|
|
140980
|
+
const key = body2.toLowerCase();
|
|
140981
|
+
if (key in NAMED_ENTITIES)
|
|
140982
|
+
return NAMED_ENTITIES[key];
|
|
140983
|
+
if (body2.startsWith("#x") || body2.startsWith("#X")) {
|
|
140984
|
+
const code = Number.parseInt(body2.slice(2), 16);
|
|
140985
|
+
return Number.isFinite(code) ? safeFromCodePoint(code) : whole;
|
|
140986
|
+
}
|
|
140987
|
+
if (body2.startsWith("#")) {
|
|
140988
|
+
const code = Number.parseInt(body2.slice(1), 10);
|
|
140989
|
+
return Number.isFinite(code) ? safeFromCodePoint(code) : whole;
|
|
140990
|
+
}
|
|
140991
|
+
return whole;
|
|
140992
|
+
});
|
|
140993
|
+
}
|
|
140994
|
+
function safeFromCodePoint(code) {
|
|
140995
|
+
try {
|
|
140996
|
+
return String.fromCodePoint(code);
|
|
140997
|
+
} catch {
|
|
140998
|
+
return "";
|
|
140999
|
+
}
|
|
141000
|
+
}
|
|
141001
|
+
function stripSpans(input, open3, close) {
|
|
141002
|
+
const lower = input.toLowerCase();
|
|
141003
|
+
let out2 = "";
|
|
141004
|
+
let i2 = 0;
|
|
141005
|
+
while (i2 < input.length) {
|
|
141006
|
+
const start2 = lower.indexOf(open3, i2);
|
|
141007
|
+
if (start2 === -1) {
|
|
141008
|
+
out2 += input.slice(i2);
|
|
141009
|
+
break;
|
|
141010
|
+
}
|
|
141011
|
+
out2 += input.slice(i2, start2);
|
|
141012
|
+
const end = lower.indexOf(close, start2 + open3.length);
|
|
141013
|
+
if (end === -1)
|
|
141014
|
+
break;
|
|
141015
|
+
i2 = end + close.length;
|
|
141016
|
+
}
|
|
141017
|
+
return out2;
|
|
141018
|
+
}
|
|
141019
|
+
function htmlToText(html) {
|
|
141020
|
+
let withoutScripts = stripSpans(html, "<script", "</script>");
|
|
141021
|
+
withoutScripts = stripSpans(withoutScripts, "<style", "</style>");
|
|
141022
|
+
withoutScripts = stripSpans(withoutScripts, "<noscript", "</noscript>");
|
|
141023
|
+
withoutScripts = stripSpans(withoutScripts, "<!--", "-->");
|
|
141024
|
+
const withBreaks = withoutScripts.replace(/<\/(p|div|li|h[1-6]|tr|section|article|header|footer)>/gi, `
|
|
141025
|
+
`).replace(/<br\s*\/?>/gi, `
|
|
141026
|
+
`);
|
|
141027
|
+
const noTags = withBreaks.replace(/<[^>]+>/g, " ");
|
|
141028
|
+
const decoded = decodeEntities(noTags);
|
|
141029
|
+
return decoded.replace(/[ \t\f\v]+/g, " ").replace(/\s*\n\s*/g, `
|
|
141030
|
+
`).replace(/\n{3,}/g, `
|
|
141031
|
+
|
|
141032
|
+
`).trim();
|
|
141033
|
+
}
|
|
141034
|
+
function makeAbortError() {
|
|
141035
|
+
try {
|
|
141036
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
141037
|
+
} catch {
|
|
141038
|
+
const err3 = new Error("The operation was aborted");
|
|
141039
|
+
err3.name = "AbortError";
|
|
141040
|
+
return err3;
|
|
141041
|
+
}
|
|
141042
|
+
}
|
|
141043
|
+
function performHttpRequest(args2) {
|
|
141044
|
+
const { url: url3, pinnedAddress, signal, headers } = args2;
|
|
141045
|
+
const isHttps = url3.protocol === "https:";
|
|
141046
|
+
const port = url3.port ? Number(url3.port) : isHttps ? 443 : 80;
|
|
141047
|
+
const hostNoBrackets = url3.hostname.replace(/^\[|\]$/g, "");
|
|
141048
|
+
const useSni = isHttps && isIP(hostNoBrackets) === 0;
|
|
141049
|
+
const options = {
|
|
141050
|
+
host: pinnedAddress,
|
|
141051
|
+
port,
|
|
141052
|
+
path: `${url3.pathname}${url3.search}`,
|
|
141053
|
+
method: "GET",
|
|
141054
|
+
headers: { Host: url3.host, ...headers }
|
|
141055
|
+
};
|
|
141056
|
+
if (useSni)
|
|
141057
|
+
options.servername = url3.hostname;
|
|
141058
|
+
return new Promise((resolve72, reject) => {
|
|
141059
|
+
let req;
|
|
141060
|
+
const onResponse = (res) => {
|
|
141061
|
+
const normHeaders = {};
|
|
141062
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
141063
|
+
normHeaders[key] = Array.isArray(value) ? value[0] : value;
|
|
141064
|
+
}
|
|
141065
|
+
res.on("error", () => {});
|
|
141066
|
+
resolve72({
|
|
141067
|
+
status: res.statusCode ?? 0,
|
|
141068
|
+
headers: normHeaders,
|
|
141069
|
+
body: res,
|
|
141070
|
+
cancel: () => req.destroy()
|
|
141071
|
+
});
|
|
141072
|
+
};
|
|
141073
|
+
req = isHttps ? https.request(options, onResponse) : http.request(options, onResponse);
|
|
141074
|
+
const onAbort = () => req.destroy(makeAbortError());
|
|
141075
|
+
if (signal.aborted)
|
|
141076
|
+
req.destroy(makeAbortError());
|
|
141077
|
+
else
|
|
141078
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
141079
|
+
req.on("error", reject);
|
|
141080
|
+
req.end();
|
|
141081
|
+
});
|
|
141082
|
+
}
|
|
141083
|
+
async function boundedFetch(start2, maxBytes, timeoutMs, deps) {
|
|
141084
|
+
let current = start2;
|
|
141085
|
+
const controller = new AbortController;
|
|
141086
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
141087
|
+
try {
|
|
141088
|
+
for (let hop = 0;hop <= MAX_REDIRECTS; hop++) {
|
|
141089
|
+
let raw;
|
|
141090
|
+
try {
|
|
141091
|
+
raw = await deps.httpRequest({
|
|
141092
|
+
url: current.url,
|
|
141093
|
+
pinnedAddress: current.address,
|
|
141094
|
+
signal: controller.signal,
|
|
141095
|
+
headers: {
|
|
141096
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json;q=0.9,*/*;q=0.5"
|
|
141097
|
+
}
|
|
141098
|
+
});
|
|
141099
|
+
} catch (err3) {
|
|
141100
|
+
if (controller.signal.aborted) {
|
|
141101
|
+
return {
|
|
141102
|
+
ok: false,
|
|
141103
|
+
reason: "timeout",
|
|
141104
|
+
message: `Fetch exceeded ${timeoutMs}ms for ${current.url.toString()}`
|
|
141105
|
+
};
|
|
141106
|
+
}
|
|
141107
|
+
return {
|
|
141108
|
+
ok: false,
|
|
141109
|
+
reason: "network_error",
|
|
141110
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
141111
|
+
};
|
|
141112
|
+
}
|
|
141113
|
+
if (raw.status >= 300 && raw.status < 400) {
|
|
141114
|
+
const location = raw.headers.location;
|
|
141115
|
+
raw.cancel?.();
|
|
141116
|
+
if (!location) {
|
|
141117
|
+
return {
|
|
141118
|
+
ok: false,
|
|
141119
|
+
reason: "bad_redirect",
|
|
141120
|
+
message: `Redirect ${raw.status} with no Location header.`
|
|
141121
|
+
};
|
|
141122
|
+
}
|
|
141123
|
+
let next;
|
|
141124
|
+
try {
|
|
141125
|
+
next = new URL(location, current.url);
|
|
141126
|
+
} catch {
|
|
141127
|
+
return {
|
|
141128
|
+
ok: false,
|
|
141129
|
+
reason: "bad_redirect",
|
|
141130
|
+
message: `Invalid redirect target: ${location}`
|
|
141131
|
+
};
|
|
141132
|
+
}
|
|
141133
|
+
const revalidated = await validateFetchUrl(next.toString(), deps.dnsLookup);
|
|
141134
|
+
if (!revalidated.ok)
|
|
141135
|
+
return revalidated;
|
|
141136
|
+
if (hop === MAX_REDIRECTS) {
|
|
141137
|
+
return {
|
|
141138
|
+
ok: false,
|
|
141139
|
+
reason: "too_many_redirects",
|
|
141140
|
+
message: `Exceeded ${MAX_REDIRECTS} redirect hops.`
|
|
141141
|
+
};
|
|
141142
|
+
}
|
|
141143
|
+
current = { url: revalidated.url, address: revalidated.address };
|
|
141144
|
+
continue;
|
|
141145
|
+
}
|
|
141146
|
+
if (raw.status < 200 || raw.status >= 300) {
|
|
141147
|
+
raw.cancel?.();
|
|
141148
|
+
return {
|
|
141149
|
+
ok: false,
|
|
141150
|
+
reason: "http_error",
|
|
141151
|
+
message: `HTTP ${raw.status} for ${current.url.toString()}`
|
|
141152
|
+
};
|
|
141153
|
+
}
|
|
141154
|
+
const contentType = raw.headers["content-type"] ?? null;
|
|
141155
|
+
if (!isAllowedContentType(contentType)) {
|
|
141156
|
+
raw.cancel?.();
|
|
141157
|
+
return {
|
|
141158
|
+
ok: false,
|
|
141159
|
+
reason: "unsupported_content_type",
|
|
141160
|
+
message: `Refusing to read non-text content type "${contentType}".`
|
|
141161
|
+
};
|
|
141162
|
+
}
|
|
141163
|
+
let activeBody = raw.body;
|
|
141164
|
+
if (raw.body !== null && raw.body instanceof Readable) {
|
|
141165
|
+
const encoding = (raw.headers["content-encoding"] ?? "").toLowerCase();
|
|
141166
|
+
let decoder = null;
|
|
141167
|
+
if (encoding === "gzip" || encoding === "x-gzip")
|
|
141168
|
+
decoder = raw.body.pipe(zlib.createGunzip());
|
|
141169
|
+
else if (encoding === "deflate")
|
|
141170
|
+
decoder = raw.body.pipe(zlib.createInflate());
|
|
141171
|
+
else if (encoding === "br")
|
|
141172
|
+
decoder = raw.body.pipe(zlib.createBrotliDecompress());
|
|
141173
|
+
if (decoder) {
|
|
141174
|
+
decoder.on("error", () => {});
|
|
141175
|
+
activeBody = decoder;
|
|
141176
|
+
}
|
|
141177
|
+
}
|
|
141178
|
+
let body2;
|
|
141179
|
+
try {
|
|
141180
|
+
body2 = await readBounded(activeBody, maxBytes);
|
|
141181
|
+
} catch (err3) {
|
|
141182
|
+
raw.cancel?.();
|
|
141183
|
+
if (controller.signal.aborted) {
|
|
141184
|
+
return {
|
|
141185
|
+
ok: false,
|
|
141186
|
+
reason: "timeout",
|
|
141187
|
+
message: `Fetch exceeded ${timeoutMs}ms while reading the body of ${current.url.toString()}`
|
|
141188
|
+
};
|
|
141189
|
+
}
|
|
141190
|
+
return {
|
|
141191
|
+
ok: false,
|
|
141192
|
+
reason: "network_error",
|
|
141193
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
141194
|
+
};
|
|
141195
|
+
}
|
|
141196
|
+
raw.cancel?.();
|
|
141197
|
+
return {
|
|
141198
|
+
ok: true,
|
|
141199
|
+
outcome: {
|
|
141200
|
+
status: raw.status,
|
|
141201
|
+
finalUrl: current.url.toString(),
|
|
141202
|
+
contentType,
|
|
141203
|
+
bytes: body2.bytes,
|
|
141204
|
+
truncated: body2.truncated
|
|
141205
|
+
}
|
|
141206
|
+
};
|
|
141207
|
+
}
|
|
141208
|
+
return {
|
|
141209
|
+
ok: false,
|
|
141210
|
+
reason: "too_many_redirects",
|
|
141211
|
+
message: `Exceeded ${MAX_REDIRECTS} redirect hops.`
|
|
141212
|
+
};
|
|
141213
|
+
} finally {
|
|
141214
|
+
clearTimeout(timer);
|
|
141215
|
+
}
|
|
141216
|
+
}
|
|
141217
|
+
async function readBounded(body2, maxBytes) {
|
|
141218
|
+
if (!body2)
|
|
141219
|
+
return { bytes: new Uint8Array(0), truncated: false };
|
|
141220
|
+
const chunks = [];
|
|
141221
|
+
let received = 0;
|
|
141222
|
+
let truncated = false;
|
|
141223
|
+
for await (const value of body2) {
|
|
141224
|
+
if (!value || value.byteLength === 0)
|
|
141225
|
+
continue;
|
|
141226
|
+
chunks.push(value);
|
|
141227
|
+
received += value.byteLength;
|
|
141228
|
+
if (received > maxBytes) {
|
|
141229
|
+
truncated = true;
|
|
141230
|
+
break;
|
|
141231
|
+
}
|
|
141232
|
+
}
|
|
141233
|
+
const merged = new Uint8Array(Math.min(received, maxBytes));
|
|
141234
|
+
let offset = 0;
|
|
141235
|
+
for (const chunk of chunks) {
|
|
141236
|
+
if (offset >= merged.length)
|
|
141237
|
+
break;
|
|
141238
|
+
const slice = chunk.subarray(0, merged.length - offset);
|
|
141239
|
+
merged.set(slice, offset);
|
|
141240
|
+
offset += slice.byteLength;
|
|
141241
|
+
}
|
|
141242
|
+
return { bytes: merged, truncated };
|
|
141243
|
+
}
|
|
141244
|
+
var web_fetch = createSwarmTool({
|
|
141245
|
+
description: "Fetch the readable text of a single http(s) URL for architect-driven deep research (MODE: DEEP_RESEARCH). " + "Returns decoded page text (HTML stripped to plain text), the document title, the final URL after redirects, " + "and an evidence reference stored alongside web_search results. Use it to read primary sources that web_search " + "only surfaces as snippets. Config-gated on council.general.enabled; no search API key required. " + "Blocks private/loopback/link-local/metadata addresses (re-validated across redirects), enforces a timeout, " + "and caps the response body size.",
|
|
141246
|
+
args: {
|
|
141247
|
+
url: exports_external.string().min(1).max(2048).describe("Absolute http(s) URL to fetch (1–2048 chars)."),
|
|
141248
|
+
max_bytes: exports_external.number().int().min(1024).max(MAX_BYTES_HARD_CAP).optional().describe(`Max decoded response bytes to read (1024..${MAX_BYTES_HARD_CAP}, default ${DEFAULT_MAX_BYTES}).`),
|
|
141249
|
+
timeout_ms: exports_external.number().int().min(1000).max(MAX_TIMEOUT_MS2).optional().describe(`Request timeout in ms (1000..${MAX_TIMEOUT_MS2}, default ${DEFAULT_TIMEOUT_MS4}).`),
|
|
141250
|
+
working_directory: exports_external.string().optional().describe("Project root for config resolution and evidence storage. Optional.")
|
|
141251
|
+
},
|
|
141252
|
+
execute: async (args2, directory) => {
|
|
141253
|
+
const parsed = ArgsSchema6.safeParse(args2);
|
|
141254
|
+
if (!parsed.success) {
|
|
141255
|
+
const fail = {
|
|
141256
|
+
success: false,
|
|
141257
|
+
reason: "invalid_args",
|
|
141258
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
141259
|
+
};
|
|
141260
|
+
return JSON.stringify(fail, null, 2);
|
|
141261
|
+
}
|
|
141262
|
+
const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
|
|
141263
|
+
if (!dirResult.success) {
|
|
141264
|
+
const fail = {
|
|
141265
|
+
success: false,
|
|
141266
|
+
reason: "invalid_working_directory",
|
|
141267
|
+
message: dirResult.message
|
|
141268
|
+
};
|
|
141269
|
+
return JSON.stringify(fail, null, 2);
|
|
141270
|
+
}
|
|
141271
|
+
const config3 = _internals102.loadPluginConfig(dirResult.directory);
|
|
141272
|
+
const generalConfig = config3.council?.general;
|
|
141273
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
141274
|
+
const fail = {
|
|
141275
|
+
success: false,
|
|
141276
|
+
reason: "council_general_disabled",
|
|
141277
|
+
message: "web_fetch is disabled - set council.general.enabled: true in the resolved config: global ~/.config/opencode/opencode-swarm.json or project .opencode/opencode-swarm.json."
|
|
141278
|
+
};
|
|
141279
|
+
return JSON.stringify(fail, null, 2);
|
|
141280
|
+
}
|
|
141281
|
+
const validated = await validateFetchUrl(parsed.data.url, _internals102.dnsLookup);
|
|
141282
|
+
if (!validated.ok) {
|
|
141283
|
+
const fail = {
|
|
141284
|
+
success: false,
|
|
141285
|
+
reason: validated.reason,
|
|
141286
|
+
message: validated.message
|
|
141287
|
+
};
|
|
141288
|
+
return JSON.stringify(fail, null, 2);
|
|
141289
|
+
}
|
|
141290
|
+
const maxBytes = parsed.data.max_bytes ?? DEFAULT_MAX_BYTES;
|
|
141291
|
+
const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
|
|
141292
|
+
const result = await boundedFetch({ url: validated.url, address: validated.address }, maxBytes, timeoutMs, _internals102);
|
|
141293
|
+
if (!result.ok) {
|
|
141294
|
+
const fail = {
|
|
141295
|
+
success: false,
|
|
141296
|
+
reason: result.reason,
|
|
141297
|
+
message: result.message
|
|
141298
|
+
};
|
|
141299
|
+
return JSON.stringify(fail, null, 2);
|
|
141300
|
+
}
|
|
141301
|
+
const { outcome } = result;
|
|
141302
|
+
const raw = new TextDecoder("utf-8", { fatal: false }).decode(outcome.bytes);
|
|
141303
|
+
const isHtml = (outcome.contentType ?? "").toLowerCase().includes("html") || /<html|<!doctype html/i.test(raw);
|
|
141304
|
+
const title = isHtml ? extractTitle(raw) : undefined;
|
|
141305
|
+
const bodyText = isHtml ? htmlToText(raw) : raw.replace(/\s*\n\s*/g, `
|
|
141306
|
+
`).trim();
|
|
141307
|
+
const text = bodyText.length > MAX_TEXT_LENGTH2 ? `${bodyText.slice(0, MAX_TEXT_LENGTH2)}…` : bodyText;
|
|
141308
|
+
const textTruncated = outcome.truncated || bodyText.length > MAX_TEXT_LENGTH2;
|
|
141309
|
+
const evidence = await captureFetchEvidence(dirResult.directory, outcome.finalUrl, title, text);
|
|
141310
|
+
const ok2 = {
|
|
141311
|
+
success: true,
|
|
141312
|
+
url: parsed.data.url,
|
|
141313
|
+
finalUrl: outcome.finalUrl,
|
|
141314
|
+
status: outcome.status,
|
|
141315
|
+
contentType: outcome.contentType ?? undefined,
|
|
141316
|
+
title,
|
|
141317
|
+
text,
|
|
141318
|
+
truncated: textTruncated,
|
|
141319
|
+
bytesReturned: outcome.bytes.byteLength,
|
|
141320
|
+
evidence
|
|
141321
|
+
};
|
|
141322
|
+
return JSON.stringify(ok2, null, 2);
|
|
141323
|
+
}
|
|
141324
|
+
});
|
|
141325
|
+
async function captureFetchEvidence(directory, url3, title, text) {
|
|
141326
|
+
try {
|
|
141327
|
+
const written = await _internals102.writeEvidenceDocuments(directory, [
|
|
141328
|
+
{
|
|
141329
|
+
sourceType: "crawl",
|
|
141330
|
+
url: url3,
|
|
141331
|
+
title,
|
|
141332
|
+
text,
|
|
141333
|
+
createdBy: "web_fetch"
|
|
141334
|
+
}
|
|
141335
|
+
]);
|
|
141336
|
+
return {
|
|
141337
|
+
stored: written.records.length > 0,
|
|
141338
|
+
ref: written.refs[0],
|
|
141339
|
+
path: written.path
|
|
141340
|
+
};
|
|
141341
|
+
} catch (err3) {
|
|
141342
|
+
return {
|
|
141343
|
+
stored: false,
|
|
141344
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
141345
|
+
};
|
|
141346
|
+
}
|
|
141347
|
+
}
|
|
141348
|
+
var _internals102 = {
|
|
141349
|
+
httpRequest: performHttpRequest,
|
|
141350
|
+
dnsLookup: lookup,
|
|
141351
|
+
loadPluginConfig,
|
|
141352
|
+
writeEvidenceDocuments
|
|
141353
|
+
};
|
|
141354
|
+
|
|
140506
141355
|
// src/tools/web-search.ts
|
|
140507
141356
|
init_zod();
|
|
140508
141357
|
init_loader();
|
|
@@ -140699,88 +141548,11 @@ function createWebSearchProvider(config3) {
|
|
|
140699
141548
|
}
|
|
140700
141549
|
}
|
|
140701
141550
|
|
|
140702
|
-
// src/evidence/documents.ts
|
|
140703
|
-
init_utils2();
|
|
140704
|
-
init_redaction();
|
|
140705
|
-
import { createHash as createHash16 } from "node:crypto";
|
|
140706
|
-
import { appendFile as appendFile17, mkdir as mkdir31 } from "node:fs/promises";
|
|
140707
|
-
import * as path188 from "node:path";
|
|
140708
|
-
var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
|
|
140709
|
-
var MAX_EVIDENCE_TEXT_LENGTH = 4000;
|
|
140710
|
-
async function writeEvidenceDocuments(directory, inputs, now = () => new Date) {
|
|
140711
|
-
const filePath = validateSwarmPath(directory, EVIDENCE_CACHE_FILE);
|
|
140712
|
-
const capturedAt = now().toISOString();
|
|
140713
|
-
const records = inputs.map((input) => createEvidenceDocumentRecord(input, capturedAt)).filter((record3) => record3 !== null);
|
|
140714
|
-
if (records.length > 0) {
|
|
140715
|
-
await mkdir31(path188.dirname(filePath), { recursive: true });
|
|
140716
|
-
await appendFile17(filePath, `${records.map((record3) => JSON.stringify(record3)).join(`
|
|
140717
|
-
`)}
|
|
140718
|
-
`, "utf-8");
|
|
140719
|
-
}
|
|
140720
|
-
return {
|
|
140721
|
-
path: ".swarm/evidence-cache/documents.jsonl",
|
|
140722
|
-
records,
|
|
140723
|
-
refs: records.map((record3) => record3.ref)
|
|
140724
|
-
};
|
|
140725
|
-
}
|
|
140726
|
-
function createEvidenceDocumentRecord(input, defaultCapturedAt) {
|
|
140727
|
-
const text = normalizeEvidenceText(input.text ?? input.snippet ?? "");
|
|
140728
|
-
if (!text)
|
|
140729
|
-
return null;
|
|
140730
|
-
const capturedAt = input.capturedAt ?? defaultCapturedAt;
|
|
140731
|
-
const base = {
|
|
140732
|
-
sourceType: input.sourceType,
|
|
140733
|
-
query: normalizeOptional(input.query),
|
|
140734
|
-
title: normalizeOptional(input.title),
|
|
140735
|
-
url: normalizeOptional(input.url),
|
|
140736
|
-
text
|
|
140737
|
-
};
|
|
140738
|
-
const id = createEvidenceDocumentId(base);
|
|
140739
|
-
return {
|
|
140740
|
-
id,
|
|
140741
|
-
ref: `evidence-cache:${id}`,
|
|
140742
|
-
...base,
|
|
140743
|
-
capturedAt,
|
|
140744
|
-
createdBy: normalizeOptional(input.createdBy),
|
|
140745
|
-
metadata: input.metadata ?? {}
|
|
140746
|
-
};
|
|
140747
|
-
}
|
|
140748
|
-
function createEvidenceDocumentId(input) {
|
|
140749
|
-
const hash4 = createHash16("sha256").update([
|
|
140750
|
-
input.sourceType,
|
|
140751
|
-
input.query ?? "",
|
|
140752
|
-
input.title ?? "",
|
|
140753
|
-
input.url ?? "",
|
|
140754
|
-
input.text
|
|
140755
|
-
].join(`
|
|
140756
|
-
`)).digest("hex");
|
|
140757
|
-
return `evd_${hash4.slice(0, 16)}`;
|
|
140758
|
-
}
|
|
140759
|
-
function normalizeEvidenceText(text) {
|
|
140760
|
-
const normalized = redactSecrets(text.replace(/\s+/g, " ").trim());
|
|
140761
|
-
return truncateEvidenceText(normalized, MAX_EVIDENCE_TEXT_LENGTH);
|
|
140762
|
-
}
|
|
140763
|
-
function truncateEvidenceText(text, maxLength) {
|
|
140764
|
-
if (text.length <= maxLength)
|
|
140765
|
-
return text;
|
|
140766
|
-
const truncated = text.slice(0, maxLength);
|
|
140767
|
-
const lastPlaceholderStart = truncated.lastIndexOf("[REDACTED:");
|
|
140768
|
-
const lastPlaceholderEnd = truncated.lastIndexOf("]");
|
|
140769
|
-
if (lastPlaceholderStart > lastPlaceholderEnd) {
|
|
140770
|
-
return truncated.slice(0, lastPlaceholderStart).trimEnd();
|
|
140771
|
-
}
|
|
140772
|
-
return truncated;
|
|
140773
|
-
}
|
|
140774
|
-
function normalizeOptional(value) {
|
|
140775
|
-
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
140776
|
-
return normalized ? redactSecrets(normalized) : undefined;
|
|
140777
|
-
}
|
|
140778
|
-
|
|
140779
141551
|
// src/tools/web-search.ts
|
|
140780
141552
|
init_create_tool();
|
|
140781
141553
|
init_resolve_working_directory();
|
|
140782
141554
|
var MAX_RESULTS_HARD_CAP = 10;
|
|
140783
|
-
var
|
|
141555
|
+
var ArgsSchema7 = exports_external.object({
|
|
140784
141556
|
query: exports_external.string().min(1).max(500),
|
|
140785
141557
|
max_results: exports_external.number().int().min(1).max(20).optional(),
|
|
140786
141558
|
freshness: exports_external.enum(["auto", "none", "day", "week", "month", "year"]).default("auto"),
|
|
@@ -140795,7 +141567,7 @@ var web_search = createSwarmTool({
|
|
|
140795
141567
|
working_directory: exports_external.string().optional().describe("Project root for config resolution. Optional.")
|
|
140796
141568
|
},
|
|
140797
141569
|
execute: async (args2, directory) => {
|
|
140798
|
-
const parsed =
|
|
141570
|
+
const parsed = ArgsSchema7.safeParse(args2);
|
|
140799
141571
|
if (!parsed.success) {
|
|
140800
141572
|
const fail = {
|
|
140801
141573
|
success: false,
|
|
@@ -140878,7 +141650,7 @@ var web_search = createSwarmTool({
|
|
|
140878
141650
|
});
|
|
140879
141651
|
async function captureSearchEvidence(directory, query, results) {
|
|
140880
141652
|
try {
|
|
140881
|
-
const written = await
|
|
141653
|
+
const written = await _internals103.writeEvidenceDocuments(directory, results.map((result) => ({
|
|
140882
141654
|
sourceType: "web_search",
|
|
140883
141655
|
query,
|
|
140884
141656
|
title: result.title,
|
|
@@ -140906,7 +141678,7 @@ async function captureSearchEvidence(directory, query, results) {
|
|
|
140906
141678
|
};
|
|
140907
141679
|
}
|
|
140908
141680
|
}
|
|
140909
|
-
var
|
|
141681
|
+
var _internals103 = {
|
|
140910
141682
|
writeEvidenceDocuments
|
|
140911
141683
|
};
|
|
140912
141684
|
|
|
@@ -140937,7 +141709,7 @@ var KnowledgeRecommendationSchema2 = exports_external.object({
|
|
|
140937
141709
|
confidence: exports_external.number().min(0).max(1).default(0.5),
|
|
140938
141710
|
evidence_refs: exports_external.array(exports_external.string().min(1)).default([])
|
|
140939
141711
|
});
|
|
140940
|
-
var
|
|
141712
|
+
var ArgsSchema8 = exports_external.object({
|
|
140941
141713
|
phase: exports_external.number().int().min(0).max(999),
|
|
140942
141714
|
verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
140943
141715
|
findings: exports_external.array(FindingSchema2).default([]),
|
|
@@ -140958,7 +141730,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
|
|
|
140958
141730
|
provenance_session_id: exports_external.string().min(1).optional().describe("Session ID of the agent that produced this evidence (optional provenance).")
|
|
140959
141731
|
},
|
|
140960
141732
|
execute: async (rawArgs, directory) => {
|
|
140961
|
-
const parsed =
|
|
141733
|
+
const parsed = ArgsSchema8.safeParse(rawArgs);
|
|
140962
141734
|
if (!parsed.success) {
|
|
140963
141735
|
return JSON.stringify({
|
|
140964
141736
|
success: false,
|
|
@@ -141237,7 +142009,7 @@ var VerdictSchema3 = exports_external.object({
|
|
|
141237
142009
|
criteriaUnmet: exports_external.array(exports_external.string()),
|
|
141238
142010
|
durationMs: exports_external.number().nonnegative()
|
|
141239
142011
|
});
|
|
141240
|
-
var
|
|
142012
|
+
var ArgsSchema9 = exports_external.object({
|
|
141241
142013
|
phase: exports_external.number().int().min(1),
|
|
141242
142014
|
projectSummary: exports_external.string().min(1),
|
|
141243
142015
|
roundNumber: exports_external.number().int().min(1).max(10).optional(),
|
|
@@ -141253,7 +142025,7 @@ function normalizeFinalVerdict(verdict, requiredFixesCount) {
|
|
|
141253
142025
|
return requiredFixesCount > 0 ? "rejected" : "concerns";
|
|
141254
142026
|
}
|
|
141255
142027
|
async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
141256
|
-
const parsed =
|
|
142028
|
+
const parsed = ArgsSchema9.safeParse(args2);
|
|
141257
142029
|
if (!parsed.success) {
|
|
141258
142030
|
return JSON.stringify({
|
|
141259
142031
|
success: false,
|
|
@@ -141373,7 +142145,7 @@ var write_final_council_evidence = createSwarmTool({
|
|
|
141373
142145
|
verdicts: exports_external.array(VerdictSchema3).min(1).max(5).describe("Collected CouncilMemberVerdict objects from critic, reviewer, sme, test_engineer, and explorer.")
|
|
141374
142146
|
},
|
|
141375
142147
|
execute: async (args2, directory) => {
|
|
141376
|
-
const parsed =
|
|
142148
|
+
const parsed = ArgsSchema9.safeParse(args2);
|
|
141377
142149
|
if (!parsed.success) {
|
|
141378
142150
|
return JSON.stringify({
|
|
141379
142151
|
success: false,
|
|
@@ -141707,6 +142479,7 @@ var TOOL_MANIFEST = defineHandlers({
|
|
|
141707
142479
|
get_qa_gate_profile: () => get_qa_gate_profile,
|
|
141708
142480
|
set_qa_gates: () => set_qa_gates,
|
|
141709
142481
|
web_search: () => web_search,
|
|
142482
|
+
web_fetch: () => web_fetch,
|
|
141710
142483
|
convene_general_council: () => convene_general_council,
|
|
141711
142484
|
write_final_council_evidence: () => write_final_council_evidence,
|
|
141712
142485
|
skill_generate: () => skill_generate,
|
|
@@ -141834,7 +142607,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
141834
142607
|
};
|
|
141835
142608
|
async function initializeOpenCodeSwarm(ctx) {
|
|
141836
142609
|
const { config: config3, loadedFromFile } = await loadPluginConfigWithMetaAsync(ctx.directory);
|
|
141837
|
-
|
|
142610
|
+
clearDeferredWarnings();
|
|
141838
142611
|
if (config3.full_auto?.enabled === true) {
|
|
141839
142612
|
const criticModel = config3.full_auto.critic_model ?? config3.agents?.critic?.model ?? DEFAULT_MODELS.critic;
|
|
141840
142613
|
const architectModel = config3.agents?.architect?.model ?? DEFAULT_MODELS.default;
|
|
@@ -142389,6 +143162,10 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
142389
143162
|
template: "/swarm deep-dive $ARGUMENTS",
|
|
142390
143163
|
description: "Use /swarm deep-dive to launch a read-only deep audit with parallel explorer waves, dual reviewers, and critic challenge"
|
|
142391
143164
|
},
|
|
143165
|
+
"swarm-deep-research": {
|
|
143166
|
+
template: "/swarm deep-research $ARGUMENTS",
|
|
143167
|
+
description: "Use /swarm deep-research <question> to run a multi-source, fact-checked deep research pass and synthesize a cited report [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]"
|
|
143168
|
+
},
|
|
142392
143169
|
"swarm-codebase-review": {
|
|
142393
143170
|
template: "/swarm codebase-review $ARGUMENTS",
|
|
142394
143171
|
description: "Use /swarm codebase-review to launch codebase-review-swarm for a quote-grounded full-repo or large-subsystem audit"
|