opencode-swarm 7.95.0 → 7.97.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.
Files changed (26) hide show
  1. package/.opencode/skills/codebase-review-swarm/references/review-protocol-v8.2.md +1 -1
  2. package/.opencode/skills/council/SKILL.md +4 -2
  3. package/.opencode/skills/deep-dive/SKILL.md +3 -1
  4. package/.opencode/skills/deep-research/SKILL.md +13 -7
  5. package/.opencode/skills/execute/SKILL.md +2 -2
  6. package/.opencode/skills/pre-phase-briefing/SKILL.md +21 -5
  7. package/.opencode/skills/swarm-pr-feedback/SKILL.md +19 -7
  8. package/.opencode/skills/swarm-pr-review/SKILL.md +3 -1
  9. package/dist/cli/{config-doctor-n5w1jexx.js → config-doctor-h1xrvq83.js} +2 -2
  10. package/dist/cli/{evidence-summary-service-mr9sns2d.js → evidence-summary-service-g4x5e3e3.js} +1 -1
  11. package/dist/cli/{guardrail-explain-1ad236sh.js → guardrail-explain-t6svvtmn.js} +6 -6
  12. package/dist/cli/{guardrail-log-0v924awy.js → guardrail-log-9yyeccv5.js} +3 -3
  13. package/dist/cli/{index-y1z6yaq4.js → index-4c5jpmn9.js} +3 -3
  14. package/dist/cli/{index-0xkf7xn2.js → index-a9ghr5cx.js} +2 -2
  15. package/dist/cli/{index-c7xgx041.js → index-b223mczb.js} +1 -1
  16. package/dist/cli/{index-7d38zerp.js → index-byb9tgay.js} +7 -7
  17. package/dist/cli/{index-5zwr6xz4.js → index-rdc6nvmw.js} +1 -1
  18. package/dist/cli/{index-wz5ex5q7.js → index-sgdr2e4n.js} +17 -13
  19. package/dist/cli/{index-w015kchv.js → index-tqbb2jx6.js} +2 -2
  20. package/dist/cli/{index-tht37da9.js → index-xx3sv77e.js} +1 -1
  21. package/dist/cli/index.js +5 -5
  22. package/dist/cli/{schema-qz5mgmz3.js → schema-a8fneygm.js} +1 -1
  23. package/dist/index.js +139 -73
  24. package/dist/plan/checkpoint.d.ts +16 -2
  25. package/dist/tools/dispatch-lanes.d.ts +18 -0
  26. package/package.json +1 -1
@@ -128,7 +128,7 @@ Before writing under `.swarm/`, verify `.swarm/` is ignored or locally excluded.
128
128
  1. Run 0A alone.
129
129
  2. After 0A, run 0B and 0C through `dispatch_lanes_async` only if the repository is large enough to benefit. While those lanes run, the Architect continues deterministic inventory work that does not depend on their results.
130
130
  3. After 0B, run 0D and 0E through `dispatch_lanes_async` only if 0E can leave `linked_claims` blank for Architect linking in 0J. Otherwise run 0D before 0E.
131
- 4. Preferred async batch order: batch 1 = 0F and 0G; batch 2 = 0H and 0I. Never exceed two Phase 0 agents.
131
+ 4. Preferred async batch order: batch 1 = 0F and 0G; batch 2 = 0H and 0I. Never exceed two Phase 0 agents — Phase 0 inventory units (0A→0J) form a largely sequential dependency chain, so concurrency is intentionally capped at 2 to respect that ordering rather than scaled toward the 8-lane dispatch limit.
132
132
  5. Run 0F after 0E when possible.
133
133
  6. Run 0G after 0B and 0C.
134
134
  7. Run 0H and 0I after 0B and 0C.
@@ -114,8 +114,10 @@ Do NOT share other agents' responses at this stage.
114
114
  (synthesis outline, citation normalization, disagreement categories). Only
115
115
  use `wait: true` if lanes are still pending and no more independent work
116
116
  remains. All three lanes must be settled before proceeding to synthesis.
117
- If `dispatch_lanes_async` is unavailable, use blocking parallel dispatch
118
- and record that async advisory lanes were unavailable. The
117
+ If `dispatch_lanes_async` is unavailable, use blocking `dispatch_lanes`
118
+ and record that async advisory lanes were unavailable; do not substitute
119
+ per-agent Task calls for this fallback unless lane tools are unavailable and
120
+ you explicitly verify equivalent agent type, prompt, scope, and isolation. The
119
121
  `round1Responses` array will contain entries with `memberId` of
120
122
  `council_generalist`, `council_skeptic`, and `council_domain_expert` and
121
123
  `role` of `generalist`, `skeptic`, and `domain_expert` respectively. If
@@ -18,7 +18,7 @@ Read-only deep audit of a specified codebase scope using parallel explorer waves
18
18
  Parse the MODE: DEEP_DIVE header to extract:
19
19
  - `scope`: the codebase area to audit (e.g., "auth", "payment flow", "src/hooks/")
20
20
  - `profile`: one of standard | security | ux | architecture | full (default: standard)
21
- - `max_explorers`: integer 1..8 (default: 6, or 8 for full profile)
21
+ - `max_explorers`: integer 1..8 — upper bound on explorer waves (default: 6, or 8 for full profile). This is a CAP, not a fixed count: scale the actual wave size to the resolved scope surface — a trivial scope needs 1–2 explorers, a typical scope 3–5, a large multi-module scope up to the cap — never fix the count in advance.
22
22
  - `output`: markdown | json (default: markdown)
23
23
  - `update_main`: boolean (default: true) — whether to fetch/ff-only main before starting
24
24
  - `allow_dirty`: boolean (default: false) — whether to proceed with uncommitted changes
@@ -52,6 +52,8 @@ Dispatch explorer waves with `dispatch_lanes_async` when available. Each wave co
52
52
  - ~3500 total lines across all files in a mission
53
53
  - Group files by import proximity (files that import each other go in the same mission)
54
54
 
55
+ **Partition is the contract:** missions own non-overlapping file sets — no file appears in two missions — and the union of all missions must cover every file in the Step 2 scope map. Any scope-map file not assigned to a mission is an explicit coverage gap, not an optional skip.
56
+
55
57
  **Profile-based lane selection — each profile activates specific lanes:**
56
58
 
57
59
  | Lane | Template | standard | security | ux | architecture | full |
@@ -118,13 +118,19 @@ include:
118
118
  - `OUTPUT`: claims with evidence refs, contradictions noted, confidence (0–1)
119
119
  - `SKILLS: none`
120
120
 
121
- The sme synthesizes only from the provided evidence — it does not fetch. Before
122
- Step 5, call `collect_lane_results` with `wait: true` for every open synthesis
123
- batch. Collect all completed worker responses into a candidate findings set, each
124
- finding tagged with its subtopic, evidence refs, and the worker's confidence.
125
- Treat missing, stale, cancelled, or failed lanes as explicit coverage gaps. If
126
- `dispatch_lanes_async` is unavailable, use blocking parallel dispatch and record
127
- that async advisory lanes were unavailable.
121
+ The sme synthesizes only from the provided evidence — it does not fetch. While
122
+ synthesis lanes run, poll with `collect_lane_results` without `wait` (or
123
+ `wait: false`) to process completed worker responses as they settle while
124
+ continuing independent architect work between polls. Before Step 5, call
125
+ `collect_lane_results` with `wait: true` for every open synthesis batch only if
126
+ lanes are still pending and no independent work remains. Do not advance to Step 5
127
+ until every synthesis lane is settled. Collect all completed worker responses into
128
+ a candidate findings set, each finding tagged with its subtopic, evidence refs,
129
+ and the worker's confidence. Treat missing, stale, cancelled, or failed lanes as
130
+ explicit coverage gaps. If `dispatch_lanes_async` is unavailable, use blocking
131
+ `dispatch_lanes` and record that async advisory lanes were unavailable; do not
132
+ substitute per-agent Task calls for this fallback unless lane tools are unavailable
133
+ and you explicitly verify equivalent agent type, prompt, scope, and isolation.
128
134
 
129
135
  ## Step 5 — Dual-Reviewer Claim Verification
130
136
 
@@ -204,9 +204,9 @@ This step supplements (not replaces) the existing regression-sweep and test-drif
204
204
 
205
205
  ## Dispatch-lanes empty-output fallback
206
206
 
207
- When an agent dispatched via `dispatch_lanes` returns empty output (0 chars, `output_digest` matching SHA-256 of empty string `e3b0c442...b855`), this indicates a suspected incompatibility between the `dispatch_lanes` code path and that specific agent.
207
+ This fallback applies only to a settled, blocking `dispatch_lanes` result with empty output (0 chars, `output_digest` matching SHA-256 of empty string `e3b0c442...b855`). It does **not** apply to `dispatch_lanes_async` rows that are still pending/running, an early `collect_lane_results` poll, or an async result whose full text is available through `retrieve_lane_output`.
208
208
 
209
- **Do NOT re-dispatch via `dispatch_lanes`** it will fail again (confirmed pattern). Immediately retry the **same agent** via the **Task tool** (`Task(subagent_type=..., prompt=...)`), which uses a different session-creation code path. Record which dispatch mechanism succeeded for debugging.
209
+ For read-only advisory lanes, do **not** jump straight to Task. First re-collect async lanes with `collect_lane_results` (`wait: true` when no independent work remains) and inspect any `output_ref` with `retrieve_lane_output`. If a settled blocking `dispatch_lanes` lane is genuinely empty, prefer retrying the same agent through `dispatch_lanes_async` when promptAsync is available. Use the **Task tool** (`Task(subagent_type=..., prompt=...)`) only as a last-resort equivalent dispatch mechanism after the lane tools are unavailable or have produced a confirmed empty settled result; record the same agent, same prompt, same scope, and which dispatch mechanism succeeded.
210
210
 
211
211
  If the Task tool also returns empty, **then** escalate to substitute review (4-member council without the broken agent) or surface to the user. Never fabricate or substitute a verdict for the missing agent.
