opencode-swarm-plugin 0.28.1 → 0.29.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.
@@ -1,5 +1,5 @@
1
1
  $ bun build ./src/index.ts --outdir ./dist --target node --external @electric-sql/pglite --external swarm-mail && bun build ./src/plugin.ts --outfile ./dist/plugin.js --target node --external @electric-sql/pglite --external swarm-mail && tsc
2
- Bundled 200 modules in 34ms
2
+ Bundled 200 modules in 35ms
3
3
 
4
4
  index.js 1.20 MB (entry point)
5
5
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.29.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 Cell IDs Now Wear Their Project Colors
8
+
9
+ > _"We may fantasize about being International Men of Mystery, but our code needs to be mundane and clear. One of the most important parts of clear code is good names."_
10
+ > — Martin Fowler, _Refactoring_
11
+
12
+ Cell IDs finally know where they came from. Instead of anonymous `bd-xxx` prefixes,
13
+ new cells proudly display their project name: `swarm-mail-lf2p4u-abc123`.
14
+
15
+ ### What Changed
16
+
17
+ **swarm-mail:**
18
+
19
+ - `generateBeadId()` now reads `package.json` name field from project directory
20
+ - Added `slugifyProjectName()` for safe ID generation (lowercase, special chars → dashes)
21
+ - Falls back to `cell-` prefix if no package.json or no name field
22
+
23
+ **opencode-swarm-plugin:**
24
+
25
+ - Removed all `bd` CLI usage from `swarm-orchestrate.ts` - now uses HiveAdapter
26
+ - Improved compaction hook swarm detection with confidence levels (high/medium/low)
27
+ - Added fallback detection prompt for uncertain swarm states
28
+
29
+ ### Examples
30
+
31
+ | Before | After |
32
+ | ----------------------- | ------------------------------- |
33
+ | `bd-lf2p4u-mjbneh7mqah` | `swarm-mail-lf2p4u-mjbneh7mqah` |
34
+ | `bd-abc123-xyz` | `my-cool-app-abc123-xyz` |
35
+ | (no package.json) | `cell-abc123-xyz` |
36
+
37
+ ### Why It Matters
38
+
39
+ - **Identifiable at a glance** - Know which project a cell belongs to without looking it up
40
+ - **Multi-project workspaces** - Filter/search cells by project prefix
41
+ - **Terminology cleanup** - Removes legacy "bead" (`bd-`) from user-facing IDs
42
+
43
+ ### Backward Compatible
44
+
45
+ Existing `bd-*` IDs still work fine. No migration needed - only NEW cells get project prefixes.
46
+
47
+ ### Compaction: Keeping the Swarm Alive
48
+
49
+ > _"Intelligent and structured group dynamics that emerge not from a leader, but from the local interactions of the elements themselves."_
50
+ > — Daniel Shiffman, _The Nature of Code_
51
+
52
+ The compaction hook now uses multi-signal detection to keep swarms cooking through context compression:
53
+
54
+ - **HIGH confidence:** Active reservations, in_progress cells → full swarm context
55
+ - **MEDIUM confidence:** Open subtasks, unclosed epics → full swarm context
56
+ - **LOW confidence:** Any cells exist → fallback detection prompt
57
+
58
+ Philosophy: Err on the side of continuation. A false positive costs context space. A false negative loses the swarm.
59
+
60
+ ### Patch Changes
61
+
62
+ - Updated dependencies [[`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380)]:
63
+ - swarm-mail@0.4.0
64
+
65
+ ## 0.28.2
66
+
67
+ ### Patch Changes
68
+
69
+ - Updated dependencies [[`90409ef`](https://github.com/joelhooks/swarm-tools/commit/90409ef4f353844b25fe04221bc80d6f930eced2)]:
70
+ - swarm-mail@0.3.4
71
+
3
72
  ## 0.28.1
4
73
 
5
74
  ### Patch Changes
@@ -5,6 +5,12 @@
5
5
  * When context is compacted, this hook injects instructions for the summarizer
6
6
  * to preserve swarm coordination state and enable seamless resumption.
7
7
  *
8
+ * ## Philosophy: Err on the Side of Continuation
9
+ *
10
+ * It's better to inject swarm context unnecessarily than to lose an active swarm.
11
+ * The cost of a false positive (extra context) is low.
12
+ * The cost of a false negative (lost swarm) is high - wasted work, confused agents.
13
+ *
8
14
  * Hook signature (from @opencode-ai/plugin):
9
15
  * ```typescript
10
16
  * "experimental.session.compacting"?: (
@@ -33,11 +39,23 @@
33
39
  * autonomously after context compression.
34
40
  */
35
41
  export declare const SWARM_COMPACTION_CONTEXT = "## \uD83D\uDC1D SWARM ACTIVE - Keep Cooking\n\nYou are the **COORDINATOR** of an active swarm. Context was compacted but the swarm is still running.\n\n**YOUR JOB:** Keep orchestrating. Spawn agents. Monitor progress. Unblock work. Ship it.\n\n### Preserve in Summary\n\nExtract from session context:\n\n1. **Epic & Subtasks** - IDs, titles, status, file assignments\n2. **What's Running** - Which agents are active, what they're working on \n3. **What's Blocked** - Blockers and what's needed to unblock\n4. **What's Done** - Completed work and any follow-ups needed\n5. **What's Next** - Pending subtasks ready to spawn\n\n### Summary Format\n\n```\n## \uD83D\uDC1D Swarm State\n\n**Epic:** <bd-xxx> - <title>\n**Project:** <path>\n**Progress:** X/Y subtasks complete\n\n**Active:**\n- <bd-xxx>: <title> [in_progress] \u2192 <agent> working on <files>\n\n**Blocked:**\n- <bd-xxx>: <title> - BLOCKED: <reason>\n\n**Completed:**\n- <bd-xxx>: <title> \u2713\n\n**Ready to Spawn:**\n- <bd-xxx>: <title> (files: <...>)\n```\n\n### On Resume - IMMEDIATELY\n\n1. `swarm_status(epic_id=\"<epic>\", project_key=\"<path>\")` - Get current state\n2. `swarmmail_inbox(limit=5)` - Check for agent messages\n3. **Spawn ready subtasks** - Don't wait, fire them off\n4. **Unblock blocked work** - Resolve dependencies, reassign if needed\n5. **Collect completed work** - Close done subtasks, verify quality\n\n### Keep the Swarm Cooking\n\n- **Spawn aggressively** - If a subtask is ready and unblocked, spawn an agent\n- **Monitor actively** - Check status, read messages, respond to blockers\n- **Close the loop** - When all subtasks done, verify and close the epic\n- **Don't stop** - The swarm runs until the epic is closed\n\n**You are not waiting for instructions. You are the coordinator. Coordinate.**\n";
42
+ /**
43
+ * Fallback detection prompt - tells the compactor what to look for
44
+ *
45
+ * Used when we can't definitively detect a swarm but want to be safe.
46
+ * The compactor can check the conversation context for these patterns.
47
+ */
48
+ export declare const SWARM_DETECTION_FALLBACK = "## \uD83D\uDC1D Swarm Detection - Check Your Context\n\n**IMPORTANT:** Before summarizing, check if this session involves an active swarm.\n\nLook for ANY of these patterns in the conversation:\n\n### Tool Calls (definite swarm sign)\n- `swarm_decompose`, `swarm_spawn_subtask`, `swarm_status`, `swarm_complete`\n- `swarmmail_init`, `swarmmail_reserve`, `swarmmail_send`\n- `hive_create_epic`, `hive_start`, `hive_close`\n\n### IDs and Names\n- Cell IDs: `bd-xxx`, `bd-xxx.N` (subtask format)\n- Agent names: BlueLake, RedMountain, GreenValley, etc.\n- Epic references: \"epic\", \"subtask\", \"parent\"\n\n### Coordination Language\n- \"spawn\", \"worker\", \"coordinator\"\n- \"reserve\", \"reservation\", \"files\"\n- \"blocked\", \"unblock\", \"dependency\"\n- \"progress\", \"complete\", \"in_progress\"\n\n### If You Find Swarm Evidence\n\nInclude this in your summary:\n1. Epic ID and title\n2. Project path\n3. Subtask status (running/blocked/done/pending)\n4. Any blockers or issues\n5. What should happen next\n\n**Then tell the resumed session:**\n\"This is an active swarm. Check swarm_status and swarmmail_inbox immediately.\"\n";
36
49
  /**
37
50
  * Create the compaction hook for use in plugin registration
38
51
  *
39
- * Only injects swarm context if there's an active swarm (in-progress beads).
40
- * This keeps the coordinator cooking after compaction.
52
+ * Injects swarm context based on detection confidence:
53
+ * - HIGH/MEDIUM: Full swarm context (definitely/probably a swarm)
54
+ * - LOW: Fallback detection prompt (let compactor check context)
55
+ * - NONE: No injection (probably not a swarm)
56
+ *
57
+ * Philosophy: Err on the side of continuation. A false positive costs
58
+ * a bit of context space. A false negative loses the swarm.
41
59
  *
42
60
  * @example
43
61
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAyCF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAMjB"}
1
+ {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAoIF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAcjB"}
package/dist/hive.d.ts CHANGED
@@ -301,11 +301,11 @@ export declare const hive_sync: {
301
301
  export declare const hive_link_thread: {
302
302
  description: string;
303
303
  args: {
304
- cell_id: z.ZodString;
304
+ bead_id: z.ZodString;
305
305
  thread_id: z.ZodString;
306
306
  };
307
307
  execute(args: {
308
- cell_id: string;
308
+ bead_id: string;
309
309
  thread_id: string;
310
310
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
311
311
  };
@@ -461,11 +461,11 @@ export declare const hiveTools: {
461
461
  hive_link_thread: {
462
462
  description: string;
463
463
  args: {
464
- cell_id: z.ZodString;
464
+ bead_id: z.ZodString;
465
465
  thread_id: z.ZodString;
466
466
  };
467
467
  execute(args: {
468
- cell_id: string;
468
+ bead_id: string;
469
469
  thread_id: string;
470
470
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
471
471
  };
@@ -648,11 +648,11 @@ export declare const beads_sync: {
648
648
  export declare const beads_link_thread: {
649
649
  description: string;
650
650
  args: {
651
- cell_id: z.ZodString;
651
+ bead_id: z.ZodString;
652
652
  thread_id: z.ZodString;
653
653
  };
654
654
  execute(args: {
655
- cell_id: string;
655
+ bead_id: string;
656
656
  thread_id: string;
657
657
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
658
658
  };
@@ -811,11 +811,11 @@ export declare const beadsTools: {
811
811
  beads_link_thread: {
812
812
  description: string;
813
813
  args: {
814
- cell_id: z.ZodString;
814
+ bead_id: z.ZodString;
815
815
  thread_id: z.ZodString;
816
816
  };
817
817
  execute(args: {
818
- cell_id: string;
818
+ bead_id: string;
819
819
  thread_id: string;
820
820
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
821
821
  };
package/dist/index.d.ts CHANGED
@@ -1287,11 +1287,11 @@ export declare const allTools: {
1287
1287
  readonly hive_link_thread: {
1288
1288
  description: string;
1289
1289
  args: {
1290
- cell_id: import("zod").ZodString;
1290
+ bead_id: import("zod").ZodString;
1291
1291
  thread_id: import("zod").ZodString;
1292
1292
  };
1293
1293
  execute(args: {
1294
- cell_id: string;
1294
+ bead_id: string;
1295
1295
  thread_id: string;
1296
1296
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
1297
1297
  };
package/dist/index.js CHANGED
@@ -28258,30 +28258,30 @@ var hive_sync = tool({
28258
28258
  var hive_link_thread = tool({
28259
28259
  description: "Add metadata linking cell to Agent Mail thread",
28260
28260
  args: {
28261
- cell_id: tool.schema.string().describe("Cell ID"),
28261
+ bead_id: tool.schema.string().describe("Cell ID"),
28262
28262
  thread_id: tool.schema.string().describe("Agent Mail thread ID")
28263
28263
  },
28264
28264
  async execute(args, ctx) {
28265
28265
  const projectKey = getHiveWorkingDirectory();
28266
28266
  const adapter = await getHiveAdapter(projectKey);
28267
28267
  try {
28268
- const cell = await adapter.getCell(projectKey, args.cell_id);
28268
+ const cell = await adapter.getCell(projectKey, args.bead_id);
28269
28269
  if (!cell) {
28270
- throw new HiveError(`Cell not found: ${args.cell_id}`, "hive_link_thread");
28270
+ throw new HiveError(`Cell not found: ${args.bead_id}`, "hive_link_thread");
28271
28271
  }
28272
28272
  const existingDesc = cell.description || "";
28273
28273
  const threadMarker = `[thread:${args.thread_id}]`;
28274
28274
  if (existingDesc.includes(threadMarker)) {
28275
- return `Cell ${args.cell_id} already linked to thread ${args.thread_id}`;
28275
+ return `Cell ${args.bead_id} already linked to thread ${args.thread_id}`;
28276
28276
  }
28277
28277
  const newDesc = existingDesc ? `${existingDesc}
28278
28278
 
28279
28279
  ${threadMarker}` : threadMarker;
28280
- await adapter.updateCell(projectKey, args.cell_id, {
28280
+ await adapter.updateCell(projectKey, args.bead_id, {
28281
28281
  description: newDesc
28282
28282
  });
28283
- await adapter.markDirty(projectKey, args.cell_id);
28284
- return `Linked cell ${args.cell_id} to thread ${args.thread_id}`;
28283
+ await adapter.markDirty(projectKey, args.bead_id);
28284
+ return `Linked cell ${args.bead_id} to thread ${args.thread_id}`;
28285
28285
  } catch (error45) {
28286
28286
  const message = error45 instanceof Error ? error45.message : String(error45);
28287
28287
  throw new HiveError(`Failed to link thread: ${message}`, "hive_link_thread");
@@ -28523,27 +28523,11 @@ var toolCheckers = {
28523
28523
  }
28524
28524
  },
28525
28525
  beads: async () => {
28526
- const exists = await commandExists("bd");
28527
- if (!exists) {
28528
- return {
28529
- available: false,
28530
- checkedAt: new Date().toISOString(),
28531
- error: "bd command not found"
28532
- };
28533
- }
28534
- try {
28535
- const result = await Bun.$`bd --version`.quiet().nothrow();
28536
- return {
28537
- available: result.exitCode === 0,
28538
- checkedAt: new Date().toISOString()
28539
- };
28540
- } catch (e) {
28541
- return {
28542
- available: false,
28543
- checkedAt: new Date().toISOString(),
28544
- error: String(e)
28545
- };
28546
- }
28526
+ return {
28527
+ available: false,
28528
+ checkedAt: new Date().toISOString(),
28529
+ error: "bd CLI is deprecated - use hive_* tools with HiveAdapter instead"
28530
+ };
28547
28531
  },
28548
28532
  "swarm-mail": async () => {
28549
28533
  try {
@@ -32663,27 +32647,25 @@ var reviewTools = {
32663
32647
  };
32664
32648
 
32665
32649
  // src/swarm-orchestrate.ts
32666
- async function queryEpicSubtasks(epicId) {
32667
- const beadsAvailable = await isToolAvailable("beads");
32668
- if (!beadsAvailable) {
32669
- warnMissingTool("beads");
32670
- return [];
32671
- }
32672
- const result = await Bun.$`bd list --parent ${epicId} --json`.quiet().nothrow();
32673
- if (result.exitCode !== 0) {
32674
- console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, result.stderr.toString());
32675
- return [];
32676
- }
32650
+ async function queryEpicSubtasks(projectKey, epicId) {
32677
32651
  try {
32678
- const parsed = JSON.parse(result.stdout.toString());
32679
- return exports_external.array(BeadSchema).parse(parsed);
32652
+ const adapter = await getHiveAdapter(projectKey);
32653
+ const cells = await adapter.queryCells(projectKey, { parent_id: epicId });
32654
+ return cells.filter((cell) => cell.status !== "tombstone").map((cell) => ({
32655
+ id: cell.id,
32656
+ title: cell.title,
32657
+ description: cell.description || "",
32658
+ status: cell.status,
32659
+ priority: cell.priority,
32660
+ issue_type: cell.type,
32661
+ created_at: new Date(cell.created_at).toISOString(),
32662
+ updated_at: cell.updated_at ? new Date(cell.updated_at).toISOString() : undefined,
32663
+ dependencies: [],
32664
+ metadata: {}
32665
+ }));
32680
32666
  } catch (error45) {
32681
- if (error45 instanceof exports_external.ZodError) {
32682
- console.error(`[swarm] ERROR: Invalid bead data for epic ${epicId}:`, error45.message);
32683
- return [];
32684
- }
32685
- console.error(`[swarm] ERROR: Failed to parse beads for epic ${epicId}:`, error45);
32686
- throw error45;
32667
+ console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, error45 instanceof Error ? error45.message : String(error45));
32668
+ return [];
32687
32669
  }
32688
32670
  }
32689
32671
  async function querySwarmMessages(projectKey, threadId) {
@@ -33035,7 +33017,7 @@ var swarm_status = tool({
33035
33017
  project_key: tool.schema.string().describe("Project path (for Agent Mail queries)")
33036
33018
  },
33037
33019
  async execute(args) {
33038
- const subtasks = await queryEpicSubtasks(args.epic_id);
33020
+ const subtasks = await queryEpicSubtasks(args.project_key, args.epic_id);
33039
33021
  const statusCounts = {
33040
33022
  running: 0,
33041
33023
  completed: 0,
@@ -33110,8 +33092,13 @@ var swarm_progress = tool({
33110
33092
  };
33111
33093
  const validated = AgentProgressSchema.parse(progress);
33112
33094
  if (args.status === "blocked" || args.status === "in_progress") {
33113
- const beadStatus = args.status === "blocked" ? "blocked" : "in_progress";
33114
- await Bun.$`bd update ${args.bead_id} --status ${beadStatus} --json`.quiet().nothrow();
33095
+ try {
33096
+ const adapter = await getHiveAdapter(args.project_key);
33097
+ const newStatus = args.status === "blocked" ? "blocked" : "in_progress";
33098
+ await adapter.changeCellStatus(args.project_key, args.bead_id, newStatus);
33099
+ } catch (error45) {
33100
+ console.error(`[swarm] Failed to update cell status: ${error45 instanceof Error ? error45.message : String(error45)}`);
33101
+ }
33115
33102
  }
33116
33103
  const epicId = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
33117
33104
  await sendSwarmMessage3({
@@ -33255,6 +33242,22 @@ Or use skip_review=true to bypass (not recommended for production work).`
33255
33242
  }
33256
33243
  try {
33257
33244
  const projectKey = args.project_key.replace(/\//g, "-").replace(/\\/g, "-");
33245
+ const adapter = await getHiveAdapter(args.project_key);
33246
+ const cell = await adapter.getCell(projectKey, args.bead_id);
33247
+ if (!cell) {
33248
+ return JSON.stringify({
33249
+ success: false,
33250
+ error: `Bead not found: ${args.bead_id}`,
33251
+ hint: "Check the bead ID is correct. Use hive_query to list open cells."
33252
+ });
33253
+ }
33254
+ if (cell.status === "closed") {
33255
+ return JSON.stringify({
33256
+ success: false,
33257
+ error: `Bead already closed: ${args.bead_id}`,
33258
+ hint: "This bead was already completed. No action needed."
33259
+ });
33260
+ }
33258
33261
  let agentRegistered = false;
33259
33262
  let registrationWarning = "";
33260
33263
  try {
@@ -33339,32 +33342,25 @@ Continuing with completion, but this should be fixed for future subtasks.`;
33339
33342
  }, null, 2);
33340
33343
  }
33341
33344
  }
33342
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.cwd(args.project_key).quiet().nothrow();
33343
- if (closeResult.exitCode !== 0) {
33344
- const stderrOutput = closeResult.stderr.toString().trim();
33345
- const stdoutOutput = closeResult.stdout.toString().trim();
33346
- const isNoDatabaseError = stderrOutput.includes("no beads database found");
33347
- const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
33345
+ try {
33346
+ await adapter.closeCell(args.project_key, args.bead_id, args.summary);
33347
+ } catch (closeError) {
33348
+ const errorMessage = closeError instanceof Error ? closeError.message : String(closeError);
33348
33349
  return JSON.stringify({
33349
33350
  success: false,
33350
33351
  error: "Failed to close cell",
33351
- failed_step: "bd close",
33352
- details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
33352
+ failed_step: "closeCell",
33353
+ details: errorMessage,
33353
33354
  bead_id: args.bead_id,
33354
33355
  project_key: args.project_key,
33355
33356
  recovery: {
33356
- steps: isNoDatabaseError ? [
33357
- `1. Verify project_key is correct: "${args.project_key}"`,
33358
- `2. Check .beads/ exists in that directory`,
33359
- `3. Cell ID prefix "${args.bead_id.split("-")[0]}" should match project`,
33360
- `4. Try: hive_close(id="${args.bead_id}", reason="...")`
33361
- ] : [
33362
- `1. Check cell exists: bd show ${args.bead_id}`,
33363
- `2. Check cell status (might already be closed): hive_query()`,
33357
+ steps: [
33358
+ `1. Check cell exists: hive_query()`,
33359
+ `2. Check cell status (might already be closed)`,
33364
33360
  `3. If cell is blocked, unblock first: hive_update(id="${args.bead_id}", status="in_progress")`,
33365
33361
  `4. Try closing directly: hive_close(id="${args.bead_id}", reason="...")`
33366
33362
  ],
33367
- hint: isNoDatabaseError ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.` : isNotFoundError ? `Cell "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.` : "If cell is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
33363
+ hint: "Cell may already be closed, or the ID is incorrect."
33368
33364
  }
33369
33365
  }, null, 2);
33370
33366
  }
@@ -33565,12 +33561,13 @@ ${errorStack.slice(0, 1000)}
33565
33561
  }
33566
33562
  return JSON.stringify({
33567
33563
  success: false,
33568
- error: errorMessage,
33564
+ error: `swarm_complete failed: ${errorMessage}`,
33569
33565
  failed_step: failedStep,
33570
33566
  bead_id: args.bead_id,
33571
33567
  agent_name: args.agent_name,
33572
33568
  coordinator_notified: notificationSent,
33573
33569
  stack_trace: errorStack?.slice(0, 500),
33570
+ hint: "Check the error message above. Common issues: bead not found, session not initialized.",
33574
33571
  context: {
33575
33572
  summary: args.summary,
33576
33573
  files_touched: args.files_touched || [],
@@ -35890,6 +35887,7 @@ async function resetStorage() {
35890
35887
  init_skills();
35891
35888
 
35892
35889
  // src/compaction-hook.ts
35890
+ import { checkSwarmHealth as checkSwarmHealth3 } from "swarm-mail";
35893
35891
  var SWARM_COMPACTION_CONTEXT = `## \uD83D\uDC1D SWARM ACTIVE - Keep Cooking
35894
35892
 
35895
35893
  You are the **COORDINATOR** of an active swarm. Context was compacted but the swarm is still running.
@@ -35945,23 +35943,128 @@ Extract from session context:
35945
35943
 
35946
35944
  **You are not waiting for instructions. You are the coordinator. Coordinate.**
35947
35945
  `;
35948
- async function hasSwarmSign() {
35946
+ var SWARM_DETECTION_FALLBACK = `## \uD83D\uDC1D Swarm Detection - Check Your Context
35947
+
35948
+ **IMPORTANT:** Before summarizing, check if this session involves an active swarm.
35949
+
35950
+ Look for ANY of these patterns in the conversation:
35951
+
35952
+ ### Tool Calls (definite swarm sign)
35953
+ - \`swarm_decompose\`, \`swarm_spawn_subtask\`, \`swarm_status\`, \`swarm_complete\`
35954
+ - \`swarmmail_init\`, \`swarmmail_reserve\`, \`swarmmail_send\`
35955
+ - \`hive_create_epic\`, \`hive_start\`, \`hive_close\`
35956
+
35957
+ ### IDs and Names
35958
+ - Cell IDs: \`bd-xxx\`, \`bd-xxx.N\` (subtask format)
35959
+ - Agent names: BlueLake, RedMountain, GreenValley, etc.
35960
+ - Epic references: "epic", "subtask", "parent"
35961
+
35962
+ ### Coordination Language
35963
+ - "spawn", "worker", "coordinator"
35964
+ - "reserve", "reservation", "files"
35965
+ - "blocked", "unblock", "dependency"
35966
+ - "progress", "complete", "in_progress"
35967
+
35968
+ ### If You Find Swarm Evidence
35969
+
35970
+ Include this in your summary:
35971
+ 1. Epic ID and title
35972
+ 2. Project path
35973
+ 3. Subtask status (running/blocked/done/pending)
35974
+ 4. Any blockers or issues
35975
+ 5. What should happen next
35976
+
35977
+ **Then tell the resumed session:**
35978
+ "This is an active swarm. Check swarm_status and swarmmail_inbox immediately."
35979
+ `;
35980
+ async function detectSwarm() {
35981
+ const reasons = [];
35982
+ let highConfidence = false;
35983
+ let mediumConfidence = false;
35984
+ let lowConfidence = false;
35949
35985
  try {
35950
35986
  const projectKey = getHiveWorkingDirectory();
35951
- const adapter = await getHiveAdapter(projectKey);
35952
- const cells = await adapter.queryCells(projectKey, {});
35953
- if (!Array.isArray(cells))
35954
- return false;
35955
- return cells.some((c) => c.status === "in_progress" || c.status === "open" && c.parent_id || c.type === "epic" && c.status !== "closed");
35987
+ try {
35988
+ const health = await checkSwarmHealth3(projectKey);
35989
+ if (health.healthy && health.stats) {
35990
+ if (health.stats.reservations > 0) {
35991
+ highConfidence = true;
35992
+ reasons.push(`${health.stats.reservations} active file reservations`);
35993
+ }
35994
+ if (health.stats.agents > 0) {
35995
+ mediumConfidence = true;
35996
+ reasons.push(`${health.stats.agents} registered agents`);
35997
+ }
35998
+ if (health.stats.messages > 0) {
35999
+ lowConfidence = true;
36000
+ reasons.push(`${health.stats.messages} swarm messages`);
36001
+ }
36002
+ }
36003
+ } catch {}
36004
+ try {
36005
+ const adapter = await getHiveAdapter(projectKey);
36006
+ const cells = await adapter.queryCells(projectKey, {});
36007
+ if (Array.isArray(cells) && cells.length > 0) {
36008
+ const inProgress = cells.filter((c) => c.status === "in_progress");
36009
+ if (inProgress.length > 0) {
36010
+ highConfidence = true;
36011
+ reasons.push(`${inProgress.length} cells in_progress`);
36012
+ }
36013
+ const subtasks = cells.filter((c) => c.status === "open" && c.parent_id);
36014
+ if (subtasks.length > 0) {
36015
+ mediumConfidence = true;
36016
+ reasons.push(`${subtasks.length} open subtasks`);
36017
+ }
36018
+ const openEpics = cells.filter((c) => c.type === "epic" && c.status !== "closed");
36019
+ if (openEpics.length > 0) {
36020
+ mediumConfidence = true;
36021
+ reasons.push(`${openEpics.length} unclosed epics`);
36022
+ }
36023
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
36024
+ const recentCells = cells.filter((c) => c.updated_at > oneHourAgo);
36025
+ if (recentCells.length > 0) {
36026
+ mediumConfidence = true;
36027
+ reasons.push(`${recentCells.length} cells updated in last hour`);
36028
+ }
36029
+ if (cells.length > 0) {
36030
+ lowConfidence = true;
36031
+ reasons.push(`${cells.length} total cells in hive`);
36032
+ }
36033
+ }
36034
+ } catch {}
35956
36035
  } catch {
35957
- return false;
36036
+ lowConfidence = true;
36037
+ reasons.push("Could not detect project, using fallback");
36038
+ }
36039
+ let confidence;
36040
+ if (highConfidence) {
36041
+ confidence = "high";
36042
+ } else if (mediumConfidence) {
36043
+ confidence = "medium";
36044
+ } else if (lowConfidence) {
36045
+ confidence = "low";
36046
+ } else {
36047
+ confidence = "none";
35958
36048
  }
36049
+ return {
36050
+ detected: confidence !== "none",
36051
+ confidence,
36052
+ reasons
36053
+ };
35959
36054
  }
35960
36055
  function createCompactionHook() {
35961
36056
  return async (_input, output) => {
35962
- const hasSign = await hasSwarmSign();
35963
- if (hasSign) {
35964
- output.context.push(SWARM_COMPACTION_CONTEXT);
36057
+ const detection = await detectSwarm();
36058
+ if (detection.confidence === "high" || detection.confidence === "medium") {
36059
+ const header = `[Swarm detected: ${detection.reasons.join(", ")}]
36060
+
36061
+ `;
36062
+ output.context.push(header + SWARM_COMPACTION_CONTEXT);
36063
+ } else if (detection.confidence === "low") {
36064
+ const header = `[Possible swarm: ${detection.reasons.join(", ")}]
36065
+
36066
+ `;
36067
+ output.context.push(header + SWARM_DETECTION_FALLBACK);
35965
36068
  }
35966
36069
  };
35967
36070
  }