opencode-swarm 6.86.2 → 6.86.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 CHANGED
@@ -14230,11 +14230,17 @@ async function readLedgerEvents(directory) {
14230
14230
  const lines = content.trim().split(`
14231
14231
  `).filter((line) => line.trim() !== "");
14232
14232
  const events = [];
14233
+ let skippedCount = 0;
14233
14234
  for (const line of lines) {
14234
14235
  try {
14235
14236
  const event = JSON.parse(line);
14236
14237
  events.push(event);
14237
- } catch {}
14238
+ } catch {
14239
+ skippedCount++;
14240
+ }
14241
+ }
14242
+ if (skippedCount > 0) {
14243
+ console.warn(`[ledger] Skipped ${skippedCount} malformed line(s) in plan-ledger.jsonl`);
14238
14244
  }
14239
14245
  events.sort((a, b) => a.seq - b.seq);
14240
14246
  return events;
@@ -14331,10 +14337,13 @@ async function takeSnapshotEvent(directory, plan, options) {
14331
14337
  }, { planHashAfter: options?.planHashAfter });
14332
14338
  }
14333
14339
  async function replayFromLedger(directory, _options) {
14334
- const events = await readLedgerEvents(directory);
14340
+ const { events, truncated, badSuffix } = await readLedgerEventsWithIntegrity(directory);
14335
14341
  if (events.length === 0) {
14336
14342
  return null;
14337
14343
  }
14344
+ if (truncated && badSuffix !== null) {
14345
+ await quarantineLedgerSuffix(directory, badSuffix);
14346
+ }
14338
14347
  const targetPlanId = events[0].plan_id;
14339
14348
  const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
14340
14349
  {
@@ -14455,6 +14464,46 @@ function applyEventToPlan(plan, event) {
14455
14464
  throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
14456
14465
  }
14457
14466
  }
14467
+ async function readLedgerEventsWithIntegrity(directory) {
14468
+ const ledgerPath = getLedgerPath(directory);
14469
+ if (!fs.existsSync(ledgerPath)) {
14470
+ return { events: [], truncated: false, badSuffix: null };
14471
+ }
14472
+ try {
14473
+ const content = fs.readFileSync(ledgerPath, "utf8");
14474
+ const lines = content.split(`
14475
+ `);
14476
+ const events = [];
14477
+ let truncated = false;
14478
+ let badSuffix = null;
14479
+ for (let i = 0;i < lines.length; i++) {
14480
+ const line = lines[i];
14481
+ if (line.trim() === "") {
14482
+ continue;
14483
+ }
14484
+ try {
14485
+ const event = JSON.parse(line);
14486
+ events.push(event);
14487
+ } catch {
14488
+ truncated = true;
14489
+ badSuffix = lines.slice(i).join(`
14490
+ `);
14491
+ break;
14492
+ }
14493
+ }
14494
+ events.sort((a, b) => a.seq - b.seq);
14495
+ return { events, truncated, badSuffix };
14496
+ } catch {
14497
+ return { events: [], truncated: false, badSuffix: null };
14498
+ }
14499
+ }
14500
+ async function quarantineLedgerSuffix(directory, badSuffix) {
14501
+ try {
14502
+ const quarantinePath = path2.join(directory, ".swarm", "plan-ledger.quarantine");
14503
+ fs.writeFileSync(quarantinePath, badSuffix, "utf8");
14504
+ console.warn(`[ledger] Corrupted suffix quarantined to ${path2.relative(directory, quarantinePath)}`);
14505
+ } catch {}
14506
+ }
14458
14507
  async function loadLastApprovedPlan(directory, expectedPlanId) {
14459
14508
  const events = await readLedgerEvents(directory);
14460
14509
  if (events.length === 0) {
@@ -15059,7 +15108,15 @@ ${markdown}`;
15059
15108
  } catch {}
15060
15109
  }
15061
15110
  } catch (mdError) {
15062
- warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${mdError instanceof Error ? mdError.message : String(mdError)}`);
15111
+ const message = mdError instanceof Error ? mdError.message : String(mdError);
15112
+ warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${message}`);
15113
+ try {
15114
+ emit("plan_md_write_failed", {
15115
+ directory,
15116
+ error: message,
15117
+ timestamp: new Date().toISOString()
15118
+ });
15119
+ } catch {}
15063
15120
  }
15064
15121
  try {
15065
15122
  const markerPath = path3.join(swarmDir, ".plan-write-marker");
@@ -15108,7 +15165,9 @@ function derivePlanMarkdown(plan) {
15108
15165
  pending: "PENDING",
15109
15166
  in_progress: "IN PROGRESS",
15110
15167
  complete: "COMPLETE",
15111
- blocked: "BLOCKED"
15168
+ completed: "COMPLETE",
15169
+ blocked: "BLOCKED",
15170
+ closed: "CLOSED"
15112
15171
  };
15113
15172
  const now = new Date().toISOString();
15114
15173
  const currentPhase = plan.current_phase ?? 1;
@@ -44369,7 +44428,9 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
44369
44428
  }
44370
44429
 
44371
44430
  // src/commands/rollback.ts
44431
+ init_plan_schema();
44372
44432
  init_utils2();
44433
+ init_ledger();
44373
44434
  import * as fs21 from "fs";
44374
44435
  import * as path32 from "path";
44375
44436
  async function handleRollbackCommand(directory, args) {
@@ -44426,9 +44487,16 @@ async function handleRollbackCommand(directory, args) {
44426
44487
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
44427
44488
  }
44428
44489
  const swarmDir = validateSwarmPath(directory, "");
44490
+ const EXCLUDE_FILES = new Set([
44491
+ "plan-ledger.jsonl",
44492
+ "plan-ledger.quarantine"
44493
+ ]);
44429
44494
  const successes = [];
44430
44495
  const failures = [];
44431
44496
  for (const file3 of checkpointFiles) {
44497
+ if (EXCLUDE_FILES.has(file3) || file3.startsWith("plan-ledger.archived-")) {
44498
+ continue;
44499
+ }
44432
44500
  const src = path32.join(checkpointDir, file3);
44433
44501
  const dest = path32.join(swarmDir, file3);
44434
44502
  try {
@@ -44439,7 +44507,41 @@ async function handleRollbackCommand(directory, args) {
44439
44507
  }
44440
44508
  }
44441
44509
  if (failures.length > 0) {
44442
- return `Rollback partially completed. Successfully restored ${successes.length} files: ${successes.join(", ") || "none"}. Failed on ${failures.length} files: ${failures.map((f) => f.file).join(", ")}. Check permissions and disk space.`;
44510
+ return [
44511
+ `Rollback partially completed. Successfully restored ${successes.length} files.`,
44512
+ `Failed on ${failures.length} files:`,
44513
+ ...failures.map((f) => ` - ${f.file}: ${f.error}`),
44514
+ "",
44515
+ "Some files could not be restored. The .swarm/ directory may be in an inconsistent state.",
44516
+ "Check permissions and disk space, then retry the rollback."
44517
+ ].join(`
44518
+ `);
44519
+ }
44520
+ const existingLedgerPath = path32.join(swarmDir, "plan-ledger.jsonl");
44521
+ if (fs21.existsSync(existingLedgerPath)) {
44522
+ fs21.unlinkSync(existingLedgerPath);
44523
+ }
44524
+ try {
44525
+ const planJsonPath = path32.join(swarmDir, "plan.json");
44526
+ if (fs21.existsSync(planJsonPath)) {
44527
+ const planRaw = fs21.readFileSync(planJsonPath, "utf-8");
44528
+ const plan = PlanSchema.parse(JSON.parse(planRaw));
44529
+ const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
44530
+ const planHash = computePlanHash(plan);
44531
+ await initLedger(directory, planId, planHash, plan);
44532
+ await appendLedgerEvent(directory, {
44533
+ event_type: "plan_rebuilt",
44534
+ source: "rollback",
44535
+ plan_id: planId
44536
+ });
44537
+ }
44538
+ } catch (initError) {
44539
+ return [
44540
+ `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
44541
+ "The .swarm/plan.json has been restored but the ledger may be out of sync.",
44542
+ "Run /swarm reset-session to reinitialize the ledger."
44543
+ ].join(`
44544
+ `);
44443
44545
  }
