opencode-swarm 7.21.0 → 7.21.2
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 +1 -1
- package/dist/index.js +90 -61
- package/dist/tools/update-task-status.d.ts +4 -5
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.21.
|
|
37
|
+
version: "7.21.2",
|
|
38
38
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
39
39
|
main: "dist/index.js",
|
|
40
40
|
types: "dist/index.d.ts",
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var package_default;
|
|
|
33
33
|
var init_package = __esm(() => {
|
|
34
34
|
package_default = {
|
|
35
35
|
name: "opencode-swarm",
|
|
36
|
-
version: "7.21.
|
|
36
|
+
version: "7.21.2",
|
|
37
37
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
38
38
|
main: "dist/index.js",
|
|
39
39
|
types: "dist/index.d.ts",
|
|
@@ -24922,6 +24922,46 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
24922
24922
|
if (/^mkfs[./]/.test(seg)) {
|
|
24923
24923
|
throw new Error(`BLOCKED: Disk format command (mkfs) detected — disk formatting operation`);
|
|
24924
24924
|
}
|
|
24925
|
+
if (/^\\?mv\s/i.test(seg)) {
|
|
24926
|
+
const mvMatch = seg.match(/^\\?mv\s+(.+)$/i);
|
|
24927
|
+
if (mvMatch) {
|
|
24928
|
+
const argsStr = mvMatch[1];
|
|
24929
|
+
const strippedArgs = argsStr.replace(/["']/g, "");
|
|
24930
|
+
if (/\.swarm(?:[\x5c/\s]|$)/.test(strippedArgs)) {
|
|
24931
|
+
throw new Error(`BLOCKED: "mv" targeting .swarm/ detected — move operations under .swarm/ are not allowed from shell commands`);
|
|
24932
|
+
}
|
|
24933
|
+
}
|
|
24934
|
+
}
|
|
24935
|
+
if (/^\\?(?:move|ren)(?:\.exe)?\s/i.test(seg)) {
|
|
24936
|
+
const moveMatch = seg.match(/^\\?(?:move|ren)(?:\.exe)?\s+(.+)$/i);
|
|
24937
|
+
if (moveMatch) {
|
|
24938
|
+
const argsStr = moveMatch[1].replace(/["']/g, "");
|
|
24939
|
+
if (/\.swarm(?:[\x5c/\s]|$)/i.test(argsStr)) {
|
|
24940
|
+
throw new Error(`BLOCKED: "move" or "ren" targeting .swarm/ detected — move/rename operations under .swarm/ are not allowed from shell commands`);
|
|
24941
|
+
}
|
|
24942
|
+
}
|
|
24943
|
+
}
|
|
24944
|
+
if (/^\\?(?:Move-Item|Rename-Item|move|mi|mv|ren|rni)\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24945
|
+
throw new Error(`BLOCKED: PowerShell Move-Item or Rename-Item targeting .swarm/ detected — move/rename operations under .swarm/ are not allowed from shell commands`);
|
|
24946
|
+
}
|
|
24947
|
+
if (/^\\?rm\b/i.test(seg) && !/^\\?rm\s+(?:-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24948
|
+
throw new Error(`BLOCKED: "rm" targeting .swarm/ detected — deleting files under .swarm/ is not allowed from shell commands`);
|
|
24949
|
+
}
|
|
24950
|
+
if (/\bcp\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg) && /\brm\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24951
|
+
throw new Error(`BLOCKED: "cp" of .swarm/ file followed by "rm" of .swarm/ source detected — copy-and-delete bypass is not allowed`);
|
|
24952
|
+
}
|
|
24953
|
+
if (/^rsync\b.*--remove-source-files\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24954
|
+
throw new Error(`BLOCKED: "rsync" with delete-source flag targeting .swarm/ detected — archive with source deletion under .swarm/ is not allowed`);
|
|
24955
|
+
}
|
|
24956
|
+
if (/^tar\b.*--remove-files\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24957
|
+
throw new Error(`BLOCKED: "tar" with delete-source flag targeting .swarm/ detected — archive with source deletion under .swarm/ is not allowed`);
|
|
24958
|
+
}
|
|
24959
|
+
if (/^zip\b.*\s-m\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24960
|
+
throw new Error(`BLOCKED: "zip" with delete-source flag targeting .swarm/ detected — archive with source deletion under .swarm/ is not allowed`);
|
|
24961
|
+
}
|
|
24962
|
+
if (/^7z\b.*\s-sdel\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24963
|
+
throw new Error(`BLOCKED: "7z" with delete-source flag targeting .swarm/ detected — archive with source deletion under .swarm/ is not allowed`);
|
|
24964
|
+
}
|
|
24925
24965
|
}
|
|
24926
24966
|
}
|
|
24927
24967
|
async function checkGateLimits(params) {
|
|
@@ -62574,6 +62614,7 @@ If a tool modifies a file, it is a CODER tool. Delegate.
|
|
|
62574
62614
|
- Before you delegate a coding task, call declare_scope with { taskId, files } where \`files\` is the exact list of paths the coder is allowed to write. Bundle any generated/lockfile paths that the change will produce (e.g. package-lock.json, Cargo.lock, dist/*).
|
|
62575
62615
|
- If coder returns "WRITE BLOCKED" for a path outside the declared list: call declare_scope again with the missing path added. Do NOT instruct the coder to use bash, sed, echo, cat, tee, dd, or any interpreter eval (python -c, node -e, bun -e, ruby -e) to bypass the block. Those routes bypass the authority check and violate scope discipline.
|
|
62576
62616
|
- Never wrap a file write in eval, bash -c, sh -c, a subshell, or a heredoc-to-file redirect. Those are bash workarounds and are banned even when scope appears to permit them — the write-authority guard is tool-scoped; bash is unguarded and must not be used as a write path.
|
|
62617
|
+
- Do NOT use mv, Move-Item, move, ren, Rename-Item, or cp-then-rm chains to relocate, rename, or delete files under \`.swarm/\` as a workaround for blocked destructive commands. Those are file-move shell bypasses and are banned. Use the tool's dedicated tools (\`.swarm/\` file management or evidence manager tools) instead.
|
|
62577
62618
|
- If you cannot enumerate files up front (e.g. a broad refactor), declare the containing directories — declare_scope accepts directory entries and grants containment.
|
|
62578
62619
|
- Rationale: declare_scope persists the allowed set to disk (.swarm/scopes/scope-\${taskId}.json) so it survives cross-process delegation. Without a call, the coder process reads an empty scope and every Edit/Write is denied.
|
|
62579
62620
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
@@ -90226,6 +90267,7 @@ function phaseIsExplicitlyNonCode(directory, phase) {
|
|
|
90226
90267
|
}
|
|
90227
90268
|
|
|
90228
90269
|
// src/tools/phase-complete.ts
|
|
90270
|
+
init_gate_evidence();
|
|
90229
90271
|
init_curator();
|
|
90230
90272
|
init_knowledge_curator();
|
|
90231
90273
|
init_knowledge_reader();
|
|
@@ -90761,6 +90803,23 @@ function safeWarn(message, error93) {
|
|
|
90761
90803
|
warn(message, error93 instanceof Error ? error93.message : String(error93));
|
|
90762
90804
|
} catch {}
|
|
90763
90805
|
}
|
|
90806
|
+
var TASK_GATE_INFERABLE_AGENTS = new Set([
|
|
90807
|
+
"coder",
|
|
90808
|
+
"reviewer",
|
|
90809
|
+
"test_engineer"
|
|
90810
|
+
]);
|
|
90811
|
+
function canInferMissingAgentsFromTaskGates(agentsMissing) {
|
|
90812
|
+
return agentsMissing.every((agent) => TASK_GATE_INFERABLE_AGENTS.has(agent));
|
|
90813
|
+
}
|
|
90814
|
+
async function allCompletedTasksHavePassedGateEvidence(directory, tasks) {
|
|
90815
|
+
for (const task of tasks) {
|
|
90816
|
+
if (task.status !== "completed")
|
|
90817
|
+
return false;
|
|
90818
|
+
if (!await hasPassedAllGates(directory, task.id))
|
|
90819
|
+
return false;
|
|
90820
|
+
}
|
|
90821
|
+
return tasks.length > 0;
|
|
90822
|
+
}
|
|
90764
90823
|
function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSessionId) {
|
|
90765
90824
|
const agents = new Set;
|
|
90766
90825
|
const contributorSessionIds = [];
|
|
@@ -90772,12 +90831,9 @@ function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSess
|
|
|
90772
90831
|
agents.add(agent);
|
|
90773
90832
|
}
|
|
90774
90833
|
}
|
|
90775
|
-
const
|
|
90776
|
-
|
|
90777
|
-
|
|
90778
|
-
agents.add(stripKnownSwarmPrefix(delegation.from));
|
|
90779
|
-
agents.add(stripKnownSwarmPrefix(delegation.to));
|
|
90780
|
-
}
|
|
90834
|
+
for (const delegation of _getDelegationsSince(callerSessionId, phaseReferenceTimestamp)) {
|
|
90835
|
+
agents.add(stripKnownSwarmPrefix(delegation.from));
|
|
90836
|
+
agents.add(stripKnownSwarmPrefix(delegation.to));
|
|
90781
90837
|
}
|
|
90782
90838
|
}
|
|
90783
90839
|
for (const [sessionId, session] of swarmState.agentSessions) {
|
|
@@ -90796,17 +90852,24 @@ function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSess
|
|
|
90796
90852
|
agents.add(agent);
|
|
90797
90853
|
}
|
|
90798
90854
|
}
|
|
90799
|
-
const
|
|
90800
|
-
|
|
90801
|
-
|
|
90802
|
-
agents.add(stripKnownSwarmPrefix(delegation.from));
|
|
90803
|
-
agents.add(stripKnownSwarmPrefix(delegation.to));
|
|
90804
|
-
}
|
|
90855
|
+
for (const delegation of _getDelegationsSince(sessionId, phaseReferenceTimestamp)) {
|
|
90856
|
+
agents.add(stripKnownSwarmPrefix(delegation.from));
|
|
90857
|
+
agents.add(stripKnownSwarmPrefix(delegation.to));
|
|
90805
90858
|
}
|
|
90806
90859
|
}
|
|
90807
90860
|
}
|
|
90808
90861
|
return { agents, contributorSessionIds };
|
|
90809
90862
|
}
|
|
90863
|
+
function _getDelegationsSince(sessionID, sinceTimestamp) {
|
|
90864
|
+
const chain = swarmState.delegationChains.get(sessionID);
|
|
90865
|
+
if (!chain) {
|
|
90866
|
+
return [];
|
|
90867
|
+
}
|
|
90868
|
+
if (sinceTimestamp === 0) {
|
|
90869
|
+
return chain;
|
|
90870
|
+
}
|
|
90871
|
+
return chain.filter((entry) => entry.timestamp > sinceTimestamp);
|
|
90872
|
+
}
|
|
90810
90873
|
function isValidRetroEntry(entry, phase) {
|
|
90811
90874
|
return entry.type === "retrospective" && "phase_number" in entry && entry.phase_number === phase && "verdict" in entry && entry.verdict === "pass";
|
|
90812
90875
|
}
|
|
@@ -91752,8 +91815,8 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
91752
91815
|
const planRaw = fs84.readFileSync(planPath, "utf-8");
|
|
91753
91816
|
const plan = JSON.parse(planRaw);
|
|
91754
91817
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
91755
|
-
if (targetPhase && targetPhase.tasks.length > 0 &&
|
|
91756
|
-
warnings.push(`Agent dispatch fallback: all ${targetPhase.tasks.length} tasks in phase ${phase} are completed in plan.json. Clearing missing agents: ${agentsMissing.join(", ")}.`);
|
|
91818
|
+
if (targetPhase && targetPhase.tasks.length > 0 && canInferMissingAgentsFromTaskGates(agentsMissing) && await allCompletedTasksHavePassedGateEvidence(dir, targetPhase.tasks)) {
|
|
91819
|
+
warnings.push(`Agent dispatch fallback: all ${targetPhase.tasks.length} tasks in phase ${phase} are completed in plan.json and durable gate evidence passed. Clearing missing agents: ${agentsMissing.join(", ")}.`);
|
|
91757
91820
|
agentsMissing = [];
|
|
91758
91821
|
}
|
|
91759
91822
|
} catch {}
|
|
@@ -103343,6 +103406,13 @@ function matchesTier3Pattern2(files) {
|
|
|
103343
103406
|
}
|
|
103344
103407
|
return false;
|
|
103345
103408
|
}
|
|
103409
|
+
function hasPassedDurableGateEvidence(workingDirectory, taskId) {
|
|
103410
|
+
const evidence = readTaskEvidenceRaw(workingDirectory, taskId);
|
|
103411
|
+
if (!evidence || !Array.isArray(evidence.required_gates) || evidence.required_gates.length === 0) {
|
|
103412
|
+
return false;
|
|
103413
|
+
}
|
|
103414
|
+
return evidence.required_gates.every((gate) => evidence.gates?.[gate] != null);
|
|
103415
|
+
}
|
|
103346
103416
|
function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = false, sessionID) {
|
|
103347
103417
|
try {
|
|
103348
103418
|
let skipStandardTurboBypass = false;
|
|
@@ -103389,12 +103459,11 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103389
103459
|
try {
|
|
103390
103460
|
const evidence = readTaskEvidenceRaw(resolvedDir, taskId);
|
|
103391
103461
|
if (evidence === null) {} else if (evidence.required_gates && Array.isArray(evidence.required_gates) && evidence.gates) {
|
|
103392
|
-
|
|
103393
|
-
if (allGatesMet) {
|
|
103462
|
+
if (evidence.required_gates.length > 0 && evidence.required_gates.every((gate) => evidence.gates[gate] != null)) {
|
|
103394
103463
|
return { blocked: false, reason: "" };
|
|
103395
103464
|
}
|
|
103396
103465
|
const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
|
|
103397
|
-
evidenceIncompleteReason = `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`;
|
|
103466
|
+
evidenceIncompleteReason = evidence.required_gates.length === 0 ? `Task ${taskId} has an evidence file with no required gates. Delegate reviewer and test_engineer before marking task as completed.` : `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`;
|
|
103398
103467
|
}
|
|
103399
103468
|
} catch (error93) {
|
|
103400
103469
|
console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
|
|
@@ -103404,7 +103473,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103404
103473
|
reason: `Evidence file for task ${taskId} is corrupt or unreadable. ` + `Fix the file at .swarm/evidence/${taskId}.json or delete it to fall through to session state.`
|
|
103405
103474
|
};
|
|
103406
103475
|
}
|
|
103407
|
-
if (swarmState.agentSessions.size === 0) {
|
|
103476
|
+
if (swarmState.agentSessions.size === 0 && !evidenceIncompleteReason) {
|
|
103408
103477
|
return { blocked: false, reason: "" };
|
|
103409
103478
|
}
|
|
103410
103479
|
let validSessionCount = 0;
|
|
@@ -103421,7 +103490,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103421
103490
|
return { blocked: false, reason: "" };
|
|
103422
103491
|
}
|
|
103423
103492
|
}
|
|
103424
|
-
if (validSessionCount === 0) {
|
|
103493
|
+
if (validSessionCount === 0 && !evidenceIncompleteReason) {
|
|
103425
103494
|
return { blocked: false, reason: "" };
|
|
103426
103495
|
}
|
|
103427
103496
|
const stateEntries = [];
|
|
@@ -103438,7 +103507,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103438
103507
|
const plan = JSON.parse(planRaw);
|
|
103439
103508
|
for (const planPhase of plan.phases ?? []) {
|
|
103440
103509
|
for (const task of planPhase.tasks ?? []) {
|
|
103441
|
-
if (task.id === taskId && task.status === "completed") {
|
|
103510
|
+
if (task.id === taskId && task.status === "completed" && hasPassedDurableGateEvidence(resolvedDir2, taskId)) {
|
|
103442
103511
|
return { blocked: false, reason: "" };
|
|
103443
103512
|
}
|
|
103444
103513
|
}
|
|
@@ -103459,26 +103528,6 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103459
103528
|
}
|
|
103460
103529
|
}
|
|
103461
103530
|
}
|
|
103462
|
-
if (!hasReviewer && !hasTestEngineer) {
|
|
103463
|
-
for (const [, chain] of swarmState.delegationChains) {
|
|
103464
|
-
let lastCoderIndex = -1;
|
|
103465
|
-
for (let i2 = chain.length - 1;i2 >= 0; i2--) {
|
|
103466
|
-
const target = stripKnownSwarmPrefix(chain[i2].to);
|
|
103467
|
-
if (target === "coder") {
|
|
103468
|
-
lastCoderIndex = i2;
|
|
103469
|
-
break;
|
|
103470
|
-
}
|
|
103471
|
-
}
|
|
103472
|
-
const searchStart = lastCoderIndex === -1 ? 0 : lastCoderIndex + 1;
|
|
103473
|
-
for (let i2 = searchStart;i2 < chain.length; i2++) {
|
|
103474
|
-
const target = stripKnownSwarmPrefix(chain[i2].to);
|
|
103475
|
-
if (target === "reviewer")
|
|
103476
|
-
hasReviewer = true;
|
|
103477
|
-
if (target === "test_engineer")
|
|
103478
|
-
hasTestEngineer = true;
|
|
103479
|
-
}
|
|
103480
|
-
}
|
|
103481
|
-
}
|
|
103482
103531
|
if (hasReviewer && hasTestEngineer) {
|
|
103483
103532
|
return { blocked: false, reason: "" };
|
|
103484
103533
|
}
|
|
@@ -103521,26 +103570,6 @@ function recoverTaskStateFromDelegations(taskId) {
|
|
|
103521
103570
|
}
|
|
103522
103571
|
}
|
|
103523
103572
|
}
|
|
103524
|
-
if (!hasReviewer && !hasTestEngineer) {
|
|
103525
|
-
for (const [, chain] of swarmState.delegationChains) {
|
|
103526
|
-
let lastCoderIndex = -1;
|
|
103527
|
-
for (let i2 = chain.length - 1;i2 >= 0; i2--) {
|
|
103528
|
-
const target = stripKnownSwarmPrefix(chain[i2].to);
|
|
103529
|
-
if (target === "coder") {
|
|
103530
|
-
lastCoderIndex = i2;
|
|
103531
|
-
break;
|
|
103532
|
-
}
|
|
103533
|
-
}
|
|
103534
|
-
const searchStart = lastCoderIndex === -1 ? 0 : lastCoderIndex + 1;
|
|
103535
|
-
for (let i2 = searchStart;i2 < chain.length; i2++) {
|
|
103536
|
-
const target = stripKnownSwarmPrefix(chain[i2].to);
|
|
103537
|
-
if (target === "reviewer")
|
|
103538
|
-
hasReviewer = true;
|
|
103539
|
-
if (target === "test_engineer")
|
|
103540
|
-
hasTestEngineer = true;
|
|
103541
|
-
}
|
|
103542
|
-
}
|
|
103543
|
-
}
|
|
103544
103573
|
if (!hasReviewer && !hasTestEngineer)
|
|
103545
103574
|
return;
|
|
103546
103575
|
for (const [, session] of swarmState.agentSessions) {
|
|
@@ -66,11 +66,10 @@ export declare function checkReviewerGate(taskId: string, workingDirectory?: str
|
|
|
66
66
|
export declare function checkReviewerGateWithScope(taskId: string, workingDirectory?: string, sessionID?: string): Promise<ReviewerGateResult>;
|
|
67
67
|
/**
|
|
68
68
|
* Recovery mechanism: reconcile task state with delegation history.
|
|
69
|
-
* When reviewer/test_engineer delegations occurred but the state
|
|
70
|
-
* was not advanced (e.g., toolAfter didn't fire
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* so that checkReviewerGate can make an accurate decision.
|
|
69
|
+
* When task-scoped reviewer/test_engineer delegations occurred but the state
|
|
70
|
+
* machine was not advanced (e.g., toolAfter didn't fire or subagent_type was
|
|
71
|
+
* missing), this function advances the task state so that checkReviewerGate can
|
|
72
|
+
* make an accurate decision without attributing unrelated delegation activity.
|
|
74
73
|
*
|
|
75
74
|
* @param taskId - The task ID to recover state for
|
|
76
75
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.21.
|
|
3
|
+
"version": "7.21.2",
|
|
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",
|