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.
- package/dist/cli/index.js +1530 -1398
- package/dist/index.js +3221 -2946
- package/dist/turbo/lean/runner.d.ts +36 -7
- package/dist/turbo/lean/state-lock.d.ts +59 -0
- package/dist/utils/swarm-artifact-cache.d.ts +23 -0
- package/package.json +1 -1
|
@@ -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
|
-
/**
|
|
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
|
-
*
|
|
330
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
342
|
-
*
|
|
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
|
+
"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",
|