opencode-swarm 7.98.1 → 7.98.2

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.
@@ -6,10 +6,10 @@ import {
6
6
  loadPlanJsonOnly,
7
7
  mergeDurableGateEntriesFromEvidence,
8
8
  readDurableGateEvidence
9
- } from "./index-4c5jpmn9.js";
9
+ } from "./index-yrzfbqqh.js";
10
10
  import"./index-mh1ej70w.js";
11
11
  import"./index-kv4dd5c5.js";
12
- import"./index-gn8n22th.js";
12
+ import"./index-we94wkty.js";
13
13
  import"./index-jtqkh8jf.js";
14
14
  import"./index-5e4e2hvv.js";
15
15
  import"./index-p0arc26j.js";
@@ -1,21 +1,21 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-0rgde8qc.js";
5
- import"./index-zgba613y.js";
4
+ } from "./index-rvweaxsd.js";
5
+ import"./index-72k57f21.js";
6
6
  import"./index-5z2e78tv.js";
7
7
  import"./index-2a6ppa65.js";
8
8
  import"./index-fjxjb66n.js";
9
9
  import"./index-hb10a2g8.js";
10
10
  import"./index-zy22fg5h.js";
11
11
  import"./index-471qxz9g.js";
12
- import"./index-4c5jpmn9.js";
12
+ import"./index-yrzfbqqh.js";
13
13
  import"./index-adz3nk9b.js";
14
14
  import"./index-v4fcn4tr.js";
15
15
  import"./index-mh1ej70w.js";
16
16
  import"./index-kv4dd5c5.js";
17
- import"./index-sf08zj91.js";
18
- import"./index-gn8n22th.js";
17
+ import"./index-a0dv2c7k.js";
18
+ import"./index-we94wkty.js";
19
19
  import"./index-jtqkh8jf.js";
20
20
  import"./index-5e4e2hvv.js";
21
21
  import"./index-p0arc26j.js";
@@ -114,7 +114,7 @@ import {
114
114
  transientBackoff,
115
115
  validateProjectRoot,
116
116
  writeProjectedSpecSync
117
- } from "./index-4c5jpmn9.js";
117
+ } from "./index-yrzfbqqh.js";
118
118
  import {
119
119
  _internals as _internals2,
120
120
  _internals1 as _internals3,
@@ -138,12 +138,12 @@ import {
138
138
  listActive,
139
139
  subscribe,
140
140
  unsubscribe
141
- } from "./index-sf08zj91.js";
141
+ } from "./index-a0dv2c7k.js";
142
142
  import {
143
143
  readSwarmFileAsync,
144
144
  safeHook,
145
145
  validateSwarmPath
146
- } from "./index-gn8n22th.js";
146
+ } from "./index-we94wkty.js";
147
147
  import {
148
148
  deepMerge
149
149
  } from "./index-p0arc26j.js";
@@ -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.98.1",
912
+ version: "7.98.2",
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",
@@ -5474,6 +5474,9 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
5474
5474
  swarmState.pendingRehydrations.add(rehydrationPromise);
5475
5475
  }
5476
5476
  }
5477
+ function endAgentSession(sessionId) {
5478
+ swarmState.agentSessions.delete(sessionId);
5479
+ }
5477
5480
  function getAgentSession(sessionId) {
5478
5481
  return swarmState.agentSessions.get(sessionId);
5479
5482
  }