212
212
 
@@ -42,15 +42,31 @@ This briefing is a HARD REQUIREMENT for ALL phases. Skipping it is a process vio
42
42
 
43
43
  ### CODEBASE REALITY CHECK (Required Before Speccing or Planning)
44
44
 
45
- Before any spec generation, plan creation, or plan ingestion begins, the Architect must dispatch the Explorer agent in targeted, scoped chunks one per logical area of the codebase referenced by the work (e.g., per module, per hook, per config surface). Each chunk must be explored with full depth rather than a broad surface pass.
45
+ Before any spec generation, plan creation, or plan ingestion begins, the Architect must verify the codebase reality of every item the work references. This runs as **asynchronous, fanned-out Explorer lanes by default**, joined behind a hard settlement gate never as a single blocking explorer call, and never as fire-and-forget.
46
46
 
47
- For each scoped chunk, Explorer must determine:
47
+ **1. Enumerate and partition the references (before dispatch).**
48
+ List every referenced item — file, module, function, API, config surface, and behavioral assumption — named or implied by the spec, the user request, or the plan. Partition them into **non-overlapping** lane assignments. The partition is the contract: no two lanes may share a reference (this prevents duplicated work), and the **union of all lanes must cover every referenced item** (this prevents gaps). Under-specified lane boundaries are the dominant fan-out failure mode — be explicit about what each lane owns.
49
+
50
+ **2. Scale the number of lanes to the size of the referenced surface.**
51
+ - Trivial surface (a single file/function, one logical area) → **1 lane**.
52
+ - Typical phase spanning a few areas → **2–4 lanes**.
53
+ - Large surface (many modules/hooks/config surfaces) → **more lanes, up to the dispatch cap of 8 lanes per batch**.
54
+
55
+ Do not fix the lane count in advance and do not over-spawn: extra lanes on a small surface waste tokens without improving coverage, while too few on a large surface leave gaps. Split by codebase area by default; when the surface is a single dense area, split by check-type instead — one lane for *existence & current state*, one for *assumption correctness & prior-work*.
56
+
57
+ **3. Dispatch asynchronously, then keep working.**
58
+ Dispatch the lanes with `dispatch_lanes_async`, record the returned `batch_id`, and continue **non-dependent** Architect work while they run — digest the retrospective and `user_directives`, review the spec/plan text for internal consistency, check governance/QA-gate config and the obligation ledger, and prepare the plan skeleton / task decomposition. This is dispatch-and-keep-busy, not fire-and-forget. Poll with `collect_lane_results` (wait omitted or false) to process settled lanes incrementally, or join with `wait: true` once independent work is exhausted.
59
+
60
+ Each lane must be given: its objective, its named (disjoint) reference subset, the fixed REALITY-CHECK output format below, and clear boundaries. Lanes are read-only — they cannot `declare_scope` or mutate the worktree.
61
+
62
+ **4. For each referenced item, the lane must determine:**
48
63
  - Does this file/module/function already exist?
49
64
  - If it exists, what is its current state? Does it already implement any part of what the plan or spec describes?
50
65
  - Is the plan's or user's assumption about the current state accurate? Flag any discrepancy between what is expected and what actually exists.
51
66
  - Has any portion of this work already been applied (partially or fully) in a prior session or commit?
52
67
 
53
- Explorer outputs a CODEBASE REALITY REPORT before any other agent proceeds. The report must list every referenced item with one of:
68
+ **5. Hard settlement gate (join before any downstream work).**
69
+ The Architect synthesizes the lane outputs into a single CODEBASE REALITY REPORT. The report must list every referenced item with one of:
54
70
  NOT STARTED | PARTIALLY DONE | ALREADY COMPLETE | ASSUMPTION INCORRECT
55
71
 
56
72
  Format:
@@ -59,11 +75,11 @@ Format:
59
75
  ✗ src/services/status-service.ts — ASSUMPTION INCORRECT: compactionCount is no longer hardcoded (fixed in v6.29.1)
60
76
  ✓ src/config/evidence-schema.ts — confirmed phase_number min(1)
61
77
 
62
- No implementation agent (coder, reviewer, test-engineer) may begin until this report is finalized.
78
+ No spec finalization, plan generation, plan ingestion, `declare_scope`, or implementation-agent dispatch (coder, reviewer, test-engineer) may begin until ALL lanes in the batch are settled (`collect_lane_results` reports `all_settled`) AND this report is finalized. A lane that is missing, failed, or timed out is an explicit coverage gap, not a pass: mark the affected references BLOCKED or SKIPPED_WITH_REASON and resolve them before proceeding — never silently continue. Async dispatch changes *when* the Architect waits, never *whether* the gate holds.
63
79
 
64
80
  This check fires automatically in:
65
81
  - MODE: SPECIFY — before explorer dispatch for context (step 2)
66
82
  - MODE: PLAN — before plan generation or validation
67
83
  - EXTERNAL PLAN IMPORT PATH — before parsing the provided plan
68
84
 
69
- GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check.
85
+ GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check. A trivial single-area surface stays a single lane rather than being force-fanned.
@@ -198,15 +198,27 @@ If a source is unavailable, retry with alternative access paths. If unavailable
198
198
  After the complete feedback ledger exists and before editing, use
199
199
  `dispatch_lanes_async` when available for independent read-only verification lanes:
200
200
  comment classification, CI/log root-cause inspection, test impact mapping,
201
- release/docs claim checks, and stale-branch/conflict analysis. Record each
202
- returned `batch_id`, then continue only ledger-safe architect work: normalize
203
- feedback IDs, gather deterministic PR metadata, prepare reproduction commands,
204
- and plan likely fix groups. Do not edit, close items, or mark feedback resolved
205
- from running lanes.
201
+ release/docs claim checks, and stale-branch/conflict analysis. Partition the
202
+ ledger so each `FB-###` item is owned by exactly one verification lane and the
203
+ union of lanes covers the entire ledger no feedback item may be left
204
+ unassigned to a lane; state each lane's owned `FB-###` range in its prompt. Scale
205
+ the lane count to the ledger size: a 1–3 item round may use a single combined
206
+ lane, while a large multi-round intake may warrant one lane per category above.
207
+ Cap each `dispatch_lanes_async` batch at 8 lanes (`MAX_LANES`); if the ledger
208
+ needs more than 8 verification lanes, dispatch in sequential batches and settle
209
+ each batch's COVERAGE GATE before the next — do not over-spawn lanes for a
210
+ trivial round. Record each returned `batch_id`, then continue only ledger-safe
211
+ architect work: normalize feedback IDs, gather deterministic PR metadata, prepare
212
+ reproduction commands, and plan likely fix groups. Do not edit, close items, or
213
+ mark feedback resolved from running lanes.
206
214
 
207
215
  Before the Verification step can mark any item `RESOLVED`, `DISPROVED`,
208
- `PRE_EXISTING`, `NEEDS_MORE_EVIDENCE`, or `NEEDS_USER_DECISION`, call
209
- `collect_lane_results` with `wait: true` for every open verification batch.
216
+ `PRE_EXISTING`, `NEEDS_MORE_EVIDENCE`, or `NEEDS_USER_DECISION`, every open
217
+ verification batch must be fully settled. Poll with `collect_lane_results` (wait
218
+ omitted or `false`) to process settled lanes incrementally — clustering confirmed
219
+ items and pre-reading files for settled findings while ledger-safe work remains —
220
+ then issue a final `collect_lane_results` with `wait: true` per batch once
221
+ independent work is exhausted, to confirm every lane is settled.
210
222
  Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed
211
223
  before marking any item RESOLVED/DISPROVED/PRE_EXISTING. Apply the COVERAGE GATE:
