opencode-swarm 7.22.1 → 7.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js
CHANGED
|
@@ -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.
|
|
37
|
+
version: "7.23.1",
|
|
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",
|
|
@@ -43447,7 +43447,7 @@ var init_handoff_service = __esm(() => {
|
|
|
43447
43447
|
});
|
|
43448
43448
|
|
|
43449
43449
|
// src/session/snapshot-writer.ts
|
|
43450
|
-
import { mkdirSync as mkdirSync10, renameSync as renameSync6 } from "fs";
|
|
43450
|
+
import { closeSync as closeSync3, fsyncSync as fsyncSync2, mkdirSync as mkdirSync10, openSync as openSync3, renameSync as renameSync6 } from "fs";
|
|
43451
43451
|
import * as path27 from "path";
|
|
43452
43452
|
function serializeAgentSession(s) {
|
|
43453
43453
|
const gateLog = {};
|
|
@@ -43464,6 +43464,12 @@ function serializeAgentSession(s) {
|
|
|
43464
43464
|
const catastrophicPhaseWarnings = Array.from(s.catastrophicPhaseWarnings ?? new Set);
|
|
43465
43465
|
const phaseAgentsDispatched = Array.from(s.phaseAgentsDispatched ?? new Set);
|
|
43466
43466
|
const lastCompletedPhaseAgentsDispatched = Array.from(s.lastCompletedPhaseAgentsDispatched ?? new Set);
|
|
43467
|
+
const stageBCompletion = {};
|
|
43468
|
+
if (s.stageBCompletion) {
|
|
43469
|
+
for (const [taskId, agents] of s.stageBCompletion) {
|
|
43470
|
+
stageBCompletion[taskId] = Array.from(agents);
|
|
43471
|
+
}
|
|
43472
|
+
}
|
|
43467
43473
|
const windows = {};
|
|
43468
43474
|
const rawWindows = s.windows ?? {};
|
|
43469
43475
|
for (const [key, win] of Object.entries(rawWindows)) {
|
|
@@ -43520,7 +43526,8 @@ function serializeAgentSession(s) {
|
|
|
43520
43526
|
fullAutoInteractionCount: s.fullAutoInteractionCount ?? 0,
|
|
43521
43527
|
fullAutoDeadlockCount: s.fullAutoDeadlockCount ?? 0,
|
|
43522
43528
|
fullAutoLastQuestionHash: s.fullAutoLastQuestionHash ?? null,
|
|
43523
|
-
sessionRehydratedAt: s.sessionRehydratedAt ?? 0
|
|
43529
|
+
sessionRehydratedAt: s.sessionRehydratedAt ?? 0,
|
|
43530
|
+
...Object.keys(stageBCompletion).length > 0 && { stageBCompletion }
|
|
43524
43531
|
};
|
|
43525
43532
|
}
|
|
43526
43533
|
async function writeSnapshot(directory, state) {
|
|
@@ -43542,6 +43549,14 @@ async function writeSnapshot(directory, state) {
|
|
|
43542
43549
|
mkdirSync10(dir, { recursive: true });
|
|
43543
43550
|
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
43544
43551
|
await bunWrite(tempPath, content);
|
|
43552
|
+
try {
|
|
43553
|
+
const fd = openSync3(tempPath, "r+");
|
|
43554
|
+
try {
|
|
43555
|
+
fsyncSync2(fd);
|
|
43556
|
+
} finally {
|
|
43557
|
+
closeSync3(fd);
|
|
43558
|
+
}
|
|
43559
|
+
} catch {}
|
|
43545
43560
|
renameSync6(tempPath, resolvedPath);
|
|
43546
43561
|
} catch (error93) {
|
|
43547
43562
|
log("[snapshot-writer] write failed", {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import type { MessageWithParts } from './knowledge-types.js';
|
|
21
|
-
import { computeSkillRelevanceScore } from './skill-scoring.js';
|
|
21
|
+
import { computeSkillRelevanceScore, formatSkillIndexWithContext } from './skill-scoring.js';
|
|
22
22
|
import { appendSkillUsageEntry, readSkillUsageEntries, readSkillUsageEntriesTail } from './skill-usage-log.js';
|
|
23
23
|
/** Agents that should receive skill context in delegations. */
|
|
24
24
|
export declare const SKILL_CAPABLE_AGENTS: Set<string>;
|
|
@@ -37,6 +37,8 @@ export interface SkillGateInput {
|
|
|
37
37
|
}
|
|
38
38
|
export interface SkillPropagationConfig {
|
|
39
39
|
enabled: boolean;
|
|
40
|
+
/** When true, blocks delegations missing SKILLS field instead of warning. */
|
|
41
|
+
enforce?: boolean;
|
|
40
42
|
}
|
|
41
43
|
export declare const _internals: {
|
|
42
44
|
readdirSync: typeof fs.readdirSync;
|
|
@@ -44,6 +46,8 @@ export declare const _internals: {
|
|
|
44
46
|
statSync: typeof fs.statSync;
|
|
45
47
|
mkdirSync: typeof fs.mkdirSync;
|
|
46
48
|
appendFileSync: typeof fs.appendFileSync;
|
|
49
|
+
readFileSync: typeof fs.readFileSync;
|
|
50
|
+
writeFileSync: typeof fs.writeFileSync;
|
|
47
51
|
skillPropagationGateBefore: typeof skillPropagationGateBefore;
|
|
48
52
|
skillPropagationTransformScan: typeof skillPropagationTransformScan;
|
|
49
53
|
SKILL_CAPABLE_AGENTS: Set<string>;
|
|
@@ -57,6 +61,7 @@ export declare const _internals: {
|
|
|
57
61
|
parseSkillPaths: typeof parseSkillPaths;
|
|
58
62
|
extractTaskIdFromPrompt: typeof extractTaskIdFromPrompt;
|
|
59
63
|
computeSkillRelevanceScore: typeof computeSkillRelevanceScore;
|
|
64
|
+
formatSkillIndexWithContext: typeof formatSkillIndexWithContext;
|
|
60
65
|
};
|
|
61
66
|
/**
|
|
62
67
|
* Scans project for available skill SKILL.md files.
|
|
@@ -94,13 +99,22 @@ export declare function extractTaskIdFromPrompt(prompt: string): string;
|
|
|
94
99
|
/**
|
|
95
100
|
* Pre-tool gate. When the architect delegates via Task tool to a skill-capable
|
|
96
101
|
* agent and the SKILLS field is missing or 'none' while skills exist in the
|
|
97
|
-
* project, logs a warning event to events.jsonl
|
|
102
|
+
* project, logs a warning event to events.jsonl and returns a warning string
|
|
103
|
+
* for visible injection into the architect prompt. When config.enforce is true,
|
|
104
|
+
* blocks the delegation entirely instead of merely warning.
|
|
98
105
|
*
|
|
99
106
|
* Also records skill delegation entries to `.swarm/skill-usage.jsonl` when
|
|
100
107
|
* the architect delegates to a skill-capable agent with a non-empty, non-"none"
|
|
101
108
|
* SKILLS field.
|
|
109
|
+
*
|
|
110
|
+
* @returns { blocked: false, reason: null } when no action needed.
|
|
111
|
+
* { blocked: false, reason: "warning message" } when warning only (enforce=false).
|
|
112
|
+
* { blocked: true, reason: "blocked: ..." } when blocking (enforce=true).
|
|
102
113
|
*/
|
|
103
|
-
export declare function skillPropagationGateBefore(directory: string, input: SkillGateInput, config: SkillPropagationConfig): Promise<
|
|
114
|
+
export declare function skillPropagationGateBefore(directory: string, input: SkillGateInput, config: SkillPropagationConfig): Promise<{
|
|
115
|
+
blocked: boolean;
|
|
116
|
+
reason: string | null;
|
|
117
|
+
}>;
|
|
104
118
|
/**
|
|
105
119
|
* Chat messages transform hook. Scans reviewer output for SKILL_COMPLIANCE
|
|
106
120
|
* verdicts and records compliance outcomes to `.swarm/skill-usage.jsonl`.
|
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.
|
|
36
|
+
version: "7.23.1",
|
|
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",
|
|
@@ -52386,7 +52386,7 @@ var init_handoff_service = __esm(() => {
|
|
|
52386
52386
|
});
|
|
52387
52387
|
|
|
52388
52388
|
// src/session/snapshot-writer.ts
|
|
52389
|
-
import { mkdirSync as mkdirSync13, renameSync as renameSync9 } from "node:fs";
|
|
52389
|
+
import { closeSync as closeSync3, fsyncSync as fsyncSync2, mkdirSync as mkdirSync13, openSync as openSync3, renameSync as renameSync9 } from "node:fs";
|
|
52390
52390
|
import * as path33 from "node:path";
|
|
52391
52391
|
function serializeAgentSession(s) {
|
|
52392
52392
|
const gateLog = {};
|
|
@@ -52403,6 +52403,12 @@ function serializeAgentSession(s) {
|
|
|
52403
52403
|
const catastrophicPhaseWarnings = Array.from(s.catastrophicPhaseWarnings ?? new Set);
|
|
52404
52404
|
const phaseAgentsDispatched = Array.from(s.phaseAgentsDispatched ?? new Set);
|
|
52405
52405
|
const lastCompletedPhaseAgentsDispatched = Array.from(s.lastCompletedPhaseAgentsDispatched ?? new Set);
|
|
52406
|
+
const stageBCompletion = {};
|
|
52407
|
+
if (s.stageBCompletion) {
|
|
52408
|
+
for (const [taskId, agents] of s.stageBCompletion) {
|
|
52409
|
+
stageBCompletion[taskId] = Array.from(agents);
|
|
52410
|
+
}
|
|
52411
|
+
}
|
|
52406
52412
|
const windows = {};
|
|
52407
52413
|
const rawWindows = s.windows ?? {};
|
|
52408
52414
|
for (const [key, win] of Object.entries(rawWindows)) {
|
|
@@ -52459,7 +52465,8 @@ function serializeAgentSession(s) {
|
|
|
52459
52465
|
fullAutoInteractionCount: s.fullAutoInteractionCount ?? 0,
|
|
52460
52466
|
fullAutoDeadlockCount: s.fullAutoDeadlockCount ?? 0,
|
|
52461
52467
|
fullAutoLastQuestionHash: s.fullAutoLastQuestionHash ?? null,
|
|
52462
|
-
sessionRehydratedAt: s.sessionRehydratedAt ?? 0
|
|
52468
|
+
sessionRehydratedAt: s.sessionRehydratedAt ?? 0,
|
|
52469
|
+
...Object.keys(stageBCompletion).length > 0 && { stageBCompletion }
|
|
52463
52470
|
};
|
|
52464
52471
|
}
|
|
52465
52472
|
async function writeSnapshot(directory, state) {
|
|
@@ -52481,6 +52488,14 @@ async function writeSnapshot(directory, state) {
|
|
|
52481
52488
|
mkdirSync13(dir, { recursive: true });
|
|
52482
52489
|
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
52483
52490
|
await bunWrite(tempPath, content);
|
|
52491
|
+
try {
|
|
52492
|
+
const fd = openSync3(tempPath, "r+");
|
|
52493
|
+
try {
|
|
52494
|
+
fsyncSync2(fd);
|
|
52495
|
+
} finally {
|
|
52496
|
+
closeSync3(fd);
|
|
52497
|
+
}
|
|
52498
|
+
} catch {}
|
|
52484
52499
|
renameSync9(tempPath, resolvedPath);
|
|
52485
52500
|
} catch (error93) {
|
|
52486
52501
|
log("[snapshot-writer] write failed", {
|
|
@@ -83382,6 +83397,8 @@ var _internals42 = {
|
|
|
83382
83397
|
statSync: fs62.statSync.bind(fs62),
|
|
83383
83398
|
mkdirSync: fs62.mkdirSync.bind(fs62),
|
|
83384
83399
|
appendFileSync: fs62.appendFileSync.bind(fs62),
|
|
83400
|
+
readFileSync: fs62.readFileSync.bind(fs62),
|
|
83401
|
+
writeFileSync: fs62.writeFileSync.bind(fs62),
|
|
83385
83402
|
skillPropagationGateBefore: null,
|
|
83386
83403
|
skillPropagationTransformScan: null,
|
|
83387
83404
|
SKILL_CAPABLE_AGENTS,
|
|
@@ -83394,7 +83411,8 @@ var _internals42 = {
|
|
|
83394
83411
|
readSkillUsageEntriesTail,
|
|
83395
83412
|
parseSkillPaths: null,
|
|
83396
83413
|
extractTaskIdFromPrompt: null,
|
|
83397
|
-
computeSkillRelevanceScore
|
|
83414
|
+
computeSkillRelevanceScore,
|
|
83415
|
+
formatSkillIndexWithContext
|
|
83398
83416
|
};
|
|
83399
83417
|
function discoverAvailableSkills(directory) {
|
|
83400
83418
|
const results = [];
|
|
@@ -83494,22 +83512,22 @@ function extractTaskIdFromPrompt(prompt) {
|
|
|
83494
83512
|
}
|
|
83495
83513
|
async function skillPropagationGateBefore(directory, input, config3) {
|
|
83496
83514
|
if (!config3.enabled)
|
|
83497
|
-
return;
|
|
83515
|
+
return { blocked: false, reason: null };
|
|
83498
83516
|
const toolName = typeof input.tool === "string" ? input.tool : "";
|
|
83499
83517
|
if (toolName !== "task" && toolName !== "Task")
|
|
83500
|
-
return;
|
|
83518
|
+
return { blocked: false, reason: null };
|
|
83501
83519
|
const agentRaw = typeof input.agent === "string" ? input.agent : "";
|
|
83502
83520
|
if (!agentRaw)
|
|
83503
|
-
return;
|
|
83521
|
+
return { blocked: false, reason: null };
|
|
83504
83522
|
const baseAgent = stripKnownSwarmPrefix(agentRaw);
|
|
83505
83523
|
if (baseAgent !== "architect")
|
|
83506
|
-
return;
|
|
83524
|
+
return { blocked: false, reason: null };
|
|
83507
83525
|
const parsed = _internals42.parseDelegationArgs(input.args);
|
|
83508
83526
|
if (!parsed)
|
|
83509
|
-
return;
|
|
83527
|
+
return { blocked: false, reason: null };
|
|
83510
83528
|
const targetBase = stripKnownSwarmPrefix(parsed.targetAgent);
|
|
83511
83529
|
if (!_internals42.SKILL_CAPABLE_AGENTS.has(targetBase))
|
|
83512
|
-
return;
|
|
83530
|
+
return { blocked: false, reason: null };
|
|
83513
83531
|
const sessionID = typeof input.sessionID === "string" ? input.sessionID : "unknown";
|
|
83514
83532
|
const availableSkills = _internals42.discoverAvailableSkills(directory);
|
|
83515
83533
|
const skillsValue = parsed.skillsField.trim();
|
|
@@ -83545,16 +83563,19 @@ async function skillPropagationGateBefore(directory, input, config3) {
|
|
|
83545
83563
|
}
|
|
83546
83564
|
}
|
|
83547
83565
|
}
|
|
83566
|
+
let scoringSkipped = false;
|
|
83567
|
+
let scored = [];
|
|
83548
83568
|
if (skillsValue && skillsValue.toLowerCase() !== "none" && availableSkills.length > 0) {
|
|
83549
83569
|
try {
|
|
83550
83570
|
const sessionEntries = _internals42.readSkillUsageEntriesTail(directory, {
|
|
83551
83571
|
sessionID
|
|
83552
83572
|
});
|
|
83553
83573
|
if (sessionEntries.length > _internals42.MAX_SCORING_SESSION_ENTRIES) {
|
|
83574
|
+
scoringSkipped = true;
|
|
83554
83575
|
warn(`[skill-propagation-gate] skipping scoring — session has ${sessionEntries.length} entries (limit: ${_internals42.MAX_SCORING_SESSION_ENTRIES})`);
|
|
83555
83576
|
} else {
|
|
83556
83577
|
const prompt = typeof input.args?.prompt === "string" ? String(input.args.prompt) : "";
|
|
83557
|
-
|
|
83578
|
+
scored = availableSkills.map((skillPath) => {
|
|
83558
83579
|
const skillEntries = sessionEntries.filter((e) => e.skillPath === skillPath);
|
|
83559
83580
|
const score = _internals42.computeSkillRelevanceScore(skillPath, prompt, skillEntries);
|
|
83560
83581
|
return { skillPath, score, usageCount: skillEntries.length };
|
|
@@ -83569,11 +83590,77 @@ async function skillPropagationGateBefore(directory, input, config3) {
|
|
|
83569
83590
|
warn(`[skill-propagation-gate] skill scoring failed (non-blocking): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
83570
83591
|
}
|
|
83571
83592
|
}
|
|
83593
|
+
if (availableSkills.length > 0) {
|
|
83594
|
+
try {
|
|
83595
|
+
let skillsForIndex = availableSkills;
|
|
83596
|
+
if (scoringSkipped) {
|
|
83597
|
+
skillsForIndex = [...availableSkills].sort((a, b) => {
|
|
83598
|
+
const nameA = path89.basename(path89.dirname(a));
|
|
83599
|
+
const nameB = path89.basename(path89.dirname(b));
|
|
83600
|
+
return nameA.localeCompare(nameB);
|
|
83601
|
+
});
|
|
83602
|
+
} else if (typeof scored !== "undefined" && scored.length > 0) {
|
|
83603
|
+
skillsForIndex = scored.map((r) => r.skillPath);
|
|
83604
|
+
}
|
|
83605
|
+
const formattedIndex = _internals42.formatSkillIndexWithContext(skillsForIndex, directory);
|
|
83606
|
+
if (formattedIndex.length > 0) {
|
|
83607
|
+
const contextPath = path89.join(directory, ".swarm", "context.md");
|
|
83608
|
+
let existingContent = "";
|
|
83609
|
+
if (_internals42.existsSync(contextPath)) {
|
|
83610
|
+
existingContent = _internals42.readFileSync(contextPath, "utf-8");
|
|
83611
|
+
}
|
|
83612
|
+
const sectionHeader = "## Available Skills";
|
|
83613
|
+
const newSection = `${sectionHeader}
|
|
83614
|
+
${formattedIndex}
|
|
83615
|
+
`;
|
|
83616
|
+
let updatedContent;
|
|
83617
|
+
if (existingContent.includes(sectionHeader)) {
|
|
83618
|
+
const sectionStart = existingContent.indexOf(sectionHeader);
|
|
83619
|
+
const sectionEnd = existingContent.indexOf(`
|
|
83620
|
+
## `, sectionStart + sectionHeader.length);
|
|
83621
|
+
if (sectionEnd !== -1) {
|
|
83622
|
+
updatedContent = existingContent.slice(0, sectionStart) + newSection + existingContent.slice(sectionEnd + 1);
|
|
83623
|
+
} else {
|
|
83624
|
+
updatedContent = existingContent.slice(0, sectionStart) + newSection;
|
|
83625
|
+
}
|
|
83626
|
+
} else {
|
|
83627
|
+
if (existingContent.length > 0 && !existingContent.endsWith(`
|
|
83628
|
+
`)) {
|
|
83629
|
+
updatedContent = existingContent + `
|
|
83630
|
+
` + newSection;
|
|
83631
|
+
} else {
|
|
83632
|
+
updatedContent = existingContent + newSection;
|
|
83633
|
+
}
|
|
83634
|
+
}
|
|
83635
|
+
const swarmDir = path89.dirname(contextPath);
|
|
83636
|
+
if (!_internals42.existsSync(swarmDir)) {
|
|
83637
|
+
_internals42.mkdirSync(swarmDir, { recursive: true });
|
|
83638
|
+
}
|
|
83639
|
+
_internals42.writeFileSync(contextPath, updatedContent, "utf-8");
|
|
83640
|
+
}
|
|
83641
|
+
} catch (err2) {
|
|
83642
|
+
warn(`[skill-propagation-gate] failed to write skill index to context.md: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
83643
|
+
}
|
|
83644
|
+
}
|
|
83645
|
+
if (targetBase === "reviewer") {
|
|
83646
|
+
const prompt = typeof input.args?.prompt === "string" ? String(input.args.prompt) : "";
|
|
83647
|
+
const hasSkillsUsedByCoder = /SKILLS_USED_BY_CODER\s*:/i.test(prompt);
|
|
83648
|
+
const coderHadSkills = skillsValue.length > 0 && skillsValue.toLowerCase() !== "none";
|
|
83649
|
+
if (!hasSkillsUsedByCoder && coderHadSkills) {
|
|
83650
|
+
const message = `SKILLS_USED_BY_CODER warning: Delegating to reviewer without SKILLS_USED_BY_CODER field. ` + `Add SKILLS_USED_BY_CODER with the skills the coder received for this task.`;
|
|
83651
|
+
return { blocked: false, reason: message };
|
|
83652
|
+
}
|
|
83653
|
+
}
|
|
83572
83654
|
if (availableSkills.length === 0)
|
|
83573
|
-
return;
|
|
83655
|
+
return { blocked: false, reason: null };
|
|
83574
83656
|
const skillsLower = skillsValue.toLowerCase();
|
|
83575
83657
|
if (skillsValue && skillsLower !== "none")
|
|
83576
|
-
return;
|
|
83658
|
+
return { blocked: false, reason: null };
|
|
83659
|
+
const skillNames = availableSkills.map((p) => {
|
|
83660
|
+
const parts2 = p.split("/");
|
|
83661
|
+
return parts2[parts2.length - 2] ?? p;
|
|
83662
|
+
});
|
|
83663
|
+
const warningMsg = `Skill propagation warning: Delegating to ${targetBase} without SKILLS field. ` + `Available skills: ${skillNames.join(", ")}`;
|
|
83577
83664
|
try {
|
|
83578
83665
|
_internals42.writeWarnEvent(directory, {
|
|
83579
83666
|
type: "skill_propagation_warn",
|
|
@@ -83586,6 +83673,11 @@ async function skillPropagationGateBefore(directory, input, config3) {
|
|
|
83586
83673
|
available_skills: availableSkills
|
|
83587
83674
|
});
|
|
83588
83675
|
} catch {}
|
|
83676
|
+
if (config3.enforce) {
|
|
83677
|
+
const blockedMsg = `Blocked by skill propagation gate: Delegating to ${targetBase} without SKILLS field. ` + `Available skills: ${skillNames.join(", ")}. ` + `Add a SKILLS: field or set enforce: false in config.`;
|
|
83678
|
+
return { blocked: true, reason: blockedMsg };
|
|
83679
|
+
}
|
|
83680
|
+
return { blocked: false, reason: warningMsg };
|
|
83589
83681
|
}
|
|
83590
83682
|
var COMPLIANCE_PATTERN = /SKILL_COMPLIANCE\s*:\s*(COMPLIANT|PARTIAL|VIOLATED)(?:\s*(?:—|-)\s*(.*))?\s*$/i;
|
|
83591
83683
|
var CODER_SKILLS_PATTERN = /SKILLS_USED_BY_CODER\s*:\s*(.+)/i;
|
|
@@ -83599,10 +83691,13 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
|
|
|
83599
83691
|
let dedupKeys = new Set;
|
|
83600
83692
|
let existingEntries = [];
|
|
83601
83693
|
try {
|
|
83602
|
-
existingEntries = _internals42.
|
|
83694
|
+
existingEntries = _internals42.readSkillUsageEntriesTail(directory, {
|
|
83603
83695
|
sessionID
|
|
83604
83696
|
});
|
|
83605
|
-
dedupKeys = new Set(existingEntries.map((e) =>
|
|
83697
|
+
dedupKeys = new Set(existingEntries.map((e, i2) => {
|
|
83698
|
+
const taskKey = e.taskID === "unknown" ? `unknown-${i2}` : e.taskID;
|
|
83699
|
+
return `${e.skillPath}|${e.agentName}|${taskKey}`;
|
|
83700
|
+
}));
|
|
83606
83701
|
} catch (err2) {
|
|
83607
83702
|
warn(`[skill-propagation-gate] dedup preload failed, continuing without dedup: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
83608
83703
|
}
|
|
@@ -83738,6 +83833,7 @@ _internals42.discoverAvailableSkills = discoverAvailableSkills;
|
|
|
83738
83833
|
_internals42.parseDelegationArgs = parseDelegationArgs;
|
|
83739
83834
|
_internals42.parseSkillPaths = parseSkillPaths;
|
|
83740
83835
|
_internals42.extractTaskIdFromPrompt = extractTaskIdFromPrompt;
|
|
83836
|
+
_internals42.formatSkillIndexWithContext = formatSkillIndexWithContext;
|
|
83741
83837
|
|
|
83742
83838
|
// src/hooks/slop-detector.ts
|
|
83743
83839
|
import * as fs63 from "node:fs";
|
|
@@ -85115,6 +85211,12 @@ function deserializeAgentSession(s) {
|
|
|
85115
85211
|
const catastrophicPhaseWarnings = new Set(s.catastrophicPhaseWarnings ?? []);
|
|
85116
85212
|
const phaseAgentsDispatched = new Set(s.phaseAgentsDispatched ?? []);
|
|
85117
85213
|
const lastCompletedPhaseAgentsDispatched = new Set(s.lastCompletedPhaseAgentsDispatched ?? []);
|
|
85214
|
+
const stageBCompletion = new Map;
|
|
85215
|
+
if (s.stageBCompletion) {
|
|
85216
|
+
for (const [taskId, agents] of Object.entries(s.stageBCompletion)) {
|
|
85217
|
+
stageBCompletion.set(taskId, new Set(agents));
|
|
85218
|
+
}
|
|
85219
|
+
}
|
|
85118
85220
|
const windows = {};
|
|
85119
85221
|
for (const [key, win] of Object.entries(s.windows ?? {})) {
|
|
85120
85222
|
windows[key] = {
|
|
@@ -85169,7 +85271,8 @@ function deserializeAgentSession(s) {
|
|
|
85169
85271
|
prmLastPatternDetected: null,
|
|
85170
85272
|
prmTrajectoryStep: 0,
|
|
85171
85273
|
prmHardStopPending: false,
|
|
85172
|
-
sessionRehydratedAt: s.sessionRehydratedAt ?? 0
|
|
85274
|
+
sessionRehydratedAt: s.sessionRehydratedAt ?? 0,
|
|
85275
|
+
stageBCompletion
|
|
85173
85276
|
};
|
|
85174
85277
|
}
|
|
85175
85278
|
async function readSnapshot(directory) {
|
|
@@ -86379,7 +86482,7 @@ function countCodeLines(content) {
|
|
|
86379
86482
|
return lines.length;
|
|
86380
86483
|
}
|
|
86381
86484
|
function isTestFile(filePath) {
|
|
86382
|
-
const
|
|
86485
|
+
const basename14 = path97.basename(filePath);
|
|
86383
86486
|
const _ext = path97.extname(filePath).toLowerCase();
|
|
86384
86487
|
const testPatterns = [
|
|
86385
86488
|
".test.",
|
|
@@ -86395,7 +86498,7 @@ function isTestFile(filePath) {
|
|
|
86395
86498
|
".spec.jsx"
|
|
86396
86499
|
];
|
|
86397
86500
|
for (const pattern of testPatterns) {
|
|
86398
|
-
if (
|
|
86501
|
+
if (basename14.includes(pattern)) {
|
|
86399
86502
|
return true;
|
|
86400
86503
|
}
|
|
86401
86504
|
}
|
|
@@ -87009,8 +87112,8 @@ import {
|
|
|
87009
87112
|
appendFileSync as appendFileSync11,
|
|
87010
87113
|
existsSync as existsSync53,
|
|
87011
87114
|
mkdirSync as mkdirSync24,
|
|
87012
|
-
readFileSync as
|
|
87013
|
-
writeFileSync as
|
|
87115
|
+
readFileSync as readFileSync44,
|
|
87116
|
+
writeFileSync as writeFileSync17
|
|
87014
87117
|
} from "node:fs";
|
|
87015
87118
|
import { join as join83 } from "node:path";
|
|
87016
87119
|
var EVIDENCE_DIR2 = ".swarm/evidence";
|
|
@@ -87052,7 +87155,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
87052
87155
|
const existingRoot = Object.create(null);
|
|
87053
87156
|
if (existsSync53(filePath)) {
|
|
87054
87157
|
try {
|
|
87055
|
-
const parsed = JSON.parse(
|
|
87158
|
+
const parsed = JSON.parse(readFileSync44(filePath, "utf-8"));
|
|
87056
87159
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
87057
87160
|
safeAssignOwnProps(existingRoot, parsed);
|
|
87058
87161
|
}
|
|
@@ -87080,7 +87183,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
87080
87183
|
updated.taskId = synthesis.taskId;
|
|
87081
87184
|
if (!Array.isArray(updated.required_gates))
|
|
87082
87185
|
updated.required_gates = [];
|
|
87083
|
-
|
|
87186
|
+
writeFileSync17(filePath, JSON.stringify(updated, null, 2));
|
|
87084
87187
|
try {
|
|
87085
87188
|
const councilDir = join83(workingDir, ".swarm", "council");
|
|
87086
87189
|
mkdirSync24(councilDir, { recursive: true });
|
|
@@ -87412,7 +87515,7 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
|
|
|
87412
87515
|
}
|
|
87413
87516
|
|
|
87414
87517
|
// src/council/criteria-store.ts
|
|
87415
|
-
import { existsSync as existsSync54, mkdirSync as mkdirSync25, readFileSync as
|
|
87518
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync25, readFileSync as readFileSync45, writeFileSync as writeFileSync18 } from "node:fs";
|
|
87416
87519
|
import { join as join84 } from "node:path";
|
|
87417
87520
|
var COUNCIL_DIR = ".swarm/council";
|
|
87418
87521
|
function writeCriteria(workingDir, taskId, criteria) {
|
|
@@ -87423,14 +87526,14 @@ function writeCriteria(workingDir, taskId, criteria) {
|
|
|
87423
87526
|
criteria,
|
|
87424
87527
|
declaredAt: new Date().toISOString()
|
|
87425
87528
|
};
|
|
87426
|
-
|
|
87529
|
+
writeFileSync18(join84(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
|
|
87427
87530
|
}
|
|
87428
87531
|
function readCriteria(workingDir, taskId) {
|
|
87429
87532
|
const filePath = join84(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
|
|
87430
87533
|
if (!existsSync54(filePath))
|
|
87431
87534
|
return null;
|
|
87432
87535
|
try {
|
|
87433
|
-
const parsed = JSON.parse(
|
|
87536
|
+
const parsed = JSON.parse(readFileSync45(filePath, "utf-8"));
|
|
87434
87537
|
if (parsed && typeof parsed === "object" && typeof parsed.taskId === "string" && Array.isArray(parsed.criteria)) {
|
|
87435
87538
|
return parsed;
|
|
87436
87539
|
}
|
|
@@ -99766,10 +99869,10 @@ init_loader();
|
|
|
99766
99869
|
import {
|
|
99767
99870
|
existsSync as existsSync72,
|
|
99768
99871
|
mkdirSync as mkdirSync31,
|
|
99769
|
-
readFileSync as
|
|
99872
|
+
readFileSync as readFileSync63,
|
|
99770
99873
|
renameSync as renameSync20,
|
|
99771
99874
|
unlinkSync as unlinkSync15,
|
|
99772
|
-
writeFileSync as
|
|
99875
|
+
writeFileSync as writeFileSync25
|
|
99773
99876
|
} from "node:fs";
|
|
99774
99877
|
import path124 from "node:path";
|
|
99775
99878
|
init_create_tool();
|
|
@@ -99903,7 +100006,7 @@ var submit_phase_council_verdicts = createSwarmTool({
|
|
|
99903
100006
|
function getPhaseMutationGapFinding(phaseNumber, workingDir) {
|
|
99904
100007
|
const mutationGatePath = path124.join(workingDir, ".swarm", "evidence", String(phaseNumber), "mutation-gate.json");
|
|
99905
100008
|
try {
|
|
99906
|
-
const raw =
|
|
100009
|
+
const raw = readFileSync63(mutationGatePath, "utf-8");
|
|
99907
100010
|
const parsed = JSON.parse(raw);
|
|
99908
100011
|
const gateEntry = (parsed.entries ?? []).find((entry) => entry?.type === "mutation-gate");
|
|
99909
100012
|
if (!gateEntry) {
|
|
@@ -99998,7 +100101,7 @@ function writePhaseCouncilEvidence(workingDir, synthesis) {
|
|
|
99998
100101
|
};
|
|
99999
100102
|
const tempFile = `${evidenceFile}.tmp-${Date.now()}`;
|
|
100000
100103
|
try {
|
|
100001
|
-
|
|
100104
|
+
writeFileSync25(tempFile, JSON.stringify(evidenceBundle, null, 2), "utf-8");
|
|
100002
100105
|
renameSync20(tempFile, evidenceFile);
|
|
100003
100106
|
} finally {
|
|
100004
100107
|
if (existsSync72(tempFile)) {
|
|
@@ -102209,7 +102312,7 @@ import * as path131 from "node:path";
|
|
|
102209
102312
|
|
|
102210
102313
|
// src/mutation/engine.ts
|
|
102211
102314
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
102212
|
-
import { unlinkSync as unlinkSync16, writeFileSync as
|
|
102315
|
+
import { unlinkSync as unlinkSync16, writeFileSync as writeFileSync26 } from "node:fs";
|
|
102213
102316
|
import * as path130 from "node:path";
|
|
102214
102317
|
|
|
102215
102318
|
// src/mutation/equivalence.ts
|
|
@@ -102352,7 +102455,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
102352
102455
|
const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
102353
102456
|
patchFile = path130.join(workingDir, `.mutation_patch_${safeId2}.diff`);
|
|
102354
102457
|
try {
|
|
102355
|
-
|
|
102458
|
+
writeFileSync26(patchFile, patch.patch);
|
|
102356
102459
|
} catch (writeErr) {
|
|
102357
102460
|
error93 = `Failed to write patch file: ${writeErr}`;
|
|
102358
102461
|
outcome = "error";
|
|
@@ -103865,7 +103968,27 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
103865
103968
|
}
|
|
103866
103969
|
}
|
|
103867
103970
|
const currentStateStr = stateEntries.length > 0 ? stateEntries.join(", ") : "no active sessions";
|
|
103868
|
-
const
|
|
103971
|
+
const chainEntries = [];
|
|
103972
|
+
for (const [sessionId, chain] of swarmState.delegationChains) {
|
|
103973
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
103974
|
+
if (session && (session.currentTaskId === taskId || session.lastCoderDelegationTaskId === taskId)) {
|
|
103975
|
+
const targets = chain.map((d) => stripKnownSwarmPrefix(d.to));
|
|
103976
|
+
chainEntries.push(`${sessionId}: [${targets.join(", ")}]`);
|
|
103977
|
+
}
|
|
103978
|
+
}
|
|
103979
|
+
const chainSummary = chainEntries.length > 0 ? chainEntries.join("; ") : "no chains for this task";
|
|
103980
|
+
const rehydratedSessionCount = [
|
|
103981
|
+
...swarmState.agentSessions.values()
|
|
103982
|
+
].filter((s) => s.sessionRehydratedAt > 0).length;
|
|
103983
|
+
const finalReason = [
|
|
103984
|
+
`Task ${taskId} has not passed QA gates.`,
|
|
103985
|
+
` Session states: [${currentStateStr}].`,
|
|
103986
|
+
` Delegation chains: [${chainSummary}].`,
|
|
103987
|
+
` Evidence: [${evidenceIncompleteReason ?? "no evidence file found"}].`,
|
|
103988
|
+
` Rehydrated sessions: ${rehydratedSessionCount}.`,
|
|
103989
|
+
` Missing required state: tests_run or complete.`
|
|
103990
|
+
].join(`
|
|
103991
|
+
`);
|
|
103869
103992
|
telemetry.gateFailed("", "qa_gate", taskId, evidenceIncompleteReason ? `Missing gates: evidence incomplete` : `Missing state: tests_run or complete`);
|
|
103870
103993
|
return {
|
|
103871
103994
|
blocked: true,
|
|
@@ -103887,7 +104010,7 @@ async function checkReviewerGateWithScope(taskId, workingDirectory, sessionID) {
|
|
|
103887
104010
|
${scopeWarning}` : scopeWarning
|
|
103888
104011
|
};
|
|
103889
104012
|
}
|
|
103890
|
-
function recoverTaskStateFromDelegations(taskId) {
|
|
104013
|
+
function recoverTaskStateFromDelegations(taskId, directory) {
|
|
103891
104014
|
let hasReviewer = false;
|
|
103892
104015
|
let hasTestEngineer = false;
|
|
103893
104016
|
for (const [sessionId, chain] of swarmState.delegationChains) {
|
|
@@ -103902,8 +104025,24 @@ function recoverTaskStateFromDelegations(taskId) {
|
|
|
103902
104025
|
}
|
|
103903
104026
|
}
|
|
103904
104027
|
}
|
|
104028
|
+
if ((!hasReviewer || !hasTestEngineer) && directory) {
|
|
104029
|
+
try {
|
|
104030
|
+
const evidence = readTaskEvidenceRaw(directory, taskId);
|
|
104031
|
+
if (evidence && evidence.gates && Array.isArray(evidence.required_gates)) {
|
|
104032
|
+
if (evidence.gates["reviewer"] != null)
|
|
104033
|
+
hasReviewer = true;
|
|
104034
|
+
if (evidence.gates["test_engineer"] != null)
|
|
104035
|
+
hasTestEngineer = true;
|
|
104036
|
+
}
|
|
104037
|
+
} catch {}
|
|
104038
|
+
}
|
|
103905
104039
|
if (!hasReviewer && !hasTestEngineer)
|
|
103906
104040
|
return;
|
|
104041
|
+
if (swarmState.agentSessions.size === 0) {
|
|
104042
|
+
try {
|
|
104043
|
+
startAgentSession("recovery-session", "architect");
|
|
104044
|
+
} catch {}
|
|
104045
|
+
}
|
|
103907
104046
|
for (const [, session] of swarmState.agentSessions) {
|
|
103908
104047
|
if (!(session.taskWorkflowStates instanceof Map))
|
|
103909
104048
|
continue;
|
|
@@ -104047,7 +104186,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
|
|
|
104047
104186
|
} catch {}
|
|
104048
104187
|
}
|
|
104049
104188
|
if (args2.status === "completed") {
|
|
104050
|
-
recoverTaskStateFromDelegations(args2.task_id);
|
|
104189
|
+
recoverTaskStateFromDelegations(args2.task_id, directory);
|
|
104051
104190
|
let phaseRequiresReviewer = true;
|
|
104052
104191
|
try {
|
|
104053
104192
|
const planPath = path137.join(directory, ".swarm", "plan.json");
|
|
@@ -105762,12 +105901,20 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
105762
105901
|
agent: input.agent,
|
|
105763
105902
|
sessionID: input.sessionID
|
|
105764
105903
|
}, KnowledgeApplicationConfigSchema.parse(config3.knowledge_application ?? {}));
|
|
105765
|
-
await skillPropagationGateBefore(ctx.directory, {
|
|
105904
|
+
const skillResult = await skillPropagationGateBefore(ctx.directory, {
|
|
105766
105905
|
tool: input.tool,
|
|
105767
105906
|
agent: input.agent,
|
|
105768
105907
|
sessionID: input.sessionID,
|
|
105769
105908
|
args: input.args
|
|
105770
105909
|
}, { enabled: true });
|
|
105910
|
+
if (skillResult.blocked) {
|
|
105911
|
+
throw new Error(skillResult.reason ?? "Blocked by skill propagation gate");
|
|
105912
|
+
}
|
|
105913
|
+
if (skillResult.reason) {
|
|
105914
|
+
const skillSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
|
|
105915
|
+
skillSession.pendingAdvisoryMessages ??= [];
|
|
105916
|
+
skillSession.pendingAdvisoryMessages.push(skillResult.reason);
|
|
105917
|
+
}
|
|
105771
105918
|
if (swarmState.lastBudgetPct >= 50) {
|
|
105772
105919
|
const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
|
|
105773
105920
|
if (!pressureSession.contextPressureWarningSent) {
|
|
@@ -59,6 +59,8 @@ export interface SerializedAgentSession {
|
|
|
59
59
|
fullAutoLastQuestionHash?: string | null;
|
|
60
60
|
/** Timestamp when session was rehydrated from snapshot (0 if never rehydrated) */
|
|
61
61
|
sessionRehydratedAt?: number;
|
|
62
|
+
/** Stage B completion tracking: per-task set of completed Stage B agents. Optional for backward compat with old snapshots. */
|
|
63
|
+
stageBCompletion?: Record<string, string[]>;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* Minimal interface for serialized InvocationWindow
|
|
@@ -71,9 +71,14 @@ export declare function checkReviewerGateWithScope(taskId: string, workingDirect
|
|
|
71
71
|
* missing), this function advances the task state so that checkReviewerGate can
|
|
72
72
|
* make an accurate decision without attributing unrelated delegation activity.
|
|
73
73
|
*
|
|
74
|
+
* Falls back to reading durable evidence files when delegation chains are empty
|
|
75
|
+
* (e.g., after a crash or session restart without snapshot). This ensures
|
|
76
|
+
* recovery works even when no in-memory delegation history exists.
|
|
77
|
+
*
|
|
74
78
|
* @param taskId - The task ID to recover state for
|
|
79
|
+
* @param directory - Optional project directory for evidence file fallback
|
|
75
80
|
*/
|
|
76
|
-
export declare function recoverTaskStateFromDelegations(taskId: string): void;
|
|
81
|
+
export declare function recoverTaskStateFromDelegations(taskId: string, directory?: string): void;
|
|
77
82
|
/**
|
|
78
83
|
* Result of the council-gate check used when transitioning to 'completed'.
|
|
79
84
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.23.1",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|