@@ -5978,7 +5981,7 @@ function hasActiveEpicMode(sessionID) {
5978
5981
  async function rehydratePrSubscriptions(sessionID, directory) {
5979
5982
  const map = new Map;
5980
5983
  try {
5981
- const { listActive: listActive2 } = await import("./pr-subscriptions-2565fpsc.js");
5984
+ const { listActive: listActive2 } = await import("./pr-subscriptions-ttvr1ta9.js");
5982
5985
  const records = await listActive2(directory);
5983
5986
  for (const record of records) {
5984
5987
  if (record.sessionID !== sessionID)
@@ -13652,6 +13655,10 @@ This project was already finalized in a previous /swarm close run. The plan has
13652
13655
  ctx.warnings.push(`Failed to write close-summary.md: ${msg}`);
13653
13656
  console.warn("[close-command] Failed to write close-summary.md:", error2);
13654
13657
  }
13658
+ const sessionIdsToEnd = [...swarmState.agentSessions.keys()];
13659
+ for (const sessionId of sessionIdsToEnd) {
13660
+ _internals20.endAgentSession(sessionId);
13661
+ }
13655
13662
  _internals20.resetSwarmStatePreservingSingletons();
13656
13663
  const retroWarnings = ctx.warnings.filter((w) => w.includes("Retrospective write") || w.includes("retrospective write") || w.includes("Session retrospective"));
13657
13664
  const otherWarnings = ctx.warnings.filter((w) => !w.includes("Retrospective write") && !w.includes("retrospective write") && !w.includes("Session retrospective"));
@@ -13744,7 +13751,8 @@ var _internals20 = {
13744
13751
  runCleanStage,
13745
13752
  runAlignStage,
13746
13753
  archiveEvidence,
13747
- closePlanTerminalState
13754
+ closePlanTerminalState,
13755
+ endAgentSession
13748
13756
  };
13749
13757
 
13750
13758
  // src/commands/codebase-review.ts
@@ -17605,7 +17613,7 @@ async function handleEvidenceCommand(directory, args) {
17605
17613
  return formatTaskEvidenceMarkdown(evidenceData);
17606
17614
  }
17607
17615
  async function handleEvidenceSummaryCommand(directory) {
17608
- const { buildEvidenceSummary } = await import("./evidence-summary-service-g4x5e3e3.js");
17616
+ const { buildEvidenceSummary } = await import("./evidence-summary-service-559nas1m.js");
17609
17617
  const artifact = await buildEvidenceSummary(directory);
17610
17618
  if (!artifact) {
17611
17619
  return "No plan found. Run `/swarm plan` to check plan status.";
@@ -31856,7 +31864,7 @@ function buildDetailedHelp(commandName, entry) {
31856
31864
  async function handleHelpCommand(ctx) {
31857
31865
  const targetCommand = ctx.args.join(" ");
31858
31866
  if (!targetCommand) {
31859
- const { buildHelpText } = await import("./index-attgb1ma.js");
31867
+ const { buildHelpText } = await import("./index-chan9mkv.js");
31860
31868
  return buildHelpText();
31861
31869
  }
31862
31870
  const tokens = targetCommand.split(/\s+/);
@@ -31865,7 +31873,7 @@ async function handleHelpCommand(ctx) {
31865
31873
  return _internals45.buildDetailedHelp(resolved.key, resolved.entry);
31866
31874
  }
31867
31875
  const similar = _internals45.findSimilarCommands(targetCommand);
31868
- const { buildHelpText: fullHelp } = await import("./index-attgb1ma.js");
31876
+ const { buildHelpText: fullHelp } = await import("./index-chan9mkv.js");
31869
31877
  if (similar.length > 0) {
31870
31878
  return `Command '/swarm ${targetCommand}' not found.
31871
31879
 
@@ -31998,7 +32006,7 @@ var COMMAND_REGISTRY = {
31998
32006
  },
31999
32007
  "guardrail explain": {
32000
32008
  handler: async (ctx) => {
32001
- const { handleGuardrailExplain } = await import("./guardrail-explain-cm08h4mt.js");
32009
+ const { handleGuardrailExplain } = await import("./guardrail-explain-xktjjh53.js");
32002
32010
  return handleGuardrailExplain(ctx.directory, ctx.args);
32003
32011
  },
32004
32012
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  validateSwarmPath
4
- } from "./index-gn8n22th.js";
4
+ } from "./index-we94wkty.js";
5
5
  import {
6
6
  log
7
7
  } from "./index-zgwm4ryv.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-0rgde8qc.js";
4
+ } from "./index-rvweaxsd.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-gnd1280x.js";
@@ -78,7 +78,7 @@ import {
78
78
  handleWriteRetroCommand,
79
79
  normalizeSwarmCommandInput,
80
80
  resolveCommand
81
- } from "./index-zgba613y.js";
81
+ } from "./index-72k57f21.js";
82
82
  import"./index-5z2e78tv.js";
83
83
  import"./index-2a6ppa65.js";
84
84
  import"./index-fjxjb66n.js";
@@ -89,13 +89,13 @@ import {
89
89
  ORCHESTRATOR_NAME,
90
90
  stripKnownSwarmPrefix
91
91
  } from "./index-471qxz9g.js";
92
- import"./index-4c5jpmn9.js";
92
+ import"./index-yrzfbqqh.js";
93
93
  import"./index-adz3nk9b.js";
94
94
  import"./index-v4fcn4tr.js";
95
95
  import"./index-mh1ej70w.js";
96
96
  import"./index-kv4dd5c5.js";
97
- import"./index-sf08zj91.js";
98
- import"./index-gn8n22th.js";
97
+ import"./index-a0dv2c7k.js";
98
+ import"./index-we94wkty.js";
99
99
  import"./index-jtqkh8jf.js";
100
100
  import"./index-5e4e2hvv.js";
101
101
  import"./index-p0arc26j.js";
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-zgba613y.js";
15
+ } from "./index-72k57f21.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
@@ -14,7 +14,13 @@ import {
14
14
 
15
15
  // src/hooks/utils.ts
16
16
  import * as path from "path";
17
- var _internals = { safeHook, composeHandlers, validateSwarmPath, readSwarmFileAsync };
17
+ var _internals = {
18
+ safeHook,
19
+ composeHandlers,
20
+ validateSwarmPath,
21
+ readSwarmFileAsync,
22
+ readCachedTextFile
23
+ };
18
24
  function safeHook(fn) {
19
25
  return async (input, output) => {
20
26
  try {
@@ -67,13 +73,23 @@ function validateSwarmPath(directory, filename) {
67
73
  }
68
74
  return resolved;
69
75
  }
70
- async function readSwarmFileAsync(directory, filename) {
76
+ async function readSwarmFileAsync(directory, filename, cache) {
77
+ if (cache !== undefined) {
78
+ const key = `${directory}::${filename}`;
79
+ const cached = cache.get(key);
80
+ if (cached !== undefined) {
81
+ return cached;
82
+ }
83
+ const promise = readSwarmFileAsync(directory, filename);
84
+ cache.set(key, promise);
85
+ return promise;
86
+ }
71
87
  const maxAttempts = 5;
72
88
  const retryDelayMs = 10;
73
89
  for (let attempt = 0;attempt < maxAttempts; attempt++) {
74
90
  try {
75
91
  const resolvedPath = _internals.validateSwarmPath(directory, filename);
76
- return await readCachedTextFile(resolvedPath, async () => {
92
+ return await _internals.readCachedTextFile(resolvedPath, async () => {
77
93
  const file = bunFile(resolvedPath);
78
94
  return await file.text();
79
95
  });
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  readSwarmFileAsync,
10
10
  validateSwarmPath
11
- } from "./index-gn8n22th.js";
11
+ } from "./index-we94wkty.js";
12
12
  import {
13
13
  readCachedParsedFile
14
14
  } from "./index-jtqkh8jf.js";
@@ -2162,8 +2162,8 @@ function extractPlanHashFromMarkdown(markdown) {
2162
2162
  const match = markdown.match(/<!--\s*PLAN_HASH:\s*(\S+)\s*-->/);
2163
2163
  return match ? match[1] : null;
2164
2164
  }
2165
- async function isPlanMdInSync(directory, plan) {
2166
- const planMdContent = await readSwarmFileAsync(directory, "plan.md");
2165
+ async function isPlanMdInSync(directory, plan, cache) {
2166
+ const planMdContent = await readSwarmFileAsync(directory, "plan.md", cache);
2167
2167
  if (planMdContent === null) {
2168
2168
  return false;
2169
2169
  }
@@ -2197,8 +2197,8 @@ ${markdown}`;
2197
2197
  } catch {}
2198
2198
  }
2199
2199
  }
2200
- async function loadPlan(directory) {
2201
- const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
2200
+ async function loadPlan(directory, cache) {
2201
+ const planJsonContent = await readSwarmFileAsync(directory, "plan.json", cache);
2202
2202
  if (planJsonContent !== null) {
2203
2203
  if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
2204
2204
  warn("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
@@ -2208,7 +2208,7 @@ async function loadPlan(directory) {
2208
2208
  if (validated === null) {
2209
2209
  warn("[loadPlan] plan.json disappeared during cached parse. Falling back to plan.md migration or ledger recovery.");
2210
2210
  } else {
2211
- const inSync = await isPlanMdInSync(directory, validated);
2211
+ const inSync = await isPlanMdInSync(directory, validated, cache);
2212
2212
  if (!inSync) {
2213
2213
  try {
2214
2214
  await _internals4.regeneratePlanMarkdown(directory, validated);
@@ -2331,7 +2331,7 @@ async function loadPlan(directory) {
2331
2331
  }
2332
2332
  }
2333
2333
  }
2334
- const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
2334
+ const planMdContent2 = await readSwarmFileAsync(directory, "plan.md", cache);
2335
2335
  if (planMdContent2 !== null) {
2336
2336
  const migrated = migrateLegacyPlan(planMdContent2);
2337
2337
  const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json");
@@ -2346,7 +2346,7 @@ async function loadPlan(directory) {
2346
2346
  }
2347
2347
  }
2348
2348
  }
2349
- const planMdContent = await readSwarmFileAsync(directory, "plan.md");
2349
+ const planMdContent = await readSwarmFileAsync(directory, "plan.md", cache);
2350
2350
  if (planMdContent !== null) {
2351
2351
  const migrated = migrateLegacyPlan(planMdContent);
2352
2352
  await savePlan(directory, migrated);
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-zgba613y.js";
10
+ } from "./index-72k57f21.js";
11
11
  import"./index-5z2e78tv.js";
12
12
  import"./index-2a6ppa65.js";
13
13
  import"./index-fjxjb66n.js";
@@ -16,13 +16,13 @@ import"./index-zy22fg5h.js";
16
16
  import {
17
17
  DEFAULT_AGENT_CONFIGS
18
18
  } from "./index-471qxz9g.js";
19
- import"./index-4c5jpmn9.js";
19
+ import"./index-yrzfbqqh.js";
20
20
  import"./index-adz3nk9b.js";
21
21
  import"./index-v4fcn4tr.js";
22
22
  import"./index-mh1ej70w.js";
23
23
  import"./index-kv4dd5c5.js";
24
- import"./index-sf08zj91.js";
25
- import"./index-gn8n22th.js";
24
+ import"./index-a0dv2c7k.js";
25
+ import"./index-we94wkty.js";
26
26
  import"./index-jtqkh8jf.js";
27
27
  import"./index-5e4e2hvv.js";
28
28
  import"./index-p0arc26j.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  validateSwarmPath
4
- } from "./index-gn8n22th.js";
4
+ } from "./index-we94wkty.js";
5
5
  import"./index-jtqkh8jf.js";
6
6
  import"./index-5e4e2hvv.js";
7
7
  import"./index-p0arc26j.js";
@@ -9,8 +9,8 @@ import {
9
9
  sweepStale,
10
10
  unsubscribe,
11
11
  updateSnapshot
12
- } from "./index-sf08zj91.js";
13
- import"./index-gn8n22th.js";
12
+ } from "./index-a0dv2c7k.js";
13
+ import"./index-we94wkty.js";
14
14
  import"./index-jtqkh8jf.js";
15
15
  import"./index-5e4e2hvv.js";
16
16
  import"./index-p0arc26j.js";
@@ -8,7 +8,7 @@ import { checkHivePromotions } from '../hooks/hive-promoter';
8
8
  import { curateAndStoreSwarm } from '../hooks/knowledge-curator';
9
9
  import { closePlanTerminalState } from '../plan/manager';
10
10
  import { type SessionReflectionResult } from '../services/session-reflection';
11
- import { resetSwarmStatePreservingSingletons } from '../state';
11
+ import { endAgentSession, resetSwarmStatePreservingSingletons } from '../state';
12
12
  interface PlanPhase {
13
13
  id: number;
14
14
  name: string;
@@ -197,5 +197,6 @@ export declare const _internals: {
197
197
  runAlignStage: typeof runAlignStage;
198
198
  archiveEvidence: typeof archiveEvidence;
199
199
  closePlanTerminalState: typeof closePlanTerminalState;
200
+ endAgentSession: typeof endAgentSession;
200
201
  };
201
202
  export {};
@@ -46,4 +46,4 @@ export type ArchitectMode = 'DISCOVER' | 'PLAN' | 'EXECUTE' | 'PHASE-WRAP' | 'UN
46
46
  * @param directory - The project directory to check
47
47
  * @returns The current architect mode based on plan state
48
48
  */
49
- export declare function detectArchitectMode(directory: string): Promise<ArchitectMode>;
49
+ export declare function detectArchitectMode(directory: string, cache?: Map<string, Promise<string | null>>): Promise<ArchitectMode>;
@@ -5,6 +5,7 @@
5
5
  * including error handling, handler composition, file I/O, and
6
6
  * token estimation for swarm-related operations.
7
7
  */
8
+ import { readCachedTextFile } from '../utils/swarm-artifact-cache';
8
9
  /**
9
10
  * Test-only dependency-injection seam. Production code calls
10
11
  * `_internals.<fn>(...)` so tests can replace the function on this object
@@ -18,6 +19,7 @@ export declare const _internals: {
18
19
  composeHandlers: typeof composeHandlers;
19
20
  validateSwarmPath: typeof validateSwarmPath;
20
21
  readSwarmFileAsync: typeof readSwarmFileAsync;
22
+ readCachedTextFile: typeof readCachedTextFile;
21
23
  };
22
24
  export declare function safeHook<I, O>(fn: (input: I, output: O) => Promise<void>): (input: I, output: O) => Promise<void>;
23
25
  /**
@@ -70,5 +72,5 @@ export declare function composeBlockingHandlers<I, O>(...fns: Array<(input: I, o
70
72
  * @throws Error if the filename is invalid or attempts path traversal
71
73
  */
72
74
  export declare function validateSwarmPath(directory: string, filename: string): string;
73
- export declare function readSwarmFileAsync(directory: string, filename: string): Promise<string | null>;
75
+ export declare function readSwarmFileAsync(directory: string, filename: string, cache?: Map<string, Promise<string | null>>): Promise<string | null>;
74
76
  export declare function estimateTokens(text: string): number;
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.98.1",
72
+ version: "7.98.2",
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",
@@ -18167,13 +18167,23 @@ function validateSwarmPath(directory, filename) {
18167
18167
  }
18168
18168
  return resolved;
18169
18169
  }
18170
- async function readSwarmFileAsync(directory, filename) {
18170
+ async function readSwarmFileAsync(directory, filename, cache) {
18171
+ if (cache !== undefined) {
18172
+ const key = `${directory}::${filename}`;
18173
+ const cached2 = cache.get(key);
18174
+ if (cached2 !== undefined) {
18175
+ return cached2;
18176
+ }
18177
+ const promise2 = readSwarmFileAsync(directory, filename);
18178
+ cache.set(key, promise2);
18179
+ return promise2;
18180
+ }
18171
18181
  const maxAttempts = 5;
18172
18182
  const retryDelayMs = 10;
18173
18183
  for (let attempt = 0;attempt < maxAttempts; attempt++) {
18174
18184
  try {
18175
18185
  const resolvedPath = _internals3.validateSwarmPath(directory, filename);
18176
- return await readCachedTextFile(resolvedPath, async () => {
18186
+ return await _internals3.readCachedTextFile(resolvedPath, async () => {
18177
18187
  const file2 = bunFile(resolvedPath);
18178
18188
  return await file2.text();
18179
18189
  });
@@ -18198,7 +18208,13 @@ var init_utils2 = __esm(() => {
18198
18208
  init_utils();
18199
18209
  init_bun_compat();
18200
18210
  init_swarm_artifact_cache();
18201
- _internals3 = { safeHook, composeHandlers, validateSwarmPath, readSwarmFileAsync };
18211
+ _internals3 = {
18212
+ safeHook,
18213
+ composeHandlers,
18214
+ validateSwarmPath,
18215
+ readSwarmFileAsync,
18216
+ readCachedTextFile
18217
+ };
18202
18218
  });
18203
18219
 
18204
18220
  // src/utils/git-binary-missing-error.ts
@@ -20452,8 +20468,8 @@ function extractPlanHashFromMarkdown(markdown) {
20452
20468
  const match = markdown.match(/<!--\s*PLAN_HASH:\s*(\S+)\s*-->/);
20453
20469
  return match ? match[1] : null;
20454
20470
  }
20455
- async function isPlanMdInSync(directory, plan) {
20456
- const planMdContent = await readSwarmFileAsync(directory, "plan.md");
20471
+ async function isPlanMdInSync(directory, plan, cache) {
20472
+ const planMdContent = await readSwarmFileAsync(directory, "plan.md", cache);
20457
20473
  if (planMdContent === null) {
20458
20474
  return false;
20459
20475
  }
@@ -20487,8 +20503,8 @@ ${markdown}`;
20487
20503
  } catch {}
20488
20504
  }
20489
20505
  }
20490
- async function loadPlan(directory) {
20491
- const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
20506
+ async function loadPlan(directory, cache) {
20507
+ const planJsonContent = await readSwarmFileAsync(directory, "plan.json", cache);
20492
20508
  if (planJsonContent !== null) {
20493
20509
  if (planJsonContent.includes("\x00") || planJsonContent.includes("�")) {
20494
20510
  warn("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
@@ -20498,7 +20514,7 @@ async function loadPlan(directory) {
20498
20514
  if (validated === null) {
20499
20515
  warn("[loadPlan] plan.json disappeared during cached parse. Falling back to plan.md migration or ledger recovery.");
20500
20516
  } else {
20501
- const inSync = await isPlanMdInSync(directory, validated);
20517
+ const inSync = await isPlanMdInSync(directory, validated, cache);
20502
20518
  if (!inSync) {
20503
20519
  try {
20504
20520
  await _internals8.regeneratePlanMarkdown(directory, validated);
@@ -20621,7 +20637,7 @@ async function loadPlan(directory) {
20621
20637
  }
20622
20638
  }
20623
20639
  }
20624
- const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
20640
+ const planMdContent2 = await readSwarmFileAsync(directory, "plan.md", cache);
20625
20641
  if (planMdContent2 !== null) {
20626
20642
  const migrated = migrateLegacyPlan(planMdContent2);
20627
20643
  const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json");
@@ -20636,7 +20652,7 @@ async function loadPlan(directory) {
20636
20652
  }
20637
20653
  }
20638
20654
  }
20639
- const planMdContent = await readSwarmFileAsync(directory, "plan.md");
20655
+ const planMdContent = await readSwarmFileAsync(directory, "plan.md", cache);
20640
20656
  if (planMdContent !== null) {
20641
20657
  const migrated = migrateLegacyPlan(planMdContent);
20642
20658
  await savePlan(directory, migrated);
@@ -73875,6 +73891,10 @@ This project was already finalized in a previous /swarm close run. The plan has
73875
73891
  ctx.warnings.push(`Failed to write close-summary.md: ${msg}`);
73876
73892
  console.warn("[close-command] Failed to write close-summary.md:", error93);
73877
73893
  }
73894
+ const sessionIdsToEnd = [...swarmState.agentSessions.keys()];
73895
+ for (const sessionId of sessionIdsToEnd) {
73896
+ _internals38.endAgentSession(sessionId);
73897
+ }
73878
73898
  _internals38.resetSwarmStatePreservingSingletons();
73879
73899
  const retroWarnings = ctx.warnings.filter((w) => w.includes("Retrospective write") || w.includes("retrospective write") || w.includes("Session retrospective"));
73880
73900
  const otherWarnings = ctx.warnings.filter((w) => !w.includes("Retrospective write") && !w.includes("retrospective write") && !w.includes("Session retrospective"));
@@ -74041,7 +74061,8 @@ var init_close = __esm(() => {
74041
74061
  runCleanStage,
74042
74062
  runAlignStage,
74043
74063
  archiveEvidence,
74044
- closePlanTerminalState
74064
+ closePlanTerminalState,
74065
+ endAgentSession
74045
74066
  };
74046
74067
  });
74047
74068
 
@@ -119725,6 +119746,7 @@ function createSystemEnhancerHook(config3, directory) {
119725
119746
  return;
119726
119747
  }
119727
119748
  }
119749
+ const planReadCache = new Map;
119728
119750
  const maxInjectionTokens = config3.context_budget?.max_injection_tokens ?? 4000;
119729
119751
  let injectedTokens = 0;
119730
119752
  let actualDemand = 0;
@@ -119739,7 +119761,7 @@ function createSystemEnhancerHook(config3, directory) {
119739
119761
  } else {
119740
119762
  seAllocation = maxInjectionTokens;
119741
119763
  }
119742
- const contextContent = await readSwarmFileAsync(directory, "context.md");
119764
+ const contextContent = await readSwarmFileAsync(directory, "context.md", planReadCache);
119743
119765
  try {
119744
119766
  const { scanDocIndex: scanDocIndex2 } = await Promise.resolve().then(() => (init_doc_scan(), exports_doc_scan));
119745
119767
  const { manifest, cached: cached3 } = await scanDocIndex2(directory);
@@ -119799,19 +119821,19 @@ function createSystemEnhancerHook(config3, directory) {
119799
119821
  if (!scoringEnabled) {
119800
119822
  let plan2 = null;
119801
119823
  try {
119802
- plan2 = await loadPlan(directory);
119824
+ plan2 = await loadPlan(directory, planReadCache);
119803
119825
  } catch (error93) {
119804
119826
  warn(`Failed to load plan: ${error93 instanceof Error ? error93.message : String(error93)}`);
119805
119827
  }
119806
119828
  maybeAppendSpecDriftAdvisory(output, directory, plan2);
119807
- const mode = await detectArchitectMode(directory);
119829
+ const mode = await detectArchitectMode(directory, planReadCache);
119808
119830
  let planContent = null;
119809
119831
  let phaseHeader = "";
119810
119832
  if (plan2 && plan2.migration_status !== "migration_failed") {
119811
119833
  phaseHeader = extractCurrentPhaseFromPlan(plan2) || "";
119812
- planContent = await readSwarmFileAsync(directory, "plan.md");
119834
+ planContent = await readSwarmFileAsync(directory, "plan.md", planReadCache);
119813
119835
  } else {
119814
- planContent = await readSwarmFileAsync(directory, "plan.md");
119836
+ planContent = await readSwarmFileAsync(directory, "plan.md", planReadCache);
119815
119837
  phaseHeader = planContent ? extractCurrentPhase(planContent) || "" : "";
119816
119838
  }
119817
119839
  if (phaseHeader) {
@@ -119823,7 +119845,7 @@ function createSystemEnhancerHook(config3, directory) {
119823
119845
  }
119824
119846
  if (mode !== "DISCOVER") {
119825
119847
  try {
119826
- const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
119848
+ const handoffContent = await readSwarmFileAsync(directory, "handoff.md", planReadCache);
119827
119849
  if (handoffContent) {
119828
119850
  const handoffPath = validateSwarmPath(directory, "handoff.md");
119829
119851
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
@@ -120187,7 +120209,7 @@ ${budgetWarning}`);
120187
120209
  } else {}
120188
120210
  return;
120189
120211
  }
120190
- const mode_b = await detectArchitectMode(directory);
120212
+ const mode_b = await detectArchitectMode(directory, planReadCache);
120191
120213
  const userScoringConfig = config3.context_budget?.scoring;
120192
120214
  const candidates = [];
120193
120215
  let idCounter = 0;
@@ -120199,7 +120221,7 @@ ${budgetWarning}`);
120199
120221
  } : DEFAULT_SCORING_CONFIG;
120200
120222
  let plan = null;
120201
120223
  try {
120202
- plan = await loadPlan(directory);
120224
+ plan = await loadPlan(directory, planReadCache);
120203
120225
  } catch (error93) {
120204
120226
  warn(`Failed to load plan: ${error93 instanceof Error ? error93.message : String(error93)}`);
120205
120227
  }
@@ -120210,7 +120232,7 @@ ${budgetWarning}`);
120210
120232
  currentPhase = extractCurrentPhaseFromPlan(plan);
120211
120233
  currentTask = extractCurrentTaskFromPlan(plan);
120212
120234
  } else {
120213
- planContentForCursor = await readSwarmFileAsync(directory, "plan.md");
120235
+ planContentForCursor = await readSwarmFileAsync(directory, "plan.md", planReadCache);
120214
120236
  if (planContentForCursor) {
120215
120237
  currentPhase = extractCurrentPhase(planContentForCursor);
120216
120238
  currentTask = extractCurrentTask(planContentForCursor);
@@ -120254,7 +120276,7 @@ ${budgetWarning}`);
120254
120276
  }
120255
120277
  if (mode_b !== "DISCOVER") {
120256
120278
  try {
120257
- const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
120279
+ const handoffContent = await readSwarmFileAsync(directory, "handoff.md", planReadCache);
120258
120280
  if (handoffContent) {
120259
120281
  const handoffPath = validateSwarmPath(directory, "handoff.md");
120260
120282
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
@@ -120753,9 +120775,9 @@ ${activitySection}`;
120753
120775
  }
120754
120776
  return contextSummary;
120755
120777
  }
120756
- async function detectArchitectMode(directory) {
120778
+ async function detectArchitectMode(directory, cache4) {
120757
120779
  try {
120758
- const plan = await loadPlan(directory);
120780
+ const plan = await loadPlan(directory, cache4);
120759
120781
  if (!plan) {
120760
120782
  return "DISCOVER";
120761
120783
  }
@@ -108,7 +108,7 @@ export declare function regeneratePlanMarkdown(directory: string, plan: Plan): P
108
108
  * 3. .swarm/plan.md exists only -> migrate from plan.md, save both files, return Plan
109
109
  * 4. Neither exists -> return null
110
110
  */
111
- export declare function loadPlan(directory: string): Promise<RuntimePlan | null>;
111
+ export declare function loadPlan(directory: string, cache?: Map<string, Promise<string | null>>): Promise<RuntimePlan | null>;
112
112
  /**
113
113
  * Recovery-path helper for callers that legitimately need to replace the
114
114
  * plan task set without explicit per-id acknowledgement (e.g. rebuilding
package/dist/state.d.ts CHANGED
@@ -391,9 +391,9 @@ export declare function resetSwarmStatePreservingSingletons(): void;
391
391
  export declare function startAgentSession(sessionId: string, agentName: string, staleDurationMs?: number, directory?: string): void;
392
392
  /**
393
393
  * End an agent session by removing it from the state.
394
- * NOTE: Currently unused in production no session lifecycle teardown is wired up.
395
- * Sessions accumulate for the process lifetime. Callers should integrate this into
396
- * a session TTL or idle-timeout mechanism to prevent unbounded Map growth.
394
+ * Called at session terminal state transitions (task completion, error, or session
395
+ * teardown) to prevent unbounded Map growth. Double-calls are safe: Map.delete is
396
+ * a no-op for missing keys (FR-010).
397
397
  * @param sessionId - The session identifier to remove
398
398
  */
399
399
  export declare function endAgentSession(sessionId: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.98.1",
3
+ "version": "7.98.2",
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",