212
224
  retry failed lanes (max 2), deploy a verified equivalent alternative (same agent
@@ -517,7 +517,7 @@ Before Phase 4 or synthesis, all base lanes must be settled. `dispatch_lanes_asy
517
517
 
518
518
  For ANY lane that failed (either mode):
519
519
  1. **Retry** (max 2 attempts) with materially different parameters — different session, different prompt decomposition, or blocking `dispatch_lanes`.
520
- 2. If retries fail, **deploy an equivalent alternative** and **verify equivalence**: same agent type, same prompt, same scope, same isolation. State the equivalence verification explicitly. Different dispatch mechanism (e.g., Task tool instead of `dispatch_lanes_async`) IS acceptable when these criteria are met. Use `retrieve_lane_output` to inspect the full artifact before declaring equivalence or failure.
520
+ 2. If retries fail, **deploy an equivalent alternative** and **verify equivalence**: same agent type, same prompt, same scope, same isolation. State the equivalence verification explicitly. Task is not an early-poll or empty-partial-output fallback; use it only as a last-resort equivalent dispatch mechanism after `dispatch_lanes_async` is unavailable or a settled lane result is confirmed failed/empty, and after blocking `dispatch_lanes` is unavailable or inappropriate. Use `retrieve_lane_output` to inspect the full artifact before declaring equivalence or failure.
521
521
  3. If no equivalent alternative can be verified, **report to the user as INCOMPLETE**. Present partial findings from successful lanes alongside the INCOMPLETE verdict. The user decides whether to accept reduced coverage. The architect NEVER makes that call.
522
522
 
523
523
  ### Candidate extraction via parser
@@ -558,6 +558,8 @@ in the same batch unless intentionally replacing that exact lane before dispatch
558
558
 
559
559
  Explorers optimize for recall. Over-reporting is expected. Explorers produce candidates only.
560
560
 
561
+ The six lanes are a fixed **check-type** partition (correctness / security / deps / docs / tests / performance), not an area partition: the count is intentionally constant — every PR needs all six review dimensions — and the lanes deliberately overlap by file, each receiving the same diff via `common_prompt` and viewing it through a different lens. This is the deliberate exception to surface-scaled fan-out: the base wave is a fixed six by design, never collapsed or expanded with the size of the change. Coverage is guaranteed by the six dimensions each reading the whole diff, not by partitioning files across lanes — so the disjoint-partition rule that governs area-split fan-outs does not apply to these check-type lanes.
562
+
561
563
  | Lane | Focus | Required checks |
562
564
  |---|---|---|
563
565
  | Lane 1: Correctness and edge cases | Logic errors, null/undefined handling, incorrect operators, async ordering, races, off-by-one, error paths | input domain, nullability, async/await, loop termination, exception behavior, backward compatibility |
@@ -12,8 +12,8 @@ import {
12
12
  shouldRunOnStartup,
13
13
  writeBackupArtifact,
14
14
  writeDoctorArtifact
15
- } from "./index-tht37da9.js";
16
- import"./index-w015kchv.js";
15
+ } from "./index-xx3sv77e.js";
16
+ import"./index-tqbb2jx6.js";
17
17
  import"./index-5e4e2hvv.js";
18
18
  import"./index-p0arc26j.js";
19
19
  import"./index-zgwm4ryv.js";
@@ -6,7 +6,7 @@ import {
6
6
  loadPlanJsonOnly,
7
7
  mergeDurableGateEntriesFromEvidence,
8
8
  readDurableGateEvidence
9
- } from "./index-y1z6yaq4.js";
9
+ } from "./index-4c5jpmn9.js";
10
10
  import"./index-mh1ej70w.js";
11
11
  import"./index-kv4dd5c5.js";
12
12
  import"./index-gn8n22th.js";
@@ -1,15 +1,15 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-0xkf7xn2.js";
5
- import"./index-wz5ex5q7.js";
6
- import"./index-c7xgx041.js";
4
+ } from "./index-a9ghr5cx.js";
5
+ import"./index-sgdr2e4n.js";
6
+ import"./index-b223mczb.js";
7
7
  import"./index-2a6ppa65.js";
8
8
  import"./index-fjxjb66n.js";
9
9
  import"./index-hb10a2g8.js";
10
- import"./index-tht37da9.js";
11
- import"./index-w015kchv.js";
12
- import"./index-y1z6yaq4.js";
10
+ import"./index-xx3sv77e.js";
11
+ import"./index-tqbb2jx6.js";
12
+ import"./index-4c5jpmn9.js";
13
13
  import"./index-adz3nk9b.js";
14
14
  import"./index-v4fcn4tr.js";
15
15
  import"./index-mh1ej70w.js";
@@ -1,9 +1,9 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailLog
4
- } from "./index-5zwr6xz4.js";
5
- import"./index-c7xgx041.js";
6
- import"./index-w015kchv.js";
4
+ } from "./index-rdc6nvmw.js";
5
+ import"./index-b223mczb.js";
6
+ import"./index-tqbb2jx6.js";
7
7
  import"./index-5e4e2hvv.js";
8
8
  import"./index-p0arc26j.js";
9
9
  import"./index-zgwm4ryv.js";
@@ -2236,7 +2236,7 @@ async function loadPlan(directory) {
2236
2236
  await rebuildPlan(directory, rebuilt, {
2237
2237
  reason: "ledger_hash_mismatch_recovery"
2238
2238
  });
2239
- warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/SWARM_PLAN.md if it exists.");
2239
+ warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/plan-export/SWARM_PLAN.md if it exists.");
2240
2240
  return rebuilt;
2241
2241
  }
2242
2242
  } catch (replayError) {
@@ -2259,7 +2259,7 @@ async function loadPlan(directory) {
2259
2259
  return approved.plan;
2260
2260
  }
2261
2261
  } catch {}
2262
- warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
2262
+ warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/plan-export/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
2263
2263
  }
2264
2264
  }
2265
2265
  }
@@ -2306,7 +2306,7 @@ async function loadPlan(directory) {
2306
2306
  return validated;
2307
2307
  }
2308
2308
  } catch (error2) {
2309
- warn(`[loadPlan] plan.json validation failed: ${error2 instanceof Error ? error2.message : String(error2)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/SWARM_PLAN.md for a checkpoint.`);
2309
+ warn(`[loadPlan] plan.json validation failed: ${error2 instanceof Error ? error2.message : String(error2)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/plan-export/SWARM_PLAN.md for a checkpoint.`);
2310
2310
  let rawPlanId = null;
2311
2311
  try {
2312
2312
  const rawParsed = JSON.parse(planJsonContent);
@@ -12,14 +12,14 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-wz5ex5q7.js";
15
+ } from "./index-sgdr2e4n.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
19
19
  isInDeclaredScope,
20
20
  redactPath,
21
21
  redactShellCommand
22
- } from "./index-c7xgx041.js";
22
+ } from "./index-b223mczb.js";
23
23
 
24
24
  // src/services/guardrail-explain-service.ts
25
25
  import path from "path";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  stripKnownSwarmPrefix
4
- } from "./index-w015kchv.js";
4
+ } from "./index-tqbb2jx6.js";
5
5
  import {
6
6
  init_logger,
7
7
  warn
@@ -1,10 +1,10 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-0xkf7xn2.js";
4
+ } from "./index-a9ghr5cx.js";
5
5
  import {
6
6
  handleGuardrailLog
7
- } from "./index-5zwr6xz4.js";
7
+ } from "./index-rdc6nvmw.js";
8
8
  import {
9
9
  COMMAND_REGISTRY,
10
10
  SWARM_COMMAND_TOOL_ALLOWLIST,
@@ -78,18 +78,18 @@ import {
78
78
  handleWriteRetroCommand,
79
79
  normalizeSwarmCommandInput,
80
80
  resolveCommand
81
- } from "./index-wz5ex5q7.js";
82
- import"./index-c7xgx041.js";
81
+ } from "./index-sgdr2e4n.js";
82
+ import"./index-b223mczb.js";
83
83
  import"./index-2a6ppa65.js";
84
84
  import"./index-fjxjb66n.js";
85
85
  import"./index-hb10a2g8.js";
86
- import"./index-tht37da9.js";
86
+ import"./index-xx3sv77e.js";
87
87
  import {
88
88
  AGENT_TOOL_MAP,
89
89
  ORCHESTRATOR_NAME,
90
90
  stripKnownSwarmPrefix
91
- } from "./index-w015kchv.js";
92
- import"./index-y1z6yaq4.js";
91
+ } from "./index-tqbb2jx6.js";
92
+ import"./index-4c5jpmn9.js";
93
93
  import"./index-adz3nk9b.js";
94
94
  import"./index-v4fcn4tr.js";
95
95
  import"./index-mh1ej70w.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  redactPath,
4
4
  redactShellCommand
5
- } from "./index-c7xgx041.js";
5
+ } from "./index-b223mczb.js";
6
6
 
7
7
  // src/services/guardrail-log-service.ts
8
8
  import * as fs from "fs/promises";
@@ -57,7 +57,7 @@ import {
57
57
  readDoctorArtifact,
58
58
  removeStraySwarmDir,
59
59
  runConfigDoctor
60
- } from "./index-tht37da9.js";
60
+ } from "./index-xx3sv77e.js";
61
61
  import {
62
62
  AGENT_TOOL_MAP,
63
63
  ALL_SUBAGENT_NAMES,
@@ -70,7 +70,7 @@ import {
70
70
  TOOL_NAME_SET,
71
71
  resolveExternalSkillsConfig,
72
72
  stripKnownSwarmPrefix
73
- } from "./index-w015kchv.js";
73
+ } from "./index-tqbb2jx6.js";
74
74
  import {
75
75
  MAX_TRANSIENT_RETRIES,
76
76
  PlanSchema,
@@ -114,7 +114,7 @@ import {
114
114
  transientBackoff,
115
115
  validateProjectRoot,
116
116
  writeProjectedSpecSync
117
- } from "./index-y1z6yaq4.js";
117
+ } from "./index-4c5jpmn9.js";
118
118
  import {
119
119
  _internals as _internals2,
120
120
  _internals1 as _internals3,
@@ -909,7 +909,7 @@ var init_executor = __esm(() => {
909
909
  // package.json
910
910
  var package_default = {
911
911
  name: "opencode-swarm",
912
- version: "7.95.0",
912
+ version: "7.97.0",
913
913
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
914
914
  main: "dist/index.js",
915
915
  types: "dist/index.d.ts",
@@ -13070,7 +13070,7 @@ async function runFinalizeStage(ctx) {
13070
13070
  }
13071
13071
  }
13072
13072
  try {
13073
- const { CuratorConfigSchema: CCS } = await import("./schema-qz5mgmz3.js");
13073
+ const { CuratorConfigSchema: CCS } = await import("./schema-a8fneygm.js");
13074
13074
  const { config: pmLoadedConfig } = _internals20.loadPluginConfigWithMeta(ctx.directory);
13075
13075
  const curatorCfg = CCS.parse(pmLoadedConfig.curator ?? {});
13076
13076
  if (curatorCfg.enabled && curatorCfg.postmortem_enabled) {
@@ -13351,6 +13351,8 @@ async function runCleanStage(ctx) {
13351
13351
  }
13352
13352
  let swarmPlanFilesRemoved = 0;
13353
13353
  const candidates = [
13354
+ path25.join(ctx.directory, ".swarm", "plan-export", "SWARM_PLAN.json"),
13355
+ path25.join(ctx.directory, ".swarm", "plan-export", "SWARM_PLAN.md"),
13354
13356
  path25.join(ctx.directory, ".swarm", "SWARM_PLAN.json"),
13355
13357
  path25.join(ctx.directory, ".swarm", "SWARM_PLAN.md"),
13356
13358
  path25.join(ctx.directory, "SWARM_PLAN.json"),
@@ -13630,7 +13632,7 @@ This project was already finalized in a previous /swarm close run. The plan has
13630
13632
  `- Removed ${cleanResult.configBackupsRemoved} stale config backup file(s)`
13631
13633
  ] : [],
13632
13634
  ...cleanResult.swarmPlanFilesRemoved > 0 ? [
13633
- `- Removed ${cleanResult.swarmPlanFilesRemoved} SWARM_PLAN checkpoint artifact(s)`
13635
+ `- Removed ${cleanResult.swarmPlanFilesRemoved} SWARM_PLAN checkpoint artifact(s) from .swarm/plan-export/ and legacy locations`
13634
13636
  ] : [],
13635
13637
  ...ctx.planExists && !ctx.planAlreadyDone ? ["- Set non-completed phases/tasks to closed status"] : [],
13636
13638
  ...ctx.curationSucceeded && ctx.allLessons.length > 0 ? [`- Committed ${ctx.allLessons.length} lesson(s) to knowledge store`] : [],
@@ -16791,7 +16793,7 @@ async function handleDoctorCommand(directory, args) {
16791
16793
  const result = runConfigDoctor(config, directory);
16792
16794
  let output;
16793
16795
  if (enableAutoFix && result.hasAutoFixableIssues) {
16794
- const { runConfigDoctorWithFixes } = await import("./config-doctor-n5w1jexx.js");
16796
+ const { runConfigDoctorWithFixes } = await import("./config-doctor-h1xrvq83.js");
16795
16797
  const fixResult = await runConfigDoctorWithFixes(directory, config, true);
16796
16798
  output = formatDoctorMarkdown(fixResult.result);
16797
16799
  } else {
@@ -17604,7 +17606,7 @@ async function handleEvidenceCommand(directory, args) {
17604
17606
  return formatTaskEvidenceMarkdown(evidenceData);
17605
17607
  }
17606
17608
  async function handleEvidenceSummaryCommand(directory) {
17607
- const { buildEvidenceSummary } = await import("./evidence-summary-service-mr9sns2d.js");
17609
+ const { buildEvidenceSummary } = await import("./evidence-summary-service-g4x5e3e3.js");
17608
17610
  const artifact = await buildEvidenceSummary(directory);
17609
17611
  if (!artifact) {
17610
17612
  return "No plan found. Run `/swarm plan` to check plan status.";
@@ -29819,7 +29821,7 @@ async function handleResetCommand(directory, args) {
29819
29821
  return [
29820
29822
  "## Swarm Reset",
29821
29823
  "",
29822
- "\u26A0\uFE0F This will delete all swarm state from .swarm/ (plan, context, checkpoints, SWARM_PLAN artifacts)",
29824
+ "\u26A0\uFE0F This will delete all swarm state from .swarm/ (plan, context, checkpoints, SWARM_PLAN artifacts including .swarm/plan-export/)",
29823
29825
  "",
29824
29826
  "**Tip**: Run `/swarm export` first to backup your state.",
29825
29827
  "",
@@ -29833,6 +29835,8 @@ async function handleResetCommand(directory, args) {
29833
29835
  "context.md",
29834
29836
  "SWARM_PLAN.md",
29835
29837
  "SWARM_PLAN.json",
29838
+ "plan-export/SWARM_PLAN.md",
29839
+ "plan-export/SWARM_PLAN.json",
29836
29840
  "checkpoints.json",
29837
29841
  "events.jsonl"
29838
29842
  ];
@@ -31853,7 +31857,7 @@ function buildDetailedHelp(commandName, entry) {
31853
31857
  async function handleHelpCommand(ctx) {
31854
31858
  const targetCommand = ctx.args.join(" ");
31855
31859
  if (!targetCommand) {
31856
- const { buildHelpText } = await import("./index-7d38zerp.js");
31860
+ const { buildHelpText } = await import("./index-byb9tgay.js");
31857
31861
  return buildHelpText();
31858
31862
  }
31859
31863
  const tokens = targetCommand.split(/\s+/);
@@ -31862,7 +31866,7 @@ async function handleHelpCommand(ctx) {
31862
31866
  return _internals45.buildDetailedHelp(resolved.key, resolved.entry);
31863
31867
  }
31864
31868
  const similar = _internals45.findSimilarCommands(targetCommand);
31865
- const { buildHelpText: fullHelp } = await import("./index-7d38zerp.js");
31869
+ const { buildHelpText: fullHelp } = await import("./index-byb9tgay.js");
31866
31870
  if (similar.length > 0) {
31867
31871
  return `Command '/swarm ${targetCommand}' not found.
31868
31872
 
@@ -31995,7 +31999,7 @@ var COMMAND_REGISTRY = {
31995
31999
  },
31996
32000
  "guardrail explain": {
31997
32001
  handler: async (ctx) => {
31998
- const { handleGuardrailExplain } = await import("./guardrail-explain-1ad236sh.js");
32002
+ const { handleGuardrailExplain } = await import("./guardrail-explain-t6svvtmn.js");
31999
32003
  return handleGuardrailExplain(ctx.directory, ctx.args);
32000
32004
  },
32001
32005
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -32005,7 +32009,7 @@ var COMMAND_REGISTRY = {
32005
32009
  },
32006
32010
  "guardrail-log": {
32007
32011
  handler: async (ctx) => {
32008
- const { handleGuardrailLog } = await import("./guardrail-log-0v924awy.js");
32012
+ const { handleGuardrailLog } = await import("./guardrail-log-9yyeccv5.js");
32009
32013
  return handleGuardrailLog(ctx.directory, ctx.args);
32010
32014
  },
32011
32015
  description: "Read the guardrail decision log (use --blocks-only for blocks)",
@@ -552,11 +552,11 @@ var TOOL_METADATA = {
552
552
  agents: ["architect"]
553
553
  },
554
554
  dispatch_lanes_async: {
555
- description: "launch read-only advisory lanes non-blockingly and return a batch id immediately so you can keep working; poll incrementally with collect_lane_results (wait omitted or false) while doing independent investigation, or join with wait: true when you need all results",
555
+ description: "launch read-only advisory lanes non-blockingly and return a batch id plus lane session handles immediately so you can keep working; launch_timeout_ms is only a promptAsync acceptance budget, not a lane runtime timeout; poll incrementally with collect_lane_results (wait omitted or false) while doing independent investigation, or join with wait: true when you need all results",
556
556
  agents: ["architect"]
557
557
  },
558
558
  collect_lane_results: {
559
- description: "collect or poll results for a dispatch_lanes_async batch; supports both non-blocking polling (wait omitted or false) and blocking join (wait: true). Use non-blocking polls to check progress and process settled lanes incrementally while continuing independent work; use blocking join only when you need all lanes settled before proceeding. Does not advance workflow gates.",
559
+ description: "collect or poll results for a dispatch_lanes_async batch; supports both non-blocking polling (wait omitted or false) and blocking join (wait: true). Non-blocking polls include pending lane identities by default and process settled lanes incrementally while continuing independent work; busy/retry lanes are not timed out just because they run for a long time. Does not advance workflow gates.",
560
560
  agents: ["architect"]
561
561
  },
562
562
  summarize_work: {
@@ -3,7 +3,7 @@ import {
3
3
  ALL_AGENT_NAMES,
4
4
  PluginConfigSchema,
5
5
  stripKnownSwarmPrefix
6
- } from "./index-w015kchv.js";
6
+ } from "./index-tqbb2jx6.js";
7
7
  import {
8
8
  log
9
9
  } from "./index-zgwm4ryv.js";
package/dist/cli/index.js CHANGED
@@ -7,16 +7,16 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-wz5ex5q7.js";
11
- import"./index-c7xgx041.js";
10
+ } from "./index-sgdr2e4n.js";
11
+ import"./index-b223mczb.js";
12
12
  import"./index-2a6ppa65.js";
13
13
  import"./index-fjxjb66n.js";
14
14
  import"./index-hb10a2g8.js";
15
- import"./index-tht37da9.js";
15
+ import"./index-xx3sv77e.js";
16
16
  import {
17
17
  DEFAULT_AGENT_CONFIGS
18
- } from "./index-w015kchv.js";
19
- import"./index-y1z6yaq4.js";
18
+ } from "./index-tqbb2jx6.js";
19
+ import"./index-4c5jpmn9.js";
20
20
  import"./index-adz3nk9b.js";
21
21
  import"./index-v4fcn4tr.js";
22
22
  import"./index-mh1ej70w.js";
@@ -81,7 +81,7 @@ import {
81
81
  resolveGeneratedAgentRole,
82
82
  resolveGuardrailsConfig,
83
83
  stripKnownSwarmPrefix
84
- } from "./index-w015kchv.js";
84
+ } from "./index-tqbb2jx6.js";
85
85
  import"./index-p0arc26j.js";
86
86
  import"./index-293f68mj.js";
87
87
  import"./index-a76rekgs.js";
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.95.0",
72
+ version: "7.97.0",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -729,11 +729,11 @@ var init_tool_metadata = __esm(() => {
729
729
  agents: ["architect"]
730
730
  },
731
731
  dispatch_lanes_async: {
732
- description: "launch read-only advisory lanes non-blockingly and return a batch id immediately so you can keep working; poll incrementally with collect_lane_results (wait omitted or false) while doing independent investigation, or join with wait: true when you need all results",
732
+ description: "launch read-only advisory lanes non-blockingly and return a batch id plus lane session handles immediately so you can keep working; launch_timeout_ms is only a promptAsync acceptance budget, not a lane runtime timeout; poll incrementally with collect_lane_results (wait omitted or false) while doing independent investigation, or join with wait: true when you need all results",
733
733
  agents: ["architect"]
734
734
  },
735
735
  collect_lane_results: {
736
- description: "collect or poll results for a dispatch_lanes_async batch; supports both non-blocking polling (wait omitted or false) and blocking join (wait: true). Use non-blocking polls to check progress and process settled lanes incrementally while continuing independent work; use blocking join only when you need all lanes settled before proceeding. Does not advance workflow gates.",
736
+ description: "collect or poll results for a dispatch_lanes_async batch; supports both non-blocking polling (wait omitted or false) and blocking join (wait: true). Non-blocking polls include pending lane identities by default and process settled lanes incrementally while continuing independent work; busy/retry lanes are not timed out just because they run for a long time. Does not advance workflow gates.",
737
737
  agents: ["architect"]
738
738
  },
739
739
  summarize_work: {
@@ -20500,7 +20500,7 @@ async function loadPlan(directory) {
20500
20500
  await rebuildPlan(directory, rebuilt, {
20501
20501
  reason: "ledger_hash_mismatch_recovery"
20502
20502
  });
20503
- warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/SWARM_PLAN.md if it exists.");
20503
+ warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/plan-export/SWARM_PLAN.md if it exists.");
20504
20504
  return rebuilt;
20505
20505
  }
20506
20506
  } catch (replayError) {
@@ -20523,7 +20523,7 @@ async function loadPlan(directory) {
20523
20523
  return approved.plan;
20524
20524
  }
20525
20525
  } catch {}
20526
- warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
20526
+ warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/plan-export/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
20527
20527
  }
20528
20528
  }
20529
20529
  }
