opencode-swarm 7.71.2 → 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.
- package/.opencode/skills/deep-research/SKILL.md +172 -0
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +8 -0
- package/.opencode/skills/swarm-pr-review/SKILL.md +69 -0
- package/dist/cli/index.js +167 -31
- 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 +892 -122
- 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.0",
|
|
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"]
|
|
@@ -17101,6 +17106,7 @@ var init_bundled_skills = __esm(() => {
|
|
|
17101
17106
|
"pre-phase-briefing",
|
|
17102
17107
|
"council",
|
|
17103
17108
|
"deep-dive",
|
|
17109
|
+
"deep-research",
|
|
17104
17110
|
"codebase-review-swarm",
|
|
17105
17111
|
"design-docs",
|
|
17106
17112
|
"swarm-pr-review",
|
|
@@ -69353,6 +69359,119 @@ var init_deep_dive = __esm(() => {
|
|
|
69353
69359
|
]);
|
|
69354
69360
|
});
|
|
69355
69361
|
|
|
69362
|
+
// src/commands/deep-research.ts
|
|
69363
|
+
function sanitizeQuestion2(raw) {
|
|
69364
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
69365
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
69366
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
69367
|
+
if (normalized.length <= MAX_QUESTION_LEN2)
|
|
69368
|
+
return normalized;
|
|
69369
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN2)}…`;
|
|
69370
|
+
}
|
|
69371
|
+
function isBoundedInteger(raw, min, max) {
|
|
69372
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
69373
|
+
return false;
|
|
69374
|
+
const n = Number(raw);
|
|
69375
|
+
return Number.isInteger(n) && n >= min && n <= max;
|
|
69376
|
+
}
|
|
69377
|
+
function parseArgs4(args2) {
|
|
69378
|
+
const result = {
|
|
69379
|
+
depth: DEFAULT_DEPTH,
|
|
69380
|
+
maxResearchers: DEFAULT_MAX_RESEARCHERS,
|
|
69381
|
+
rounds: DEFAULT_ROUNDS,
|
|
69382
|
+
output: "report",
|
|
69383
|
+
rest: []
|
|
69384
|
+
};
|
|
69385
|
+
let i2 = 0;
|
|
69386
|
+
while (i2 < args2.length) {
|
|
69387
|
+
const token = args2[i2];
|
|
69388
|
+
if (token === "--depth") {
|
|
69389
|
+
if (i2 + 1 >= args2.length)
|
|
69390
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69391
|
+
const value = args2[++i2];
|
|
69392
|
+
if (!DEPTHS.has(value)) {
|
|
69393
|
+
return {
|
|
69394
|
+
...result,
|
|
69395
|
+
error: `Invalid depth "${value}". Must be one of: standard, exhaustive.`
|
|
69396
|
+
};
|
|
69397
|
+
}
|
|
69398
|
+
result.depth = value;
|
|
69399
|
+
} else if (token === "--max-researchers") {
|
|
69400
|
+
if (i2 + 1 >= args2.length)
|
|
69401
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69402
|
+
const value = args2[++i2];
|
|
69403
|
+
if (!isBoundedInteger(value, 1, 6)) {
|
|
69404
|
+
return {
|
|
69405
|
+
...result,
|
|
69406
|
+
error: `Invalid --max-researchers value "${value}". Must be an integer between 1 and 6.`
|
|
69407
|
+
};
|
|
69408
|
+
}
|
|
69409
|
+
result.maxResearchers = Number(value);
|
|
69410
|
+
result.maxResearchersExplicit = true;
|
|
69411
|
+
} else if (token === "--rounds") {
|
|
69412
|
+
if (i2 + 1 >= args2.length)
|
|
69413
|
+
return { ...result, error: `Flag "${token}" requires a value` };
|
|
69414
|
+
const value = args2[++i2];
|
|
69415
|
+
if (!isBoundedInteger(value, 1, 4)) {
|
|
69416
|
+
return {
|
|
69417
|
+
...result,
|
|
69418
|
+
error: `Invalid --rounds value "${value}". Must be an integer between 1 and 4.`
|
|
69419
|
+
};
|
|
69420
|
+
}
|
|
69421
|
+
result.rounds = Number(value);
|
|
69422
|
+
result.roundsExplicit = true;
|
|
69423
|
+
} else if (token === "--brief") {
|
|
69424
|
+
result.output = "brief";
|
|
69425
|
+
} else if (token.startsWith("--")) {
|
|
69426
|
+
return { ...result, error: `Unknown flag "${token}"` };
|
|
69427
|
+
} else {
|
|
69428
|
+
result.rest.push(token);
|
|
69429
|
+
}
|
|
69430
|
+
i2++;
|
|
69431
|
+
}
|
|
69432
|
+
return result;
|
|
69433
|
+
}
|
|
69434
|
+
async function handleDeepResearchCommand(_directory, args2) {
|
|
69435
|
+
const parsed = parseArgs4(args2);
|
|
69436
|
+
if (parsed.error) {
|
|
69437
|
+
return `Error: ${parsed.error}
|
|
69438
|
+
|
|
69439
|
+
${USAGE4}`;
|
|
69440
|
+
}
|
|
69441
|
+
const question = sanitizeQuestion2(parsed.rest.join(" "));
|
|
69442
|
+
if (!question) {
|
|
69443
|
+
return USAGE4;
|
|
69444
|
+
}
|
|
69445
|
+
if (parsed.depth === "exhaustive") {
|
|
69446
|
+
if (!parsed.maxResearchersExplicit)
|
|
69447
|
+
parsed.maxResearchers = EXHAUSTIVE_DEFAULT_MAX_RESEARCHERS;
|
|
69448
|
+
if (!parsed.roundsExplicit)
|
|
69449
|
+
parsed.rounds = EXHAUSTIVE_DEFAULT_ROUNDS;
|
|
69450
|
+
}
|
|
69451
|
+
return `[MODE: DEEP_RESEARCH depth=${parsed.depth} max_researchers=${parsed.maxResearchers} rounds=${parsed.rounds} output=${parsed.output}] ${question}`;
|
|
69452
|
+
}
|
|
69453
|
+
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]
|
|
69454
|
+
|
|
69455
|
+
Run a multi-source, fact-checked deep research pass and synthesize a cited report.
|
|
69456
|
+
|
|
69457
|
+
Examples:
|
|
69458
|
+
/swarm deep-research "What are the tradeoffs of WASM vs native plugins?"
|
|
69459
|
+
/swarm deep research "current state of deep research agents" --depth exhaustive
|
|
69460
|
+
/swarm deep-research "is Tavily or Brave better for our use case" --rounds 3 --brief
|
|
69461
|
+
|
|
69462
|
+
Flags:
|
|
69463
|
+
--depth <name> standard (focused) or exhaustive (broader fan-out)
|
|
69464
|
+
--max-researchers <N> parallel synthesis workers per round, 1..6
|
|
69465
|
+
--rounds <N> max iterative research rounds, 1..4
|
|
69466
|
+
--brief emit a short brief instead of a full cited report
|
|
69467
|
+
|
|
69468
|
+
Requires council.general.enabled: true and a configured search API key (Tavily or Brave)
|
|
69469
|
+
in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project
|
|
69470
|
+
.opencode/opencode-swarm.json overrides.`;
|
|
69471
|
+
var init_deep_research = __esm(() => {
|
|
69472
|
+
DEPTHS = new Set(["standard", "exhaustive"]);
|
|
69473
|
+
});
|
|
69474
|
+
|
|
69356
69475
|
// src/commands/design-docs.ts
|
|
69357
69476
|
function sanitizeDescription(raw) {
|
|
69358
69477
|
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
@@ -69370,7 +69489,7 @@ function cleanFlagValue(raw) {
|
|
|
69370
69489
|
return null;
|
|
69371
69490
|
return raw;
|
|
69372
69491
|
}
|
|
69373
|
-
function
|
|
69492
|
+
function parseArgs5(args2) {
|
|
69374
69493
|
const result = {
|
|
69375
69494
|
out: "docs",
|
|
69376
69495
|
lang: "auto",
|
|
@@ -69425,30 +69544,30 @@ function parseArgs4(args2) {
|
|
|
69425
69544
|
return result;
|
|
69426
69545
|
}
|
|
69427
69546
|
async function handleDesignDocsCommand(directory, args2) {
|
|
69428
|
-
const parsed =
|
|
69547
|
+
const parsed = parseArgs5(args2);
|
|
69429
69548
|
if (parsed.error) {
|
|
69430
69549
|
return `Error: ${parsed.error}
|
|
69431
69550
|
|
|
69432
|
-
${
|
|
69551
|
+
${USAGE5}`;
|
|
69433
69552
|
}
|
|
69434
69553
|
try {
|
|
69435
69554
|
const { config: config3 } = loadPluginConfigWithMeta(directory);
|
|
69436
69555
|
if (config3.design_docs?.enabled !== true) {
|
|
69437
69556
|
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
69557
|
|
|
69439
|
-
` +
|
|
69558
|
+
` + USAGE5;
|
|
69440
69559
|
}
|
|
69441
69560
|
} catch (configErr) {
|
|
69442
69561
|
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
69562
|
}
|
|
69444
69563
|
const description = sanitizeDescription(parsed.rest.join(" "));
|
|
69445
69564
|
if (!description && !parsed.update) {
|
|
69446
|
-
return
|
|
69565
|
+
return USAGE5;
|
|
69447
69566
|
}
|
|
69448
69567
|
const header = `[MODE: DESIGN_DOCS out=${parsed.out} lang=${parsed.lang} update=${parsed.update}] ${description}`;
|
|
69449
69568
|
return header.trimEnd();
|
|
69450
69569
|
}
|
|
69451
|
-
var MAX_DESC_LEN = 2000,
|
|
69570
|
+
var MAX_DESC_LEN = 2000, USAGE5 = `Usage: /swarm design-docs <description> [--out <dir>] [--lang <name>] [--update]
|
|
69452
69571
|
|
|
69453
69572
|
Generate or sync language-agnostic design docs for the project under build:
|
|
69454
69573
|
<out>/domain.md, <out>/technical-spec.md, <out>/behavior-spec.md,
|
|
@@ -75204,7 +75323,7 @@ function validateAndSanitizeUrl2(rawUrl) {
|
|
|
75204
75323
|
return { error: "Invalid URL format" };
|
|
75205
75324
|
}
|
|
75206
75325
|
}
|
|
75207
|
-
function
|
|
75326
|
+
function parseArgs6(args2) {
|
|
75208
75327
|
const out2 = {
|
|
75209
75328
|
plan: false,
|
|
75210
75329
|
trace: false,
|
|
@@ -75278,24 +75397,24 @@ function detectGitRemote2() {
|
|
|
75278
75397
|
}
|
|
75279
75398
|
}
|
|
75280
75399
|
function handleIssueCommand(_directory, args2) {
|
|
75281
|
-
const parsed =
|
|
75400
|
+
const parsed = parseArgs6(args2);
|
|
75282
75401
|
const rawInput = parsed.rest.join(" ").trim();
|
|
75283
75402
|
if (!rawInput) {
|
|
75284
|
-
return
|
|
75403
|
+
return USAGE6;
|
|
75285
75404
|
}
|
|
75286
75405
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
75287
75406
|
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl2(rawInput) : rawInput);
|
|
75288
75407
|
if (!issueInfo) {
|
|
75289
75408
|
return `Error: Could not parse issue reference from "${rawInput}"
|
|
75290
75409
|
|
|
75291
|
-
${
|
|
75410
|
+
${USAGE6}`;
|
|
75292
75411
|
}
|
|
75293
75412
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
75294
75413
|
const result = validateAndSanitizeUrl2(issueUrl);
|
|
75295
75414
|
if ("error" in result) {
|
|
75296
75415
|
return `Error: ${result.error}
|
|
75297
75416
|
|
|
75298
|
-
${
|
|
75417
|
+
${USAGE6}`;
|
|
75299
75418
|
}
|
|
75300
75419
|
const flags2 = [];
|
|
75301
75420
|
if (parsed.plan)
|
|
@@ -75307,10 +75426,10 @@ ${USAGE5}`;
|
|
|
75307
75426
|
const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
|
|
75308
75427
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
75309
75428
|
}
|
|
75310
|
-
var MAX_URL_LEN2 = 2048,
|
|
75429
|
+
var MAX_URL_LEN2 = 2048, USAGE6;
|
|
75311
75430
|
var init_issue = __esm(() => {
|
|
75312
75431
|
init_pr_ref();
|
|
75313
|
-
|
|
75432
|
+
USAGE6 = [
|
|
75314
75433
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
75315
75434
|
"",
|
|
75316
75435
|
"Ingest a GitHub issue into the swarm workflow.",
|
|
@@ -80579,7 +80698,7 @@ var init_pr_monitor_status = __esm(() => {
|
|
|
80579
80698
|
});
|
|
80580
80699
|
|
|
80581
80700
|
// src/commands/pr-review.ts
|
|
80582
|
-
function
|
|
80701
|
+
function parseArgs7(args2) {
|
|
80583
80702
|
const out2 = { council: false, rest: [] };
|
|
80584
80703
|
for (const token of args2) {
|
|
80585
80704
|
if (token === "--council") {
|
|
@@ -80598,29 +80717,29 @@ function parseArgs6(args2) {
|
|
|
80598
80717
|
return out2;
|
|
80599
80718
|
}
|
|
80600
80719
|
function handlePrReviewCommand(directory, args2) {
|
|
80601
|
-
const parsed =
|
|
80720
|
+
const parsed = parseArgs7(args2);
|
|
80602
80721
|
if (parsed.unknownFlag) {
|
|
80603
80722
|
return `Error: Unknown flag "${parsed.unknownFlag}"
|
|
80604
80723
|
|
|
80605
|
-
${
|
|
80724
|
+
${USAGE7}`;
|
|
80606
80725
|
}
|
|
80607
80726
|
const resolved = resolvePrCommandInput(parsed.rest, directory);
|
|
80608
80727
|
if (resolved === null) {
|
|
80609
|
-
return
|
|
80728
|
+
return USAGE7;
|
|
80610
80729
|
}
|
|
80611
80730
|
if ("error" in resolved) {
|
|
80612
80731
|
return `Error: ${resolved.error}
|
|
80613
80732
|
|
|
80614
|
-
${
|
|
80733
|
+
${USAGE7}`;
|
|
80615
80734
|
}
|
|
80616
80735
|
const councilFlag = parsed.council ? "council=true" : "council=false";
|
|
80617
80736
|
const signal = `[MODE: PR_REVIEW pr="${resolved.prUrl}" ${councilFlag}]`;
|
|
80618
80737
|
return resolved.instructions ? `${signal} ${resolved.instructions}` : signal;
|
|
80619
80738
|
}
|
|
80620
|
-
var
|
|
80739
|
+
var USAGE7;
|
|
80621
80740
|
var init_pr_review = __esm(() => {
|
|
80622
80741
|
init_pr_ref();
|
|
80623
|
-
|
|
80742
|
+
USAGE7 = [
|
|
80624
80743
|
"Usage: /swarm pr-review <url|owner/repo#N|N> [--council] [instructions...]",
|
|
80625
80744
|
"",
|
|
80626
80745
|
"Run a full swarm PR review on a GitHub pull request.",
|
|
@@ -87896,7 +88015,7 @@ var init_rollback = __esm(() => {
|
|
|
87896
88015
|
});
|
|
87897
88016
|
|
|
87898
88017
|
// src/commands/sdd.ts
|
|
87899
|
-
function
|
|
88018
|
+
function parseArgs8(args2) {
|
|
87900
88019
|
const parsed = { json: false, dryRun: false };
|
|
87901
88020
|
for (let i2 = 0;i2 < args2.length; i2++) {
|
|
87902
88021
|
const token = args2[i2];
|
|
@@ -87927,11 +88046,11 @@ function formatList(items) {
|
|
|
87927
88046
|
`) : "- none";
|
|
87928
88047
|
}
|
|
87929
88048
|
async function handleSddStatusCommand(directory, args2) {
|
|
87930
|
-
const parsed =
|
|
88049
|
+
const parsed = parseArgs8(args2);
|
|
87931
88050
|
if (parsed.error)
|
|
87932
88051
|
return `Error: ${parsed.error}
|
|
87933
88052
|
|
|
87934
|
-
${
|
|
88053
|
+
${USAGE8}`;
|
|
87935
88054
|
const status = loadSddStatusSync(directory);
|
|
87936
88055
|
if (parsed.json)
|
|
87937
88056
|
return JSON.stringify(status, null, 2);
|
|
@@ -87965,11 +88084,11 @@ ${formatList([
|
|
|
87965
88084
|
`);
|
|
87966
88085
|
}
|
|
87967
88086
|
async function handleSddValidateCommand(directory, args2) {
|
|
87968
|
-
const parsed =
|
|
88087
|
+
const parsed = parseArgs8(args2);
|
|
87969
88088
|
if (parsed.error)
|
|
87970
88089
|
return `Error: ${parsed.error}
|
|
87971
88090
|
|
|
87972
|
-
${
|
|
88091
|
+
${USAGE8}`;
|
|
87973
88092
|
const status = loadSddStatusSync(directory);
|
|
87974
88093
|
const projection = buildOpenSpecProjectionSync(directory, {
|
|
87975
88094
|
changeId: parsed.changeId
|
|
@@ -88007,11 +88126,11 @@ ${formatList(result.warnings)}` : ""
|
|
|
88007
88126
|
`);
|
|
88008
88127
|
}
|
|
88009
88128
|
async function handleSddProjectCommand(directory, args2) {
|
|
88010
|
-
const parsed =
|
|
88129
|
+
const parsed = parseArgs8(args2);
|
|
88011
88130
|
if (parsed.error)
|
|
88012
88131
|
return `Error: ${parsed.error}
|
|
88013
88132
|
|
|
88014
|
-
${
|
|
88133
|
+
${USAGE8}`;
|
|
88015
88134
|
const result = writeProjectedSpecSync(directory, {
|
|
88016
88135
|
changeId: parsed.changeId,
|
|
88017
88136
|
dryRun: parsed.dryRun
|
|
@@ -88031,7 +88150,7 @@ ${USAGE7}`;
|
|
|
88031
88150
|
return [
|
|
88032
88151
|
"SDD projection failed: no valid OpenSpec-compatible projection could be built.",
|
|
88033
88152
|
"",
|
|
88034
|
-
|
|
88153
|
+
USAGE8
|
|
88035
88154
|
].join(`
|
|
88036
88155
|
`);
|
|
88037
88156
|
}
|
|
@@ -88048,9 +88167,9 @@ ${formatList(result.projection.warnings)}` : ""
|
|
|
88048
88167
|
`);
|
|
88049
88168
|
}
|
|
88050
88169
|
async function handleSddCommand(_directory, _args) {
|
|
88051
|
-
return
|
|
88170
|
+
return USAGE8;
|
|
88052
88171
|
}
|
|
88053
|
-
var
|
|
88172
|
+
var USAGE8 = `Usage:
|
|
88054
88173
|
/swarm sdd status [--json]
|
|
88055
88174
|
/swarm sdd validate [--json] [--change <id>]
|
|
88056
88175
|
/swarm sdd project [--dry-run] [--json] [--change <id>]
|
|
@@ -89556,6 +89675,7 @@ __export(exports_commands, {
|
|
|
89556
89675
|
handleEvidenceCommand: () => handleEvidenceCommand,
|
|
89557
89676
|
handleDoctorCommand: () => handleDoctorCommand,
|
|
89558
89677
|
handleDiagnoseCommand: () => handleDiagnoseCommand,
|
|
89678
|
+
handleDeepResearchCommand: () => handleDeepResearchCommand,
|
|
89559
89679
|
handleDeepDiveCommand: () => handleDeepDiveCommand,
|
|
89560
89680
|
handleDarkMatterCommand: () => handleDarkMatterCommand,
|
|
89561
89681
|
handleCurateCommand: () => handleCurateCommand,
|
|
@@ -89834,6 +89954,7 @@ var init_commands = __esm(() => {
|
|
|
89834
89954
|
init_curate();
|
|
89835
89955
|
init_dark_matter();
|
|
89836
89956
|
init_deep_dive();
|
|
89957
|
+
init_deep_research();
|
|
89837
89958
|
init_diagnose();
|
|
89838
89959
|
init_doctor();
|
|
89839
89960
|
init_evidence();
|
|
@@ -90066,6 +90187,7 @@ var init_registry = __esm(() => {
|
|
|
90066
90187
|
init_curate();
|
|
90067
90188
|
init_dark_matter();
|
|
90068
90189
|
init_deep_dive();
|
|
90190
|
+
init_deep_research();
|
|
90069
90191
|
init_design_docs();
|
|
90070
90192
|
init_diagnose();
|
|
90071
90193
|
init_doctor();
|
|
@@ -90461,6 +90583,20 @@ Subcommands:
|
|
|
90461
90583
|
category: "agent",
|
|
90462
90584
|
aliasOf: "deep-dive"
|
|
90463
90585
|
},
|
|
90586
|
+
"deep-research": {
|
|
90587
|
+
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleDeepResearchCommand),
|
|
90588
|
+
description: "Launch a multi-source, fact-checked deep research pass and synthesize a cited report [question]",
|
|
90589
|
+
args: "<question> [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]",
|
|
90590
|
+
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.",
|
|
90591
|
+
category: "agent"
|
|
90592
|
+
},
|
|
90593
|
+
"deep research": {
|
|
90594
|
+
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleDeepResearchCommand),
|
|
90595
|
+
description: "Alias for /swarm deep-research — launch a cited deep research pass",
|
|
90596
|
+
args: "<question> [--depth standard|exhaustive] [--max-researchers 1..6] [--rounds 1..4] [--brief]",
|
|
90597
|
+
category: "agent",
|
|
90598
|
+
aliasOf: "deep-research"
|
|
90599
|
+
},
|
|
90464
90600
|
"codebase-review": {
|
|
90465
90601
|
handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleCodebaseReviewCommand),
|
|
90466
90602
|
description: "Launch codebase-review-swarm for a quote-grounded full-repo or large-subsystem audit",
|
|
@@ -91957,6 +92093,22 @@ HARD CONSTRAINTS (apply regardless of skill load success):
|
|
|
91957
92093
|
- Explorers generate candidate findings only — reviewers verify or reject
|
|
91958
92094
|
- Critics challenge only HIGH/CRITICAL findings — do NOT waste cycles on lower severity
|
|
91959
92095
|
|
|
92096
|
+
### MODE: DEEP_RESEARCH
|
|
92097
|
+
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.
|
|
92098
|
+
|
|
92099
|
+
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.
|
|
92100
|
+
|
|
92101
|
+
ACTION: Load skill file:.opencode/skills/deep-research/SKILL.md immediately and follow its protocol.
|
|
92102
|
+
|
|
92103
|
+
HARD CONSTRAINTS (apply regardless of skill load success):
|
|
92104
|
+
- Do NOT delegate to coder
|
|
92105
|
+
- Do NOT call declare_scope
|
|
92106
|
+
- Do NOT mutate source code or write any files outside .swarm/
|
|
92107
|
+
- You (architect) own \`web_search\` and \`web_fetch\`; sme workers receive gathered evidence in their dispatch message — do NOT expect sme to fetch
|
|
92108
|
+
- Every claim in the final report MUST cite a source from the gathered evidence; reviewers verify claim↔citation before a claim is reported
|
|
92109
|
+
- Critics challenge only high-stakes / contested claims — do NOT waste cycles on well-supported ones
|
|
92110
|
+
- If council.general.enabled is false or no search API key is configured, surface that and STOP — do not produce ungrounded research
|
|
92111
|
+
|
|
91960
92112
|
### MODE: CODEBASE_REVIEW
|
|
91961
92113
|
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
92114
|
|
|
@@ -101144,7 +101296,7 @@ var init_design_doc_drift = __esm(() => {
|
|
|
101144
101296
|
var exports_project_context = {};
|
|
101145
101297
|
__export(exports_project_context, {
|
|
101146
101298
|
buildProjectContext: () => buildProjectContext,
|
|
101147
|
-
_internals: () =>
|
|
101299
|
+
_internals: () => _internals104,
|
|
101148
101300
|
LANG_BACKEND_DETECTION_TIMEOUT_MS: () => LANG_BACKEND_DETECTION_TIMEOUT_MS
|
|
101149
101301
|
});
|
|
101150
101302
|
import * as fs136 from "node:fs";
|
|
@@ -101228,7 +101380,7 @@ function selectLintCommand(backend, directory) {
|
|
|
101228
101380
|
return null;
|
|
101229
101381
|
}
|
|
101230
101382
|
async function buildProjectContext(directory) {
|
|
101231
|
-
const backend = await
|
|
101383
|
+
const backend = await _internals104.pickBackend(directory);
|
|
101232
101384
|
if (!backend)
|
|
101233
101385
|
return null;
|
|
101234
101386
|
const ctx = emptyProjectContext();
|
|
@@ -101267,17 +101419,17 @@ async function buildProjectContext(directory) {
|
|
|
101267
101419
|
if (backend.prompts.reviewerChecklist.length > 0) {
|
|
101268
101420
|
ctx.REVIEWER_CHECKLIST = bulletList(backend.prompts.reviewerChecklist);
|
|
101269
101421
|
}
|
|
101270
|
-
const profiles =
|
|
101422
|
+
const profiles = _internals104.pickedProfiles(directory);
|
|
101271
101423
|
if (profiles.length > 1) {
|
|
101272
101424
|
ctx.PROJECT_CONTEXT_SECONDARY_LANGUAGES = profiles.slice(1).map((p) => p.id).join(", ");
|
|
101273
101425
|
}
|
|
101274
101426
|
return ctx;
|
|
101275
101427
|
}
|
|
101276
|
-
var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300,
|
|
101428
|
+
var LANG_BACKEND_DETECTION_TIMEOUT_MS = 300, _internals104;
|
|
101277
101429
|
var init_project_context = __esm(() => {
|
|
101278
101430
|
init_dispatch();
|
|
101279
101431
|
init_framework_detector();
|
|
101280
|
-
|
|
101432
|
+
_internals104 = {
|
|
101281
101433
|
pickBackend,
|
|
101282
101434
|
pickedProfiles
|
|
101283
101435
|
};
|
|
@@ -140503,6 +140655,696 @@ var update_task_status = createSwarmTool({
|
|
|
140503
140655
|
}
|
|
140504
140656
|
});
|
|
140505
140657
|
|
|
140658
|
+
// src/tools/web-fetch.ts
|
|
140659
|
+
init_zod();
|
|
140660
|
+
init_loader();
|
|
140661
|
+
import { lookup } from "node:dns/promises";
|
|
140662
|
+
import * as http from "node:http";
|
|
140663
|
+
import * as https from "node:https";
|
|
140664
|
+
import { isIP } from "node:net";
|
|
140665
|
+
import { Readable } from "node:stream";
|
|
140666
|
+
import * as zlib from "node:zlib";
|
|
140667
|
+
|
|
140668
|
+
// src/evidence/documents.ts
|
|
140669
|
+
init_utils2();
|
|
140670
|
+
init_redaction();
|
|
140671
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
140672
|
+
import { appendFile as appendFile17, mkdir as mkdir31 } from "node:fs/promises";
|
|
140673
|
+
import * as path188 from "node:path";
|
|
140674
|
+
var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
|
|
140675
|
+
var MAX_EVIDENCE_TEXT_LENGTH = 4000;
|
|
140676
|
+
async function writeEvidenceDocuments(directory, inputs, now = () => new Date) {
|
|
140677
|
+
const filePath = validateSwarmPath(directory, EVIDENCE_CACHE_FILE);
|
|
140678
|
+
const capturedAt = now().toISOString();
|
|
140679
|
+
const records = inputs.map((input) => createEvidenceDocumentRecord(input, capturedAt)).filter((record3) => record3 !== null);
|
|
140680
|
+
if (records.length > 0) {
|
|
140681
|
+
await mkdir31(path188.dirname(filePath), { recursive: true });
|
|
140682
|
+
await appendFile17(filePath, `${records.map((record3) => JSON.stringify(record3)).join(`
|
|
140683
|
+
`)}
|
|
140684
|
+
`, "utf-8");
|
|
140685
|
+
}
|
|
140686
|
+
return {
|
|
140687
|
+
path: ".swarm/evidence-cache/documents.jsonl",
|
|
140688
|
+
records,
|
|
140689
|
+
refs: records.map((record3) => record3.ref)
|
|
140690
|
+
};
|
|
140691
|
+
}
|
|
140692
|
+
function createEvidenceDocumentRecord(input, defaultCapturedAt) {
|
|
140693
|
+
const text = normalizeEvidenceText(input.text ?? input.snippet ?? "");
|
|
140694
|
+
if (!text)
|
|
140695
|
+
return null;
|
|
140696
|
+
const capturedAt = input.capturedAt ?? defaultCapturedAt;
|
|
140697
|
+
const base = {
|
|
140698
|
+
sourceType: input.sourceType,
|
|
140699
|
+
query: normalizeOptional(input.query),
|
|
140700
|
+
title: normalizeOptional(input.title),
|
|
140701
|
+
url: normalizeOptional(input.url),
|
|
140702
|
+
text
|
|
140703
|
+
};
|
|
140704
|
+
const id = createEvidenceDocumentId(base);
|
|
140705
|
+
return {
|
|
140706
|
+
id,
|
|
140707
|
+
ref: `evidence-cache:${id}`,
|
|
140708
|
+
...base,
|
|
140709
|
+
capturedAt,
|
|
140710
|
+
createdBy: normalizeOptional(input.createdBy),
|
|
140711
|
+
metadata: input.metadata ?? {}
|
|
140712
|
+
};
|
|
140713
|
+
}
|
|
140714
|
+
function createEvidenceDocumentId(input) {
|
|
140715
|
+
const hash4 = createHash16("sha256").update([
|
|
140716
|
+
input.sourceType,
|
|
140717
|
+
input.query ?? "",
|
|
140718
|
+
input.title ?? "",
|
|
140719
|
+
input.url ?? "",
|
|
140720
|
+
input.text
|
|
140721
|
+
].join(`
|
|
140722
|
+
`)).digest("hex");
|
|
140723
|
+
return `evd_${hash4.slice(0, 16)}`;
|
|
140724
|
+
}
|
|
140725
|
+
function normalizeEvidenceText(text) {
|
|
140726
|
+
const normalized = redactSecrets(text.replace(/\s+/g, " ").trim());
|
|
140727
|
+
return truncateEvidenceText(normalized, MAX_EVIDENCE_TEXT_LENGTH);
|
|
140728
|
+
}
|
|
140729
|
+
function truncateEvidenceText(text, maxLength) {
|
|
140730
|
+
if (text.length <= maxLength)
|
|
140731
|
+
return text;
|
|
140732
|
+
const truncated = text.slice(0, maxLength);
|
|
140733
|
+
const lastPlaceholderStart = truncated.lastIndexOf("[REDACTED:");
|
|
140734
|
+
const lastPlaceholderEnd = truncated.lastIndexOf("]");
|
|
140735
|
+
if (lastPlaceholderStart > lastPlaceholderEnd) {
|
|
140736
|
+
return truncated.slice(0, lastPlaceholderStart).trimEnd();
|
|
140737
|
+
}
|
|
140738
|
+
return truncated;
|
|
140739
|
+
}
|
|
140740
|
+
function normalizeOptional(value) {
|
|
140741
|
+
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
140742
|
+
return normalized ? redactSecrets(normalized) : undefined;
|
|
140743
|
+
}
|
|
140744
|
+
|
|
140745
|
+
// src/tools/web-fetch.ts
|
|
140746
|
+
init_create_tool();
|
|
140747
|
+
init_resolve_working_directory();
|
|
140748
|
+
var DEFAULT_MAX_BYTES = 1e6;
|
|
140749
|
+
var MAX_BYTES_HARD_CAP = 5000000;
|
|
140750
|
+
var DEFAULT_TIMEOUT_MS4 = 15000;
|
|
140751
|
+
var MAX_TIMEOUT_MS2 = 30000;
|
|
140752
|
+
var MAX_REDIRECTS = 5;
|
|
140753
|
+
var MAX_TEXT_LENGTH2 = 50000;
|
|
140754
|
+
var ArgsSchema6 = exports_external.object({
|
|
140755
|
+
url: exports_external.string().min(1).max(2048),
|
|
140756
|
+
max_bytes: exports_external.number().int().min(1024).max(MAX_BYTES_HARD_CAP).optional(),
|
|
140757
|
+
timeout_ms: exports_external.number().int().min(1000).max(MAX_TIMEOUT_MS2).optional(),
|
|
140758
|
+
working_directory: exports_external.string().optional()
|
|
140759
|
+
});
|
|
140760
|
+
function isBlockedAddress(address) {
|
|
140761
|
+
const family = isIP(address);
|
|
140762
|
+
if (family === 4)
|
|
140763
|
+
return isBlockedIPv4(address);
|
|
140764
|
+
if (family === 6)
|
|
140765
|
+
return isBlockedIPv6(address);
|
|
140766
|
+
return true;
|
|
140767
|
+
}
|
|
140768
|
+
function isBlockedIPv4(address) {
|
|
140769
|
+
const parts2 = address.split(".");
|
|
140770
|
+
if (parts2.length !== 4)
|
|
140771
|
+
return true;
|
|
140772
|
+
const octets = parts2.map((p) => Number(p));
|
|
140773
|
+
if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255))
|
|
140774
|
+
return true;
|
|
140775
|
+
const [a, b] = octets;
|
|
140776
|
+
if (a === 0)
|
|
140777
|
+
return true;
|
|
140778
|
+
if (a === 10)
|
|
140779
|
+
return true;
|
|
140780
|
+
if (a === 127)
|
|
140781
|
+
return true;
|
|
140782
|
+
if (a === 169 && b === 254)
|
|
140783
|
+
return true;
|
|
140784
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
140785
|
+
return true;
|
|
140786
|
+
if (a === 192 && b === 168)
|
|
140787
|
+
return true;
|
|
140788
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
140789
|
+
return true;
|
|
140790
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
140791
|
+
return true;
|
|
140792
|
+
if (a === 192 && b === 0 && octets[2] === 0)
|
|
140793
|
+
return true;
|
|
140794
|
+
if (a === 192 && b === 0 && octets[2] === 2)
|
|
140795
|
+
return true;
|
|
140796
|
+
if (a === 198 && b === 51 && octets[2] === 100)
|
|
140797
|
+
return true;
|
|
140798
|
+
if (a === 203 && b === 0 && octets[2] === 113)
|
|
140799
|
+
return true;
|
|
140800
|
+
if (a === 192 && b === 88 && octets[2] === 99)
|
|
140801
|
+
return true;
|
|
140802
|
+
if (a >= 224)
|
|
140803
|
+
return true;
|
|
140804
|
+
return false;
|
|
140805
|
+
}
|
|
140806
|
+
function expandIPv6(input) {
|
|
140807
|
+
let s = input.toLowerCase().split("%")[0];
|
|
140808
|
+
if (!s)
|
|
140809
|
+
return null;
|
|
140810
|
+
if (s.includes(".")) {
|
|
140811
|
+
const colon = s.lastIndexOf(":");
|
|
140812
|
+
if (colon === -1)
|
|
140813
|
+
return null;
|
|
140814
|
+
const v4 = s.slice(colon + 1).split(".");
|
|
140815
|
+
if (v4.length !== 4)
|
|
140816
|
+
return null;
|
|
140817
|
+
const o = v4.map((p) => Number(p));
|
|
140818
|
+
if (o.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
|
|
140819
|
+
return null;
|
|
140820
|
+
const h1 = (o[0] << 8 | o[1]).toString(16);
|
|
140821
|
+
const h2 = (o[2] << 8 | o[3]).toString(16);
|
|
140822
|
+
s = `${s.slice(0, colon + 1)}${h1}:${h2}`;
|
|
140823
|
+
}
|
|
140824
|
+
const halves = s.split("::");
|
|
140825
|
+
if (halves.length > 2)
|
|
140826
|
+
return null;
|
|
140827
|
+
const parseGroups = (part) => {
|
|
140828
|
+
if (part === "")
|
|
140829
|
+
return [];
|
|
140830
|
+
const groups = part.split(":");
|
|
140831
|
+
const out2 = [];
|
|
140832
|
+
for (const g of groups) {
|
|
140833
|
+
if (!/^[0-9a-f]{1,4}$/.test(g))
|
|
140834
|
+
return null;
|
|
140835
|
+
out2.push(Number.parseInt(g, 16));
|
|
140836
|
+
}
|
|
140837
|
+
return out2;
|
|
140838
|
+
};
|
|
140839
|
+
const head = parseGroups(halves[0]);
|
|
140840
|
+
if (head === null)
|
|
140841
|
+
return null;
|
|
140842
|
+
if (halves.length === 2) {
|
|
140843
|
+
const tail = parseGroups(halves[1]);
|
|
140844
|
+
if (tail === null)
|
|
140845
|
+
return null;
|
|
140846
|
+
const missing = 8 - head.length - tail.length;
|
|
140847
|
+
if (missing < 1)
|
|
140848
|
+
return null;
|
|
140849
|
+
return [...head, ...new Array(missing).fill(0), ...tail];
|
|
140850
|
+
}
|
|
140851
|
+
return head.length === 8 ? head : null;
|
|
140852
|
+
}
|
|
140853
|
+
function isBlockedIPv6(raw) {
|
|
140854
|
+
const h = expandIPv6(raw);
|
|
140855
|
+
if (!h)
|
|
140856
|
+
return true;
|
|
140857
|
+
if (h.every((x) => x === 0))
|
|
140858
|
+
return true;
|
|
140859
|
+
if (h.slice(0, 7).every((x) => x === 0) && h[7] === 1)
|
|
140860
|
+
return true;
|
|
140861
|
+
const mappedV4 = h.slice(0, 5).every((x) => x === 0) && h[5] === 65535;
|
|
140862
|
+
const compatV4 = h.slice(0, 6).every((x) => x === 0) && !(h[6] === 0 && h[7] <= 1);
|
|
140863
|
+
if (mappedV4 || compatV4) {
|
|
140864
|
+
const v4 = `${h[6] >> 8}.${h[6] & 255}.${h[7] >> 8}.${h[7] & 255}`;
|
|
140865
|
+
return isBlockedIPv4(v4);
|
|
140866
|
+
}
|
|
140867
|
+
const first = h[0];
|
|
140868
|
+
if (first >= 64512 && first <= 65023)
|
|
140869
|
+
return true;
|
|
140870
|
+
if (first >= 65152 && first <= 65215)
|
|
140871
|
+
return true;
|
|
140872
|
+
if (first >= 65280)
|
|
140873
|
+
return true;
|
|
140874
|
+
return false;
|
|
140875
|
+
}
|
|
140876
|
+
async function validateFetchUrl(candidate, dnsLookup) {
|
|
140877
|
+
let url3;
|
|
140878
|
+
try {
|
|
140879
|
+
url3 = new URL(candidate);
|
|
140880
|
+
} catch {
|
|
140881
|
+
return {
|
|
140882
|
+
ok: false,
|
|
140883
|
+
reason: "invalid_url",
|
|
140884
|
+
message: `Not a valid URL: ${candidate}`
|
|
140885
|
+
};
|
|
140886
|
+
}
|
|
140887
|
+
if (url3.protocol !== "http:" && url3.protocol !== "https:") {
|
|
140888
|
+
return {
|
|
140889
|
+
ok: false,
|
|
140890
|
+
reason: "blocked_scheme",
|
|
140891
|
+
message: `Only http and https URLs are allowed (got "${url3.protocol}").`
|
|
140892
|
+
};
|
|
140893
|
+
}
|
|
140894
|
+
const host = url3.hostname.replace(/^\[|\]$/g, "");
|
|
140895
|
+
if (isIP(host)) {
|
|
140896
|
+
if (isBlockedAddress(host)) {
|
|
140897
|
+
return {
|
|
140898
|
+
ok: false,
|
|
140899
|
+
reason: "blocked_host",
|
|
140900
|
+
message: `Refusing to fetch a private, loopback, or reserved address: ${host}`
|
|
140901
|
+
};
|
|
140902
|
+
}
|
|
140903
|
+
return { ok: true, url: url3, address: host };
|
|
140904
|
+
}
|
|
140905
|
+
let resolved;
|
|
140906
|
+
try {
|
|
140907
|
+
resolved = await dnsLookup(host, { all: true });
|
|
140908
|
+
} catch (err3) {
|
|
140909
|
+
return {
|
|
140910
|
+
ok: false,
|
|
140911
|
+
reason: "dns_failure",
|
|
140912
|
+
message: `Could not resolve host "${host}": ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
140913
|
+
};
|
|
140914
|
+
}
|
|
140915
|
+
if (resolved.length === 0) {
|
|
140916
|
+
return {
|
|
140917
|
+
ok: false,
|
|
140918
|
+
reason: "dns_failure",
|
|
140919
|
+
message: `Host "${host}" resolved to no addresses.`
|
|
140920
|
+
};
|
|
140921
|
+
}
|
|
140922
|
+
for (const { address } of resolved) {
|
|
140923
|
+
if (isBlockedAddress(address)) {
|
|
140924
|
+
return {
|
|
140925
|
+
ok: false,
|
|
140926
|
+
reason: "blocked_host",
|
|
140927
|
+
message: `Host "${host}" resolves to a private, loopback, or reserved address (${address}).`
|
|
140928
|
+
};
|
|
140929
|
+
}
|
|
140930
|
+
}
|
|
140931
|
+
return { ok: true, url: url3, address: resolved[0].address };
|
|
140932
|
+
}
|
|
140933
|
+
function isAllowedContentType(contentType) {
|
|
140934
|
+
if (!contentType)
|
|
140935
|
+
return true;
|
|
140936
|
+
const type = contentType.split(";")[0].trim().toLowerCase();
|
|
140937
|
+
if (type.startsWith("text/"))
|
|
140938
|
+
return true;
|
|
140939
|
+
if (type === "application/json" || type === "application/xml")
|
|
140940
|
+
return true;
|
|
140941
|
+
if (type === "application/xhtml+xml" || type.endsWith("+json") || type.endsWith("+xml")) {
|
|
140942
|
+
return true;
|
|
140943
|
+
}
|
|
140944
|
+
return false;
|
|
140945
|
+
}
|
|
140946
|
+
function extractTitle(html) {
|
|
140947
|
+
const lower = html.toLowerCase();
|
|
140948
|
+
const start2 = lower.indexOf("<title");
|
|
140949
|
+
if (start2 === -1)
|
|
140950
|
+
return;
|
|
140951
|
+
const tagEnd = lower.indexOf(">", start2);
|
|
140952
|
+
if (tagEnd === -1)
|
|
140953
|
+
return;
|
|
140954
|
+
const contentStart = tagEnd + 1;
|
|
140955
|
+
const end = lower.indexOf("</title", contentStart);
|
|
140956
|
+
if (end === -1)
|
|
140957
|
+
return;
|
|
140958
|
+
const content = html.slice(contentStart, end);
|
|
140959
|
+
const title = decodeEntities(content.replace(/\s+/g, " ").trim());
|
|
140960
|
+
return title || undefined;
|
|
140961
|
+
}
|
|
140962
|
+
var NAMED_ENTITIES = {
|
|
140963
|
+
amp: "&",
|
|
140964
|
+
lt: "<",
|
|
140965
|
+
gt: ">",
|
|
140966
|
+
quot: '"',
|
|
140967
|
+
apos: "'",
|
|
140968
|
+
nbsp: " ",
|
|
140969
|
+
"#39": "'"
|
|
140970
|
+
};
|
|
140971
|
+
function decodeEntities(text) {
|
|
140972
|
+
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, body2) => {
|
|
140973
|
+
const key = body2.toLowerCase();
|
|
140974
|
+
if (key in NAMED_ENTITIES)
|
|
140975
|
+
return NAMED_ENTITIES[key];
|
|
140976
|
+
if (body2.startsWith("#x") || body2.startsWith("#X")) {
|
|
140977
|
+
const code = Number.parseInt(body2.slice(2), 16);
|
|
140978
|
+
return Number.isFinite(code) ? safeFromCodePoint(code) : whole;
|
|
140979
|
+
}
|
|
140980
|
+
if (body2.startsWith("#")) {
|
|
140981
|
+
const code = Number.parseInt(body2.slice(1), 10);
|
|
140982
|
+
return Number.isFinite(code) ? safeFromCodePoint(code) : whole;
|
|
140983
|
+
}
|
|
140984
|
+
return whole;
|
|
140985
|
+
});
|
|
140986
|
+
}
|
|
140987
|
+
function safeFromCodePoint(code) {
|
|
140988
|
+
try {
|
|
140989
|
+
return String.fromCodePoint(code);
|
|
140990
|
+
} catch {
|
|
140991
|
+
return "";
|
|
140992
|
+
}
|
|
140993
|
+
}
|
|
140994
|
+
function stripSpans(input, open3, close) {
|
|
140995
|
+
const lower = input.toLowerCase();
|
|
140996
|
+
let out2 = "";
|
|
140997
|
+
let i2 = 0;
|
|
140998
|
+
while (i2 < input.length) {
|
|
140999
|
+
const start2 = lower.indexOf(open3, i2);
|
|
141000
|
+
if (start2 === -1) {
|
|
141001
|
+
out2 += input.slice(i2);
|
|
141002
|
+
break;
|
|
141003
|
+
}
|
|
141004
|
+
out2 += input.slice(i2, start2);
|
|
141005
|
+
const end = lower.indexOf(close, start2 + open3.length);
|
|
141006
|
+
if (end === -1)
|
|
141007
|
+
break;
|
|
141008
|
+
i2 = end + close.length;
|
|
141009
|
+
}
|
|
141010
|
+
return out2;
|
|
141011
|
+
}
|
|
141012
|
+
function htmlToText(html) {
|
|
141013
|
+
let withoutScripts = stripSpans(html, "<script", "</script>");
|
|
141014
|
+
withoutScripts = stripSpans(withoutScripts, "<style", "</style>");
|
|
141015
|
+
withoutScripts = stripSpans(withoutScripts, "<noscript", "</noscript>");
|
|
141016
|
+
withoutScripts = stripSpans(withoutScripts, "<!--", "-->");
|
|
141017
|
+
const withBreaks = withoutScripts.replace(/<\/(p|div|li|h[1-6]|tr|section|article|header|footer)>/gi, `
|
|
141018
|
+
`).replace(/<br\s*\/?>/gi, `
|
|
141019
|
+
`);
|
|
141020
|
+
const noTags = withBreaks.replace(/<[^>]+>/g, " ");
|
|
141021
|
+
const decoded = decodeEntities(noTags);
|
|
141022
|
+
return decoded.replace(/[ \t\f\v]+/g, " ").replace(/\s*\n\s*/g, `
|
|
141023
|
+
`).replace(/\n{3,}/g, `
|
|
141024
|
+
|
|
141025
|
+
`).trim();
|
|
141026
|
+
}
|
|
141027
|
+
function makeAbortError() {
|
|
141028
|
+
try {
|
|
141029
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
141030
|
+
} catch {
|
|
141031
|
+
const err3 = new Error("The operation was aborted");
|
|
141032
|
+
err3.name = "AbortError";
|
|
141033
|
+
return err3;
|
|
141034
|
+
}
|
|
141035
|
+
}
|
|
141036
|
+
function performHttpRequest(args2) {
|
|
141037
|
+
const { url: url3, pinnedAddress, signal, headers } = args2;
|
|
141038
|
+
const isHttps = url3.protocol === "https:";
|
|
141039
|
+
const port = url3.port ? Number(url3.port) : isHttps ? 443 : 80;
|
|
141040
|
+
const hostNoBrackets = url3.hostname.replace(/^\[|\]$/g, "");
|
|
141041
|
+
const useSni = isHttps && isIP(hostNoBrackets) === 0;
|
|
141042
|
+
const options = {
|
|
141043
|
+
host: pinnedAddress,
|
|
141044
|
+
port,
|
|
141045
|
+
path: `${url3.pathname}${url3.search}`,
|
|
141046
|
+
method: "GET",
|
|
141047
|
+
headers: { Host: url3.host, ...headers }
|
|
141048
|
+
};
|
|
141049
|
+
if (useSni)
|
|
141050
|
+
options.servername = url3.hostname;
|
|
141051
|
+
return new Promise((resolve72, reject) => {
|
|
141052
|
+
let req;
|
|
141053
|
+
const onResponse = (res) => {
|
|
141054
|
+
const normHeaders = {};
|
|
141055
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
141056
|
+
normHeaders[key] = Array.isArray(value) ? value[0] : value;
|
|
141057
|
+
}
|
|
141058
|
+
res.on("error", () => {});
|
|
141059
|
+
resolve72({
|
|
141060
|
+
status: res.statusCode ?? 0,
|
|
141061
|
+
headers: normHeaders,
|
|
141062
|
+
body: res,
|
|
141063
|
+
cancel: () => req.destroy()
|
|
141064
|
+
});
|
|
141065
|
+
};
|
|
141066
|
+
req = isHttps ? https.request(options, onResponse) : http.request(options, onResponse);
|
|
141067
|
+
const onAbort = () => req.destroy(makeAbortError());
|
|
141068
|
+
if (signal.aborted)
|
|
141069
|
+
req.destroy(makeAbortError());
|
|
141070
|
+
else
|
|
141071
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
141072
|
+
req.on("error", reject);
|
|
141073
|
+
req.end();
|
|
141074
|
+
});
|
|
141075
|
+
}
|
|
141076
|
+
async function boundedFetch(start2, maxBytes, timeoutMs, deps) {
|
|
141077
|
+
let current = start2;
|
|
141078
|
+
const controller = new AbortController;
|
|
141079
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
141080
|
+
try {
|
|
141081
|
+
for (let hop = 0;hop <= MAX_REDIRECTS; hop++) {
|
|
141082
|
+
let raw;
|
|
141083
|
+
try {
|
|
141084
|
+
raw = await deps.httpRequest({
|
|
141085
|
+
url: current.url,
|
|
141086
|
+
pinnedAddress: current.address,
|
|
141087
|
+
signal: controller.signal,
|
|
141088
|
+
headers: {
|
|
141089
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json;q=0.9,*/*;q=0.5"
|
|
141090
|
+
}
|
|
141091
|
+
});
|
|
141092
|
+
} catch (err3) {
|
|
141093
|
+
if (controller.signal.aborted) {
|
|
141094
|
+
return {
|
|
141095
|
+
ok: false,
|
|
141096
|
+
reason: "timeout",
|
|
141097
|
+
message: `Fetch exceeded ${timeoutMs}ms for ${current.url.toString()}`
|
|
141098
|
+
};
|
|
141099
|
+
}
|
|
141100
|
+
return {
|
|
141101
|
+
ok: false,
|
|
141102
|
+
reason: "network_error",
|
|
141103
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
141104
|
+
};
|
|
141105
|
+
}
|
|
141106
|
+
if (raw.status >= 300 && raw.status < 400) {
|
|
141107
|
+
const location = raw.headers.location;
|
|
141108
|
+
raw.cancel?.();
|
|
141109
|
+
if (!location) {
|
|
141110
|
+
return {
|
|
141111
|
+
ok: false,
|
|
141112
|
+
reason: "bad_redirect",
|
|
141113
|
+
message: `Redirect ${raw.status} with no Location header.`
|
|
141114
|
+
};
|
|
141115
|
+
}
|
|
141116
|
+
let next;
|
|
141117
|
+
try {
|
|
141118
|
+
next = new URL(location, current.url);
|
|
141119
|
+
} catch {
|
|
141120
|
+
return {
|
|
141121
|
+
ok: false,
|
|
141122
|
+
reason: "bad_redirect",
|
|
141123
|
+
message: `Invalid redirect target: ${location}`
|
|
141124
|
+
};
|
|
141125
|
+
}
|
|
141126
|
+
const revalidated = await validateFetchUrl(next.toString(), deps.dnsLookup);
|
|
141127
|
+
if (!revalidated.ok)
|
|
141128
|
+
return revalidated;
|
|
141129
|
+
if (hop === MAX_REDIRECTS) {
|
|
141130
|
+
return {
|
|
141131
|
+
ok: false,
|
|
141132
|
+
reason: "too_many_redirects",
|
|
141133
|
+
message: `Exceeded ${MAX_REDIRECTS} redirect hops.`
|
|
141134
|
+
};
|
|
141135
|
+
}
|
|
141136
|
+
current = { url: revalidated.url, address: revalidated.address };
|
|
141137
|
+
continue;
|
|
141138
|
+
}
|
|
141139
|
+
if (raw.status < 200 || raw.status >= 300) {
|
|
141140
|
+
raw.cancel?.();
|
|
141141
|
+
return {
|
|
141142
|
+
ok: false,
|
|
141143
|
+
reason: "http_error",
|
|
141144
|
+
message: `HTTP ${raw.status} for ${current.url.toString()}`
|
|
141145
|
+
};
|
|
141146
|
+
}
|
|
141147
|
+
const contentType = raw.headers["content-type"] ?? null;
|
|
141148
|
+
if (!isAllowedContentType(contentType)) {
|
|
141149
|
+
raw.cancel?.();
|
|
141150
|
+
return {
|
|
141151
|
+
ok: false,
|
|
141152
|
+
reason: "unsupported_content_type",
|
|
141153
|
+
message: `Refusing to read non-text content type "${contentType}".`
|
|
141154
|
+
};
|
|
141155
|
+
}
|
|
141156
|
+
let activeBody = raw.body;
|
|
141157
|
+
if (raw.body !== null && raw.body instanceof Readable) {
|
|
141158
|
+
const encoding = (raw.headers["content-encoding"] ?? "").toLowerCase();
|
|
141159
|
+
let decoder = null;
|
|
141160
|
+
if (encoding === "gzip" || encoding === "x-gzip")
|
|
141161
|
+
decoder = raw.body.pipe(zlib.createGunzip());
|
|
141162
|
+
else if (encoding === "deflate")
|
|
141163
|
+
decoder = raw.body.pipe(zlib.createInflate());
|
|
141164
|
+
else if (encoding === "br")
|
|
141165
|
+
decoder = raw.body.pipe(zlib.createBrotliDecompress());
|
|
141166
|
+
if (decoder) {
|
|
141167
|
+
decoder.on("error", () => {});
|
|
141168
|
+
activeBody = decoder;
|
|
141169
|
+
}
|
|
141170
|
+
}
|
|
141171
|
+
let body2;
|
|
141172
|
+
try {
|
|
141173
|
+
body2 = await readBounded(activeBody, maxBytes);
|
|
141174
|
+
} catch (err3) {
|
|
141175
|
+
raw.cancel?.();
|
|
141176
|
+
if (controller.signal.aborted) {
|
|
141177
|
+
return {
|
|
141178
|
+
ok: false,
|
|
141179
|
+
reason: "timeout",
|
|
141180
|
+
message: `Fetch exceeded ${timeoutMs}ms while reading the body of ${current.url.toString()}`
|
|
141181
|
+
};
|
|
141182
|
+
}
|
|
141183
|
+
return {
|
|
141184
|
+
ok: false,
|
|
141185
|
+
reason: "network_error",
|
|
141186
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
141187
|
+
};
|
|
141188
|
+
}
|
|
141189
|
+
raw.cancel?.();
|
|
141190
|
+
return {
|
|
141191
|
+
ok: true,
|
|
141192
|
+
outcome: {
|
|
141193
|
+
status: raw.status,
|
|
141194
|
+
finalUrl: current.url.toString(),
|
|
141195
|
+
contentType,
|
|
141196
|
+
bytes: body2.bytes,
|
|
141197
|
+
truncated: body2.truncated
|
|
141198
|
+
}
|
|
141199
|
+
};
|
|
141200
|
+
}
|
|
141201
|
+
return {
|
|
141202
|
+
ok: false,
|
|
141203
|
+
reason: "too_many_redirects",
|
|
141204
|
+
message: `Exceeded ${MAX_REDIRECTS} redirect hops.`
|
|
141205
|
+
};
|
|
141206
|
+
} finally {
|
|
141207
|
+
clearTimeout(timer);
|
|
141208
|
+
}
|
|
141209
|
+
}
|
|
141210
|
+
async function readBounded(body2, maxBytes) {
|
|
141211
|
+
if (!body2)
|
|
141212
|
+
return { bytes: new Uint8Array(0), truncated: false };
|
|
141213
|
+
const chunks = [];
|
|
141214
|
+
let received = 0;
|
|
141215
|
+
let truncated = false;
|
|
141216
|
+
for await (const value of body2) {
|
|
141217
|
+
if (!value || value.byteLength === 0)
|
|
141218
|
+
continue;
|
|
141219
|
+
chunks.push(value);
|
|
141220
|
+
received += value.byteLength;
|
|
141221
|
+
if (received > maxBytes) {
|
|
141222
|
+
truncated = true;
|
|
141223
|
+
break;
|
|
141224
|
+
}
|
|
141225
|
+
}
|
|
141226
|
+
const merged = new Uint8Array(Math.min(received, maxBytes));
|
|
141227
|
+
let offset = 0;
|
|
141228
|
+
for (const chunk of chunks) {
|
|
141229
|
+
if (offset >= merged.length)
|
|
141230
|
+
break;
|
|
141231
|
+
const slice = chunk.subarray(0, merged.length - offset);
|
|
141232
|
+
merged.set(slice, offset);
|
|
141233
|
+
offset += slice.byteLength;
|
|
141234
|
+
}
|
|
141235
|
+
return { bytes: merged, truncated };
|
|
141236
|
+
}
|
|
141237
|
+
var web_fetch = createSwarmTool({
|
|
141238
|
+
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.",
|
|
141239
|
+
args: {
|
|
141240
|
+
url: exports_external.string().min(1).max(2048).describe("Absolute http(s) URL to fetch (1–2048 chars)."),
|
|
141241
|
+
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}).`),
|
|
141242
|
+
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}).`),
|
|
141243
|
+
working_directory: exports_external.string().optional().describe("Project root for config resolution and evidence storage. Optional.")
|
|
141244
|
+
},
|
|
141245
|
+
execute: async (args2, directory) => {
|
|
141246
|
+
const parsed = ArgsSchema6.safeParse(args2);
|
|
141247
|
+
if (!parsed.success) {
|
|
141248
|
+
const fail = {
|
|
141249
|
+
success: false,
|
|
141250
|
+
reason: "invalid_args",
|
|
141251
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
141252
|
+
};
|
|
141253
|
+
return JSON.stringify(fail, null, 2);
|
|
141254
|
+
}
|
|
141255
|
+
const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
|
|
141256
|
+
if (!dirResult.success) {
|
|
141257
|
+
const fail = {
|
|
141258
|
+
success: false,
|
|
141259
|
+
reason: "invalid_working_directory",
|
|
141260
|
+
message: dirResult.message
|
|
141261
|
+
};
|
|
141262
|
+
return JSON.stringify(fail, null, 2);
|
|
141263
|
+
}
|
|
141264
|
+
const config3 = _internals102.loadPluginConfig(dirResult.directory);
|
|
141265
|
+
const generalConfig = config3.council?.general;
|
|
141266
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
141267
|
+
const fail = {
|
|
141268
|
+
success: false,
|
|
141269
|
+
reason: "council_general_disabled",
|
|
141270
|
+
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."
|
|
141271
|
+
};
|
|
141272
|
+
return JSON.stringify(fail, null, 2);
|
|
141273
|
+
}
|
|
141274
|
+
const validated = await validateFetchUrl(parsed.data.url, _internals102.dnsLookup);
|
|
141275
|
+
if (!validated.ok) {
|
|
141276
|
+
const fail = {
|
|
141277
|
+
success: false,
|
|
141278
|
+
reason: validated.reason,
|
|
141279
|
+
message: validated.message
|
|
141280
|
+
};
|
|
141281
|
+
return JSON.stringify(fail, null, 2);
|
|
141282
|
+
}
|
|
141283
|
+
const maxBytes = parsed.data.max_bytes ?? DEFAULT_MAX_BYTES;
|
|
141284
|
+
const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
|
|
141285
|
+
const result = await boundedFetch({ url: validated.url, address: validated.address }, maxBytes, timeoutMs, _internals102);
|
|
141286
|
+
if (!result.ok) {
|
|
141287
|
+
const fail = {
|
|
141288
|
+
success: false,
|
|
141289
|
+
reason: result.reason,
|
|
141290
|
+
message: result.message
|
|
141291
|
+
};
|
|
141292
|
+
return JSON.stringify(fail, null, 2);
|
|
141293
|
+
}
|
|
141294
|
+
const { outcome } = result;
|
|
141295
|
+
const raw = new TextDecoder("utf-8", { fatal: false }).decode(outcome.bytes);
|
|
141296
|
+
const isHtml = (outcome.contentType ?? "").toLowerCase().includes("html") || /<html|<!doctype html/i.test(raw);
|
|
141297
|
+
const title = isHtml ? extractTitle(raw) : undefined;
|
|
141298
|
+
const bodyText = isHtml ? htmlToText(raw) : raw.replace(/\s*\n\s*/g, `
|
|
141299
|
+
`).trim();
|
|
141300
|
+
const text = bodyText.length > MAX_TEXT_LENGTH2 ? `${bodyText.slice(0, MAX_TEXT_LENGTH2)}…` : bodyText;
|
|
141301
|
+
const textTruncated = outcome.truncated || bodyText.length > MAX_TEXT_LENGTH2;
|
|
141302
|
+
const evidence = await captureFetchEvidence(dirResult.directory, outcome.finalUrl, title, text);
|
|
141303
|
+
const ok2 = {
|
|
141304
|
+
success: true,
|
|
141305
|
+
url: parsed.data.url,
|
|
141306
|
+
finalUrl: outcome.finalUrl,
|
|
141307
|
+
status: outcome.status,
|
|
141308
|
+
contentType: outcome.contentType ?? undefined,
|
|
141309
|
+
title,
|
|
141310
|
+
text,
|
|
141311
|
+
truncated: textTruncated,
|
|
141312
|
+
bytesReturned: outcome.bytes.byteLength,
|
|
141313
|
+
evidence
|
|
141314
|
+
};
|
|
141315
|
+
return JSON.stringify(ok2, null, 2);
|
|
141316
|
+
}
|
|
141317
|
+
});
|
|
141318
|
+
async function captureFetchEvidence(directory, url3, title, text) {
|
|
141319
|
+
try {
|
|
141320
|
+
const written = await _internals102.writeEvidenceDocuments(directory, [
|
|
141321
|
+
{
|
|
141322
|
+
sourceType: "crawl",
|
|
141323
|
+
url: url3,
|
|
141324
|
+
title,
|
|
141325
|
+
text,
|
|
141326
|
+
createdBy: "web_fetch"
|
|
141327
|
+
}
|
|
141328
|
+
]);
|
|
141329
|
+
return {
|
|
141330
|
+
stored: written.records.length > 0,
|
|
141331
|
+
ref: written.refs[0],
|
|
141332
|
+
path: written.path
|
|
141333
|
+
};
|
|
141334
|
+
} catch (err3) {
|
|
141335
|
+
return {
|
|
141336
|
+
stored: false,
|
|
141337
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
141338
|
+
};
|
|
141339
|
+
}
|
|
141340
|
+
}
|
|
141341
|
+
var _internals102 = {
|
|
141342
|
+
httpRequest: performHttpRequest,
|
|
141343
|
+
dnsLookup: lookup,
|
|
141344
|
+
loadPluginConfig,
|
|
141345
|
+
writeEvidenceDocuments
|
|
141346
|
+
};
|
|
141347
|
+
|
|
140506
141348
|
// src/tools/web-search.ts
|
|
140507
141349
|
init_zod();
|
|
140508
141350
|
init_loader();
|
|
@@ -140699,88 +141541,11 @@ function createWebSearchProvider(config3) {
|
|
|
140699
141541
|
}
|
|
140700
141542
|
}
|
|
140701
141543
|
|
|
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
141544
|
// src/tools/web-search.ts
|
|
140780
141545
|
init_create_tool();
|
|
140781
141546
|
init_resolve_working_directory();
|
|
140782
141547
|
var MAX_RESULTS_HARD_CAP = 10;
|
|
140783
|
-
var
|
|
141548
|
+
var ArgsSchema7 = exports_external.object({
|
|
140784
141549
|
query: exports_external.string().min(1).max(500),
|
|
140785
141550
|
max_results: exports_external.number().int().min(1).max(20).optional(),
|
|
140786
141551
|
freshness: exports_external.enum(["auto", "none", "day", "week", "month", "year"]).default("auto"),
|
|
@@ -140795,7 +141560,7 @@ var web_search = createSwarmTool({
|
|
|
140795
141560
|
working_directory: exports_external.string().optional().describe("Project root for config resolution. Optional.")
|
|
140796
141561
|
},
|
|
140797
141562
|
execute: async (args2, directory) => {
|
|
140798
|
-
const parsed =
|
|
141563
|
+
const parsed = ArgsSchema7.safeParse(args2);
|
|
140799
141564
|
if (!parsed.success) {
|
|
140800
141565
|
const fail = {
|
|
140801
141566
|
success: false,
|
|
@@ -140878,7 +141643,7 @@ var web_search = createSwarmTool({
|
|
|
140878
141643
|
});
|
|
140879
141644
|
async function captureSearchEvidence(directory, query, results) {
|
|
140880
141645
|
try {
|
|
140881
|
-
const written = await
|
|
141646
|
+
const written = await _internals103.writeEvidenceDocuments(directory, results.map((result) => ({
|
|
140882
141647
|
sourceType: "web_search",
|
|
140883
141648
|
query,
|
|
140884
141649
|
title: result.title,
|
|
@@ -140906,7 +141671,7 @@ async function captureSearchEvidence(directory, query, results) {
|
|
|
140906
141671
|
};
|
|
140907
141672
|
}
|
|
140908
141673
|
}
|
|
140909
|
-
var
|
|
141674
|
+
var _internals103 = {
|
|
140910
141675
|
writeEvidenceDocuments
|
|
140911
141676
|
};
|
|
140912
141677
|
|
|
@@ -140937,7 +141702,7 @@ var KnowledgeRecommendationSchema2 = exports_external.object({
|
|
|
140937
141702
|
confidence: exports_external.number().min(0).max(1).default(0.5),
|
|
140938
141703
|
evidence_refs: exports_external.array(exports_external.string().min(1)).default([])
|
|
140939
141704
|
});
|
|
140940
|
-
var
|
|
141705
|
+
var ArgsSchema8 = exports_external.object({
|
|
140941
141706
|
phase: exports_external.number().int().min(0).max(999),
|
|
140942
141707
|
verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
140943
141708
|
findings: exports_external.array(FindingSchema2).default([]),
|
|
@@ -140958,7 +141723,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
|
|
|
140958
141723
|
provenance_session_id: exports_external.string().min(1).optional().describe("Session ID of the agent that produced this evidence (optional provenance).")
|
|
140959
141724
|
},
|
|
140960
141725
|
execute: async (rawArgs, directory) => {
|
|
140961
|
-
const parsed =
|
|
141726
|
+
const parsed = ArgsSchema8.safeParse(rawArgs);
|
|
140962
141727
|
if (!parsed.success) {
|
|
140963
141728
|
return JSON.stringify({
|
|
140964
141729
|
success: false,
|
|
@@ -141237,7 +142002,7 @@ var VerdictSchema3 = exports_external.object({
|
|
|
141237
142002
|
criteriaUnmet: exports_external.array(exports_external.string()),
|
|
141238
142003
|
durationMs: exports_external.number().nonnegative()
|
|
141239
142004
|
});
|
|
141240
|
-
var
|
|
142005
|
+
var ArgsSchema9 = exports_external.object({
|
|
141241
142006
|
phase: exports_external.number().int().min(1),
|
|
141242
142007
|
projectSummary: exports_external.string().min(1),
|
|
141243
142008
|
roundNumber: exports_external.number().int().min(1).max(10).optional(),
|
|
@@ -141253,7 +142018,7 @@ function normalizeFinalVerdict(verdict, requiredFixesCount) {
|
|
|
141253
142018
|
return requiredFixesCount > 0 ? "rejected" : "concerns";
|
|
141254
142019
|
}
|
|
141255
142020
|
async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
141256
|
-
const parsed =
|
|
142021
|
+
const parsed = ArgsSchema9.safeParse(args2);
|
|
141257
142022
|
if (!parsed.success) {
|
|
141258
142023
|
return JSON.stringify({
|
|
141259
142024
|
success: false,
|
|
@@ -141373,7 +142138,7 @@ var write_final_council_evidence = createSwarmTool({
|
|
|
141373
142138
|
verdicts: exports_external.array(VerdictSchema3).min(1).max(5).describe("Collected CouncilMemberVerdict objects from critic, reviewer, sme, test_engineer, and explorer.")
|
|
141374
142139
|
},
|
|
141375
142140
|
execute: async (args2, directory) => {
|
|
141376
|
-
const parsed =
|
|
142141
|
+
const parsed = ArgsSchema9.safeParse(args2);
|
|
141377
142142
|
if (!parsed.success) {
|
|
141378
142143
|
return JSON.stringify({
|
|
141379
142144
|
success: false,
|
|
@@ -141707,6 +142472,7 @@ var TOOL_MANIFEST = defineHandlers({
|
|
|
141707
142472
|
get_qa_gate_profile: () => get_qa_gate_profile,
|
|
141708
142473
|
set_qa_gates: () => set_qa_gates,
|
|
141709
142474
|
web_search: () => web_search,
|
|
142475
|
+
web_fetch: () => web_fetch,
|
|
141710
142476
|
convene_general_council: () => convene_general_council,
|
|
141711
142477
|
write_final_council_evidence: () => write_final_council_evidence,
|
|
141712
142478
|
skill_generate: () => skill_generate,
|
|
@@ -142389,6 +143155,10 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
142389
143155
|
template: "/swarm deep-dive $ARGUMENTS",
|
|
142390
143156
|
description: "Use /swarm deep-dive to launch a read-only deep audit with parallel explorer waves, dual reviewers, and critic challenge"
|
|
142391
143157
|
},
|
|
143158
|
+
"swarm-deep-research": {
|
|
143159
|
+
template: "/swarm deep-research $ARGUMENTS",
|
|
143160
|
+
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]"
|
|
143161
|
+
},
|
|
142392
143162
|
"swarm-codebase-review": {
|
|
142393
143163
|
template: "/swarm codebase-review $ARGUMENTS",
|
|
142394
143164
|
description: "Use /swarm codebase-review to launch codebase-review-swarm for a quote-grounded full-repo or large-subsystem audit"
|