44444
44546
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
44445
44547
  const rollbackEvent = {
package/dist/index.js CHANGED
@@ -16158,11 +16158,17 @@ async function readLedgerEvents(directory) {
16158
16158
  const lines = content.trim().split(`
16159
16159
  `).filter((line) => line.trim() !== "");
16160
16160
  const events = [];
16161
+ let skippedCount = 0;
16161
16162
  for (const line of lines) {
16162
16163
  try {
16163
16164
  const event = JSON.parse(line);
16164
16165
  events.push(event);
16165
- } catch {}
16166
+ } catch {
16167
+ skippedCount++;
16168
+ }
16169
+ }
16170
+ if (skippedCount > 0) {
16171
+ console.warn(`[ledger] Skipped ${skippedCount} malformed line(s) in plan-ledger.jsonl`);
16166
16172
  }
16167
16173
  events.sort((a, b) => a.seq - b.seq);
16168
16174
  return events;
@@ -16259,10 +16265,13 @@ async function takeSnapshotEvent(directory, plan, options) {
16259
16265
  }, { planHashAfter: options?.planHashAfter });
16260
16266
  }
16261
16267
  async function replayFromLedger(directory, _options) {
16262
- const events = await readLedgerEvents(directory);
16268
+ const { events, truncated, badSuffix } = await readLedgerEventsWithIntegrity(directory);
16263
16269
  if (events.length === 0) {
16264
16270
  return null;
16265
16271
  }
16272
+ if (truncated && badSuffix !== null) {
16273
+ await quarantineLedgerSuffix(directory, badSuffix);
16274
+ }
16266
16275
  const targetPlanId = events[0].plan_id;
16267
16276
  const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
16268
16277
  {
@@ -16383,6 +16392,46 @@ function applyEventToPlan(plan, event) {
16383
16392
  throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
16384
16393
  }
16385
16394
  }
16395
+ async function readLedgerEventsWithIntegrity(directory) {
16396
+ const ledgerPath = getLedgerPath(directory);
16397
+ if (!fs4.existsSync(ledgerPath)) {
16398
+ return { events: [], truncated: false, badSuffix: null };
16399
+ }
16400
+ try {
16401
+ const content = fs4.readFileSync(ledgerPath, "utf8");
16402
+ const lines = content.split(`
16403
+ `);
16404
+ const events = [];
16405
+ let truncated = false;
16406
+ let badSuffix = null;
16407
+ for (let i2 = 0;i2 < lines.length; i2++) {
16408
+ const line = lines[i2];
16409
+ if (line.trim() === "") {
16410
+ continue;
16411
+ }
16412
+ try {
16413
+ const event = JSON.parse(line);
16414
+ events.push(event);
16415
+ } catch {
16416
+ truncated = true;
16417
+ badSuffix = lines.slice(i2).join(`
16418
+ `);
16419
+ break;
16420
+ }
16421
+ }
16422
+ events.sort((a, b) => a.seq - b.seq);
16423
+ return { events, truncated, badSuffix };
16424
+ } catch {
16425
+ return { events: [], truncated: false, badSuffix: null };
16426
+ }
16427
+ }
16428
+ async function quarantineLedgerSuffix(directory, badSuffix) {
16429
+ try {
16430
+ const quarantinePath = path4.join(directory, ".swarm", "plan-ledger.quarantine");
16431
+ fs4.writeFileSync(quarantinePath, badSuffix, "utf8");
16432
+ console.warn(`[ledger] Corrupted suffix quarantined to ${path4.relative(directory, quarantinePath)}`);
16433
+ } catch {}
16434
+ }
16386
16435
  async function loadLastApprovedPlan(directory, expectedPlanId) {
16387
16436
  const events = await readLedgerEvents(directory);
16388
16437
  if (events.length === 0) {
@@ -16987,7 +17036,15 @@ ${markdown}`;
16987
17036
  } catch {}
16988
17037
  }
16989
17038
  } catch (mdError) {
16990
- warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${mdError instanceof Error ? mdError.message : String(mdError)}`);
17039
+ const message = mdError instanceof Error ? mdError.message : String(mdError);
17040
+ warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${message}`);
17041
+ try {
17042
+ emit("plan_md_write_failed", {
17043
+ directory,
17044
+ error: message,
17045
+ timestamp: new Date().toISOString()
17046
+ });
17047
+ } catch {}
16991
17048
  }
16992
17049
  try {
16993
17050
  const markerPath = path5.join(swarmDir, ".plan-write-marker");
@@ -17088,7 +17145,9 @@ function derivePlanMarkdown(plan) {
17088
17145
  pending: "PENDING",
17089
17146
  in_progress: "IN PROGRESS",
17090
17147
  complete: "COMPLETE",
17091
- blocked: "BLOCKED"
17148
+ completed: "COMPLETE",
17149
+ blocked: "BLOCKED",
17150
+ closed: "CLOSED"
17092
17151
  };
17093
17152
  const now = new Date().toISOString();
17094
17153
  const currentPhase = plan.current_phase ?? 1;
@@ -24990,7 +25049,7 @@ function createDelegationGateHook(config2, directory) {
24990
25049
  });
24991
25050
  if (councilActive && result.overallVerdict === "APPROVE" && result.allCriteriaMet === true && (result.requiredFixesCount ?? 0) === 0) {
24992
25051
  try {
24993
- advanceTaskState(session, taskId, "complete");
25052
+ await advanceTaskStateAndPersist(session, taskId, "complete", directory);
24994
25053
  } catch (err2) {
24995
25054
  console.warn(`[delegation-gate] toolAfter convene_council: could not advance ${taskId} \u2192 complete: ${err2 instanceof Error ? err2.message : String(err2)}`);
24996
25055
  }
@@ -25377,7 +25436,7 @@ ${trimComment}${after}`;
25377
25436
  pendingCoderScopeByTaskId.delete(currentTaskId);
25378
25437
  }
25379
25438
  try {
25380
- advanceTaskState(session, currentTaskId, "coder_delegated");
25439
+ await advanceTaskStateAndPersist(session, currentTaskId, "coder_delegated", directory);
25381
25440
  } catch (err2) {
25382
25441
  console.warn(`[delegation-gate] state machine warn: ${err2 instanceof Error ? err2.message : String(err2)}`);
25383
25442
  }
@@ -25561,6 +25620,7 @@ __export(exports_state, {
25561
25620
  buildRehydrationCache: () => buildRehydrationCache,
25562
25621
  beginInvocation: () => beginInvocation,
25563
25622
  applyRehydrationCache: () => applyRehydrationCache,
25623
+ advanceTaskStateAndPersist: () => advanceTaskStateAndPersist,
25564
25624
  advanceTaskState: () => advanceTaskState,
25565
25625
  _resetCouncilDisagreementWarnings: () => _resetCouncilDisagreementWarnings,
25566
25626
  AgentRunContext: () => AgentRunContext
@@ -25933,6 +25993,18 @@ function advanceTaskState(session, taskId, newState) {
25933
25993
  session.taskWorkflowStates.set(taskId, newState);
25934
25994
  telemetry.taskStateChanged(session.agentName, taskId, newState, current);
25935
25995
  }
25996
+ async function advanceTaskStateAndPersist(session, taskId, newState, directory) {
25997
+ advanceTaskState(session, taskId, newState);
25998
+ if (newState !== "coder_delegated" && newState !== "complete") {
25999
+ return;
26000
+ }
26001
+ const planStatus = newState === "complete" ? "completed" : "in_progress";
26002
+ try {
26003
+ await updateTaskStatus(directory, taskId, planStatus);
26004
+ } catch (err2) {
26005
+ console.warn(`[advanceTaskStateAndPersist] persist ${taskId} \u2192 ${planStatus} failed (in-memory state still advanced): ${err2 instanceof Error ? err2.message : String(err2)}`);
26006
+ }
26007
+ }
25936
26008
  function getTaskState(session, taskId) {
25937
26009
  if (!isValidTaskId2(taskId)) {
25938
26010
  return "idle";
@@ -53226,9 +53298,16 @@ async function handleRollbackCommand(directory, args2) {
53226
53298
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
53227
53299
  }
53228
53300
  const swarmDir = validateSwarmPath(directory, "");
53301
+ const EXCLUDE_FILES = new Set([
53302
+ "plan-ledger.jsonl",
53303
+ "plan-ledger.quarantine"
53304
+ ]);
53229
53305
  const successes = [];
53230
53306
  const failures = [];
53231
53307
  for (const file3 of checkpointFiles) {
53308
+ if (EXCLUDE_FILES.has(file3) || file3.startsWith("plan-ledger.archived-")) {
53309
+ continue;
53310
+ }
53232
53311
  const src = path40.join(checkpointDir, file3);
53233
53312
  const dest = path40.join(swarmDir, file3);
53234
53313
  try {
@@ -53239,7 +53318,41 @@ async function handleRollbackCommand(directory, args2) {
53239
53318
  }
53240
53319
  }
53241
53320
  if (failures.length > 0) {
53242
- return `Rollback partially completed. Successfully restored ${successes.length} files: ${successes.join(", ") || "none"}. Failed on ${failures.length} files: ${failures.map((f) => f.file).join(", ")}. Check permissions and disk space.`;
53321
+ return [
53322
+ `Rollback partially completed. Successfully restored ${successes.length} files.`,
53323
+ `Failed on ${failures.length} files:`,
53324
+ ...failures.map((f) => ` - ${f.file}: ${f.error}`),
53325
+ "",
53326
+ "Some files could not be restored. The .swarm/ directory may be in an inconsistent state.",
53327
+ "Check permissions and disk space, then retry the rollback."
53328
+ ].join(`
53329
+ `);
53330
+ }
53331
+ const existingLedgerPath = path40.join(swarmDir, "plan-ledger.jsonl");
53332
+ if (fs28.existsSync(existingLedgerPath)) {
53333
+ fs28.unlinkSync(existingLedgerPath);
53334
+ }
53335
+ try {
53336
+ const planJsonPath = path40.join(swarmDir, "plan.json");
53337
+ if (fs28.existsSync(planJsonPath)) {
53338
+ const planRaw = fs28.readFileSync(planJsonPath, "utf-8");
53339
+ const plan = PlanSchema.parse(JSON.parse(planRaw));
53340
+ const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
53341
+ const planHash = computePlanHash(plan);
53342
+ await initLedger(directory, planId, planHash, plan);
53343
+ await appendLedgerEvent(directory, {
53344
+ event_type: "plan_rebuilt",
53345
+ source: "rollback",
53346
+ plan_id: planId
53347
+ });
53348
+ }
53349
+ } catch (initError) {
53350
+ return [
53351
+ `Rollback restored files but failed to initialize ledger: ${initError instanceof Error ? initError.message : String(initError)}`,
53352
+ "The .swarm/plan.json has been restored but the ledger may be out of sync.",
53353
+ "Run /swarm reset-session to reinitialize the ledger."
53354
+ ].join(`
53355
+ `);
53243
53356
  }
53244
53357
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
53245
53358
  const rollbackEvent = {
@@ -53257,7 +53370,9 @@ async function handleRollbackCommand(directory, args2) {
53257
53370
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
53258
53371
  }
53259
53372
  var init_rollback = __esm(() => {
53373
+ init_plan_schema();
53260
53374
  init_utils2();
53375
+ init_ledger();
53261
53376
  });
53262
53377
 
53263
53378
  // src/commands/simulate.ts
@@ -63224,7 +63339,7 @@ init_schema();
63224
63339
  init_state();
63225
63340
  init_utils();
63226
63341
  init_utils2();
63227
- import { renameSync as renameSync11, unlinkSync as unlinkSync7 } from "fs";
63342
+ import { renameSync as renameSync11, unlinkSync as unlinkSync8 } from "fs";
63228
63343
  import * as nodePath2 from "path";
63229
63344
  function createAgentActivityHooks(config3, directory) {
63230
63345
  if (config3.hooks?.agent_activity === false) {
@@ -63301,7 +63416,7 @@ async function doFlush(directory) {
63301
63416
  renameSync11(tempPath, path46);
63302
63417
  } catch (writeError) {
63303
63418
  try {
63304
- unlinkSync7(tempPath);
63419
+ unlinkSync8(tempPath);
63305
63420
  } catch {}
63306
63421
  throw writeError;
63307
63422
  }
@@ -85502,6 +85617,17 @@ init_state();
85502
85617
  function slugify2(str) {
85503
85618
  return str.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_");
85504
85619
  }
85620
+ function extractJsonArray(text) {
85621
+ const trimmed = text.trim();
85622
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
85623
+ if (fenceMatch)
85624
+ return fenceMatch[1].trim();
85625
+ const start2 = trimmed.search(/\[\s*[{["0-9\]tfn]/);
85626
+ const end = trimmed.lastIndexOf("]");
85627
+ if (start2 !== -1 && end > start2)
85628
+ return trimmed.slice(start2, end + 1);
85629
+ return trimmed;
85630
+ }
85505
85631
  async function generateMutants(files, ctx) {
85506
85632
  if (!ctx) {
85507
85633
  console.warn("[generateMutants] No ToolContext \u2014 cannot call LLM; returning empty patch set");
@@ -85546,9 +85672,9 @@ Return a JSON array where each element has:
85546
85672
  - id: unique string like "mut-001"
85547
85673
  - mutationType: one of: ${mutationTypes}
85548
85674
  - patch: unified diff format (--- a/file\\n+++ a/file\\n@@ ... @@\\n-old\\n+new)
85549
- - Generate 5-10 mutations per function
85675
+ - Generate 3-5 mutations per function
85550
85676
 
85551
- Return ONLY valid JSON array, no markdown, no explanation.`;
85677
+ Return ONLY a valid JSON array. No markdown, no code fences, no explanation. Start your response with [ and end with ].`;
85552
85678
  const promptResult = await client.session.prompt({
85553
85679
  path: { id: ephemeralSessionId },
85554
85680
  body: {
@@ -85566,9 +85692,11 @@ Return ONLY valid JSON array, no markdown, no explanation.`;
85566
85692
  `);
85567
85693
  let parsed;
85568
85694
  try {
85569
- parsed = JSON.parse(rawText);
85695
+ parsed = JSON.parse(extractJsonArray(rawText));
85570
85696
  } catch (error93) {
85571
- console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${error93 instanceof Error ? error93.message : String(error93)}; returning empty patch set`);
85697
+ const msg = error93 instanceof Error ? error93.message : String(error93);
85698
+ const hint = msg.includes("EOF") || msg.includes("Unexpected end") ? " (response appears truncated \u2014 LLM may have hit an output token limit)" : "";
85699
+ console.warn(`[generateMutants] Failed to parse LLM response as MutationPatch[]: ${msg}${hint}; returning empty patch set`);
85572
85700
  return [];
85573
85701
  }
85574
85702
  if (!Array.isArray(parsed) || parsed.length === 0) {
@@ -85781,7 +85909,7 @@ import * as path95 from "path";
85781
85909
 
85782
85910
  // src/mutation/engine.ts
85783
85911
  import { spawnSync as spawnSync3 } from "child_process";
85784
- import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
85912
+ import { unlinkSync as unlinkSync13, writeFileSync as writeFileSync18 } from "fs";
85785
85913
  import * as path94 from "path";
85786
85914
 
85787
85915
  // src/mutation/equivalence.ts
@@ -86015,7 +86143,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
86015
86143
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
86016
86144
  }
86017
86145
  try {
86018
- unlinkSync12(patchFile);
86146
+ unlinkSync13(patchFile);
86019
86147
  } catch (_unlinkErr) {}
86020
86148
  }
86021
86149
  }
package/dist/state.d.ts CHANGED
@@ -360,6 +360,24 @@ export declare function recordPhaseAgentDispatch(sessionId: string, agentName: s
360
360
  * @param newState - The requested new state
361
361
  */
362
362
  export declare function advanceTaskState(session: AgentSessionState, taskId: string, newState: TaskWorkflowState): void;
363
+ /**
364
+ * Advance the per-task workflow state machine AND persist the corresponding
365
+ * plan.json status at meaningful workflow boundaries.
366
+ *
367
+ * The two-layer model splits in-memory workflow state (Layer 1, fast, used by
368
+ * gates) from the durable plan (Layer 2, projected to plan.md). Without this
369
+ * bridge, council APPROVE → 'complete' updates Layer 1 only and plan.md goes
370
+ * stale. This helper closes the gap by mapping:
371
+ * - 'coder_delegated' → plan.json status 'in_progress'
372
+ * - 'complete' → plan.json status 'completed'
373
+ * Other transitions are in-memory only (the task is already in_progress on disk
374
+ * once coder_delegated has fired).
375
+ *
376
+ * Persistence errors are logged and swallowed so a transient disk failure does
377
+ * not break the in-memory state machine — matches the existing defensive
378
+ * pattern around advanceTaskState call sites.
379
+ */
380
+ export declare function advanceTaskStateAndPersist(session: AgentSessionState, taskId: string, newState: TaskWorkflowState, directory: string): Promise<void>;
363
381
  /**
364
382
  * Get the current workflow state for a task.
365
383
  * Returns 'idle' if no entry exists.
@@ -1,4 +1,4 @@
1
- export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activated' | 'delegation_begin' | 'delegation_end' | 'task_state_changed' | 'gate_passed' | 'gate_failed' | 'phase_changed' | 'budget_updated' | 'model_fallback' | 'hard_limit_hit' | 'revision_limit_hit' | 'loop_detected' | 'scope_violation' | 'qa_skip_violation' | 'heartbeat' | 'turbo_mode_changed' | 'auto_oversight_escalation' | 'environment_detected' | 'evidence_lock_acquired' | 'evidence_lock_contended' | 'evidence_lock_stale_recovered' | 'plan_ledger_cas_retry' | 'prm_pattern_detected' | 'prm_course_correction_injected' | 'prm_escalation_triggered' | 'prm_hard_stop';
1
+ export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activated' | 'delegation_begin' | 'delegation_end' | 'task_state_changed' | 'gate_passed' | 'gate_failed' | 'phase_changed' | 'budget_updated' | 'model_fallback' | 'hard_limit_hit' | 'revision_limit_hit' | 'loop_detected' | 'scope_violation' | 'qa_skip_violation' | 'heartbeat' | 'turbo_mode_changed' | 'auto_oversight_escalation' | 'environment_detected' | 'evidence_lock_acquired' | 'evidence_lock_contended' | 'evidence_lock_stale_recovered' | 'plan_ledger_cas_retry' | 'plan_md_write_failed' | 'prm_pattern_detected' | 'prm_course_correction_injected' | 'prm_escalation_triggered' | 'prm_hard_stop';
2
2
  export type TelemetryListener = (event: TelemetryEvent, data: Record<string, unknown>) => void;
3
3
  /** @internal - For testing only */
4
4
  export declare function resetTelemetryForTesting(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.86.2",
3
+ "version": "6.86.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",