@@ -20570,7 +20570,7 @@ async function loadPlan(directory) {
20570
20570
  return validated;
20571
20571
  }
20572
20572
  } catch (error49) {
20573
- warn(`[loadPlan] plan.json validation failed: ${error49 instanceof Error ? error49.message : String(error49)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/SWARM_PLAN.md for a checkpoint.`);
20573
+ warn(`[loadPlan] plan.json validation failed: ${error49 instanceof Error ? error49.message : String(error49)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/plan-export/SWARM_PLAN.md for a checkpoint.`);
20574
20574
  let rawPlanId = null;
20575
20575
  try {
20576
20576
  const rawParsed = JSON.parse(planJsonContent);
@@ -73548,6 +73548,8 @@ async function runCleanStage(ctx) {
73548
73548
  }
73549
73549
  let swarmPlanFilesRemoved = 0;
73550
73550
  const candidates = [
73551
+ path60.join(ctx.directory, ".swarm", "plan-export", "SWARM_PLAN.json"),
73552
+ path60.join(ctx.directory, ".swarm", "plan-export", "SWARM_PLAN.md"),
73551
73553
  path60.join(ctx.directory, ".swarm", "SWARM_PLAN.json"),
73552
73554
  path60.join(ctx.directory, ".swarm", "SWARM_PLAN.md"),
73553
73555
  path60.join(ctx.directory, "SWARM_PLAN.json"),
@@ -73827,7 +73829,7 @@ This project was already finalized in a previous /swarm close run. The plan has
73827
73829
  `- Removed ${cleanResult.configBackupsRemoved} stale config backup file(s)`
73828
73830
  ] : [],
73829
73831
  ...cleanResult.swarmPlanFilesRemoved > 0 ? [
73830
- `- Removed ${cleanResult.swarmPlanFilesRemoved} SWARM_PLAN checkpoint artifact(s)`
73832
+ `- Removed ${cleanResult.swarmPlanFilesRemoved} SWARM_PLAN checkpoint artifact(s) from .swarm/plan-export/ and legacy locations`
73831
73833
  ] : [],
73832
73834
  ...ctx.planExists && !ctx.planAlreadyDone ? ["- Set non-completed phases/tasks to closed status"] : [],
73833
73835
  ...ctx.curationSucceeded && ctx.allLessons.length > 0 ? [`- Committed ${ctx.allLessons.length} lesson(s) to knowledge store`] : [],
@@ -96336,7 +96338,7 @@ async function handleResetCommand(directory, args2) {
96336
96338
  return [
96337
96339
  "## Swarm Reset",
96338
96340
  "",
96339
- "⚠️ This will delete all swarm state from .swarm/ (plan, context, checkpoints, SWARM_PLAN artifacts)",
96341
+ "⚠️ This will delete all swarm state from .swarm/ (plan, context, checkpoints, SWARM_PLAN artifacts including .swarm/plan-export/)",
96340
96342
  "",
96341
96343
  "**Tip**: Run `/swarm export` first to backup your state.",
96342
96344
  "",
@@ -96350,6 +96352,8 @@ async function handleResetCommand(directory, args2) {
96350
96352
  "context.md",
96351
96353
  "SWARM_PLAN.md",
96352
96354
  "SWARM_PLAN.json",
96355
+ "plan-export/SWARM_PLAN.md",
96356
+ "plan-export/SWARM_PLAN.json",
96353
96357
  "checkpoints.json",
96354
96358
  "events.jsonl"
96355
96359
  ];
@@ -101929,7 +101933,7 @@ If a tool modifies a file, it is a CODER tool. Delegate.
101929
101933
  2. ONE agent per message. Send, STOP, wait for response.
101930
101934
  Exception: Stage B reviewer/test_engineer gate agents for the SAME completed coder task may be dispatched together before waiting when both gates are required. This exception NEVER applies to coder delegations. Preserve ONE task per coder call.
101931
101935
  Separate parallel-mode exception (distinct from the Stage B exception above, and the ONLY case where more than one coder may be dispatched before waiting): when an active \`[PARALLEL EXECUTION PROFILE]\` directive is present in your context (parallelization_enabled=true), you MAY dispatch multiple {{AGENT_PREFIX}}coder agents in a single message — up to the stated max_concurrent_tasks — but ONLY for distinct, dependency-ready tasks whose declared file scopes do NOT overlap. Each coder still requires its own \`declare_scope\` call and carries exactly ONE task (Rule 3 still holds: never batch multiple objectives into one coder). Parallel coders each run in an isolated git worktree, so their writes never collide and are merged back automatically. If no \`[PARALLEL EXECUTION PROFILE]\` directive is present, dispatch coders one at a time.
101932
- Read-only advisory-lane exception (NON-BLOCKING; distinct from both exceptions above): the "Send, STOP, wait" rule governs MUTATION delegations (coder, and the test_engineer/reviewer Stage B completion gates). It does NOT govern read-only advisory exploration/review lanes. When you dispatch read-only advisory lanes — \`{{AGENT_PREFIX}}explorer\`, \`{{AGENT_PREFIX}}sme\`, \`{{AGENT_PREFIX}}researcher\`, the council members (\`council_generalist\`/\`council_skeptic\`/\`council_domain_expert\`), or an advisory \`{{AGENT_PREFIX}}critic\` lane — use the NON-BLOCKING path so you keep working while they run. Dispatch PROMPTLY: emit the \`dispatch_lanes_async\` call EARLY with compact lane prompts — do not accumulate long planning prose or build oversized inline prompts first, or the tool call can be truncated out of your message and the lanes never launch (a real failure mode on smaller models). The lane mechanism is a SINGLE \`dispatch_lanes_async\` call carrying all lane specs — NOT a per-agent Task/run-in-background pattern. Call \`dispatch_lanes_async\` with all lane specs in one call, record the returned \`batch_id\`, then IMMEDIATELY continue non-dependent architect work (refine the plan/obligation ledger, inspect metadata, prepare the synthesis/reviewer structure, run deterministic read-only tools). Do NOT sit idle waiting on running lanes, and do NOT synthesize findings from still-running lanes. Join later by calling \`collect_lane_results\` with \`wait: true\` as the explicit barrier immediately before you synthesize. Use blocking \`dispatch_lanes\` only when \`dispatch_lanes_async\`/promptAsync is unavailable. Keep each lane prompt compact: send large shared context (PR diff, ledger, scope) ONCE via the \`common_prompt\` field, or have lanes read it from a file by absolute path, instead of inlining the same blob into every lane prompt — inlining large context into many lanes is what produces malformed or truncated tool-call JSON and forces clumsy file workarounds. This non-blocking exception applies ONLY to read-only advisory lanes; it NEVER applies to coder delegations, to the test_engineer/reviewer Stage B completion gates, or to the critic PLAN-review gate, which all still follow "Send, STOP, wait" (or the Stage B parallel-dispatch exception above).
101936
+ Read-only advisory-lane exception (NON-BLOCKING; distinct from both exceptions above): the "Send, STOP, wait" rule governs MUTATION delegations (coder, and the test_engineer/reviewer Stage B completion gates). It does NOT govern read-only advisory exploration/review lanes. When you dispatch read-only advisory lanes — \`{{AGENT_PREFIX}}explorer\`, \`{{AGENT_PREFIX}}sme\`, \`{{AGENT_PREFIX}}researcher\`, the council members (\`council_generalist\`/\`council_skeptic\`/\`council_domain_expert\`), or an advisory \`{{AGENT_PREFIX}}critic\` lane — use the NON-BLOCKING path so you keep working while they run. Dispatch PROMPTLY: emit the \`dispatch_lanes_async\` call EARLY with compact lane prompts — do not accumulate long planning prose or build oversized inline prompts first, or the tool call can be truncated out of your message and the lanes never launch (a real failure mode on smaller models). The lane mechanism is a SINGLE \`dispatch_lanes_async\` call carrying all lane specs — NOT a per-agent Task/run-in-background pattern. Call \`dispatch_lanes_async\` with all lane specs in one call, record the returned \`batch_id\`, then IMMEDIATELY continue non-dependent architect work (refine the plan/obligation ledger, inspect metadata, prepare the synthesis/reviewer structure, run deterministic read-only tools). Poll incrementally with \`collect_lane_results\` without \`wait\` (or with \`wait: false\`) to harvest lanes as they settle; process completed lane output immediately while other lanes remain pending/running, then continue independent work between polls. Do NOT sit idle waiting on running lanes, and do NOT synthesize findings from still-running lanes. Join later by calling \`collect_lane_results\` with \`wait: true\` as the explicit barrier immediately before you synthesize. Use blocking \`dispatch_lanes\` only when \`dispatch_lanes_async\`/promptAsync is unavailable. Keep each lane prompt compact: send large shared context (PR diff, ledger, scope) ONCE via the \`common_prompt\` field, or have lanes read it from a file by absolute path, instead of inlining the same blob into every lane prompt — inlining large context into many lanes is what produces malformed or truncated tool-call JSON and forces clumsy file workarounds. This non-blocking exception applies ONLY to read-only advisory lanes; it NEVER applies to coder delegations, to the test_engineer/reviewer Stage B completion gates, or to the critic PLAN-review gate, which all still follow "Send, STOP, wait" (or the Stage B parallel-dispatch exception above).
101933
101937
  3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
101934
101938
  3a. PRE-DELEGATION SCOPE CALL (required): BEFORE every {{AGENT_PREFIX}}coder delegation, you MUST call \`declare_scope\` with { taskId, files } listing the exact file(s) this task will modify (including generated/lockfile paths). No \`declare_scope\` call → no coder delegation. See Rule 1a.
101935
101939
  3b. PRE-DELEGATION SCOPE CALL (test_engineer): BEFORE any {{AGENT_PREFIX}}test_engineer delegation that will CREATE or MODIFY test files, you MUST call \`declare_scope\` with { taskId, files } listing the exact test file path(s) to write. Omitting this call leaves the write scope undeclared and will block the write. See Rule 1a.
@@ -102289,7 +102293,7 @@ Available Tools: {{AVAILABLE_TOOLS}}
102289
102293
 
102290
102294
  ## DELEGATION FORMAT
102291
102295
 
102292
- Delegations are performed ONLY by calling the **Task** tool. Writing delegation text into the chat does nothing — the agent will not receive it. Every delegation below is the content you pass to the Task tool, not text you output to the conversation.
102296
+ Mutation delegations are performed by calling the **Task** tool. Read-only advisory lanes are the explicit exception: dispatch them with \`dispatch_lanes_async\` plus incremental \`collect_lane_results\` polls (no \`wait\` / \`wait: false\`) before a final \`wait: true\` join, or blocking \`dispatch_lanes\` only when async is unavailable. Never turn advisory lanes into a per-agent Task/run-in-background pattern. Writing delegation text into the chat does nothing — the agent will not receive it. Every mutation delegation below is the content you pass to the Task tool, not text you output to the conversation.
102293
102297
 
102294
102298
  All delegations MUST follow the receiving agent's INPUT FORMAT exactly. Do NOT invent fields, omit required fields, or force one agent's schema onto another. Every delegation MUST begin with the agent name, include \`TASK:\`, and include \`SKILLS:\` when that agent prompt supports skills.
102295
102299
  Do NOT add conversational preamble before the agent prefix. Begin directly with the agent name.
@@ -102533,7 +102537,7 @@ Purpose: Read the previous retrospective and produce a codebase reality report b
102533
102537
  ACTION: Load skill file:.opencode/skills/pre-phase-briefing/SKILL.md immediately. Follow the protocol defined there.
102534
102538
 
102535
102539
  HARD CONSTRAINTS:
102536
- - Complete the codebase reality report before starting or resuming phase implementation.
102540
+ - Complete the codebase reality report before spec finalization, plan generation, plan ingestion, declare_scope, or starting/resuming phase implementation. Dispatching the reality-check lanes asynchronously is allowed and preferred; settling all lanes before any of that downstream work is not optional.
102537
102541
 
102538
102542
  ### MODE: COUNCIL
102539
102543
  Activates when the user invokes /swarm council or requests a council-style decision review.
@@ -107428,6 +107432,7 @@ async function scanDocIndex(directory) {
107428
107432
  const resolvedDirectory = await realpath4(directory).catch(() => directory);
107429
107433
  const discoveredFiles = [];
107430
107434
  let rootReadable = false;
107435
+ let truncated = false;
107431
107436
  const walkDir2 = async (dir) => {
107432
107437
  let entries;
107433
107438
  try {
@@ -107438,8 +107443,10 @@ async function scanDocIndex(directory) {
107438
107443
  return;
107439
107444
  }
107440
107445
  for (const entry of entries) {
107441
- if (discoveredFiles.length >= MAX_INDEXED_FILES)
107446
+ if (discoveredFiles.length >= MAX_INDEXED_FILES) {
107447
+ truncated = true;
107442
107448
  return;
107449
+ }
107443
107450
  const isDir = entry.isDirectory();
107444
107451
  let isFile = entry.isFile();
107445
107452
  if (entry.isSymbolicLink()) {
@@ -107511,7 +107518,6 @@ async function scanDocIndex(directory) {
107511
107518
  };
107512
107519
  }
107513
107520
  discoveredFiles.sort((a, b) => a.path.toLowerCase().localeCompare(b.path.toLowerCase()));
107514
- let truncated = false;
107515
107521
  if (discoveredFiles.length > MAX_INDEXED_FILES) {
107516
107522
  discoveredFiles.splice(MAX_INDEXED_FILES);
107517
107523
  truncated = true;
@@ -131427,6 +131433,7 @@ var COMMON_PROMPT_SEPARATOR = `
131427
131433
 
131428
131434
  `;
131429
131435
  var DEFAULT_TIMEOUT_MS3 = 300000;
131436
+ var DEFAULT_ASYNC_LAUNCH_TIMEOUT_MS = 30000;
131430
131437
  var MAX_TIMEOUT_MS2 = 1800000;
131431
131438
  var MAX_LANE_OUTPUT_CHARS = 20000;
131432
131439
  var ASYNC_MESSAGE_FETCH_LIMIT = 50;
@@ -131502,9 +131509,10 @@ var DispatchLanesArgsSchema = exports_external.object({
131502
131509
  lanes: exports_external.array(LaneSchema).min(1).max(MAX_LANES).describe("Read-only lane specs to dispatch concurrently"),
131503
131510
  common_prompt: exports_external.string().min(1).regex(/\S/, "common_prompt must contain non-whitespace content").max(MAX_PROMPT_CHARS - COMMON_PROMPT_SEPARATOR.length - 1).optional().describe("Optional shared context prepended to every lane prompt. Send large shared context (PR diff, obligation ledger, scope) ONCE here instead of inlining the same blob into each lane prompt; this keeps the tool-call payload small and avoids malformed/truncated tool-call JSON. Combined common_prompt + per-lane prompt must not exceed the per-lane character limit."),
131504
131511
  max_concurrent: exports_external.number().int().min(1).max(MAX_LANES).optional().describe("Maximum lanes in flight at once; defaults to lane count"),
131505
- timeout_ms: exports_external.number().int().min(10).max(MAX_TIMEOUT_MS2).optional().describe("Per-lane session create/prompt timeout in milliseconds")
131512
+ timeout_ms: exports_external.number().int().min(10).max(MAX_TIMEOUT_MS2).optional().describe("Per-lane timeout in milliseconds. For blocking dispatch this covers session create and prompt execution; for async dispatch this covers launch only, never lane runtime.")
131506
131513
  });
131507
131514
  var DispatchLanesAsyncArgsSchema = DispatchLanesArgsSchema.extend({
131515
+ launch_timeout_ms: exports_external.number().int().min(10).max(MAX_TIMEOUT_MS2).optional().describe("Async launch acceptance timeout in milliseconds. This only bounds session creation and promptAsync acceptance; it is never a lane runtime timeout. Deprecated alias: timeout_ms."),
131508
131516
  batch_id: exports_external.string().min(1).max(MAX_BATCH_ID_CHARS).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/).optional().describe("Stable async batch id for later collection; generated when omitted"),
131509
131517
  mode: exports_external.string().min(1).max(80).optional().describe("Advisory workflow mode, such as deep-dive or swarm-pr-review"),
131510
131518
  pr_head_sha: exports_external.string().min(1).max(80).optional(),
@@ -131514,7 +131522,7 @@ var CollectLaneResultsArgsSchema = exports_external.object({
131514
131522
  batch_id: exports_external.string().min(1).max(MAX_BATCH_ID_CHARS),
131515
131523
  wait: exports_external.boolean().optional().describe("Poll until all lanes settle or timeout"),
131516
131524
  timeout_ms: exports_external.number().int().min(0).max(MAX_COLLECT_TIMEOUT_MS).optional().describe("Total wait budget when wait=true"),
131517
- include_pending: exports_external.boolean().optional().describe("Include pending/running lanes in results with partial output; useful for progress checks during non-blocking polls"),
131525
+ include_pending: exports_external.boolean().optional().describe("Include pending/running lanes in lane_results. Defaults to true for non-blocking polls and false for wait=true joins."),
131518
131526
  cancel_pending: exports_external.boolean().optional().describe("Abort and mark pending/running lanes cancelled")
131519
131527
  });
131520
131528
  var _internals94 = {
@@ -131614,7 +131622,7 @@ async function executeDispatchLanesAsync(args2, directory, context = {}) {
131614
131622
  });
131615
131623
  }
131616
131624
  const maxConcurrent = Math.min(parsed.data.max_concurrent ?? lanes.length, lanes.length, MAX_LANES);
131617
- const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_TIMEOUT_MS3;
131625
+ const launchTimeoutMs = parsed.data.launch_timeout_ms ?? parsed.data.timeout_ms ?? DEFAULT_ASYNC_LAUNCH_TIMEOUT_MS;
131618
131626
  const dispatcher = _internals94.createParallelDispatcher({
131619
131627
  enabled: true,
131620
131628
  maxConcurrentTasks: maxConcurrent,
@@ -131627,7 +131635,7 @@ async function executeDispatchLanesAsync(args2, directory, context = {}) {
131627
131635
  dispatcher,
131628
131636
  lane,
131629
131637
  directory,
131630
- timeoutMs,
131638
+ timeoutMs: launchTimeoutMs,
131631
131639
  context,
131632
131640
  batchId,
131633
131641
  mode: parsed.data.mode,
@@ -131645,7 +131653,8 @@ async function executeDispatchLanesAsync(args2, directory, context = {}) {
131645
131653
  failed: failed.length,
131646
131654
  rejected: rejected.length,
131647
131655
  max_concurrent: maxConcurrent,
131648
- timeout_ms: timeoutMs,
131656
+ launch_timeout_ms: launchTimeoutMs,
131657
+ timeout_ms: launchTimeoutMs,
131649
131658
  lane_results: laneResults
131650
131659
  };
131651
131660
  } finally {
@@ -131673,7 +131682,6 @@ async function executeCollectLaneResults(args2, directory, context = {}) {
131673
131682
  const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_COLLECT_TIMEOUT_MS;
131674
131683
  const deadline = _internals94.now() + timeoutMs;
131675
131684
  const batchFilter = context.sessionID !== undefined ? { parentSessionId: context.sessionID } : undefined;
131676
- await sweepStaleDelegations(directory, DEFAULT_ASYNC_STALE_TIMEOUT_MS);
131677
131685
  let records = findByBatchId(directory, parsed.data.batch_id, batchFilter);
131678
131686
  if (records.length === 0) {
131679
131687
  return collectFailureResult({
@@ -131686,7 +131694,7 @@ async function executeCollectLaneResults(args2, directory, context = {}) {
131686
131694
  let pollIntervalMs = COLLECT_POLL_INTERVAL_MS;
131687
131695
  while (keepPolling) {
131688
131696
  await collectOnce(session, directory, records, parsed.data.cancel_pending === true);
131689
- await sweepStaleDelegations(directory, DEFAULT_ASYNC_STALE_TIMEOUT_MS);
131697
+ await sweepStaleAsyncLaneRecords(session, directory, records, DEFAULT_ASYNC_STALE_TIMEOUT_MS);
131690
131698
  records = findByBatchId(directory, parsed.data.batch_id, batchFilter);
131691
131699
  if (allSettled(records) || parsed.data.wait !== true) {
131692
131700
  keepPolling = false;
@@ -131699,7 +131707,7 @@ async function executeCollectLaneResults(args2, directory, context = {}) {
131699
131707
  await _internals94.sleep(Math.min(pollIntervalMs, Math.max(0, deadline - _internals94.now())));
131700
131708
  pollIntervalMs = nextCollectPollInterval(pollIntervalMs);
131701
131709
  }
131702
- return buildCollectResult(parsed.data.batch_id, records, parsed.data.include_pending === true);
131710
+ return buildCollectResult(parsed.data.batch_id, records, parsed.data.include_pending ?? parsed.data.wait !== true);
131703
131711
  }
131704
131712
  async function launchAsyncLane(args2) {
131705
131713
  const validation2 = validateLaneAgent(args2.lane.agent, args2.context);
@@ -131773,54 +131781,17 @@ async function launchAsyncLane(args2) {
131773
131781
  scope: args2.scope ?? null
131774
131782
  },
131775
131783
  generation: 1
131776
- }, { staleTimeoutMs: DEFAULT_ASYNC_STALE_TIMEOUT_MS });
131784
+ });
131777
131785
  if (!pendingRecord) {
131778
131786
  cleanupAsyncLaunchSession(args2.session, sessionId);
131779
131787
  return failedLane(args2.lane, role, startedAt, "Failed to record async lane in background delegation ledger", decision.slot.slotId, decision.slot.runId);
131780
131788
  }
131781
- const promptController = new AbortController;
131782
- let promptResult;
131783
- try {
131784
- promptResult = await withTimeout2(args2.session.promptAsync({
131785
- path: { id: sessionId },
131786
- query: { directory: args2.directory },
131787
- body: {
131788
- agent: args2.lane.agent,
131789
- tools: buildReadOnlyTools(),
131790
- parts: [{ type: "text", text: args2.lane.prompt }]
131791
- },
131792
- signal: promptController.signal
131793
- }), args2.timeoutMs, `Lane "${args2.lane.id}" session.promptAsync timed out after ${args2.timeoutMs}ms`, promptController);
131794
- } catch (error93) {
131795
- const message = formatError3(error93);
131796
- await appendDelegationTransition(args2.directory, sessionId, {
131797
- status: "error",
131798
- result: {
131799
- error: message,
131800
- chars: message.length,
131801
- truncated: false,
131802
- digest: digestText2(message)
131803
- }
131804
- });
131805
- cleanupAsyncLaunchSession(args2.session, sessionId);
131806
- return failedLane(args2.lane, role, startedAt, message, decision.slot.slotId, decision.slot.runId);
131807
- }
131808
- if (promptResult.error) {
131809
- const error93 = `session.promptAsync failed: ${formatError3(promptResult.error)}`;
131810
- await appendDelegationTransition(args2.directory, sessionId, {
131811
- status: "error",
131812
- result: {
131813
- error: error93,
131814
- chars: error93.length,
131815
- truncated: false,
131816
- digest: digestText2(error93)
131817
- }
131818
- });
131819
- cleanupAsyncLaunchSession(args2.session, sessionId);
131820
- return failedLane(args2.lane, role, startedAt, error93, decision.slot.slotId, decision.slot.runId);
131821
- }
131822
- await appendDelegationTransition(args2.directory, sessionId, {
131823
- status: "running"
131789
+ scheduleAsyncLanePrompt({
131790
+ session: args2.session,
131791
+ directory: args2.directory,
131792
+ sessionId,
131793
+ lane: args2.lane,
131794
+ timeoutMs: args2.timeoutMs
131824
131795
  });
131825
131796
  return {
131826
131797
  id: args2.lane.id,
@@ -131854,6 +131825,9 @@ async function collectOnce(session, directory, records, cancelPending) {
131854
131825
  });
131855
131826
  continue;
131856
131827
  }
131828
+ const readyForCollection = await isLaneReadyForCollection(session, directory, record3.subagentSessionId);
131829
+ if (!readyForCollection)
131830
+ continue;
131857
131831
  let messages;
131858
131832
  try {
131859
131833
  messages = await session.messages({
@@ -131899,6 +131873,82 @@ async function collectOnce(session, directory, records, cancelPending) {
131899
131873
  });
131900
131874
  }
131901
131875
  }
131876
+ function scheduleAsyncLanePrompt(args2) {
131877
+ queueMicrotask(() => {
131878
+ startAsyncLanePrompt(args2).catch(async (error93) => {
131879
+ const message = formatError3(error93);
131880
+ await appendAsyncLaneLaunchError(args2.directory, args2.session, args2.sessionId, message);
131881
+ });
131882
+ });
131883
+ }
131884
+ async function startAsyncLanePrompt(args2) {
131885
+ const promptController = new AbortController;
131886
+ let promptResult;
131887
+ try {
131888
+ promptResult = await withTimeout2(args2.session.promptAsync({
131889
+ path: { id: args2.sessionId },
131890
+ query: { directory: args2.directory },
131891
+ body: {
131892
+ agent: args2.lane.agent,
131893
+ tools: buildReadOnlyTools(),
131894
+ parts: [{ type: "text", text: args2.lane.prompt }]
131895
+ },
131896
+ signal: promptController.signal
131897
+ }), args2.timeoutMs, `Lane "${args2.lane.id}" session.promptAsync launch timed out after ${args2.timeoutMs}ms`, promptController);
131898
+ } catch (error93) {
131899
+ await appendAsyncLaneLaunchError(args2.directory, args2.session, args2.sessionId, formatError3(error93));
131900
+ return;
131901
+ }
131902
+ if (promptResult.error) {
131903
+ await appendAsyncLaneLaunchError(args2.directory, args2.session, args2.sessionId, `session.promptAsync launch failed: ${formatError3(promptResult.error)}`);
131904
+ return;
131905
+ }
131906
+ await appendDelegationTransition(args2.directory, args2.sessionId, {
131907
+ status: "running"
131908
+ });
131909
+ }
131910
+ async function appendAsyncLaneLaunchError(directory, session, sessionId, message) {
131911
+ await appendDelegationTransition(directory, sessionId, {
131912
+ status: "error",
131913
+ result: {
131914
+ error: message,
131915
+ chars: message.length,
131916
+ truncated: false,
131917
+ digest: digestText2(message)
131918
+ }
131919
+ });
131920
+ cleanupAsyncLaunchSession(session, sessionId);
131921
+ }
131922
+ async function isLaneReadyForCollection(session, directory, sessionId) {
131923
+ if (typeof session.status !== "function")
131924
+ return true;
131925
+ try {
131926
+ const status = await session.status({ query: { directory } });
131927
+ if (status.error || !status.data)
131928
+ return false;
131929
+ const current = status.data[sessionId];
131930
+ return current === undefined || current.type === "idle";
131931
+ } catch {
131932
+ return false;
131933
+ }
131934
+ }
131935
+ async function sweepStaleAsyncLaneRecords(session, directory, records, staleTimeoutMs) {
131936
+ if (staleTimeoutMs <= 0)
131937
+ return;
131938
+ const now = _internals94.now();
131939
+ for (const record3 of records) {
131940
+ if (record3.status !== "pending" && record3.status !== "running" && record3.status !== "ingestion_error")
131941
+ continue;
131942
+ if (now - record3.updatedAt <= staleTimeoutMs)
131943
+ continue;
131944
+ const readyForCollection = await isLaneReadyForCollection(session, directory, record3.subagentSessionId);
131945
+ if (!readyForCollection)
131946
+ continue;
131947
+ await appendDelegationTransition(directory, record3.correlationId, {
131948
+ status: "stale"
131949
+ });
131950
+ }
131951
+ }
131902
131952
  function extractAssistantTranscript(messages) {
131903
131953
  const assistantTexts = [];
131904
131954
  for (const message of messages) {
@@ -132207,6 +132257,7 @@ function asyncFailureResult(args2) {
132207
132257
  failed: 0,
132208
132258
  rejected: 0,
132209
132259
  max_concurrent: 0,
132260
+ launch_timeout_ms: 0,
132210
132261
  timeout_ms: 0,
132211
132262
  lane_results: [],
132212
132263
  errors: args2.errors
@@ -132360,11 +132411,12 @@ var dispatch_lanes = createSwarmTool({
132360
132411
  }
132361
132412
  });
132362
132413
  var dispatch_lanes_async = createSwarmTool({
132363
- description: "Launch multiple read-only advisory lanes with OpenCode promptAsync and return IMMEDIATELY with a batch id (non-blocking). After launching, keep working on non-dependent investigation while lanes run — poll incrementally with collect_lane_results (wait omitted or false) to process settled lanes as they complete, or use wait: true only at workflow boundaries where all results are needed. Keep each lane prompt compact: send large shared context once via common_prompt (or have lanes read it from a file by absolute path) instead of inlining it into every lane prompt, which can produce oversized or malformed tool-call JSON.",
132414
+ description: "Launch multiple read-only advisory lanes with OpenCode promptAsync and return IMMEDIATELY with a batch id and lane session handles (non-blocking). launch_timeout_ms only bounds session creation and promptAsync acceptance; it is NOT a lane runtime timeout. After launching, keep working on non-dependent investigation while lanes run — poll incrementally with collect_lane_results (wait omitted or false) to process settled lanes as they complete, or use wait: true only at workflow boundaries where all results are needed. Keep each lane prompt compact: send large shared context once via common_prompt (or have lanes read it from a file by absolute path) instead of inlining it into every lane prompt, which can produce oversized or malformed tool-call JSON.",
132364
132415
  args: {
132365
132416
  lanes: DispatchLanesAsyncArgsSchema.shape.lanes,
132366
132417
  common_prompt: DispatchLanesAsyncArgsSchema.shape.common_prompt,
132367
132418
  max_concurrent: DispatchLanesAsyncArgsSchema.shape.max_concurrent,
132419
+ launch_timeout_ms: DispatchLanesAsyncArgsSchema.shape.launch_timeout_ms,
132368
132420
  timeout_ms: DispatchLanesAsyncArgsSchema.shape.timeout_ms,
132369
132421
  batch_id: DispatchLanesAsyncArgsSchema.shape.batch_id,
132370
132422
  mode: DispatchLanesAsyncArgsSchema.shape.mode,
@@ -132380,7 +132432,7 @@ var dispatch_lanes_async = createSwarmTool({
132380
132432
  }
132381
132433
  });
132382
132434
  var collect_lane_results = createSwarmTool({
132383
- description: "Collect or poll results for a dispatch_lanes_async batch. Supports two modes: (1) non-blocking poll (wait omitted or false) — performs one collection pass and returns current lane status and any settled results so you can process completed lanes while continuing independent work; (2) blocking join (wait: true) — polls until all lanes settle or timeout. Use non-blocking polls to check progress incrementally; use blocking join only at workflow boundaries where all results are required. Does not advance workflow gates.",
132435
+ description: "Collect or poll results for a dispatch_lanes_async batch. Supports two modes: (1) non-blocking poll (wait omitted or false) — performs one collection pass and returns current lane status, including pending lane identities by default, and any settled results so you can process completed lanes while continuing independent work; (2) blocking join (wait: true) — polls until all lanes settle or the collection wait budget expires. Busy/retry lanes do not become stale solely because they run for a long time. Does not advance workflow gates.",
132384
132436
  args: {
132385
132437
  batch_id: CollectLaneResultsArgsSchema.shape.batch_id,
132386
132438
  wait: CollectLaneResultsArgsSchema.shape.wait,
@@ -141306,18 +141358,32 @@ init_ledger();
141306
141358
  init_manager();
141307
141359
  import * as fs115 from "node:fs";
141308
141360
  import * as path187 from "node:path";
141361
+ var _fsInternals = {
141362
+ mkdirSync: (...args2) => fs115.mkdirSync(...args2),
141363
+ writeFileSync: (path188, data, encoding) => fs115.writeFileSync(path188, data, encoding ?? "utf8"),
141364
+ readFileSync: (path188, encoding) => fs115.readFileSync(path188, encoding ?? "utf8"),
141365
+ existsSync: (path188) => fs115.existsSync(path188)
141366
+ };
141309
141367
  async function writeCheckpoint(directory) {
141310
141368
  try {
141311
141369
  const plan = await loadPlan(directory);
141312
141370
  if (!plan)
141313
141371
  return;
141314
- const swarmDir = path187.join(directory, ".swarm");
141315
- fs115.mkdirSync(swarmDir, { recursive: true });
141316
- const jsonPath = path187.join(swarmDir, "SWARM_PLAN.json");
141317
- const mdPath = path187.join(swarmDir, "SWARM_PLAN.md");
141318
- fs115.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf8");
141372
+ const exportDir = path187.join(directory, ".swarm", "plan-export");
141373
+ _fsInternals.mkdirSync(exportDir, { recursive: true });
141374
+ const jsonPath = path187.join(exportDir, "SWARM_PLAN.json");
141375
+ const mdPath = path187.join(exportDir, "SWARM_PLAN.md");
141376
+ _fsInternals.writeFileSync(jsonPath, JSON.stringify(plan, null, 2));
141319
141377
  const md = derivePlanMarkdown(plan);
141320
- fs115.writeFileSync(mdPath, md, "utf8");
141378
+ const header = `<!--
141379
+ AUTO-GENERATED EXPORT/CHECKPOINT SNAPSHOT — DO NOT EDIT
141380
+ This file is NOT the live plan. It is a derived export artifact.
141381
+ - .swarm/plan-ledger.jsonl is the authoritative source of plan state.
141382
+ - .swarm/plan.json and .swarm/plan.md are derived projections.
141383
+ Regenerated on: save_plan and phase_complete.
141384
+ -->
141385
+ `;
141386
+ _fsInternals.writeFileSync(mdPath, header + md);
141321
141387
  } catch (error93) {
141322
141388
  console.warn(`[checkpoint] Failed to write SWARM_PLAN checkpoint: ${error93 instanceof Error ? error93.message : String(error93)}`);
141323
141389
  }
@@ -1,6 +1,20 @@
1
+ /**
2
+ * Checkpoint artifact writer.
3
+ * Writes SWARM_PLAN.md and SWARM_PLAN.json inside .swarm/plan-export/.
4
+ * Export-only — not a live runtime source of truth.
5
+ * Called on: save_plan, phase completion, /swarm close.
6
+ * NOT called on every task update.
7
+ */
8
+ import * as fs from 'node:fs';
9
+ export declare const _fsInternals: {
10
+ mkdirSync: (path: fs.PathLike, options?: fs.MakeDirectoryOptions | fs.Mode | null | undefined) => string | undefined;
11
+ writeFileSync: (path: string, data: string, encoding?: BufferEncoding) => void;
12
+ readFileSync: (path: string, encoding?: BufferEncoding) => string;
13
+ existsSync: (path: string) => boolean;
14
+ };
1
15
  import { type Plan } from '../config/plan-schema';
2
16
  /**
3
- * Write SWARM_PLAN.json and SWARM_PLAN.md inside the .swarm/ directory under the project root.
17
+ * Write SWARM_PLAN.json and SWARM_PLAN.md inside the .swarm/plan-export/ directory under the project root.
4
18
  * Non-blocking: logs a warning on failure but never throws.
5
19
  * @param directory - The working directory (project root)
6
20
  */
@@ -14,7 +28,7 @@ export interface ImportCheckpointResult {
14
28
  error?: string;
15
29
  }
16
30
  /**
17
- * Import a checkpoint from .swarm/SWARM_PLAN.json (with backward-compat fallback to project root).
31
+ * Import a checkpoint from .swarm/plan-export/SWARM_PLAN.json (with backward-compat fallback to .swarm/ and project root).
18
32
  * Validates the checkpoint against PlanSchema, persists it as the live plan
19
33
  * via savePlan, and appends a 'plan_rebuilt' ledger event.
20
34
  *
@@ -26,6 +26,7 @@ declare const DispatchLanesAsyncArgsSchema: z.ZodObject<{
26
26
  common_prompt: z.ZodOptional<z.ZodString>;
27
27
  max_concurrent: z.ZodOptional<z.ZodNumber>;
28
28
  timeout_ms: z.ZodOptional<z.ZodNumber>;
29
+ launch_timeout_ms: z.ZodOptional<z.ZodNumber>;
29
30
  batch_id: z.ZodOptional<z.ZodString>;
30
31
  mode: z.ZodOptional<z.ZodString>;
31
32
  pr_head_sha: z.ZodOptional<z.ZodString>;
@@ -88,6 +89,8 @@ export interface DispatchLanesAsyncResult {
88
89
  failed: number;
89
90
  rejected: number;
90
91
  max_concurrent: number;
92
+ launch_timeout_ms: number;
93
+ /** Deprecated alias for launch_timeout_ms; retained for existing callers. */
91
94
  timeout_ms: number;
92
95
  lane_results: DispatchLaneResult[];
93
96
  errors?: string[];
@@ -181,6 +184,16 @@ export interface SessionOps {
181
184
  }> | null;
182
185
  error?: unknown;
183
186
  }>;
187
+ status?: (args: {
188
+ query?: {
189
+ directory?: string;
190
+ };
191
+ }) => Promise<{
192
+ data?: Record<string, {
193
+ type?: string;
194
+ }> | null;
195
+ error?: unknown;
196
+ }>;
184
197
  abort?: (args: {
185
198
  path: {
186
199
  id: string;
@@ -225,11 +238,16 @@ export declare const _test_exports: {
225
238
  common_prompt: z.ZodOptional<z.ZodString>;
226
239
  max_concurrent: z.ZodOptional<z.ZodNumber>;
227
240
  timeout_ms: z.ZodOptional<z.ZodNumber>;
241
+ launch_timeout_ms: z.ZodOptional<z.ZodNumber>;
228
242
  batch_id: z.ZodOptional<z.ZodString>;
229
243
  mode: z.ZodOptional<z.ZodString>;
230
244
  pr_head_sha: z.ZodOptional<z.ZodString>;
231
245
  scope: z.ZodOptional<z.ZodString>;
232
246
  }, z.core.$strip>;
247
+ DEFAULT_TIMEOUT_MS: number;
248
+ DEFAULT_ASYNC_LAUNCH_TIMEOUT_MS: number;
249
+ DEFAULT_ASYNC_STALE_TIMEOUT_MS: number;
250
+ DEFAULT_COLLECT_TIMEOUT_MS: number;
233
251
  };
234
252
  type ReadOnlyToolPermissions = Record<string, false> & {
235
253
  write: false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.95.0",
3
+ "version": "7.97.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",