opencode-swarm 6.76.0 → 6.77.0

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
@@ -19414,7 +19414,12 @@ var CouncilConfigSchema = exports_external.object({
19414
19414
  var ParallelizationConfigSchema = exports_external.object({
19415
19415
  enabled: exports_external.boolean().default(false),
19416
19416
  maxConcurrentTasks: exports_external.number().int().min(1).max(64).default(1),
19417
- evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000)
19417
+ evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000),
19418
+ stageB: exports_external.object({
19419
+ parallel: exports_external.object({
19420
+ enabled: exports_external.boolean().default(false)
19421
+ }).default({ enabled: false })
19422
+ }).default({ parallel: { enabled: false } })
19418
19423
  });
19419
19424
  var PluginConfigSchema = exports_external.object({
19420
19425
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
@@ -530,6 +530,11 @@ export declare const ParallelizationConfigSchema: z.ZodObject<{
530
530
  enabled: z.ZodDefault<z.ZodBoolean>;
531
531
  maxConcurrentTasks: z.ZodDefault<z.ZodNumber>;
532
532
  evidenceLockTimeoutMs: z.ZodDefault<z.ZodNumber>;
533
+ stageB: z.ZodDefault<z.ZodObject<{
534
+ parallel: z.ZodDefault<z.ZodObject<{
535
+ enabled: z.ZodDefault<z.ZodBoolean>;
536
+ }, z.core.$strip>>;
537
+ }, z.core.$strip>>;
533
538
  }, z.core.$strip>;
534
539
  export type ParallelizationConfig = z.infer<typeof ParallelizationConfigSchema>;
535
540
  export declare const PluginConfigSchema: z.ZodObject<{
@@ -893,6 +898,11 @@ export declare const PluginConfigSchema: z.ZodObject<{
893
898
  enabled: z.ZodDefault<z.ZodBoolean>;
894
899
  maxConcurrentTasks: z.ZodDefault<z.ZodNumber>;
895
900
  evidenceLockTimeoutMs: z.ZodDefault<z.ZodNumber>;
901
+ stageB: z.ZodDefault<z.ZodObject<{
902
+ parallel: z.ZodDefault<z.ZodObject<{
903
+ enabled: z.ZodDefault<z.ZodBoolean>;
904
+ }, z.core.$strip>>;
905
+ }, z.core.$strip>>;
896
906
  }, z.core.$strip>>;
897
907
  turbo_mode: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
898
908
  full_auto: z.ZodDefault<z.ZodOptional<z.ZodObject<{
package/dist/index.js CHANGED
@@ -15185,7 +15185,12 @@ var init_schema = __esm(() => {
15185
15185
  ParallelizationConfigSchema = exports_external.object({
15186
15186
  enabled: exports_external.boolean().default(false),
15187
15187
  maxConcurrentTasks: exports_external.number().int().min(1).max(64).default(1),
15188
- evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000)
15188
+ evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000),
15189
+ stageB: exports_external.object({
15190
+ parallel: exports_external.object({
15191
+ enabled: exports_external.boolean().default(false)
15192
+ }).default({ enabled: false })
15193
+ }).default({ parallel: { enabled: false } })
15189
15194
  });
15190
15195
  PluginConfigSchema = exports_external.object({
15191
15196
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
@@ -24827,60 +24832,121 @@ function createDelegationGateHook(config2, directory) {
24827
24832
  if (targetAgent === "test_engineer")
24828
24833
  hasTestEngineer = true;
24829
24834
  if (!councilActive) {
24830
- if (targetAgent === "reviewer" && session.taskWorkflowStates) {
24831
- for (const [taskId, state] of session.taskWorkflowStates) {
24832
- if (state === "coder_delegated" || state === "pre_check_passed") {
24833
- try {
24834
- advanceTaskState(session, taskId, "reviewer_run");
24835
- } catch (err2) {
24836
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24835
+ const stageBParallelEnabled = config2.parallelization?.stageB?.parallel?.enabled === true;
24836
+ if (stageBParallelEnabled) {
24837
+ if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
24838
+ const stageBEligibleStates = [
24839
+ "coder_delegated",
24840
+ "pre_check_passed",
24841
+ "reviewer_run"
24842
+ ];
24843
+ for (const [taskId, state] of session.taskWorkflowStates) {
24844
+ if (!stageBEligibleStates.includes(state))
24845
+ continue;
24846
+ const eligibleState = state;
24847
+ recordStageBCompletion(session, taskId, targetAgent);
24848
+ if (hasBothStageBCompletions(session, taskId)) {
24849
+ try {
24850
+ if (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed") {
24851
+ advanceTaskState(session, taskId, "reviewer_run");
24852
+ }
24853
+ advanceTaskState(session, taskId, "tests_run");
24854
+ } catch (err2) {
24855
+ console.warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24856
+ }
24837
24857
  }
24838
24858
  }
24839
- }
24840
- }
24841
- if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
24842
- for (const [taskId, state] of session.taskWorkflowStates) {
24843
- if (state === "reviewer_run") {
24844
- try {
24845
- advanceTaskState(session, taskId, "tests_run");
24846
- } catch (err2) {
24847
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24859
+ const seedTaskId = getSeedTaskId(session);
24860
+ if (seedTaskId) {
24861
+ for (const [, otherSession] of swarmState.agentSessions) {
24862
+ if (otherSession === session)
24863
+ continue;
24864
+ if (!otherSession.taskWorkflowStates)
24865
+ continue;
24866
+ if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
24867
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
24868
+ }
24869
+ const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
24870
+ if (!seedState || !stageBEligibleStates.includes(seedState)) {
24871
+ continue;
24872
+ }
24873
+ const seedEligibleState = seedState;
24874
+ recordStageBCompletion(otherSession, seedTaskId, targetAgent);
24875
+ if (hasBothStageBCompletions(otherSession, seedTaskId)) {
24876
+ try {
24877
+ if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
24878
+ advanceTaskState(otherSession, seedTaskId, "reviewer_run");
24879
+ }
24880
+ advanceTaskState(otherSession, seedTaskId, "tests_run");
24881
+ } catch (err2) {
24882
+ console.warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24883
+ }
24884
+ }
24848
24885
  }
24849
24886
  }
24850
24887
  }
24851
- }
24852
- if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
24853
- for (const [, otherSession] of swarmState.agentSessions) {
24854
- if (otherSession === session)
24855
- continue;
24856
- if (!otherSession.taskWorkflowStates)
24857
- continue;
24858
- if (targetAgent === "reviewer") {
24859
- const seedTaskId = getSeedTaskId(session);
24860
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
24861
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
24888
+ } else {
24889
+ if (targetAgent === "reviewer" && session.taskWorkflowStates) {
24890
+ for (const [taskId, state] of session.taskWorkflowStates) {
24891
+ if (state === "coder_delegated" || state === "pre_check_passed") {
24892
+ try {
24893
+ advanceTaskState(session, taskId, "reviewer_run");
24894
+ } catch (err2) {
24895
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24896
+ }
24862
24897
  }
24863
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
24864
- if (state === "coder_delegated" || state === "pre_check_passed") {
24865
- try {
24866
- advanceTaskState(otherSession, taskId, "reviewer_run");
24867
- } catch (err2) {
24868
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24869
- }
24898
+ }
24899
+ }
24900
+ if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
24901
+ for (const [taskId, state] of session.taskWorkflowStates) {
24902
+ if (state === "reviewer_run") {
24903
+ try {
24904
+ advanceTaskState(session, taskId, "tests_run");
24905
+ } catch (err2) {
24906
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24870
24907
  }
24871
24908
  }
24872
24909
  }
24873
- if (targetAgent === "test_engineer") {
24874
- const seedTaskId = getSeedTaskId(session);
24875
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
24876
- otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
24910
+ }
24911
+ if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
24912
+ for (const [, otherSession] of swarmState.agentSessions) {
24913
+ if (otherSession === session)
24914
+ continue;
24915
+ if (!otherSession.taskWorkflowStates)
24916
+ continue;
24917
+ if (targetAgent === "reviewer") {
24918
+ const seedTaskId = getSeedTaskId(session);
24919
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
24920
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
24921
+ }
24922
+ for (const [
24923
+ taskId,
24924
+ state
24925
+ ] of otherSession.taskWorkflowStates) {
24926
+ if (state === "coder_delegated" || state === "pre_check_passed") {
24927
+ try {
24928
+ advanceTaskState(otherSession, taskId, "reviewer_run");
24929
+ } catch (err2) {
24930
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24931
+ }
24932
+ }
24933
+ }
24877
24934
  }
24878
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
24879
- if (state === "reviewer_run") {
24880
- try {
24881
- advanceTaskState(otherSession, taskId, "tests_run");
24882
- } catch (err2) {
24883
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24935
+ if (targetAgent === "test_engineer") {
24936
+ const seedTaskId = getSeedTaskId(session);
24937
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
24938
+ otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
24939
+ }
24940
+ for (const [
24941
+ taskId,
24942
+ state
24943
+ ] of otherSession.taskWorkflowStates) {
24944
+ if (state === "reviewer_run") {
24945
+ try {
24946
+ advanceTaskState(otherSession, taskId, "tests_run");
24947
+ } catch (err2) {
24948
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
24949
+ }
24884
24950
  }
24885
24951
  }
24886
24952
  }
@@ -25293,9 +25359,11 @@ __export(exports_state, {
25293
25359
  setSessionEnvironment: () => setSessionEnvironment,
25294
25360
  resetSwarmState: () => resetSwarmState,
25295
25361
  rehydrateSessionFromDisk: () => rehydrateSessionFromDisk,
25362
+ recordStageBCompletion: () => recordStageBCompletion,
25296
25363
  recordPhaseAgentDispatch: () => recordPhaseAgentDispatch,
25297
25364
  pruneOldWindows: () => pruneOldWindows,
25298
25365
  isCouncilGateActive: () => isCouncilGateActive,
25366
+ hasBothStageBCompletions: () => hasBothStageBCompletions,
25299
25367
  hasActiveTurboMode: () => hasActiveTurboMode,
25300
25368
  hasActiveFullAuto: () => hasActiveFullAuto,
25301
25369
  getTaskState: () => getTaskState,
@@ -25376,6 +25444,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
25376
25444
  qaSkipCount: 0,
25377
25445
  qaSkipTaskIds: [],
25378
25446
  taskWorkflowStates: new Map,
25447
+ stageBCompletion: new Map,
25379
25448
  taskCouncilApproved: new Map,
25380
25449
  lastGateOutcome: null,
25381
25450
  declaredCoderScope: null,
@@ -25491,6 +25560,9 @@ function ensureAgentSession(sessionId, agentName, directory) {
25491
25560
  if (!session.taskWorkflowStates) {
25492
25561
  session.taskWorkflowStates = new Map;
25493
25562
  }
25563
+ if (!session.stageBCompletion) {
25564
+ session.stageBCompletion = new Map;
25565
+ }
25494
25566
  if (!session.taskCouncilApproved) {
25495
25567
  session.taskCouncilApproved = new Map;
25496
25568
  }
@@ -25667,6 +25739,27 @@ function getTaskState(session, taskId) {
25667
25739
  }
25668
25740
  return session.taskWorkflowStates.get(taskId) ?? "idle";
25669
25741
  }
25742
+ function recordStageBCompletion(session, taskId, agent) {
25743
+ if (!isValidTaskId2(taskId))
25744
+ return;
25745
+ if (!session.stageBCompletion) {
25746
+ session.stageBCompletion = new Map;
25747
+ }
25748
+ const existing = session.stageBCompletion.get(taskId);
25749
+ if (existing) {
25750
+ existing.add(agent);
25751
+ } else {
25752
+ session.stageBCompletion.set(taskId, new Set([agent]));
25753
+ }
25754
+ }
25755
+ function hasBothStageBCompletions(session, taskId) {
25756
+ if (!isValidTaskId2(taskId))
25757
+ return false;
25758
+ const completions = session.stageBCompletion?.get(taskId);
25759
+ if (!completions)
25760
+ return false;
25761
+ return completions.has("reviewer") && completions.has("test_engineer");
25762
+ }
25670
25763
  function derivePlanIdFromPlan(plan) {
25671
25764
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
25672
25765
  }
@@ -83764,7 +83857,7 @@ function matchesTier3Pattern(files) {
83764
83857
  }
83765
83858
  return false;
83766
83859
  }
83767
- function checkReviewerGate(taskId, workingDirectory) {
83860
+ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = false) {
83768
83861
  try {
83769
83862
  if (hasActiveTurboMode()) {
83770
83863
  const resolvedDir2 = workingDirectory;
@@ -83823,6 +83916,9 @@ function checkReviewerGate(taskId, workingDirectory) {
83823
83916
  if (state === "tests_run" || state === "complete") {
83824
83917
  return { blocked: false, reason: "" };
83825
83918
  }
83919
+ if (stageBParallelEnabled && hasBothStageBCompletions(session, taskId)) {
83920
+ return { blocked: false, reason: "" };
83921
+ }
83826
83922
  }
83827
83923
  if (validSessionCount === 0) {
83828
83924
  return { blocked: false, reason: "" };
@@ -83897,7 +83993,14 @@ function checkReviewerGate(taskId, workingDirectory) {
83897
83993
  }
83898
83994
  }
83899
83995
  async function checkReviewerGateWithScope(taskId, workingDirectory) {
83900
- const result = checkReviewerGate(taskId, workingDirectory);
83996
+ let stageBParallelEnabled = false;
83997
+ if (workingDirectory) {
83998
+ try {
83999
+ const cfg = await loadPluginConfig(workingDirectory);
84000
+ stageBParallelEnabled = cfg.parallelization?.stageB?.parallel?.enabled === true;
84001
+ } catch {}
84002
+ }
84003
+ const result = checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled);
83901
84004
  const scopeWarning = await validateDiffScope(taskId, workingDirectory).catch(() => null);
83902
84005
  if (!scopeWarning)
83903
84006
  return result;
@@ -1,2 +1,12 @@
1
1
  export { createNoopDispatcher, type NoopDispatcher, } from './noop-dispatcher.js';
2
+ export { createParallelDispatcher, type ParallelDispatcher, } from './parallel-dispatcher.js';
2
3
  export type { DispatchDecision, DispatcherConfig, RunSlot, TaskExecutionHandle, } from './types.js';
4
+ import { type NoopDispatcher } from './noop-dispatcher.js';
5
+ import { type ParallelDispatcher } from './parallel-dispatcher.js';
6
+ import type { DispatcherConfig } from './types.js';
7
+ /**
8
+ * Factory: returns the appropriate dispatcher based on config.
9
+ * When disabled or maxConcurrentTasks <= 1, returns the no-op dispatcher.
10
+ * When enabled and maxConcurrentTasks > 1, returns the parallel dispatcher.
11
+ */
12
+ export declare function createDispatcher(config: DispatcherConfig): NoopDispatcher | ParallelDispatcher;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Parallel dispatcher — enabled path for Stage B concurrent task execution.
3
+ *
4
+ * Uses p-limit for bounded concurrency. Returns 'dispatch' when a slot is
5
+ * available, 'defer' when at max capacity, 'reject' when disabled.
6
+ *
7
+ * PR 2: implements the enabled dispatcher alongside the existing NoopDispatcher.
8
+ * No production code imports this directly — it is wired in via createDispatcher().
9
+ */
10
+ import type { DispatchDecision, DispatcherConfig, TaskExecutionHandle } from './types.js';
11
+ export interface ParallelDispatcher {
12
+ readonly config: DispatcherConfig;
13
+ dispatch(taskId: string): DispatchDecision;
14
+ handles(): TaskExecutionHandle[];
15
+ releaseSlot(slotId: string): void;
16
+ shutdown(): void;
17
+ }
18
+ export declare function createParallelDispatcher(config: DispatcherConfig): ParallelDispatcher;
package/dist/state.d.ts CHANGED
@@ -103,6 +103,13 @@ export interface AgentSessionState {
103
103
  qaSkipTaskIds: string[];
104
104
  /** Per-task workflow state — taskId → current state */
105
105
  taskWorkflowStates: Map<string, TaskWorkflowState>;
106
+ /**
107
+ * PR 2 Stage B barrier: per-task set of completed Stage B agents.
108
+ * Order-independent — either 'reviewer' or 'test_engineer' may complete first.
109
+ * When both are present, the task may advance to tests_run regardless of order.
110
+ * Only populated when parallelization.stageB.parallel.enabled = true.
111
+ */
112
+ stageBCompletion?: Map<string, Set<'reviewer' | 'test_engineer'>>;
106
113
  /** v6.71+ Council mode: per-task council verdict, recorded by delegation-gate when convene_council resolves. */
107
114
  taskCouncilApproved?: Map<string, {
108
115
  verdict: 'APPROVE' | 'REJECT' | 'CONCERNS';
@@ -350,6 +357,25 @@ export declare function advanceTaskState(session: AgentSessionState, taskId: str
350
357
  * @returns Current task workflow state
351
358
  */
352
359
  export declare function getTaskState(session: AgentSessionState, taskId: string): TaskWorkflowState;
360
+ /**
361
+ * PR 2 Stage B barrier: record that a Stage B agent has completed for a task.
362
+ * Order-independent — either 'reviewer' or 'test_engineer' may complete first.
363
+ * Initializes the per-task set on first write.
364
+ *
365
+ * @param session - The agent session state
366
+ * @param taskId - The task identifier
367
+ * @param agent - Which Stage B agent completed ('reviewer' or 'test_engineer')
368
+ */
369
+ export declare function recordStageBCompletion(session: AgentSessionState, taskId: string, agent: 'reviewer' | 'test_engineer'): void;
370
+ /**
371
+ * PR 2 Stage B barrier: returns true iff both 'reviewer' and 'test_engineer' have
372
+ * been recorded for the given task in this session.
373
+ *
374
+ * @param session - The agent session state
375
+ * @param taskId - The task identifier
376
+ * @returns true when both Stage B agents have completed
377
+ */
378
+ export declare function hasBothStageBCompletions(session: AgentSessionState, taskId: string): boolean;
353
379
  /**
354
380
  * Returns true iff council is authoritative for the current plan.
355
381
  *
@@ -49,12 +49,14 @@ export interface ReviewerGateResult {
49
49
  * both reviewer delegation and test_engineer runs have been recorded.
50
50
  * @param taskId - The task ID to check gate state for
51
51
  * @param workingDirectory - Optional working directory for plan.json fallback
52
+ * @param stageBParallelEnabled - When true, also accept both-markers-present as passing (PR 2 barrier)
52
53
  * @returns ReviewerGateResult indicating whether the gate is blocked
53
54
  */
54
- export declare function checkReviewerGate(taskId: string, workingDirectory?: string): ReviewerGateResult;
55
+ export declare function checkReviewerGate(taskId: string, workingDirectory?: string, stageBParallelEnabled?: boolean): ReviewerGateResult;
55
56
  /**
56
57
  * Wrapper around checkReviewerGate that appends a diff-scope advisory warning.
57
58
  * Keeps checkReviewerGate synchronous for backward compatibility.
59
+ * Also resolves the PR 2 stageB.parallel.enabled flag from config.
58
60
  * @param taskId - The task ID to check gate state for
59
61
  * @param workingDirectory - Optional working directory for plan.json fallback
60
62
  * @returns ReviewerGateResult with optional scope warning appended to reason
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.76.0",
3
+ "version": "6.77.0",
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",