opencode-swarm 6.66.0 → 6.67.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/dist/cli/index.js CHANGED
@@ -18535,9 +18535,9 @@ var AGENT_TOOL_MAP = {
18535
18535
  "checkpoint",
18536
18536
  "check_gate_status",
18537
18537
  "completion_verify",
18538
+ "complexity_hotspots",
18538
18539
  "convene_council",
18539
18540
  "declare_council_criteria",
18540
- "complexity_hotspots",
18541
18541
  "detect_domains",
18542
18542
  "evidence_check",
18543
18543
  "extract_code_blocks",
@@ -37050,7 +37050,7 @@ function checkAgentToolMapAlignment(registeredKeys) {
37050
37050
  id: `agent-tool-map-mismatch-${agentName}-${toolName}`,
37051
37051
  title: "AGENT_TOOL_MAP alignment gap",
37052
37052
  description: `Tool "${toolName}" is assigned to agent "${agentName}" in AGENT_TOOL_MAP but is not registered in the plugin's tool: {} block. The agent will not be able to use this tool.`,
37053
- severity: "warn",
37053
+ severity: "error",
37054
37054
  path: `AGENT_TOOL_MAP.${agentName}`,
37055
37055
  currentValue: toolName,
37056
37056
  autoFixable: false
@@ -37159,6 +37159,11 @@ function formatToolDoctorMarkdown(result) {
37159
37159
  }
37160
37160
  lines.push("");
37161
37161
  }
37162
+ if (result.summary.error > 0) {
37163
+ lines.push("---", "");
37164
+ lines.push(`**BLOCKING**: ${result.summary.error} error-severity finding(s) must be resolved before release. ` + `AGENT_TOOL_MAP alignment errors mean an agent's system prompt instructs the model to call a tool that opencode has not registered \u2014 the agent's workflow will silently fail at runtime.`);
37165
+ lines.push("");
37166
+ }
37162
37167
  }
37163
37168
  return lines.join(`
37164
37169
  `);
@@ -1,3 +1,10 @@
1
+ import { type ConfigDoctorResult } from '../services/config-doctor';
2
+ /**
3
+ * Format tool doctor result as markdown for command output.
4
+ *
5
+ * Exported for unit testing of the BLOCKING footer enforcement path.
6
+ */
7
+ export declare function formatToolDoctorMarkdown(result: ConfigDoctorResult): string;
1
8
  /**
2
9
  * Handle /swarm config doctor command.
3
10
  * Maps to: config doctor service (runConfigDoctor)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Adversarial security tests for safeAssignOwnProps.
3
+ * Tests prototype pollution attacks, constructor pollution, array smuggling,
4
+ * and boundary violations.
5
+ */
6
+ export {};
@@ -48,6 +48,8 @@ export interface CouncilSynthesis {
48
48
  /** 1-indexed */
49
49
  roundNumber: number;
50
50
  allCriteriaMet: boolean;
51
+ /** true when called with an empty verdicts array — the APPROVE is vacuous */
52
+ emptyVerdictsWarning?: boolean;
51
53
  }
