opencode-swarm 7.23.0 → 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.23.0",
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", {
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.23.0",
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", {
@@ -85196,6 +85211,12 @@ function deserializeAgentSession(s) {
85196
85211
  const catastrophicPhaseWarnings = new Set(s.catastrophicPhaseWarnings ?? []);
85197
85212
  const phaseAgentsDispatched = new Set(s.phaseAgentsDispatched ?? []);
85198
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
+ }
85199
85220
  const windows = {};
85200
85221
  for (const [key, win] of Object.entries(s.windows ?? {})) {
85201
85222
  windows[key] = {
@@ -85250,7 +85271,8 @@ function deserializeAgentSession(s) {
85250
85271
  prmLastPatternDetected: null,
85251
85272
  prmTrajectoryStep: 0,
85252
85273
  prmHardStopPending: false,
85253
- sessionRehydratedAt: s.sessionRehydratedAt ?? 0
85274
+ sessionRehydratedAt: s.sessionRehydratedAt ?? 0,
85275
+ stageBCompletion
85254
85276
  };
85255
85277
  }
85256
85278
  async function readSnapshot(directory) {
@@ -103946,7 +103968,27 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
103946
103968
  }
103947
103969
  }
103948
103970
  const currentStateStr = stateEntries.length > 0 ? stateEntries.join(", ") : "no active sessions";
103949
- const finalReason = evidenceIncompleteReason ?? `Task ${taskId} has not passed QA gates. Current state by session: [${currentStateStr}]. Missing required state: tests_run or complete in at least one valid session. Do not write directly to plan files — use update_task_status after running the reviewer and test_engineer agents.`;
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
+ `);
103950
103992
  telemetry.gateFailed("", "qa_gate", taskId, evidenceIncompleteReason ? `Missing gates: evidence incomplete` : `Missing state: tests_run or complete`);
103951
103993
  return {
103952
103994
  blocked: true,
@@ -103968,7 +104010,7 @@ async function checkReviewerGateWithScope(taskId, workingDirectory, sessionID) {
103968
104010
  ${scopeWarning}` : scopeWarning
103969
104011
  };
103970
104012
  }
103971
- function recoverTaskStateFromDelegations(taskId) {
104013
+ function recoverTaskStateFromDelegations(taskId, directory) {
103972
104014
  let hasReviewer = false;
103973
104015
  let hasTestEngineer = false;
103974
104016
  for (const [sessionId, chain] of swarmState.delegationChains) {
@@ -103983,8 +104025,24 @@ function recoverTaskStateFromDelegations(taskId) {
103983
104025
  }
103984
104026
  }
103985
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
+ }
103986
104039
  if (!hasReviewer && !hasTestEngineer)
103987
104040
  return;
104041
+ if (swarmState.agentSessions.size === 0) {
104042
+ try {
104043
+ startAgentSession("recovery-session", "architect");
104044
+ } catch {}
104045
+ }
103988
104046
  for (const [, session] of swarmState.agentSessions) {
103989
104047
  if (!(session.taskWorkflowStates instanceof Map))
103990
104048
  continue;
@@ -104128,7 +104186,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
104128
104186
  } catch {}
104129
104187
  }
104130
104188
  if (args2.status === "completed") {
104131
- recoverTaskStateFromDelegations(args2.task_id);
104189
+ recoverTaskStateFromDelegations(args2.task_id, directory);
104132
104190
  let phaseRequiresReviewer = true;
104133
104191
  try {
104134
104192
  const planPath = path137.join(directory, ".swarm", "plan.json");
@@ -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.23.0",
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",