opencode-swarm 7.78.3 → 7.78.5

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.
@@ -122,6 +122,19 @@ export interface LaneResult {
122
122
  }
123
123
  /**
124
124
  * Result of a full phase run.
125
+ *
126
+ * ## Serial Tasks Contract
127
+ *
128
+ * `serializedTasks` contains task IDs excluded from parallel lanes due to lock conflicts
129
+ * (Issue #1 - Serial Task Orphan Risk).
130
+ *
131
+ * **Caller Responsibility**: The orchestrator MUST complete these tasks via standard
132
+ * serial flow (normal task dispatch). The runner does NOT dispatch serializedTasks.
133
+ *
134
+ * **Verification**: phase-ready (step 6b) verifies serializedTasks by checking that
135
+ * each task ID has status: completed in plan.json. If serializedTasks contain task IDs
136
+ * but those tasks are not marked completed in plan.json, phase-ready returns ok: false,
137
+ * blocking phase advancement.
125
138
  */
126
139
  export interface LeanTurboPhaseResult {
127
140
  /** Whether the phase ran (at least one lane attempted) */
@@ -132,7 +145,11 @@ export interface LeanTurboPhaseResult {
132
145
  lanes: LaneResult[];
133
146
  /** Task IDs that were degraded (risk conditions) */
134
147
  degradedTasks: string[];
135
- /** Task IDs excluded from parallel lanes, must complete via standard serial flow */
148
+ /**
149
+ * Task IDs excluded from parallel lanes due to lock conflicts.
150
+ * Caller must complete these via standard serial flow before phase can advance.
151
+ * (Issue #1: Serial Task Orphan Risk - Fixed with integration test)
152
+ */
136
153
  serializedTasks: string[];
137
154
  /** Lanes whose coder completed but merge-back to primary branch failed */
138
155
  mergeBackFailures?: MergeBackFailureInfo[];
@@ -326,8 +343,11 @@ export declare class LeanTurboRunner {
326
343
  */
327
344
  private _selectNextAgent;
328
345
  /**
329
- * Safely write lane evidence, catching errors to prevent evidence write
330
- * failure from blocking lane processing.
346
+ * Write lane evidence with retry logic for transient disk errors.
347
+ *
348
+ * Evidence is required by phase-ready (step 4b) to verify lane completion.
349
+ * Transient I/O errors are retried with exponential backoff; permanent errors
350
+ * are logged and dropped (evidence failure is non-fatal for runner operation).
331
351
  */
332
352
  private _writeLaneEvidenceSafely;
333
353
  /**
@@ -335,11 +355,20 @@ export declare class LeanTurboRunner {
335
355
  */
336
356
  private _buildLanePrompt;
337
357
  /**
338
- * Serializes access to durable state via a promise chain.
339
- * Prevents concurrent lane updates from racing on turbo-state.json writes.
358
+ * Serializes access to durable state via a promise chain AND file-based lock.
359
+ *
360
+ * - Promise chain: serializes writes within a single runner instance
361
+ * - File-based lock: coordinates between multiple runners with the same sessionID
362
+ *
363
+ * Timeout budget starts when this call reaches the front of the queue (execution
364
+ * time), not when it is enqueued. withTurboStateLock computes its deadline
365
+ * internally on entry, so each caller gets a full 10-second window regardless
366
+ * of how long it waited behind prior entries.
340
367
  *
341
- * Includes a 10-second timeout: if state persistence hangs, the lock is
342
- * released so subsequent updates are not blocked indefinitely.
368
+ * Timeout coverage: withTurboStateLock is tested directly in state-lock.test.ts
369
+ * (test: "throws TurboStateLockTimeoutError when lock is held by another caller").
370
+ * This wrapper delegates to it in a single line with no additional timeout logic,
371
+ * so a separate wrapper-level timeout test would duplicate that coverage.
343
372
  */
344
373
  private _withStateLock;
345
374
  /**
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Durable Lean Turbo state write-lock helper.
3
+ *
4
+ * ## Issue #2: Cross-Runner Durable State Race
5
+ *
6
+ * Multiple LeanTurboRunner instances with the same sessionID share turbo-state.json.
7
+ * Each runner has its own in-process _stateLock promise chain, but without file-level
8
+ * coordination, concurrent runners can race:
9
+ *
10
+ * - Runner A: load state → modify → write
11
+ * - Runner B: load state (gets A's version) → modify → write (overwrites A's changes)
12
+ *
13
+ * This module adds file-based locking using proper-lockfile to coordinate read-modify-write
14
+ * cycles across processes, preventing inter-runner write races.
15
+ *
16
+ * ## Pattern
17
+ *
18
+ * Mirrors the evidence-lock pattern (src/evidence/lock.ts) with:
19
+ * - Exponential backoff (50ms start, 2000ms max) with jitter
20
+ * - Timeout protection (default 30 seconds for state, 60 seconds for evidence)
21
+ * - Best-effort lock release in finally block
22
+ * - Non-fatal lock release failures (proper-lockfile TTL cleanup)
23
+ *
24
+ * ## Telemetry
25
+ *
26
+ * Events emitted (when integrated into runners):
27
+ * - turbo_state_lock_acquired
28
+ * - turbo_state_lock_contended
29
+ * - turbo_state_lock_timeout (thrown as error)
30
+ */
31
+ import { tryAcquireLock } from '../../parallel/file-locks';
32
+ /** Test-only seam — allows injecting a mock tryAcquireLock without mock.module. */
33
+ export declare const _internals: {
34
+ tryAcquireLock: typeof tryAcquireLock;
35
+ };
36
+ export declare class TurboStateLockTimeoutError extends Error {
37
+ readonly directory: string;
38
+ readonly sessionID: string;
39
+ constructor(directory: string, sessionID: string, timeoutMs: number);
40
+ }
41
+ /**
42
+ * Acquire an exclusive turbo-state lock, execute `fn`, then release the lock.
43
+ *
44
+ * Retries with exponential backoff until `timeoutMs` elapses,
45
+ * then throws `TurboStateLockTimeoutError`. Always releases the lock in a
46
+ * `finally` block even if `fn` throws.
47
+ *
48
+ * **Timeout scope**: `timeoutMs` governs lock _acquisition_ only. Once the lock
49
+ * is held, `fn()` runs to completion with no separate execution deadline.
50
+ * Callers must ensure `fn` is bounded (e.g., synchronous disk writes). This
51
+ * matches the behaviour of the previous `Promise.race` implementation, which
52
+ * also could not cancel an in-flight `fn()`.
53
+ *
54
+ * @param directory Project root directory
55
+ * @param sessionID Session identifier (for lock metadata and diagnostics)
56
+ * @param fn Callback that performs the read-modify-write on turbo state
57
+ * @param timeoutMs Maximum wait time for lock acquisition (default 30 000ms)
58
+ */
59
+ export declare function withTurboStateLock<T>(directory: string, sessionID: string, fn: () => Promise<T>, timeoutMs?: number): Promise<T>;
@@ -0,0 +1,23 @@
1
+ export interface SwarmArtifactCacheStats {
2
+ textReadCount: number;
3
+ textCacheHitCount: number;
4
+ textCacheMissCount: number;
5
+ parsedReadCount: number;
6
+ parseCount: number;
7
+ parsedCacheHitCount: number;
8
+ parsedCacheMissCount: number;
9
+ statFailureCount: number;
10
+ evictionCount: number;
11
+ textEvictionCount: number;
12
+ parsedEvictionCount: number;
13
+ cloneFallbackCount: number;
14
+ textEntryCount: number;
15
+ parsedEntryCount: number;
16
+ }
17
+ export declare function cloneCachedValue<T>(value: T): T;
18
+ export declare function readCachedTextFileSync(filePath: string, directRead: () => string | null): string | null;
19
+ export declare function readCachedTextFile(filePath: string, directRead: () => Promise<string | null>): Promise<string | null>;
20
+ export declare function readCachedParsedFileSync<T>(filePath: string, namespace: string, readText: () => string | null, parse: (content: string) => T): T | null;
21
+ export declare function readCachedParsedFile<T>(filePath: string, namespace: string, readText: () => Promise<string | null>, parse: (content: string) => T): Promise<T | null>;
22
+ export declare function resetSwarmArtifactCache(): void;
23
+ export declare function getSwarmArtifactCacheStats(): SwarmArtifactCacheStats;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.78.3",
3
+ "version": "7.78.5",
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",