52
54
  export interface CouncilCriteriaItem {
53
55
  id: string;
@@ -71,7 +73,14 @@ export interface CouncilConfig {
71
73
  vetoPriority: boolean;
72
74
  /** Default false — when true, convene_council rejects unless all 5 member verdicts are provided */
73
75
  requireAllMembers: boolean;
74
- /** Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet. */
76
+ /**
77
+ * Optional webhook URL or handler name for auto-escalation when maxRounds is
78
+ * reached without APPROVE. Reserved for forward compatibility — NOT yet
79
+ * implemented. Currently, maxRounds exhaustion surfaces a user-facing message
80
+ * via `buildUnifiedFeedbackMd` in council-service.ts (see the "Escalate to
81
+ * user" block), and the architect must relay it to the user. Future wiring
82
+ * options: critic_oversight agent, HTTP webhook, or configurable handler.
83
+ */
75
84
  escalateOnMaxRounds?: string;
76
85
  }
77
86
  export declare const COUNCIL_DEFAULTS: CouncilConfig;
package/dist/index.js CHANGED
@@ -175,9 +175,9 @@ var init_constants = __esm(() => {
175
175
  "checkpoint",
176
176
  "check_gate_status",
177
177
  "completion_verify",
178
+ "complexity_hotspots",
178
179
  "convene_council",
179
180
  "declare_council_criteria",
180
- "complexity_hotspots",
181
181
  "detect_domains",
182
182
  "evidence_check",
183
183
  "extract_code_blocks",
@@ -35284,7 +35284,7 @@ function checkAgentToolMapAlignment(registeredKeys) {
35284
35284
  id: `agent-tool-map-mismatch-${agentName}-${toolName}`,
35285
35285
  title: "AGENT_TOOL_MAP alignment gap",
35286
35286
  description: `Tool "${toolName}" is assigned to agent "${agentName}" in AGENT_TOOL_MAP but is not registered in the plugin's tool: {} block. The agent will not be able to use this tool.`,
35287
- severity: "warn",
35287
+ severity: "error",
35288
35288
  path: `AGENT_TOOL_MAP.${agentName}`,
35289
35289
  currentValue: toolName,
35290
35290
  autoFixable: false
@@ -50015,6 +50015,11 @@ function formatToolDoctorMarkdown(result) {
50015
50015
  }
50016
50016
  lines.push("");
50017
50017
  }
50018
+ if (result.summary.error > 0) {
50019
+ lines.push("---", "");
50020
+ lines.push(`**BLOCKING**: ${result.summary.error} error-severity finding(s) must be resolved before release. ` + `AGENT_TOOL_MAP alignment errors mean an agent's system prompt instructs the model to call a tool that opencode has not registered \u2014 the agent's workflow will silently fail at runtime.`);
50021
+ lines.push("");
50022
+ }
50018
50023
  }
50019
50024
  return lines.join(`
50020
50025
  `);
@@ -54733,16 +54738,17 @@ ${customAppendPrompt}`;
54733
54738
  }
54734
54739
  prompt = prompt?.replace("{{YOUR_TOOLS}}", buildYourToolsList())?.replace("{{AVAILABLE_TOOLS}}", buildAvailableToolsList())?.replace("{{SLASH_COMMANDS}}", buildSlashCommandsList());
54735
54740
  const councilBlock = buildCouncilWorkflow(council);
54741
+ const hasPlaceholder = prompt?.includes("{{COUNCIL_WORKFLOW}}") === true;
54736
54742
  if (councilBlock === "") {
54737
- prompt = prompt?.replace(`
54738
-
54739
- {{COUNCIL_WORKFLOW}}
54740
-
54741
- `, `
54743
+ prompt = prompt?.replace(/\n\n\{\{COUNCIL_WORKFLOW\}\}\n\n/g, `
54742
54744
 
54743
54745
  `);
54746
+ } else if (hasPlaceholder) {
54747
+ prompt = prompt?.replace(/\{\{COUNCIL_WORKFLOW\}\}/g, councilBlock);
54744
54748
  } else {
54745
- prompt = prompt?.replace("{{COUNCIL_WORKFLOW}}", councilBlock);
54749
+ prompt = `${prompt ?? ""}
54750
+
54751
+ ${councilBlock}`;
54746
54752
  }
54747
54753
  const advEnabled = adversarialTesting?.enabled ?? true;
54748
54754
  const advScope = adversarialTesting?.scope ?? "all";
@@ -56494,6 +56500,13 @@ function getAgentConfigs(config3, directory, sessionId) {
56494
56500
  } else {
56495
56501
  allowedTools = AGENT_TOOL_MAP[baseAgentName];
56496
56502
  }
56503
+ if (baseAgentName === "architect" && config3?.council?.enabled === true && override !== undefined) {
56504
+ const required3 = ["declare_council_criteria", "convene_council"];
56505
+ const missing = required3.filter((t) => !override.includes(t));
56506
+ if (missing.length > 0) {
56507
+ throw new Error(`[opencode-swarm] Conflicting config: council.enabled=true but tool_filter.overrides.architect omits ${missing.join(", ")}. ` + `Either set council.enabled=false, remove the architect override entirely to fall back on AGENT_TOOL_MAP, or add the missing council tools to the override. ` + `Refusing to silently override your explicit tool_filter.overrides.architect.`);
56508
+ }
56509
+ }
56497
56510
  if (!allowedTools && !Object.hasOwn(toolFilterOverrides, baseAgentName)) {
56498
56511
  if (!warnedMissingWhitelist.has(baseAgentName)) {
56499
56512
  console.warn(`[getAgentConfigs] Unknown agent '${baseAgentName}', defaulting to minimal toolset.`);
@@ -68371,7 +68384,13 @@ ${body2}`);
68371
68384
  }
68372
68385
 
68373
68386
  // src/council/council-evidence-writer.ts
68374
- import { existsSync as existsSync36, mkdirSync as mkdirSync16, readFileSync as readFileSync35, writeFileSync as writeFileSync11 } from "fs";
68387
+ import {
68388
+ appendFileSync as appendFileSync7,
68389
+ existsSync as existsSync36,
68390
+ mkdirSync as mkdirSync16,
68391
+ readFileSync as readFileSync35,
68392
+ writeFileSync as writeFileSync11
68393
+ } from "fs";
68375
68394
  import { join as join59 } from "path";
68376
68395
  var EVIDENCE_DIR2 = ".swarm/evidence";
68377
68396
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
@@ -68382,7 +68401,23 @@ function safeAssignOwnProps(target, source) {
68382
68401
  for (const key of Object.keys(source)) {
68383
68402
  if (FORBIDDEN_KEYS.has(key))
68384
68403
  continue;
68385
- target[key] = source[key];
68404
+ const value = source[key];
68405
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
68406
+ const nested = Object.create(null);
68407
+ safeAssignOwnProps(nested, value);
68408
+ target[key] = nested;
68409
+ } else if (Array.isArray(value)) {
68410
+ target[key] = value.map((item) => {
68411
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
68412
+ const nested = Object.create(null);
68413
+ safeAssignOwnProps(nested, item);
68414
+ return nested;
68415
+ }
68416
+ return item;
68417
+ });
68418
+ } else {
68419
+ target[key] = value;
68420
+ }
68386
68421
  }
68387
68422
  return target;
68388
68423
  }
@@ -68420,6 +68455,20 @@ function writeCouncilEvidence(workingDir, synthesis) {
68420
68455
  safeAssignOwnProps(updated, existingRoot);
68421
68456
  updated.gates = mergedGates;
68422
68457
  writeFileSync11(filePath, JSON.stringify(updated, null, 2));
68458
+ try {
68459
+ const councilDir = join59(workingDir, ".swarm", "council");
68460
+ mkdirSync16(councilDir, { recursive: true });
68461
+ const auditLine = JSON.stringify({
68462
+ round: synthesis.roundNumber,
68463
+ verdict: synthesis.overallVerdict,
68464
+ timestamp: synthesis.timestamp,
68465
+ vetoedBy: synthesis.vetoedBy
68466
+ });
68467
+ appendFileSync7(join59(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
68468
+ `);
68469
+ } catch (auditError) {
68470
+ console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
68471
+ }
68423
68472
  }
