opencode-swarm 6.86.6 → 6.86.7
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 +5 -4
- package/dist/config/schema.d.ts +2 -0
- package/dist/council/types.d.ts +5 -1
- package/dist/db/qa-gate-profile.d.ts +1 -1
- package/dist/index.js +145 -57
- package/dist/state.d.ts +16 -3
- package/dist/tools/convene-council.d.ts +10 -5
- package/dist/tools/declare-council-criteria.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/tool-names.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -18580,7 +18580,7 @@ import * as path32 from "path";
|
|
|
18580
18580
|
// package.json
|
|
18581
18581
|
var package_default = {
|
|
18582
18582
|
name: "opencode-swarm",
|
|
18583
|
-
version: "6.86.
|
|
18583
|
+
version: "6.86.7",
|
|
18584
18584
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
18585
18585
|
main: "dist/index.js",
|
|
18586
18586
|
types: "dist/index.d.ts",
|
|
@@ -18742,7 +18742,7 @@ var TOOL_NAMES = [
|
|
|
18742
18742
|
"evidence_check",
|
|
18743
18743
|
"check_gate_status",
|
|
18744
18744
|
"completion_verify",
|
|
18745
|
-
"
|
|
18745
|
+
"submit_council_verdicts",
|
|
18746
18746
|
"declare_council_criteria",
|
|
18747
18747
|
"sbom_generate",
|
|
18748
18748
|
"checkpoint",
|
|
@@ -18822,7 +18822,7 @@ var AGENT_TOOL_MAP = {
|
|
|
18822
18822
|
"check_gate_status",
|
|
18823
18823
|
"completion_verify",
|
|
18824
18824
|
"complexity_hotspots",
|
|
18825
|
-
"
|
|
18825
|
+
"submit_council_verdicts",
|
|
18826
18826
|
"declare_council_criteria",
|
|
18827
18827
|
"detect_domains",
|
|
18828
18828
|
"evidence_check",
|
|
@@ -19624,7 +19624,8 @@ var CouncilConfigSchema = exports_external.object({
|
|
|
19624
19624
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
19625
19625
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
19626
19626
|
vetoPriority: exports_external.boolean().default(true),
|
|
19627
|
-
requireAllMembers: exports_external.boolean().default(false).describe("When true,
|
|
19627
|
+
requireAllMembers: exports_external.boolean().default(false).describe("When true, submit_council_verdicts rejects if fewer than 5 member verdicts are provided. Equivalent to minimumMembers: 5."),
|
|
19628
|
+
minimumMembers: exports_external.number().int().min(1).max(5).default(3).describe("Minimum distinct council member verdicts required for synthesis. Default 3. Set to 1 to disable quorum enforcement. requireAllMembers: true overrides this to 5 (stricter constraint wins)."),
|
|
19628
19629
|
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."),
|
|
19629
19630
|
general: GeneralCouncilConfigSchema.optional()
|
|
19630
19631
|
}).strict();
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -576,6 +576,7 @@ export declare const CouncilConfigSchema: z.ZodObject<{
|
|
|
576
576
|
parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
577
577
|
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
578
578
|
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
579
|
+
minimumMembers: z.ZodDefault<z.ZodNumber>;
|
|
579
580
|
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
580
581
|
general: z.ZodOptional<z.ZodObject<{
|
|
581
582
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -996,6 +997,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
996
997
|
parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
997
998
|
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
998
999
|
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
1000
|
+
minimumMembers: z.ZodDefault<z.ZodNumber>;
|
|
999
1001
|
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
1000
1002
|
general: z.ZodOptional<z.ZodObject<{
|
|
1001
1003
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
package/dist/council/types.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface CouncilSynthesis {
|
|
|
48
48
|
/** 1-indexed */
|
|
49
49
|
roundNumber: number;
|
|
50
50
|
allCriteriaMet: boolean;
|
|
51
|
+
/** Distinct council members that produced verdicts (deduplicated count). */
|
|
52
|
+
quorumSize: number;
|
|
51
53
|
/** true when called with an empty verdicts array — the APPROVE is vacuous */
|
|
52
54
|
emptyVerdictsWarning?: boolean;
|
|
53
55
|
}
|
|
@@ -71,8 +73,10 @@ export interface CouncilConfig {
|
|
|
71
73
|
parallelTimeoutMs: number;
|
|
72
74
|
/** Default true — any REJECT blocks */
|
|
73
75
|
vetoPriority: boolean;
|
|
74
|
-
/** Default false — when true,
|
|
76
|
+
/** Default false — when true, submit_council_verdicts rejects unless all 5 member verdicts are provided */
|
|
75
77
|
requireAllMembers: boolean;
|
|
78
|
+
/** Default 3 — minimum distinct council members required for quorum. requireAllMembers: true overrides this to 5. */
|
|
79
|
+
minimumMembers: number;
|
|
76
80
|
/**
|
|
77
81
|
* Optional webhook URL or handler name for auto-escalation when maxRounds is
|
|
78
82
|
* reached without APPROVE. Reserved for forward compatibility — NOT yet
|
|
@@ -85,7 +85,7 @@ export declare function computeProfileHash(profile: QaGateProfile): string;
|
|
|
85
85
|
* machine; blocks coder→next-coder advancement until reviewer + test_engineer
|
|
86
86
|
* delegations observed).
|
|
87
87
|
* - council_mode — src/state.ts isCouncilGateActive + src/hooks/delegation-gate.ts
|
|
88
|
-
* (Stage B replaced by
|
|
88
|
+
* (Stage B replaced by submit_council_verdicts verdict).
|
|
89
89
|
* - sme_enabled — consumed during MODE: BRAINSTORM/SPECIFY architect dialogue.
|
|
90
90
|
* - critic_pre_plan — consumed by MODE: PLAN critic delegation before save_plan.
|
|
91
91
|
* - sast_enabled — consumed inside pre_check_batch tool.
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ var init_tool_names = __esm(() => {
|
|
|
50
50
|
"evidence_check",
|
|
51
51
|
"check_gate_status",
|
|
52
52
|
"completion_verify",
|
|
53
|
-
"
|
|
53
|
+
"submit_council_verdicts",
|
|
54
54
|
"declare_council_criteria",
|
|
55
55
|
"sbom_generate",
|
|
56
56
|
"checkpoint",
|
|
@@ -196,7 +196,7 @@ var init_constants = __esm(() => {
|
|
|
196
196
|
"check_gate_status",
|
|
197
197
|
"completion_verify",
|
|
198
198
|
"complexity_hotspots",
|
|
199
|
-
"
|
|
199
|
+
"submit_council_verdicts",
|
|
200
200
|
"declare_council_criteria",
|
|
201
201
|
"detect_domains",
|
|
202
202
|
"evidence_check",
|
|
@@ -453,7 +453,7 @@ var init_constants = __esm(() => {
|
|
|
453
453
|
co_change_analyzer: "detect hidden couplings by analyzing git history",
|
|
454
454
|
check_gate_status: "check the gate status of a specific task",
|
|
455
455
|
completion_verify: "verify completed tasks have required evidence",
|
|
456
|
-
|
|
456
|
+
submit_council_verdicts: "submit pre-collected council member verdicts for synthesis (architect MUST dispatch critic/reviewer/sme/test_engineer/explorer as Agent tasks first; this tool synthesizes only, it does not contact members)",
|
|
457
457
|
declare_council_criteria: "pre-declare acceptance criteria for a task before the coder starts work; criteria are read back during council evaluation",
|
|
458
458
|
detect_domains: "detect which SME domains are relevant for a given text",
|
|
459
459
|
extract_code_blocks: "extract code blocks from text content and save them to files",
|
|
@@ -15258,7 +15258,8 @@ var init_schema = __esm(() => {
|
|
|
15258
15258
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
15259
15259
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
15260
15260
|
vetoPriority: exports_external.boolean().default(true),
|
|
15261
|
-
requireAllMembers: exports_external.boolean().default(false).describe("When true,
|
|
15261
|
+
requireAllMembers: exports_external.boolean().default(false).describe("When true, submit_council_verdicts rejects if fewer than 5 member verdicts are provided. Equivalent to minimumMembers: 5."),
|
|
15262
|
+
minimumMembers: exports_external.number().int().min(1).max(5).default(3).describe("Minimum distinct council member verdicts required for synthesis. Default 3. Set to 1 to disable quorum enforcement. requireAllMembers: true overrides this to 5 (stricter constraint wins)."),
|
|
15262
15263
|
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."),
|
|
15263
15264
|
general: GeneralCouncilConfigSchema.optional()
|
|
15264
15265
|
}).strict();
|
|
@@ -25058,7 +25059,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25058
25059
|
return;
|
|
25059
25060
|
const normalized = normalizeToolName(input.tool);
|
|
25060
25061
|
const councilActive = await isCouncilGateActive(directory, config2.council);
|
|
25061
|
-
if (normalized === "
|
|
25062
|
+
if (normalized === "submit_council_verdicts") {
|
|
25062
25063
|
try {
|
|
25063
25064
|
const parsed = typeof _output === "string" ? JSON.parse(_output) : _output;
|
|
25064
25065
|
const result = parsed;
|
|
@@ -25072,19 +25073,20 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25072
25073
|
session.taskCouncilApproved = new Map;
|
|
25073
25074
|
session.taskCouncilApproved.set(taskId, {
|
|
25074
25075
|
verdict: result.overallVerdict,
|
|
25075
|
-
roundNumber: typeof result.roundNumber === "number" ? result.roundNumber : 1
|
|
25076
|
+
roundNumber: typeof result.roundNumber === "number" ? result.roundNumber : 1,
|
|
25077
|
+
quorumSize: typeof result.quorumSize === "number" ? result.quorumSize : 1
|
|
25076
25078
|
});
|
|
25077
25079
|
if (councilActive && result.overallVerdict === "APPROVE" && result.allCriteriaMet === true && (result.requiredFixesCount ?? 0) === 0) {
|
|
25078
25080
|
try {
|
|
25079
|
-
await advanceTaskStateAndPersist(session, taskId, "complete", directory);
|
|
25081
|
+
await advanceTaskStateAndPersist(session, taskId, "complete", directory, config2.council);
|
|
25080
25082
|
} catch (err2) {
|
|
25081
|
-
console.warn(`[delegation-gate] toolAfter
|
|
25083
|
+
console.warn(`[delegation-gate] toolAfter submit_council_verdicts: could not advance ${taskId} \u2192 complete: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25082
25084
|
}
|
|
25083
25085
|
}
|
|
25084
25086
|
}
|
|
25085
25087
|
}
|
|
25086
25088
|
} catch (err2) {
|
|
25087
|
-
console.warn(`[delegation-gate] toolAfter
|
|
25089
|
+
console.warn(`[delegation-gate] toolAfter submit_council_verdicts: failed to parse output: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25088
25090
|
}
|
|
25089
25091
|
return;
|
|
25090
25092
|
}
|
|
@@ -25988,7 +25990,7 @@ function isValidTaskId2(taskId) {
|
|
|
25988
25990
|
const trimmed = taskId.trim();
|
|
25989
25991
|
return trimmed.length > 0;
|
|
25990
25992
|
}
|
|
25991
|
-
function advanceTaskState(session, taskId, newState) {
|
|
25993
|
+
function advanceTaskState(session, taskId, newState, councilConfig) {
|
|
25992
25994
|
if (!isValidTaskId2(taskId)) {
|
|
25993
25995
|
return;
|
|
25994
25996
|
}
|
|
@@ -26011,7 +26013,8 @@ function advanceTaskState(session, taskId, newState) {
|
|
|
26011
26013
|
}
|
|
26012
26014
|
if (newState === "complete" && current !== "tests_run") {
|
|
26013
26015
|
const councilEntry = session.taskCouncilApproved?.get(taskId);
|
|
26014
|
-
const
|
|
26016
|
+
const effectiveMinimum = councilConfig?.requireAllMembers ? 5 : councilConfig?.minimumMembers ?? 3;
|
|
26017
|
+
const councilApproved = councilEntry?.verdict === "APPROVE" && (councilEntry.quorumSize ?? 0) >= effectiveMinimum;
|
|
26015
26018
|
const pastPreCheck = currentIndex >= STATE_ORDER.indexOf("pre_check_passed");
|
|
26016
26019
|
if (!councilApproved || !pastPreCheck) {
|
|
26017
26020
|
throw new Error(`INVALID_TASK_STATE_TRANSITION: ${taskId} cannot reach complete from ${current} \u2014 must pass through tests_run first (or have council APPROVE after pre_check)`);
|
|
@@ -26020,8 +26023,8 @@ function advanceTaskState(session, taskId, newState) {
|
|
|
26020
26023
|
session.taskWorkflowStates.set(taskId, newState);
|
|
26021
26024
|
telemetry.taskStateChanged(session.agentName, taskId, newState, current);
|
|
26022
26025
|
}
|
|
26023
|
-
async function advanceTaskStateAndPersist(session, taskId, newState, directory) {
|
|
26024
|
-
advanceTaskState(session, taskId, newState);
|
|
26026
|
+
async function advanceTaskStateAndPersist(session, taskId, newState, directory, councilConfig) {
|
|
26027
|
+
advanceTaskState(session, taskId, newState, councilConfig);
|
|
26025
26028
|
if (newState !== "coder_delegated" && newState !== "complete") {
|
|
26026
26029
|
return;
|
|
26027
26030
|
}
|
|
@@ -26244,9 +26247,12 @@ function applyRehydrationCache(session) {
|
|
|
26244
26247
|
if (typeof roundNumber !== "number" || !Number.isFinite(roundNumber)) {
|
|
26245
26248
|
roundNumber = 1;
|
|
26246
26249
|
}
|
|
26250
|
+
const rawQuorumSize = council.quorumSize;
|
|
26251
|
+
const quorumSize = typeof rawQuorumSize === "number" && Number.isFinite(rawQuorumSize) && rawQuorumSize >= 1 ? rawQuorumSize : 1;
|
|
26247
26252
|
session.taskCouncilApproved.set(taskId, {
|
|
26248
26253
|
verdict,
|
|
26249
|
-
roundNumber
|
|
26254
|
+
roundNumber,
|
|
26255
|
+
quorumSize
|
|
26250
26256
|
});
|
|
26251
26257
|
}
|
|
26252
26258
|
}
|
|
@@ -43807,7 +43813,7 @@ var package_default;
|
|
|
43807
43813
|
var init_package = __esm(() => {
|
|
43808
43814
|
package_default = {
|
|
43809
43815
|
name: "opencode-swarm",
|
|
43810
|
-
version: "6.86.
|
|
43816
|
+
version: "6.86.7",
|
|
43811
43817
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
43812
43818
|
main: "dist/index.js",
|
|
43813
43819
|
types: "dist/index.d.ts",
|
|
@@ -54348,10 +54354,15 @@ var init_registry = __esm(() => {
|
|
|
54348
54354
|
function buildCouncilWorkflow(council) {
|
|
54349
54355
|
if (council?.enabled !== true)
|
|
54350
54356
|
return "";
|
|
54351
|
-
return `##
|
|
54357
|
+
return `## COUNCIL WORKFLOW (submit_council_verdicts)
|
|
54352
54358
|
|
|
54353
|
-
|
|
54354
|
-
|
|
54359
|
+
CRITICAL: \`submit_council_verdicts\` does NOT run council members.
|
|
54360
|
+
It synthesizes verdicts that you must collect BEFORE calling it.
|
|
54361
|
+
|
|
54362
|
+
When \`council.enabled\` is true, every task goes through this verification
|
|
54363
|
+
gate before advancing to \`complete\`. The council REPLACES Stage B
|
|
54364
|
+
(reviewer + test_engineer as standalone delegations). Stage A
|
|
54365
|
+
(\`pre_check_batch\`) still runs as the pre-review gate.
|
|
54355
54366
|
|
|
54356
54367
|
### Phase 0 \u2014 Pre-declare criteria (at plan time, BEFORE dispatching the coder)
|
|
54357
54368
|
Call \`declare_council_criteria\` for each task with at least 3 concrete,
|
|
@@ -54360,9 +54371,13 @@ testable acceptance criteria. Mark functional/correctness criteria
|
|
|
54360
54371
|
follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
|
|
54361
54372
|
\`.swarm/council/{taskId}.json\` and read back automatically at synthesis time.
|
|
54362
54373
|
|
|
54363
|
-
###
|
|
54364
|
-
|
|
54365
|
-
|
|
54374
|
+
### MANDATORY SEQUENCE \u2014 never skip or reorder
|
|
54375
|
+
|
|
54376
|
+
#### STEP 1 \u2014 DISPATCH all council members as parallel Agent tasks
|
|
54377
|
+
Dispatch \`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`, and \`explorer\`
|
|
54378
|
+
(or at minimum \`council.minimumMembers\` distinct members; default 3) in a
|
|
54379
|
+
SINGLE message using parallel Agent tool calls. Provide each member with
|
|
54380
|
+
their role-specific scope:
|
|
54366
54381
|
- \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results + approved-plan baseline comparison (via \`get_approved_plan\`) and spec-intent drift analysis against the approved baseline
|
|
54367
54382
|
- \`reviewer\` \u2014 semantic diff summary + blast radius (files importing changed files) + style guide
|
|
54368
54383
|
- \`sme\` \u2014 task domain context + relevant knowledge base entries
|
|
@@ -54371,31 +54386,65 @@ Each receives ONLY their role-relevant context, not the full conversation:
|
|
|
54371
54386
|
(explorer hunts for lazy implementations, hallucinated APIs,
|
|
54372
54387
|
cargo-cult patterns, spec drift, lazy abstractions)
|
|
54373
54388
|
|
|
54374
|
-
|
|
54375
|
-
|
|
54376
|
-
|
|
54389
|
+
Wait for ALL dispatched agents to return their verdict objects.
|
|
54390
|
+
|
|
54391
|
+
#### STEP 2 \u2014 COLLECT verdicts
|
|
54392
|
+
Read each agent's response and extract their \`CouncilMemberVerdict\` object.
|
|
54393
|
+
Each member must return all fields: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
|
|
54394
|
+
\`confidence\` (0.0\u20131.0), \`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`,
|
|
54395
|
+
\`durationMs\`.
|
|
54396
|
+
|
|
54397
|
+
Do NOT fabricate, infer, or substitute a verdict. If an agent did not return
|
|
54398
|
+
a valid verdict, re-dispatch that agent.
|
|
54399
|
+
|
|
54400
|
+
#### STEP 3 \u2014 CALL submit_council_verdicts
|
|
54401
|
+
ONLY after collecting real verdicts from real agent dispatches, call
|
|
54402
|
+
\`submit_council_verdicts\` with the collected verdicts array, the task id,
|
|
54403
|
+
swarm id, and current round number (1-indexed).
|
|
54404
|
+
|
|
54405
|
+
#### STEP 4 \u2014 READ the response
|
|
54406
|
+
Inspect \`membersAbsent\` in the response. If \`membersAbsent\` is non-empty,
|
|
54407
|
+
the council is incomplete \u2014 dispatch the missing members and re-collect.
|
|
54408
|
+
Inspect \`overallVerdict\`. APPROVE is valid only when \`membersAbsent\` is
|
|
54409
|
+
empty (or fewer members than \`council.minimumMembers\` are absent).
|
|
54410
|
+
|
|
54411
|
+
The response also includes: \`vetoedBy\`, \`unifiedFeedbackMd\`,
|
|
54412
|
+
\`requiredFixesCount\`, \`advisoryFindingsCount\`, \`allCriteriaMet\`,
|
|
54413
|
+
\`quorumSize\`, \`quorumMet\`.
|
|
54377
54414
|
|
|
54378
|
-
|
|
54379
|
-
|
|
54380
|
-
|
|
54381
|
-
\`overallVerdict\`, \`vetoedBy\`, \`unifiedFeedbackMd\`, \`requiredFixesCount\`,
|
|
54382
|
-
\`allCriteriaMet\`.
|
|
54415
|
+
If \`success: false\` and \`reason: 'insufficient_quorum'\`, the response
|
|
54416
|
+
includes \`membersVoted\`, \`membersAbsent\`, and \`quorumRequired\` \u2014 dispatch
|
|
54417
|
+
the absent members and re-call the tool.
|
|
54383
54418
|
|
|
54384
|
-
|
|
54419
|
+
#### STEP 5 \u2014 ACT on the verdict
|
|
54385
54420
|
- **APPROVE**: Advance task to complete via \`update_task_status\`. If
|
|
54386
|
-
advisoryFindingsCount > 0
|
|
54387
|
-
single non-blocking note. Otherwise, advance silently.
|
|
54388
|
-
- **CONCERNS**: Send \`unifiedFeedbackMd\` to the coder as ONE coherent
|
|
54389
|
-
Do NOT enumerate individual member verdicts.
|
|
54390
|
-
roundNumber on the next council call. CONCERNS
|
|
54391
|
-
advancement at the update_task_status level \u2014
|
|
54392
|
-
severity whether to advance or retry.
|
|
54393
|
-
- **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
|
|
54394
|
-
the BLOCKING flag. The coder must resolve all
|
|
54395
|
-
before re-submitting. Maximum
|
|
54396
|
-
(default 3). If
|
|
54397
|
-
|
|
54398
|
-
do NOT
|
|
54421
|
+
\`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as
|
|
54422
|
+
a single non-blocking note. Otherwise, advance silently.
|
|
54423
|
+
- **CONCERNS**: Send \`unifiedFeedbackMd\` to the coder as ONE coherent
|
|
54424
|
+
document. Do NOT enumerate individual member verdicts.
|
|
54425
|
+
Increment \`roundNumber\` on the next council call. CONCERNS
|
|
54426
|
+
does not block advancement at the update_task_status level \u2014
|
|
54427
|
+
decide per severity whether to advance or retry.
|
|
54428
|
+
- **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
|
|
54429
|
+
with the BLOCKING flag. The coder must resolve all
|
|
54430
|
+
\`requiredFixes\` before re-submitting. Maximum
|
|
54431
|
+
\`council.maxRounds\` rounds (default 3). If
|
|
54432
|
+
\`roundNumber >= maxRounds\` and verdict is still REJECT,
|
|
54433
|
+
surface \`unifiedFeedbackMd\` to the user and HALT \u2014 do NOT
|
|
54434
|
+
auto-advance.
|
|
54435
|
+
|
|
54436
|
+
### ANTI-PATTERNS \u2014 any of these are council bypass violations
|
|
54437
|
+
- \u2717 Calling \`submit_council_verdicts\` without first dispatching council members.
|
|
54438
|
+
- \u2717 Passing a verdict you inferred or fabricated rather than received from a dispatched agent.
|
|
54439
|
+
- \u2717 Claiming "Council APPROVED" when \`membersAbsent\` is non-empty.
|
|
54440
|
+
- \u2717 Treating a prior round's APPROVE as valid for a new task or new round.
|
|
54441
|
+
- \u2717 Incrementing \`roundNumber\` without re-dispatching all members for the new round.
|
|
54442
|
+
|
|
54443
|
+
### ROUND 2 DELIBERATION
|
|
54444
|
+
If round 1 produces REJECT or CONCERNS, dispatch only the disputing members
|
|
54445
|
+
for round 2 focused on the specific disagreement areas. Round 2 must produce
|
|
54446
|
+
NEW agent responses \u2014 do NOT reuse round 1 verdicts with a higher
|
|
54447
|
+
\`roundNumber\`.
|
|
54399
54448
|
|
|
54400
54449
|
### Retry protocol
|
|
54401
54450
|
On re-submission after REJECT or CONCERNS, the council reads the same
|
|
@@ -54412,7 +54461,7 @@ function buildYourToolsList(council) {
|
|
|
54412
54461
|
const qaCouncilEnabled = council?.enabled === true;
|
|
54413
54462
|
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54414
54463
|
const filtered = sorted.filter((t) => {
|
|
54415
|
-
if (!qaCouncilEnabled && (t === "
|
|
54464
|
+
if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria")) {
|
|
54416
54465
|
return false;
|
|
54417
54466
|
}
|
|
54418
54467
|
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
@@ -54445,7 +54494,7 @@ function buildAvailableToolsList(council) {
|
|
|
54445
54494
|
const qaCouncilEnabled = council?.enabled === true;
|
|
54446
54495
|
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54447
54496
|
const filtered = sorted.filter((t) => {
|
|
54448
|
-
if (!qaCouncilEnabled && (t === "
|
|
54497
|
+
if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria")) {
|
|
54449
54498
|
return false;
|
|
54450
54499
|
}
|
|
54451
54500
|
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
@@ -58105,14 +58154,20 @@ function getAgentConfigs(config3, directory, sessionId) {
|
|
|
58105
58154
|
allowedTools = AGENT_TOOL_MAP[baseAgentName];
|
|
58106
58155
|
}
|
|
58107
58156
|
if (baseAgentName === "architect" && config3?.council?.enabled === true && override !== undefined) {
|
|
58108
|
-
const required3 = [
|
|
58157
|
+
const required3 = [
|
|
58158
|
+
"declare_council_criteria",
|
|
58159
|
+
"submit_council_verdicts"
|
|
58160
|
+
];
|
|
58109
58161
|
const missing = required3.filter((t) => !override.includes(t));
|
|
58110
58162
|
if (missing.length > 0) {
|
|
58111
58163
|
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.`);
|
|
58112
58164
|
}
|
|
58113
58165
|
}
|
|
58114
58166
|
if (baseAgentName === "architect" && config3?.council?.enabled !== true && override !== undefined) {
|
|
58115
|
-
const councilTools = [
|
|
58167
|
+
const councilTools = [
|
|
58168
|
+
"declare_council_criteria",
|
|
58169
|
+
"submit_council_verdicts"
|
|
58170
|
+
];
|
|
58116
58171
|
const present = councilTools.filter((t) => override.includes(t));
|
|
58117
58172
|
if (present.length > 0 && !quiet) {
|
|
58118
58173
|
console.warn(`[opencode-swarm] tool_filter.overrides.architect includes ${present.join(", ")} but council.enabled is not true. ` + `The runtime gate will reject these calls. Either set council.enabled=true, or remove ${present.join(", ")} from the architect override.`);
|
|
@@ -74438,7 +74493,8 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
74438
74493
|
verdict: synthesis.overallVerdict,
|
|
74439
74494
|
vetoedBy: synthesis.vetoedBy,
|
|
74440
74495
|
roundNumber: synthesis.roundNumber,
|
|
74441
|
-
allCriteriaMet: synthesis.allCriteriaMet
|
|
74496
|
+
allCriteriaMet: synthesis.allCriteriaMet,
|
|
74497
|
+
quorumSize: synthesis.quorumSize
|
|
74442
74498
|
};
|
|
74443
74499
|
const updated = Object.create(null);
|
|
74444
74500
|
safeAssignOwnProps(updated, existingRoot);
|
|
@@ -74470,13 +74526,15 @@ var COUNCIL_DEFAULTS = {
|
|
|
74470
74526
|
maxRounds: 3,
|
|
74471
74527
|
parallelTimeoutMs: 30000,
|
|
74472
74528
|
vetoPriority: true,
|
|
74473
|
-
requireAllMembers: false
|
|
74529
|
+
requireAllMembers: false,
|
|
74530
|
+
minimumMembers: 3
|
|
74474
74531
|
};
|
|
74475
74532
|
|
|
74476
74533
|
// src/council/council-service.ts
|
|
74477
74534
|
function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNumber, config3 = {}) {
|
|
74478
74535
|
const cfg = { ...COUNCIL_DEFAULTS, ...config3 };
|
|
74479
74536
|
const timestamp = new Date().toISOString();
|
|
74537
|
+
const quorumSize = new Set(verdicts.map((v) => v.agent)).size;
|
|
74480
74538
|
const rejectingMembers = verdicts.filter((v) => v.verdict === "REJECT").map((v) => v.agent);
|
|
74481
74539
|
let overallVerdict;
|
|
74482
74540
|
if (cfg.vetoPriority && rejectingMembers.length > 0) {
|
|
@@ -74512,6 +74570,7 @@ function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNum
|
|
|
74512
74570
|
unifiedFeedbackMd,
|
|
74513
74571
|
roundNumber,
|
|
74514
74572
|
allCriteriaMet,
|
|
74573
|
+
quorumSize,
|
|
74515
74574
|
...verdicts.length === 0 && { emptyVerdictsWarning: true }
|
|
74516
74575
|
};
|
|
74517
74576
|
}
|
|
@@ -74644,8 +74703,8 @@ var ArgsSchema = exports_external.object({
|
|
|
74644
74703
|
verdicts: exports_external.array(VerdictSchema).min(1).max(5),
|
|
74645
74704
|
working_directory: exports_external.string().optional()
|
|
74646
74705
|
});
|
|
74647
|
-
var
|
|
74648
|
-
description: "
|
|
74706
|
+
var submit_council_verdicts = createSwarmTool({
|
|
74707
|
+
description: "Submit pre-collected council member verdicts for synthesis. PREREQUISITE \u2014 " + "you MUST dispatch each council member (critic, reviewer, sme, test_engineer, " + "explorer) as separate Agent tasks and collect their verdict responses BEFORE " + "calling this tool. This tool performs synthesis only \u2014 it does NOT dispatch, " + "invoke, or contact council members. Calling this tool without first " + "collecting real verdicts from dispatched agents constitutes a council bypass. " + "Returns the synthesized verdict, required fixes, and a quorum report showing " + "which members voted and which were absent. Architect-only. Config-gated via " + "council.enabled.",
|
|
74649
74708
|
args: {
|
|
74650
74709
|
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID being evaluated, e.g. "1.1", "1.2.3"'),
|
|
74651
74710
|
swarmId: exports_external.string().min(1).describe('Swarm identifier, e.g. "mega"'),
|
|
@@ -74698,10 +74757,25 @@ var convene_council = createSwarmTool({
|
|
|
74698
74757
|
reason: "council feature is disabled \u2014 set council.enabled: true in .opencode/opencode-swarm.json to enable"
|
|
74699
74758
|
}, null, 2);
|
|
74700
74759
|
}
|
|
74701
|
-
|
|
74760
|
+
const effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
|
|
74761
|
+
const ALL_MEMBERS = [
|
|
74762
|
+
"critic",
|
|
74763
|
+
"reviewer",
|
|
74764
|
+
"sme",
|
|
74765
|
+
"test_engineer",
|
|
74766
|
+
"explorer"
|
|
74767
|
+
];
|
|
74768
|
+
const distinctMembers = new Set(input.verdicts.map((v) => v.agent));
|
|
74769
|
+
const membersVoted = [...distinctMembers];
|
|
74770
|
+
const membersAbsent = ALL_MEMBERS.filter((m) => !distinctMembers.has(m));
|
|
74771
|
+
if (membersVoted.length < effectiveMinimum) {
|
|
74702
74772
|
return JSON.stringify({
|
|
74703
74773
|
success: false,
|
|
74704
|
-
reason:
|
|
74774
|
+
reason: "insufficient_quorum",
|
|
74775
|
+
message: `Council quorum not met: ${membersVoted.length} of ${effectiveMinimum} required members provided verdicts. ` + `Members voted: [${membersVoted.join(", ")}]. ` + `Members absent: [${membersAbsent.join(", ")}]. ` + `Dispatch the absent council members as Agent tasks and collect their verdicts before calling submit_council_verdicts.`,
|
|
74776
|
+
membersVoted,
|
|
74777
|
+
membersAbsent,
|
|
74778
|
+
quorumRequired: effectiveMinimum
|
|
74705
74779
|
}, null, 2);
|
|
74706
74780
|
}
|
|
74707
74781
|
const criteria = readCriteria(workingDir, input.taskId);
|
|
@@ -74726,6 +74800,10 @@ var convene_council = createSwarmTool({
|
|
|
74726
74800
|
requiredFixesCount: synthesis.requiredFixes.length,
|
|
74727
74801
|
advisoryFindingsCount: synthesis.advisoryFindings.length,
|
|
74728
74802
|
unresolvedConflictsCount: synthesis.unresolvedConflicts.length,
|
|
74803
|
+
membersVoted,
|
|
74804
|
+
membersAbsent,
|
|
74805
|
+
quorumSize: membersVoted.length,
|
|
74806
|
+
quorumMet: true,
|
|
74729
74807
|
unifiedFeedbackMd: synthesis.unifiedFeedbackMd
|
|
74730
74808
|
}, null, 2);
|
|
74731
74809
|
}
|
|
@@ -87457,9 +87535,11 @@ function recoverTaskStateFromDelegations(taskId) {
|
|
|
87457
87535
|
}
|
|
87458
87536
|
function checkCouncilGate(workingDirectory, taskId) {
|
|
87459
87537
|
let councilEnabled = false;
|
|
87538
|
+
let effectiveMinimum = 3;
|
|
87460
87539
|
try {
|
|
87461
87540
|
const config3 = loadPluginConfig(workingDirectory);
|
|
87462
87541
|
councilEnabled = config3.council?.enabled === true;
|
|
87542
|
+
effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
|
|
87463
87543
|
} catch {
|
|
87464
87544
|
return { blocked: false, reason: "" };
|
|
87465
87545
|
}
|
|
@@ -87486,20 +87566,28 @@ function checkCouncilGate(workingDirectory, taskId) {
|
|
|
87486
87566
|
} catch {
|
|
87487
87567
|
return {
|
|
87488
87568
|
blocked: true,
|
|
87489
|
-
reason: "council gate required but not yet run \u2014 architect must call
|
|
87569
|
+
reason: "council gate required but not yet run \u2014 architect must call submit_council_verdicts before advancing this task"
|
|
87490
87570
|
};
|
|
87491
87571
|
}
|
|
87492
87572
|
const councilGate = evidence?.gates?.council;
|
|
87493
87573
|
if (!councilGate) {
|
|
87494
87574
|
return {
|
|
87495
87575
|
blocked: true,
|
|
87496
|
-
reason: "council gate required but not yet run \u2014 architect must call
|
|
87576
|
+
reason: "council gate required but not yet run \u2014 architect must call submit_council_verdicts before advancing this task"
|
|
87497
87577
|
};
|
|
87498
87578
|
}
|
|
87499
87579
|
if (councilGate.verdict === "REJECT") {
|
|
87500
87580
|
return {
|
|
87501
87581
|
blocked: true,
|
|
87502
|
-
reason: "council gate blocked advancement \u2014 resolve requiredFixes and re-run
|
|
87582
|
+
reason: "council gate blocked advancement \u2014 resolve requiredFixes and re-run submit_council_verdicts"
|
|
87583
|
+
};
|
|
87584
|
+
}
|
|
87585
|
+
const rawQuorumSize = councilGate.quorumSize;
|
|
87586
|
+
const quorumSize = typeof rawQuorumSize === "number" && Number.isFinite(rawQuorumSize) && rawQuorumSize >= 1 ? rawQuorumSize : 1;
|
|
87587
|
+
if (quorumSize < effectiveMinimum) {
|
|
87588
|
+
return {
|
|
87589
|
+
blocked: true,
|
|
87590
|
+
reason: `council gate blocked advancement \u2014 recorded verdict has insufficient quorum (${quorumSize} of ${effectiveMinimum} required members). Re-run submit_council_verdicts with the missing council members.`
|
|
87503
87591
|
};
|
|
87504
87592
|
}
|
|
87505
87593
|
return { blocked: false, reason: "" };
|
|
@@ -88717,7 +88805,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
88717
88805
|
checkpoint,
|
|
88718
88806
|
completion_verify,
|
|
88719
88807
|
complexity_hotspots,
|
|
88720
|
-
|
|
88808
|
+
submit_council_verdicts,
|
|
88721
88809
|
convene_general_council,
|
|
88722
88810
|
curator_analyze,
|
|
88723
88811
|
declare_council_criteria,
|
package/dist/state.d.ts
CHANGED
|
@@ -112,10 +112,17 @@ export interface AgentSessionState {
|
|
|
112
112
|
* Only populated when parallelization.stageB.parallel.enabled = true.
|
|
113
113
|
*/
|
|
114
114
|
stageBCompletion?: Map<string, Set<'reviewer' | 'test_engineer'>>;
|
|
115
|
-
/** v6.71+ Council mode: per-task council verdict, recorded by delegation-gate when
|
|
115
|
+
/** v6.71+ Council mode: per-task council verdict, recorded by delegation-gate when submit_council_verdicts resolves. */
|
|
116
116
|
taskCouncilApproved?: Map<string, {
|
|
117
117
|
verdict: 'APPROVE' | 'REJECT' | 'CONCERNS';
|
|
118
118
|
roundNumber: number;
|
|
119
|
+
/**
|
|
120
|
+
* Distinct council members that voted on this verdict.
|
|
121
|
+
* Validated by the council fast-path against `council.minimumMembers`
|
|
122
|
+
* (default 3). Old evidence files without this field rehydrate as
|
|
123
|
+
* quorumSize: 1 — conservative; forces a fresh council run.
|
|
124
|
+
*/
|
|
125
|
+
quorumSize: number;
|
|
119
126
|
}>;
|
|
120
127
|
/** Last gate outcome for deliberation preamble injection */
|
|
121
128
|
lastGateOutcome: {
|
|
@@ -359,7 +366,10 @@ export declare function recordPhaseAgentDispatch(sessionId: string, agentName: s
|
|
|
359
366
|
* @param taskId - The task identifier
|
|
360
367
|
* @param newState - The requested new state
|
|
361
368
|
*/
|
|
362
|
-
export declare function advanceTaskState(session: AgentSessionState, taskId: string, newState: TaskWorkflowState
|
|
369
|
+
export declare function advanceTaskState(session: AgentSessionState, taskId: string, newState: TaskWorkflowState, councilConfig?: {
|
|
370
|
+
minimumMembers?: number;
|
|
371
|
+
requireAllMembers?: boolean;
|
|
372
|
+
}): void;
|
|
363
373
|
/**
|
|
364
374
|
* Advance the per-task workflow state machine AND persist the corresponding
|
|
365
375
|
* plan.json status at meaningful workflow boundaries.
|
|
@@ -377,7 +387,10 @@ export declare function advanceTaskState(session: AgentSessionState, taskId: str
|
|
|
377
387
|
* not break the in-memory state machine — matches the existing defensive
|
|
378
388
|
* pattern around advanceTaskState call sites.
|
|
379
389
|
*/
|
|
380
|
-
export declare function advanceTaskStateAndPersist(session: AgentSessionState, taskId: string, newState: TaskWorkflowState, directory: string
|
|
390
|
+
export declare function advanceTaskStateAndPersist(session: AgentSessionState, taskId: string, newState: TaskWorkflowState, directory: string, councilConfig?: {
|
|
391
|
+
minimumMembers?: number;
|
|
392
|
+
requireAllMembers?: boolean;
|
|
393
|
+
}): Promise<void>;
|
|
381
394
|
/**
|
|
382
395
|
* Get the current workflow state for a task.
|
|
383
396
|
* Returns 'idle' if no entry exists.
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Submit Council Verdicts — architect-only tool.
|
|
3
3
|
*
|
|
4
|
-
* Accepts parallel verdicts from critic, reviewer, sme,
|
|
5
|
-
* then synthesizes them into a veto-aware
|
|
6
|
-
* and a single unified feedback document.
|
|
4
|
+
* Accepts pre-collected parallel verdicts from critic, reviewer, sme,
|
|
5
|
+
* test_engineer, and explorer, then synthesizes them into a veto-aware
|
|
6
|
+
* overall verdict with required fixes and a single unified feedback document.
|
|
7
|
+
*
|
|
8
|
+
* PREREQUISITE: The architect must dispatch each council member as a separate
|
|
9
|
+
* Agent task and collect the resulting CouncilMemberVerdict objects BEFORE
|
|
10
|
+
* calling this tool. This tool performs synthesis only — it does NOT dispatch,
|
|
11
|
+
* invoke, or contact council members.
|
|
7
12
|
*
|
|
8
13
|
* Config-gated (council.enabled must be true) and architect-only via
|
|
9
14
|
* AGENT_TOOL_MAP. Follows the check-gate-status.ts pattern.
|
|
@@ -45,4 +50,4 @@ export declare const ArgsSchema: z.ZodObject<{
|
|
|
45
50
|
}, z.core.$strip>>;
|
|
46
51
|
working_directory: z.ZodOptional<z.ZodString>;
|
|
47
52
|
}, z.core.$strip>;
|
|
48
|
-
export declare const
|
|
53
|
+
export declare const submit_council_verdicts: ReturnType<typeof tool>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Lets the architect declare acceptance criteria at plan time, before the
|
|
5
5
|
* coder starts work. Criteria are persisted to .swarm/council/{safeId}.json
|
|
6
|
-
* and later read back during council evaluation (
|
|
6
|
+
* and later read back during council evaluation (submit_council_verdicts) so that
|
|
7
7
|
* reviewers assess a stable, pre-committed contract rather than whatever
|
|
8
8
|
* criteria happen to be invented at review time.
|
|
9
9
|
*
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { checkpoint } from './checkpoint';
|
|
|
5
5
|
export { co_change_analyzer } from './co-change-analyzer';
|
|
6
6
|
export { completion_verify } from './completion-verify';
|
|
7
7
|
export { complexity_hotspots } from './complexity-hotspots';
|
|
8
|
-
export {
|
|
8
|
+
export { submit_council_verdicts } from './convene-council';
|
|
9
9
|
export { convene_general_council } from './convene-general-council';
|
|
10
10
|
export { curator_analyze } from './curator-analyze';
|
|
11
11
|
export { declare_council_criteria } from './declare-council-criteria';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Used for constants and agent setup references.
|
|
4
4
|
*/
|
|
5
5
|
/** Union type of all valid tool names */
|
|
6
|
-
export type ToolName = 'diff' | 'diff_summary' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'check_gate_status' | 'completion_verify' | '
|
|
6
|
+
export type ToolName = 'diff' | 'diff_summary' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'check_gate_status' | 'completion_verify' | 'submit_council_verdicts' | 'declare_council_criteria' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'test_impact' | 'mutation_test' | 'generate_mutants' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan' | 'update_task_status' | 'lint_spec' | 'write_retro' | 'write_drift_evidence' | 'write_hallucination_evidence' | 'write_mutation_evidence' | 'declare_scope' | 'knowledge_query' | 'doc_scan' | 'doc_extract' | 'curator_analyze' | 'knowledge_add' | 'knowledge_recall' | 'knowledge_remove' | 'co_change_analyzer' | 'search' | 'batch_symbols' | 'suggest_patch' | 'req_coverage' | 'get_approved_plan' | 'repo_map' | 'get_qa_gate_profile' | 'set_qa_gates' | 'web_search' | 'convene_general_council';
|
|
7
7
|
/** Readonly array of all tool names */
|
|
8
8
|
export declare const TOOL_NAMES: readonly ToolName[];
|
|
9
9
|
/** Set for O(1) tool name validation */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.86.
|
|
3
|
+
"version": "6.86.7",
|
|
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",
|