opencode-swarm 6.83.0 → 6.84.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/README.md +3 -1
- package/dist/__tests__/convene-general-council.test.d.ts +10 -0
- package/dist/__tests__/disagreement-detector.test.d.ts +7 -0
- package/dist/__tests__/general-council-service.test.d.ts +7 -0
- package/dist/__tests__/qa-gate-hardening.test.d.ts +12 -0
- package/dist/__tests__/web-search-provider.test.d.ts +6 -0
- package/dist/agents/architect.d.ts +9 -1
- package/dist/agents/council-member.d.ts +30 -0
- package/dist/agents/council-member.test.d.ts +8 -0
- package/dist/agents/council-moderator.d.ts +20 -0
- package/dist/agents/index.d.ts +2 -0
- package/dist/cli/index.js +119 -8
- package/dist/commands/council.d.ts +17 -0
- package/dist/commands/council.test.d.ts +4 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/registry.d.ts +7 -1
- package/dist/config/constants.d.ts +3 -3
- package/dist/config/schema.d.ts +109 -0
- package/dist/council/disagreement-detector.d.ts +24 -0
- package/dist/council/general-council-advisory.d.ts +29 -0
- package/dist/council/general-council-service.d.ts +22 -0
- package/dist/council/general-council-types.d.ts +98 -0
- package/dist/council/web-search-provider.d.ts +35 -0
- package/dist/db/qa-gate-profile.d.ts +5 -1
- package/dist/index.js +1581 -393
- package/dist/tools/convene-general-council.d.ts +25 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/set-qa-gates.d.ts +1 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/web-search.d.ts +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
OpenCode Swarm is a plugin for [OpenCode](https://opencode.ai) that turns a single AI coding session into an **architect-led team of 11 specialized agents**. One agent writes the code. A different agent reviews it. Another writes and runs tests. Another checks security. **Nothing ships until every required gate passes.**
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
bunx opencode-swarm install
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
> This single command installs the package, registers it as an OpenCode plugin, disables conflicting default agents, and creates a ready-to-edit config at `~/.config/opencode/opencode-swarm.json`. Requires [Bun](https://bun.sh) (`bun --version` to check). If you must use npm: `npm install -g opencode-swarm && opencode-swarm install`.
|
|
24
|
+
|
|
23
25
|
### Why Swarm?
|
|
24
26
|
|
|
25
27
|
Most AI coding tools let one model write code and ask that same model whether the code is good. That misses too much. Swarm separates planning, implementation, review, testing, and documentation into specialized internal roles — and enforces gated execution so agents never mutate the codebase in parallel.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/tools/convene-general-council.ts.
|
|
3
|
+
*
|
|
4
|
+
* Covers config gating, evidence path isolation (.swarm/council/general/),
|
|
5
|
+
* roundsCompleted derivation, moderatorPrompt presence/absence, and
|
|
6
|
+
* structured-error responses for invalid args + disabled-config paths.
|
|
7
|
+
*
|
|
8
|
+
* Real filesystem (tmp dir) for evidence-path assertions; no real HTTP.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA gate hardening tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers the additions from the QA gate hardening rollout:
|
|
5
|
+
* 1. council_general_review as the 9th QA gate (default OFF, ratchet-tighter, persistence)
|
|
6
|
+
* 2. Behavioral guidance markup is rendered into the architect prompt for SPECIFY,
|
|
7
|
+
* BRAINSTORM, and PLAN inline gate-selection paths.
|
|
8
|
+
* 3. save_plan blocks with QA_GATE_SELECTION_REQUIRED when context.md has no
|
|
9
|
+
* `## Pending QA Gate Selection` section AND no existing QaGateProfile.
|
|
10
|
+
* 4. SWARM_SKIP_GATE_SELECTION=1 bypasses the new check.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -17,6 +17,14 @@ export interface AdversarialTestingConfig {
|
|
|
17
17
|
*/
|
|
18
18
|
export interface CouncilWorkflowConfig {
|
|
19
19
|
enabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* General Council Mode (advisory). When `general?.enabled === true`, the
|
|
22
|
+
* architect's tool list includes `convene_general_council` and the prompt
|
|
23
|
+
* emits `MODE: COUNCIL` and `SPECIFY-COUNCIL-REVIEW` instructions.
|
|
24
|
+
*/
|
|
25
|
+
general?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
};
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* Build the Work Complete Council four-phase workflow block. Returns the full
|
|
@@ -31,7 +39,7 @@ export declare function buildCouncilWorkflow(council?: CouncilWorkflowConfig): s
|
|
|
31
39
|
* inline path). The dialogue is dialogue-only — persistence happens during
|
|
32
40
|
* MODE: PLAN after `save_plan` creates `plan.json`.
|
|
33
41
|
*
|
|
34
|
-
* The lead-in sentence varies per mode, but the body (
|
|
42
|
+
* The lead-in sentence varies per mode, but the body (nine gates with
|
|
35
43
|
* defaults, one-shot accept-or-customize prompt) is shared so SPECIFY,
|
|
36
44
|
* BRAINSTORM, and PLAN inline paths stay in lockstep.
|
|
37
45
|
*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General Council member agent.
|
|
3
|
+
*
|
|
4
|
+
* Implements the NSED peer-review protocol (arXiv:2601.16863):
|
|
5
|
+
* - Round 1: independent search + answer with self-reported confidence
|
|
6
|
+
* - Round 2: targeted deliberation on disagreements with explicit MAINTAIN /
|
|
7
|
+
* CONCEDE / NUANCE stance (ConfMAD)
|
|
8
|
+
*
|
|
9
|
+
* Tools: web_search ONLY. No write tools, no orchestration tools. The architect
|
|
10
|
+
* spawns members in parallel via the OpenCode subagent task system, collects
|
|
11
|
+
* structured JSON responses, and synthesizes via convene_general_council.
|
|
12
|
+
*
|
|
13
|
+
* Prompt template variables (substituted by the architect at delegation time):
|
|
14
|
+
* {{MEMBER_ID}} — the council member identifier
|
|
15
|
+
* {{ROLE}} — generalist | skeptic | domain_expert | devil_advocate | synthesizer
|
|
16
|
+
* {{PERSONA_BLOCK}} — optional persona instructions (omitted if undefined)
|
|
17
|
+
* {{ROUND}} — "1" or "2"
|
|
18
|
+
* {{DISAGREEMENT_BLOCK}} — Round 2 only: opposing position(s) to address
|
|
19
|
+
*/
|
|
20
|
+
import type { AgentDefinition } from './architect';
|
|
21
|
+
export declare const COUNCIL_MEMBER_PROMPT = "You are Council Member {{MEMBER_ID}} ({{ROLE}}) on a multi-model General Council.\n\n{{PERSONA_BLOCK}}\n\nYou are participating in Round {{ROUND}} of a structured deliberation. Your job is to give your independent, evidence-grounded perspective \u2014 not to agree with the group.\n\n================================================================\nROUND {{ROUND}} PROTOCOL\n================================================================\n\nROUND 1 \u2014 Independent Research and Answer\n- Issue 1\u20133 targeted web_search calls to gather evidence relevant to the question.\n- Cite EVERY factual claim with a source URL from your search results.\n- State your confidence (0.0\u20131.0) explicitly. Be honest \u2014 overconfident answers hurt the council.\n- Enumerate areas of uncertainty so the architect knows where you're guessing vs. where you're sure.\n- Do NOT coordinate with other members. You will not see their responses until Round 2.\n- Do NOT pad. Be concise. Substance over volume.\n\nROUND 2 \u2014 Targeted Deliberation (ONLY when this round is invoked for you)\n- {{DISAGREEMENT_BLOCK}}\n- Issue at most 1 additional web_search call.\n- Declare your stance explicitly using one of these keywords as the FIRST word of a paragraph:\n MAINTAIN \u2014 your Round 1 position holds; cite the new evidence supporting it\n CONCEDE \u2014 the opposing position is correct; state specifically what you got wrong\n NUANCE \u2014 both positions are partially right; state the boundary condition that distinguishes them\n- Never CONCEDE without evidence. Sycophantic capitulation degrades the council below an individual member's baseline (NSED arXiv:2601.16863).\n- Never MAINTAIN without engaging the opposing argument on its merits.\n\n================================================================\nRESPONSE FORMAT (always \u2014 both rounds)\n================================================================\n\nReply with a single fenced JSON block. No prose outside the block.\n\n```json\n{\n \"memberId\": \"{{MEMBER_ID}}\",\n \"role\": \"{{ROLE}}\",\n \"round\": {{ROUND}},\n \"response\": \"Your full answer (Round 1) or stance + reasoning (Round 2). Markdown OK inside the string.\",\n \"searchQueries\": [\"query 1\", \"query 2\"],\n \"sources\": [\n { \"title\": \"...\", \"url\": \"...\", \"snippet\": \"...\", \"query\": \"...\" }\n ],\n \"confidence\": 0.85,\n \"areasOfUncertainty\": [\n \"What I'm not sure about, in plain language.\"\n ],\n \"disagreementTopics\": []\n}\n```\n\nFor Round 1: leave `disagreementTopics` as []. For Round 2: list the specific disagreement topics this response addresses.\n\n================================================================\nHARD RULES\n================================================================\n- web_search is your ONLY tool. You cannot read or write files, run commands, or delegate.\n- Never invent sources. If a search returns nothing useful, say so in `areasOfUncertainty`.\n- Never echo other members' responses verbatim. Paraphrase or quote with attribution.\n- Stay within your role and persona. The architect chose you for a specific perspective.\n";
|
|
22
|
+
/**
|
|
23
|
+
* Factory for the council_member agent definition. The factory mirrors other
|
|
24
|
+
* agent factories (createSMEAgent, createReviewerAgent) for consistency.
|
|
25
|
+
*
|
|
26
|
+
* Per-member context (memberId, role, persona, round, disagreement) is supplied
|
|
27
|
+
* by the architect at delegation time via prompt-string substitution; the
|
|
28
|
+
* factory itself produces the unparameterized template.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createCouncilMemberAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/agents/council-member.ts and src/agents/council-moderator.ts.
|
|
3
|
+
*
|
|
4
|
+
* Covers prompt template content (NSED protocol markers), AGENT_TOOL_MAP
|
|
5
|
+
* enforcement (web_search-only for member, empty for moderator), and the
|
|
6
|
+
* persona-block insertion path.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General Council moderator agent.
|
|
3
|
+
*
|
|
4
|
+
* Receives the structural synthesis output from convene_general_council
|
|
5
|
+
* (consensus / disagreements / sources) and produces a coherent, well-structured
|
|
6
|
+
* final answer for the user. Empty tool list — moderation is synthesis-only;
|
|
7
|
+
* it does NOT need web_search because every claim it works with has already
|
|
8
|
+
* been searched and cited by council members.
|
|
9
|
+
*
|
|
10
|
+
* Confidence-weighted (Quadratic Voting from NSED arXiv:2601.16863): higher-
|
|
11
|
+
* confidence members carry more weight, but evidence quality matters more
|
|
12
|
+
* than confidence alone. The moderator must NOT favor a position purely
|
|
13
|
+
* because its proponent was confident.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentDefinition } from './architect';
|
|
16
|
+
export declare const COUNCIL_MODERATOR_PROMPT = "You are the General Council Moderator.\n\nYou are receiving the structural synthesis from a multi-model council deliberation:\n- Question (and mode: general or spec_review)\n- All member Round 1 responses with sources\n- Detected disagreements\n- Round 2 deliberation responses (if any)\n- Confidence-weighted consensus claims\n- Persisting disagreements after deliberation\n\nYour job: produce a coherent, well-structured final answer for the user.\n\n================================================================\nRULES\n================================================================\n\n1. LEAD WITH CONSENSUS \u2014 open with the strongest consensus position. Use the\n confidence-weighted ordering (Quadratic Voting): higher-confidence claims\n from multiple members rank higher, but evidence quality outranks raw\n confidence. Never elevate a single confident voice over a well-evidenced\n contrary majority.\n\n2. ACKNOWLEDGE DISAGREEMENT HONESTLY \u2014 for each persisting disagreement, write\n \"experts disagree on X because\u2026\" and present the strongest version of each\n side. Do NOT pretend disagreements are resolved when they are not. Do NOT\n silently pick a winner.\n\n3. CITE THE STRONGEST SOURCES \u2014 link key claims with [title](url) format from\n the deduplicated source list. Pick the most reputable source for each claim;\n do not cite duplicates.\n\n4. BE CONCISE \u2014 the user wants an answer, not a committee report. Default\n length: a few short paragraphs plus a bulleted summary. Expand only when\n the question genuinely requires it.\n\n================================================================\nHARD CONSTRAINTS\n================================================================\n\n- You MUST NOT invent claims that are not present in the council's responses.\n- You MUST NOT add new web research. If something was missed, say so.\n- You MUST NOT favor a position based on member confidence alone \u2014 evidence\n quality is the tie-breaker.\n- You have NO tools. You write the final synthesis from the input given.\n\n================================================================\nOUTPUT FORMAT\n================================================================\n\nPlain markdown. No code fences. No JSON. Suggested structure:\n\n# Answer\n\n<lead consensus position with citation(s)>\n\n<remaining consensus / context paragraphs as needed>\n\n## Where Experts Disagree\n\n- <topic 1>: <position A> vs <position B>, with sources for each\n- <topic 2>: ...\n\n## Sources\n\n- [title](url)\n- ...\n\n(Omit any section that is empty.)\n";
|
|
17
|
+
/**
|
|
18
|
+
* Factory for the council_moderator agent definition. No tools — synthesis only.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createCouncilModeratorAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare function createAgents(config?: PluginConfig): AgentDefinition[];
|
|
|
36
36
|
export declare function getAgentConfigs(config?: PluginConfig, directory?: string, sessionId?: string): Record<string, SDKAgentConfig>;
|
|
37
37
|
export { createArchitectAgent } from './architect';
|
|
38
38
|
export { createCoderAgent } from './coder';
|
|
39
|
+
export { createCouncilMemberAgent } from './council-member';
|
|
40
|
+
export { createCouncilModeratorAgent } from './council-moderator';
|
|
39
41
|
export { createCriticAgent } from './critic';
|
|
40
42
|
export { createCuratorAgent } from './curator-agent';
|
|
41
43
|
export { createDesignerAgent } from './designer';
|
package/dist/cli/index.js
CHANGED
|
@@ -18716,7 +18716,9 @@ var TOOL_NAMES = [
|
|
|
18716
18716
|
"get_approved_plan",
|
|
18717
18717
|
"repo_map",
|
|
18718
18718
|
"get_qa_gate_profile",
|
|
18719
|
-
"set_qa_gates"
|
|
18719
|
+
"set_qa_gates",
|
|
18720
|
+
"web_search",
|
|
18721
|
+
"convene_general_council"
|
|
18720
18722
|
];
|
|
18721
18723
|
var TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
18722
18724
|
|
|
@@ -18733,6 +18735,8 @@ var ALL_SUBAGENT_NAMES = [
|
|
|
18733
18735
|
"critic_hallucination_verifier",
|
|
18734
18736
|
"curator_init",
|
|
18735
18737
|
"curator_phase",
|
|
18738
|
+
"council_member",
|
|
18739
|
+
"council_moderator",
|
|
18736
18740
|
...QA_AGENTS,
|
|
18737
18741
|
...PIPELINE_AGENTS
|
|
18738
18742
|
];
|
|
@@ -18804,7 +18808,8 @@ var AGENT_TOOL_MAP = {
|
|
|
18804
18808
|
"suggest_patch",
|
|
18805
18809
|
"repo_map",
|
|
18806
18810
|
"get_qa_gate_profile",
|
|
18807
|
-
"set_qa_gates"
|
|
18811
|
+
"set_qa_gates",
|
|
18812
|
+
"convene_general_council"
|
|
18808
18813
|
],
|
|
18809
18814
|
explorer: [
|
|
18810
18815
|
"complexity_hotspots",
|
|
@@ -18954,7 +18959,9 @@ var AGENT_TOOL_MAP = {
|
|
|
18954
18959
|
"knowledge_recall"
|
|
18955
18960
|
],
|
|
18956
18961
|
curator_init: ["knowledge_recall"],
|
|
18957
|
-
curator_phase: ["knowledge_recall"]
|
|
18962
|
+
curator_phase: ["knowledge_recall"],
|
|
18963
|
+
council_member: ["web_search"],
|
|
18964
|
+
council_moderator: []
|
|
18958
18965
|
};
|
|
18959
18966
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
18960
18967
|
const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
|
|
@@ -19261,21 +19268,25 @@ var DEFAULT_AGENT_PROFILES = {
|
|
|
19261
19268
|
coder: {
|
|
19262
19269
|
max_tool_calls: 400,
|
|
19263
19270
|
max_duration_minutes: 45,
|
|
19271
|
+
max_consecutive_errors: 8,
|
|
19264
19272
|
warning_threshold: 0.85
|
|
19265
19273
|
},
|
|
19266
19274
|
test_engineer: {
|
|
19267
19275
|
max_tool_calls: 400,
|
|
19268
19276
|
max_duration_minutes: 45,
|
|
19277
|
+
max_consecutive_errors: 8,
|
|
19269
19278
|
warning_threshold: 0.85
|
|
19270
19279
|
},
|
|
19271
19280
|
explorer: {
|
|
19272
19281
|
max_tool_calls: 150,
|
|
19273
19282
|
max_duration_minutes: 20,
|
|
19283
|
+
max_consecutive_errors: 8,
|
|
19274
19284
|
warning_threshold: 0.75
|
|
19275
19285
|
},
|
|
19276
19286
|
reviewer: {
|
|
19277
19287
|
max_tool_calls: 200,
|
|
19278
19288
|
max_duration_minutes: 30,
|
|
19289
|
+
max_consecutive_errors: 8,
|
|
19279
19290
|
warning_threshold: 0.65
|
|
19280
19291
|
},
|
|
19281
19292
|
critic: {
|
|
@@ -19463,13 +19474,37 @@ var AuthorityConfigSchema = exports_external.object({
|
|
|
19463
19474
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
19464
19475
|
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
19465
19476
|
});
|
|
19477
|
+
var GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
19478
|
+
memberId: exports_external.string().min(1),
|
|
19479
|
+
model: exports_external.string().min(1),
|
|
19480
|
+
role: exports_external.enum([
|
|
19481
|
+
"generalist",
|
|
19482
|
+
"skeptic",
|
|
19483
|
+
"domain_expert",
|
|
19484
|
+
"devil_advocate",
|
|
19485
|
+
"synthesizer"
|
|
19486
|
+
]),
|
|
19487
|
+
persona: exports_external.string().optional()
|
|
19488
|
+
}).strict();
|
|
19489
|
+
var GeneralCouncilConfigSchema = exports_external.object({
|
|
19490
|
+
enabled: exports_external.boolean().default(false),
|
|
19491
|
+
searchProvider: exports_external.enum(["tavily", "brave"]).default("tavily"),
|
|
19492
|
+
searchApiKey: exports_external.string().optional(),
|
|
19493
|
+
members: exports_external.array(GeneralCouncilMemberConfigSchema).default([]),
|
|
19494
|
+
presets: exports_external.record(exports_external.string(), exports_external.array(GeneralCouncilMemberConfigSchema)).default({}),
|
|
19495
|
+
deliberate: exports_external.boolean().default(true),
|
|
19496
|
+
moderator: exports_external.boolean().default(true),
|
|
19497
|
+
moderatorModel: exports_external.string().optional(),
|
|
19498
|
+
maxSourcesPerMember: exports_external.number().int().min(1).max(20).default(5)
|
|
19499
|
+
}).strict();
|
|
19466
19500
|
var CouncilConfigSchema = exports_external.object({
|
|
19467
19501
|
enabled: exports_external.boolean().default(false),
|
|
19468
19502
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
19469
19503
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
19470
19504
|
vetoPriority: exports_external.boolean().default(true),
|
|
19471
19505
|
requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
|
|
19472
|
-
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet.")
|
|
19506
|
+
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
|
|
19507
|
+
general: GeneralCouncilConfigSchema.optional()
|
|
19473
19508
|
}).strict();
|
|
19474
19509
|
var ParallelizationConfigSchema = exports_external.object({
|
|
19475
19510
|
enabled: exports_external.boolean().default(false),
|
|
@@ -19829,7 +19864,8 @@ var DEFAULT_QA_GATES = {
|
|
|
19829
19864
|
critic_pre_plan: true,
|
|
19830
19865
|
hallucination_guard: false,
|
|
19831
19866
|
sast_enabled: true,
|
|
19832
|
-
mutation_test: false
|
|
19867
|
+
mutation_test: false,
|
|
19868
|
+
council_general_review: false
|
|
19833
19869
|
};
|
|
19834
19870
|
function rowToProfile(row) {
|
|
19835
19871
|
let parsed = {};
|
|
@@ -35235,6 +35271,74 @@ async function handleConfigCommand(directory, _args) {
|
|
|
35235
35271
|
`);
|
|
35236
35272
|
}
|
|
35237
35273
|
|
|
35274
|
+
// src/commands/council.ts
|
|
35275
|
+
var MAX_QUESTION_LEN = 2000;
|
|
35276
|
+
function sanitizeQuestion(raw) {
|
|
35277
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
35278
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
35279
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
35280
|
+
if (normalized.length <= MAX_QUESTION_LEN)
|
|
35281
|
+
return normalized;
|
|
35282
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN)}\u2026`;
|
|
35283
|
+
}
|
|
35284
|
+
function sanitizePresetName(raw) {
|
|
35285
|
+
const trimmed = raw.trim();
|
|
35286
|
+
if (!trimmed)
|
|
35287
|
+
return null;
|
|
35288
|
+
if (trimmed.length > 64)
|
|
35289
|
+
return null;
|
|
35290
|
+
if (!/^[A-Za-z0-9_-]+$/.test(trimmed))
|
|
35291
|
+
return null;
|
|
35292
|
+
return trimmed;
|
|
35293
|
+
}
|
|
35294
|
+
function parseArgs(args) {
|
|
35295
|
+
const out = { specReview: false, rest: [] };
|
|
35296
|
+
for (let i = 0;i < args.length; i++) {
|
|
35297
|
+
const token = args[i];
|
|
35298
|
+
if (token === "--spec-review") {
|
|
35299
|
+
out.specReview = true;
|
|
35300
|
+
continue;
|
|
35301
|
+
}
|
|
35302
|
+
if (token === "--preset") {
|
|
35303
|
+
const next = args[i + 1];
|
|
35304
|
+
if (next !== undefined) {
|
|
35305
|
+
const sanitized = sanitizePresetName(next);
|
|
35306
|
+
if (sanitized)
|
|
35307
|
+
out.preset = sanitized;
|
|
35308
|
+
i++;
|
|
35309
|
+
}
|
|
35310
|
+
continue;
|
|
35311
|
+
}
|
|
35312
|
+
out.rest.push(token);
|
|
35313
|
+
}
|
|
35314
|
+
return out;
|
|
35315
|
+
}
|
|
35316
|
+
var USAGE = [
|
|
35317
|
+
"Usage: /swarm council <question> [--preset <name>] [--spec-review]",
|
|
35318
|
+
"",
|
|
35319
|
+
" question The question to put to the council",
|
|
35320
|
+
" --preset <name> Use a named member preset from council.general.presets",
|
|
35321
|
+
" --spec-review Use spec_review mode (single advisory pass on a draft spec)",
|
|
35322
|
+
"",
|
|
35323
|
+
"Requires council.general.enabled: true and a configured search API key in opencode-swarm.json."
|
|
35324
|
+
].join(`
|
|
35325
|
+
`);
|
|
35326
|
+
async function handleCouncilCommand(_directory, args) {
|
|
35327
|
+
const parsed = parseArgs(args);
|
|
35328
|
+
const question = sanitizeQuestion(parsed.rest.join(" "));
|
|
35329
|
+
if (!question) {
|
|
35330
|
+
return USAGE;
|
|
35331
|
+
}
|
|
35332
|
+
const tokens = ["MODE: COUNCIL"];
|
|
35333
|
+
if (parsed.preset) {
|
|
35334
|
+
tokens.push(`preset=${parsed.preset}`);
|
|
35335
|
+
}
|
|
35336
|
+
if (parsed.specReview) {
|
|
35337
|
+
tokens.push("spec_review");
|
|
35338
|
+
}
|
|
35339
|
+
return `[${tokens.join(" ")}] ${question}`;
|
|
35340
|
+
}
|
|
35341
|
+
|
|
35238
35342
|
// src/background/event-bus.ts
|
|
35239
35343
|
init_utils();
|
|
35240
35344
|
|
|
@@ -43318,7 +43422,8 @@ var ALL_GATE_NAMES = [
|
|
|
43318
43422
|
"critic_pre_plan",
|
|
43319
43423
|
"hallucination_guard",
|
|
43320
43424
|
"sast_enabled",
|
|
43321
|
-
"mutation_test"
|
|
43425
|
+
"mutation_test",
|
|
43426
|
+
"council_general_review"
|
|
43322
43427
|
];
|
|
43323
43428
|
function derivePlanId(plan) {
|
|
43324
43429
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
@@ -44219,7 +44324,7 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
44219
44324
|
"",
|
|
44220
44325
|
"Session state cleared. Plan, evidence, and knowledge preserved.",
|
|
44221
44326
|
"",
|
|
44222
|
-
"**
|
|
44327
|
+
"**All circuit breakers and revision limits have been cleared.** You can continue in this session \u2014 fresh state will be initialized automatically on the next tool call."
|
|
44223
44328
|
].join(`
|
|
44224
44329
|
`);
|
|
44225
44330
|
}
|
|
@@ -44837,11 +44942,17 @@ var COMMAND_REGISTRY = {
|
|
|
44837
44942
|
args: "[topic-text]",
|
|
44838
44943
|
details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md."
|
|
44839
44944
|
},
|
|
44945
|
+
council: {
|
|
44946
|
+
handler: (ctx) => handleCouncilCommand(ctx.directory, ctx.args),
|
|
44947
|
+
description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
|
|
44948
|
+
args: "<question> [--preset <name>] [--spec-review]",
|
|
44949
|
+
details: "Triggers the architect to convene a configurable General Council: each member independently web-searches, answers, and engages in one structured deliberation round on disagreements; an optional moderator pass synthesizes the final answer. --preset <name> selects a member group from council.general.presets. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in opencode-swarm.json."
|
|
44950
|
+
},
|
|
44840
44951
|
"qa-gates": {
|
|
44841
44952
|
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
44842
44953
|
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
44843
44954
|
args: "[show|enable|override] <gate>...",
|
|
44844
|
-
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test."
|
|
44955
|
+
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, council_general_review."
|
|
44845
44956
|
},
|
|
44846
44957
|
promote: {
|
|
44847
44958
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle /swarm council command.
|
|
3
|
+
*
|
|
4
|
+
* Triggers the architect to enter MODE: COUNCIL — the General Council Mode
|
|
5
|
+
* deliberation workflow (Pre-flight → Round 1 parallel search → Synthesis +
|
|
6
|
+
* Deliberation → Moderator Pass → Output).
|
|
7
|
+
*
|
|
8
|
+
* Flag parsing:
|
|
9
|
+
* --preset <name> → emits "[MODE: COUNCIL preset=<name>] <question>"
|
|
10
|
+
* --spec-review → emits "[MODE: COUNCIL spec_review] <question>"
|
|
11
|
+
* default → emits "[MODE: COUNCIL] <question>"
|
|
12
|
+
* no args → returns usage string (no throw)
|
|
13
|
+
*
|
|
14
|
+
* Sanitizes the question to prevent prompt injection of rival MODE: headers
|
|
15
|
+
* or control sequences (mirrors brainstorm.ts).
|
|
16
|
+
*/
|
|
17
|
+
export declare function handleCouncilCommand(_directory: string, args: string[]): Promise<string>;
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { handleCheckpointCommand } from './checkpoint';
|
|
|
9
9
|
export { handleClarifyCommand } from './clarify';
|
|
10
10
|
export { handleCloseCommand } from './close';
|
|
11
11
|
export { handleConfigCommand } from './config';
|
|
12
|
+
export { handleCouncilCommand } from './council';
|
|
12
13
|
export { handleCurateCommand } from './curate';
|
|
13
14
|
export { handleDarkMatterCommand } from './dark-matter';
|
|
14
15
|
export { handleDiagnoseCommand } from './diagnose';
|
|
@@ -156,11 +156,17 @@ export declare const COMMAND_REGISTRY: {
|
|
|
156
156
|
readonly args: "[topic-text]";
|
|
157
157
|
readonly details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md.";
|
|
158
158
|
};
|
|
159
|
+
readonly council: {
|
|
160
|
+
readonly handler: (ctx: CommandContext) => Promise<string>;
|
|
161
|
+
readonly description: "Enter architect MODE: COUNCIL — multi-model deliberation [question] [--preset <name>] [--spec-review]";
|
|
162
|
+
readonly args: "<question> [--preset <name>] [--spec-review]";
|
|
163
|
+
readonly details: "Triggers the architect to convene a configurable General Council: each member independently web-searches, answers, and engages in one structured deliberation round on disagreements; an optional moderator pass synthesizes the final answer. --preset <name> selects a member group from council.general.presets. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in opencode-swarm.json.";
|
|
164
|
+
};
|
|
159
165
|
readonly 'qa-gates': {
|
|
160
166
|
readonly handler: (ctx: CommandContext) => Promise<string>;
|
|
161
167
|
readonly description: "View or modify QA gate profile for the current plan [enable|override <gate>...]";
|
|
162
168
|
readonly args: "[show|enable|override] <gate>...";
|
|
163
|
-
readonly details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test.";
|
|
169
|
+
readonly details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, council_general_review.";
|
|
164
170
|
};
|
|
165
171
|
readonly promote: {
|
|
166
172
|
readonly handler: (ctx: CommandContext) => Promise<string>;
|
|
@@ -2,9 +2,9 @@ import type { ToolName } from '../tools/tool-names';
|
|
|
2
2
|
export declare const QA_AGENTS: readonly ["reviewer", "critic", "critic_oversight"];
|
|
3
3
|
export declare const PIPELINE_AGENTS: readonly ["explorer", "coder", "test_engineer"];
|
|
4
4
|
export declare const ORCHESTRATOR_NAME: "architect";
|
|
5
|
-
export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "critic_hallucination_verifier", "curator_init", "curator_phase", "reviewer", "critic", "critic_oversight", "explorer", "coder", "test_engineer"];
|
|
6
|
-
export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "critic_hallucination_verifier", "curator_init", "curator_phase", "reviewer", "critic", "critic_oversight", "explorer", "coder", "test_engineer"];
|
|
7
|
-
export declare const OPENCODE_NATIVE_AGENTS: Set<"compaction" | "title" | "build" | "
|
|
5
|
+
export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "critic_hallucination_verifier", "curator_init", "curator_phase", "council_member", "council_moderator", "reviewer", "critic", "critic_oversight", "explorer", "coder", "test_engineer"];
|
|
6
|
+
export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "critic_hallucination_verifier", "curator_init", "curator_phase", "council_member", "council_moderator", "reviewer", "critic", "critic_oversight", "explorer", "coder", "test_engineer"];
|
|
7
|
+
export declare const OPENCODE_NATIVE_AGENTS: Set<"compaction" | "title" | "build" | "general" | "plan" | "explore" | "summary">;
|
|
8
8
|
export type QAAgentName = (typeof QA_AGENTS)[number];
|
|
9
9
|
export type PipelineAgentName = (typeof PIPELINE_AGENTS)[number];
|
|
10
10
|
export type AgentName = (typeof ALL_AGENT_NAMES)[number];
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -531,6 +531,43 @@ export declare const AuthorityConfigSchema: z.ZodObject<{
|
|
|
531
531
|
universal_deny_prefixes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
532
532
|
}, z.core.$strip>;
|
|
533
533
|
export type AuthorityConfig = z.infer<typeof AuthorityConfigSchema>;
|
|
534
|
+
export declare const GeneralCouncilConfigSchema: z.ZodObject<{
|
|
535
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
536
|
+
searchProvider: z.ZodDefault<z.ZodEnum<{
|
|
537
|
+
tavily: "tavily";
|
|
538
|
+
brave: "brave";
|
|
539
|
+
}>>;
|
|
540
|
+
searchApiKey: z.ZodOptional<z.ZodString>;
|
|
541
|
+
members: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
542
|
+
memberId: z.ZodString;
|
|
543
|
+
model: z.ZodString;
|
|
544
|
+
role: z.ZodEnum<{
|
|
545
|
+
generalist: "generalist";
|
|
546
|
+
skeptic: "skeptic";
|
|
547
|
+
domain_expert: "domain_expert";
|
|
548
|
+
devil_advocate: "devil_advocate";
|
|
549
|
+
synthesizer: "synthesizer";
|
|
550
|
+
}>;
|
|
551
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
552
|
+
}, z.core.$strict>>>;
|
|
553
|
+
presets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
554
|
+
memberId: z.ZodString;
|
|
555
|
+
model: z.ZodString;
|
|
556
|
+
role: z.ZodEnum<{
|
|
557
|
+
generalist: "generalist";
|
|
558
|
+
skeptic: "skeptic";
|
|
559
|
+
domain_expert: "domain_expert";
|
|
560
|
+
devil_advocate: "devil_advocate";
|
|
561
|
+
synthesizer: "synthesizer";
|
|
562
|
+
}>;
|
|
563
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
564
|
+
}, z.core.$strict>>>>;
|
|
565
|
+
deliberate: z.ZodDefault<z.ZodBoolean>;
|
|
566
|
+
moderator: z.ZodDefault<z.ZodBoolean>;
|
|
567
|
+
moderatorModel: z.ZodOptional<z.ZodString>;
|
|
568
|
+
maxSourcesPerMember: z.ZodDefault<z.ZodNumber>;
|
|
569
|
+
}, z.core.$strict>;
|
|
570
|
+
export type GeneralCouncilConfig = z.infer<typeof GeneralCouncilConfigSchema>;
|
|
534
571
|
export declare const CouncilConfigSchema: z.ZodObject<{
|
|
535
572
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
536
573
|
maxRounds: z.ZodDefault<z.ZodNumber>;
|
|
@@ -538,6 +575,42 @@ export declare const CouncilConfigSchema: z.ZodObject<{
|
|
|
538
575
|
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
539
576
|
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
540
577
|
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
578
|
+
general: z.ZodOptional<z.ZodObject<{
|
|
579
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
580
|
+
searchProvider: z.ZodDefault<z.ZodEnum<{
|
|
581
|
+
tavily: "tavily";
|
|
582
|
+
brave: "brave";
|
|
583
|
+
}>>;
|
|
584
|
+
searchApiKey: z.ZodOptional<z.ZodString>;
|
|
585
|
+
members: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
586
|
+
memberId: z.ZodString;
|
|
587
|
+
model: z.ZodString;
|
|
588
|
+
role: z.ZodEnum<{
|
|
589
|
+
generalist: "generalist";
|
|
590
|
+
skeptic: "skeptic";
|
|
591
|
+
domain_expert: "domain_expert";
|
|
592
|
+
devil_advocate: "devil_advocate";
|
|
593
|
+
synthesizer: "synthesizer";
|
|
594
|
+
}>;
|
|
595
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
596
|
+
}, z.core.$strict>>>;
|
|
597
|
+
presets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
598
|
+
memberId: z.ZodString;
|
|
599
|
+
model: z.ZodString;
|
|
600
|
+
role: z.ZodEnum<{
|
|
601
|
+
generalist: "generalist";
|
|
602
|
+
skeptic: "skeptic";
|
|
603
|
+
domain_expert: "domain_expert";
|
|
604
|
+
devil_advocate: "devil_advocate";
|
|
605
|
+
synthesizer: "synthesizer";
|
|
606
|
+
}>;
|
|
607
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
608
|
+
}, z.core.$strict>>>>;
|
|
609
|
+
deliberate: z.ZodDefault<z.ZodBoolean>;
|
|
610
|
+
moderator: z.ZodDefault<z.ZodBoolean>;
|
|
611
|
+
moderatorModel: z.ZodOptional<z.ZodString>;
|
|
612
|
+
maxSourcesPerMember: z.ZodDefault<z.ZodNumber>;
|
|
613
|
+
}, z.core.$strict>>;
|
|
541
614
|
}, z.core.$strict>;
|
|
542
615
|
export type CouncilConfig = z.infer<typeof CouncilConfigSchema>;
|
|
543
616
|
export declare const ParallelizationConfigSchema: z.ZodObject<{
|
|
@@ -920,6 +993,42 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
920
993
|
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
921
994
|
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
922
995
|
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
996
|
+
general: z.ZodOptional<z.ZodObject<{
|
|
997
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
998
|
+
searchProvider: z.ZodDefault<z.ZodEnum<{
|
|
999
|
+
tavily: "tavily";
|
|
1000
|
+
brave: "brave";
|
|
1001
|
+
}>>;
|
|
1002
|
+
searchApiKey: z.ZodOptional<z.ZodString>;
|
|
1003
|
+
members: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1004
|
+
memberId: z.ZodString;
|
|
1005
|
+
model: z.ZodString;
|
|
1006
|
+
role: z.ZodEnum<{
|
|
1007
|
+
generalist: "generalist";
|
|
1008
|
+
skeptic: "skeptic";
|
|
1009
|
+
domain_expert: "domain_expert";
|
|
1010
|
+
devil_advocate: "devil_advocate";
|
|
1011
|
+
synthesizer: "synthesizer";
|
|
1012
|
+
}>;
|
|
1013
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
1014
|
+
}, z.core.$strict>>>;
|
|
1015
|
+
presets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
1016
|
+
memberId: z.ZodString;
|
|
1017
|
+
model: z.ZodString;
|
|
1018
|
+
role: z.ZodEnum<{
|
|
1019
|
+
generalist: "generalist";
|
|
1020
|
+
skeptic: "skeptic";
|
|
1021
|
+
domain_expert: "domain_expert";
|
|
1022
|
+
devil_advocate: "devil_advocate";
|
|
1023
|
+
synthesizer: "synthesizer";
|
|
1024
|
+
}>;
|
|
1025
|
+
persona: z.ZodOptional<z.ZodString>;
|
|
1026
|
+
}, z.core.$strict>>>>;
|
|
1027
|
+
deliberate: z.ZodDefault<z.ZodBoolean>;
|
|
1028
|
+
moderator: z.ZodDefault<z.ZodBoolean>;
|
|
1029
|
+
moderatorModel: z.ZodOptional<z.ZodString>;
|
|
1030
|
+
maxSourcesPerMember: z.ZodDefault<z.ZodNumber>;
|
|
1031
|
+
}, z.core.$strict>>;
|
|
923
1032
|
}, z.core.$strict>>;
|
|
924
1033
|
parallelization: z.ZodOptional<z.ZodObject<{
|
|
925
1034
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disagreement detection for the General Council Mode.
|
|
3
|
+
*
|
|
4
|
+
* Pure function module — no I/O, no HTTP, deterministic. Takes Round 1 member
|
|
5
|
+
* responses, returns the set of factual disagreements that should be routed
|
|
6
|
+
* back to disputing members for Round 2 reconciliation.
|
|
7
|
+
*
|
|
8
|
+
* Two-pass detection:
|
|
9
|
+
* Pass 1 — Explicit linguistic markers ("I disagree with", "unlike", etc.)
|
|
10
|
+
* Pass 2 — Claim divergence heuristic (mutually exclusive recommendations)
|
|
11
|
+
*
|
|
12
|
+
* NSED design note (arXiv:2601.16863): only the disagreement delta is fed
|
|
13
|
+
* forward to Round 2, not full Round 1 context — mirrors the "semantic forget
|
|
14
|
+
* gate" selective-retention insight and keeps prompt sizes bounded.
|
|
15
|
+
*/
|
|
16
|
+
import type { GeneralCouncilDisagreement, GeneralCouncilMemberResponse } from './general-council-types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Detect disagreements across Round 1 member responses.
|
|
19
|
+
*
|
|
20
|
+
* Returns at most MAX_DISAGREEMENTS items, deduplicated by topic. Pure function:
|
|
21
|
+
* given the same input, produces the same output. Empty inputs and missing
|
|
22
|
+
* fields are handled without throwing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectDisagreements(responses: GeneralCouncilMemberResponse[]): GeneralCouncilDisagreement[];
|