68424
68473
 
68425
68474
  // src/council/types.ts
@@ -68469,7 +68518,8 @@ function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNum
68469
68518
  advisoryFindings,
68470
68519
  unifiedFeedbackMd,
68471
68520
  roundNumber,
68472
- allCriteriaMet
68521
+ allCriteriaMet,
68522
+ ...verdicts.length === 0 && { emptyVerdictsWarning: true }
68473
68523
  };
68474
68524
  }
68475
68525
  function detectConflicts(verdicts) {
@@ -80030,7 +80080,9 @@ var OpenCodeSwarm = async (ctx) => {
80030
80080
  checkpoint,
80031
80081
  completion_verify,
80032
80082
  complexity_hotspots,
80083
+ convene_council,
80033
80084
  curator_analyze,
80085
+ declare_council_criteria,
80034
80086
  knowledge_add,
80035
80087
  knowledge_recall,
80036
80088
  knowledge_remove,
@@ -80045,6 +80097,7 @@ var OpenCodeSwarm = async (ctx) => {
80045
80097
  imports,
80046
80098
  knowledge_query,
80047
80099
  lint,
80100
+ lint_spec,
80048
80101
  diff,
80049
80102
  pkg_audit,
80050
80103
  placeholder_scan,
@@ -80052,6 +80105,7 @@ var OpenCodeSwarm = async (ctx) => {
80052
80105
  pre_check_batch,
80053
80106
  quality_budget,
80054
80107
  repo_map,
80108
+ req_coverage,
80055
80109
  retrieve_summary,
80056
80110
  save_plan,
80057
80111
  sast_scan,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.66.0",
3
+ "version": "6.67.1",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",