opencode-swarm-plugin 0.29.0 → 0.30.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,9 +1,9 @@
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 35ms
2
+ Bundled 200 modules in 34ms
3
3
 
4
4
  index.js 1.20 MB (entry point)
5
5
 
6
- Bundled 201 modules in 34ms
6
+ Bundled 201 modules in 44ms
7
7
 
8
8
  plugin.js 1.16 MB (entry point)
9
9
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`f3917ad`](https://github.com/joelhooks/swarm-tools/commit/f3917ad911d3c716a2470a01c66bce3500f644f4) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 The Great bd CLI Purge
8
+
9
+ The `bd` CLI is officially dead. Long live HiveAdapter!
10
+
11
+ **What changed:**
12
+
13
+ ### `swarm init` Command Rewritten
14
+
15
+ - No longer shells out to `bd init` or `bd create`
16
+ - Uses `ensureHiveDirectory()` and `getHiveAdapter()` directly
17
+ - Supports `.beads` → `.hive` migration with user prompts
18
+ - Creates cells via HiveAdapter, not CLI
19
+
20
+ ### Auto-sync Removed from `index.ts`
21
+
22
+ - Removed `void $\`bd sync\`.quiet().nothrow()`after`hive_close`
23
+ - Users should call `hive_sync` explicitly at session end
24
+ - This was a fire-and-forget that could race with other operations
25
+
26
+ ### Plugin Template Updated
27
+
28
+ - `detectSwarm()` now has confidence levels (HIGH/MEDIUM/LOW/NONE)
29
+ - Added `SWARM_DETECTION_FALLBACK` for uncertain cases
30
+ - Compaction hook injects context based on confidence:
31
+ - HIGH/MEDIUM → Full swarm context
32
+ - LOW → Fallback detection prompt
33
+ - NONE → No injection
34
+
35
+ ### Error Handling Fixed
36
+
37
+ - `execTool()` now handles both string and object error formats
38
+ - Fixes "Tool execution failed" generic error from `swarm_complete`
39
+ - Actual error messages now propagate to the agent
40
+
41
+ **Why it matters:**
42
+
43
+ - No external CLI dependency for core functionality
44
+ - HiveAdapter is type-safe and testable
45
+ - Plugin works in environments without `bd` installed
46
+ - Better error messages for debugging
47
+
48
+ **Migration:** Run `swarm setup` to update your deployed plugin.
49
+
3
50
  ## 0.29.0
4
51
 
5
52
  ### Minor Changes
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Tests for swarm CLI file operation helpers
4
+ *
5
+ * These tests verify the verbose output helpers used in `swarm setup`:
6
+ * - writeFileWithStatus: logs created/updated/unchanged status
7
+ * - mkdirWithStatus: logs directory creation
8
+ * - rmWithStatus: logs file removal
9
+ */
10
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
11
+ import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "fs";
12
+ import { join } from "path";
13
+ import { tmpdir } from "os";
14
+
15
+ type FileStatus = "created" | "updated" | "unchanged";
16
+
17
+ /**
18
+ * Mock logger for testing (matches @clack/prompts API)
19
+ */
20
+ class MockLogger {
21
+ logs: Array<{ type: string; message: string }> = [];
22
+
23
+ success(msg: string) {
24
+ this.logs.push({ type: "success", message: msg });
25
+ }
26
+
27
+ message(msg: string) {
28
+ this.logs.push({ type: "message", message: msg });
29
+ }
30
+
31
+ reset() {
32
+ this.logs = [];
33
+ }
34
+ }
35
+
36
+ describe("File operation helpers", () => {
37
+ let testDir: string;
38
+ let logger: MockLogger;
39
+
40
+ beforeEach(() => {
41
+ testDir = join(tmpdir(), `swarm-test-${Date.now()}`);
42
+ mkdirSync(testDir, { recursive: true });
43
+ logger = new MockLogger();
44
+ });
45
+
46
+ afterEach(() => {
47
+ if (existsSync(testDir)) {
48
+ rmSync(testDir, { recursive: true, force: true });
49
+ }
50
+ });
51
+
52
+ describe("writeFileWithStatus", () => {
53
+ // Helper that mimics the implementation
54
+ function writeFileWithStatus(path: string, content: string, label: string): FileStatus {
55
+ const exists = existsSync(path);
56
+
57
+ if (exists) {
58
+ const current = readFileSync(path, "utf-8");
59
+ if (current === content) {
60
+ logger.message(` ${label}: ${path} (unchanged)`);
61
+ return "unchanged";
62
+ }
63
+ }
64
+
65
+ writeFileSync(path, content);
66
+ const status: FileStatus = exists ? "updated" : "created";
67
+ logger.success(`${label}: ${path} (${status})`);
68
+ return status;
69
+ }
70
+
71
+ test("returns 'created' for new file", () => {
72
+ const filePath = join(testDir, "new.txt");
73
+ const result = writeFileWithStatus(filePath, "content", "Test");
74
+
75
+ expect(result).toBe("created");
76
+ expect(logger.logs[0].type).toBe("success");
77
+ expect(logger.logs[0].message).toContain("(created)");
78
+ expect(existsSync(filePath)).toBe(true);
79
+ });
80
+
81
+ test("returns 'unchanged' if content is same", () => {
82
+ const filePath = join(testDir, "existing.txt");
83
+ writeFileSync(filePath, "same content");
84
+
85
+ const result = writeFileWithStatus(filePath, "same content", "Test");
86
+
87
+ expect(result).toBe("unchanged");
88
+ expect(logger.logs[0].type).toBe("message");
89
+ expect(logger.logs[0].message).toContain("(unchanged)");
90
+ });
91
+
92
+ test("returns 'updated' if content differs", () => {
93
+ const filePath = join(testDir, "existing.txt");
94
+ writeFileSync(filePath, "old content");
95
+
96
+ const result = writeFileWithStatus(filePath, "new content", "Test");
97
+
98
+ expect(result).toBe("updated");
99
+ expect(logger.logs[0].type).toBe("success");
100
+ expect(logger.logs[0].message).toContain("(updated)");
101
+ expect(readFileSync(filePath, "utf-8")).toBe("new content");
102
+ });
103
+ });
104
+
105
+ describe("mkdirWithStatus", () => {
106
+ function mkdirWithStatus(path: string): boolean {
107
+ if (!existsSync(path)) {
108
+ mkdirSync(path, { recursive: true });
109
+ logger.message(` Created directory: ${path}`);
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+
115
+ test("creates directory and logs when it doesn't exist", () => {
116
+ const dirPath = join(testDir, "newdir");
117
+ const result = mkdirWithStatus(dirPath);
118
+
119
+ expect(result).toBe(true);
120
+ expect(existsSync(dirPath)).toBe(true);
121
+ expect(logger.logs[0].type).toBe("message");
122
+ expect(logger.logs[0].message).toContain("Created directory");
123
+ });
124
+
125
+ test("returns false when directory already exists", () => {
126
+ const dirPath = join(testDir, "existing");
127
+ mkdirSync(dirPath);
128
+
129
+ const result = mkdirWithStatus(dirPath);
130
+
131
+ expect(result).toBe(false);
132
+ expect(logger.logs.length).toBe(0);
133
+ });
134
+ });
135
+
136
+ describe("rmWithStatus", () => {
137
+ function rmWithStatus(path: string, label: string): void {
138
+ if (existsSync(path)) {
139
+ rmSync(path);
140
+ logger.message(` Removed ${label}: ${path}`);
141
+ }
142
+ }
143
+
144
+ test("removes file and logs when it exists", () => {
145
+ const filePath = join(testDir, "todelete.txt");
146
+ writeFileSync(filePath, "content");
147
+
148
+ rmWithStatus(filePath, "test file");
149
+
150
+ expect(existsSync(filePath)).toBe(false);
151
+ expect(logger.logs[0].type).toBe("message");
152
+ expect(logger.logs[0].message).toContain("Removed test file");
153
+ });
154
+
155
+ test("does nothing when file doesn't exist", () => {
156
+ const filePath = join(testDir, "nonexistent.txt");
157
+
158
+ rmWithStatus(filePath, "test file");
159
+
160
+ expect(logger.logs.length).toBe(0);
161
+ });
162
+ });
163
+ });
package/bin/swarm.ts CHANGED
@@ -32,6 +32,8 @@ import {
32
32
  migrateBeadsToHive,
33
33
  mergeHistoricBeads,
34
34
  importJsonlToPGLite,
35
+ ensureHiveDirectory,
36
+ getHiveAdapter,
35
37
  } from "../src/hive";
36
38
 
37
39
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -71,6 +73,68 @@ const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`;
71
73
 
72
74
  const PACKAGE_NAME = "opencode-swarm-plugin";
73
75
 
76
+ // ============================================================================
77
+ // File Operation Helpers
78
+ // ============================================================================
79
+
80
+ type FileStatus = "created" | "updated" | "unchanged";
81
+
82
+ interface FileStats {
83
+ created: number;
84
+ updated: number;
85
+ unchanged: number;
86
+ }
87
+
88
+ /**
89
+ * Write a file with status logging (created/updated/unchanged)
90
+ * @param path - File path to write
91
+ * @param content - Content to write
92
+ * @param label - Label for logging (e.g., "Plugin", "Command")
93
+ * @returns Status of the operation
94
+ */
95
+ function writeFileWithStatus(path: string, content: string, label: string): FileStatus {
96
+ const exists = existsSync(path);
97
+
98
+ if (exists) {
99
+ const current = readFileSync(path, "utf-8");
100
+ if (current === content) {
101
+ p.log.message(dim(` ${label}: ${path} (unchanged)`));
102
+ return "unchanged";
103
+ }
104
+ }
105
+
106
+ writeFileSync(path, content);
107
+ const status: FileStatus = exists ? "updated" : "created";
108
+ p.log.success(`${label}: ${path} (${status})`);
109
+ return status;
110
+ }
111
+
112
+ /**
113
+ * Create a directory with logging
114
+ * @param path - Directory path to create
115
+ * @returns true if created, false if already exists
116
+ */
117
+ function mkdirWithStatus(path: string): boolean {
118
+ if (!existsSync(path)) {
119
+ mkdirSync(path, { recursive: true });
120
+ p.log.message(dim(` Created directory: ${path}`));
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Remove a file with logging
128
+ * @param path - File path to remove
129
+ * @param label - Label for logging
130
+ */
131
+ function rmWithStatus(path: string, label: string): void {
132
+ if (existsSync(path)) {
133
+ rmSync(path);
134
+ p.log.message(dim(` Removed ${label}: ${path}`));
135
+ }
136
+ }
137
+
74
138
  // ============================================================================
75
139
  // Seasonal Messages (inspired by Astro's Houston)
76
140
  // ============================================================================
@@ -1707,39 +1771,29 @@ async function setup() {
1707
1771
 
1708
1772
  p.log.step("Setting up OpenCode integration...");
1709
1773
 
1774
+ // Track file operation statistics
1775
+ const stats: FileStats = { created: 0, updated: 0, unchanged: 0 };
1776
+
1710
1777
  // Create directories if needed
1711
1778
  const skillsDir = join(configDir, "skills");
1712
1779
  for (const dir of [pluginDir, commandDir, agentDir, swarmAgentDir, skillsDir]) {
1713
- if (!existsSync(dir)) {
1714
- mkdirSync(dir, { recursive: true });
1715
- }
1780
+ mkdirWithStatus(dir);
1716
1781
  }
1717
1782
 
1718
- writeFileSync(pluginPath, getPluginWrapper());
1719
- p.log.success("Plugin: " + pluginPath);
1720
-
1721
- writeFileSync(commandPath, SWARM_COMMAND);
1722
- p.log.success("Command: " + commandPath);
1783
+ // Write plugin and command files
1784
+ stats[writeFileWithStatus(pluginPath, getPluginWrapper(), "Plugin")]++;
1785
+ stats[writeFileWithStatus(commandPath, SWARM_COMMAND, "Command")]++;
1723
1786
 
1724
1787
  // Write nested agent files (swarm/planner.md, swarm/worker.md)
1725
1788
  // This is the format used by Task(subagent_type="swarm/worker")
1726
- writeFileSync(plannerAgentPath, getPlannerAgent(coordinatorModel as string));
1727
- p.log.success("Planner agent: " + plannerAgentPath);
1728
-
1729
- writeFileSync(workerAgentPath, getWorkerAgent(workerModel as string));
1730
- p.log.success("Worker agent: " + workerAgentPath);
1789
+ stats[writeFileWithStatus(plannerAgentPath, getPlannerAgent(coordinatorModel as string), "Planner agent")]++;
1790
+ stats[writeFileWithStatus(workerAgentPath, getWorkerAgent(workerModel as string), "Worker agent")]++;
1731
1791
 
1732
1792
  // Clean up legacy flat agent files if they exist
1733
- if (existsSync(legacyPlannerPath)) {
1734
- rmSync(legacyPlannerPath);
1735
- p.log.message(dim(" Removed legacy: " + legacyPlannerPath));
1736
- }
1737
- if (existsSync(legacyWorkerPath)) {
1738
- rmSync(legacyWorkerPath);
1739
- p.log.message(dim(" Removed legacy: " + legacyWorkerPath));
1740
- }
1793
+ rmWithStatus(legacyPlannerPath, "legacy planner");
1794
+ rmWithStatus(legacyWorkerPath, "legacy worker");
1741
1795
 
1742
- p.log.success("Skills directory: " + skillsDir);
1796
+ p.log.message(dim(` Skills directory: ${skillsDir}`));
1743
1797
 
1744
1798
  // Show bundled skills info (and optionally sync to global skills dir)
1745
1799
  const bundledSkillsPath = join(__dirname, "..", "global-skills");
@@ -1850,17 +1904,29 @@ async function setup() {
1850
1904
  }
1851
1905
  }
1852
1906
 
1907
+ // Show setup summary
1908
+ const totalFiles = stats.created + stats.updated + stats.unchanged;
1909
+ const summaryParts: string[] = [];
1910
+ if (stats.created > 0) summaryParts.push(`${stats.created} created`);
1911
+ if (stats.updated > 0) summaryParts.push(`${stats.updated} updated`);
1912
+ if (stats.unchanged > 0) summaryParts.push(`${stats.unchanged} unchanged`);
1913
+
1914
+ p.log.message("");
1915
+ p.log.success(`Setup complete: ${totalFiles} files (${summaryParts.join(", ")})`);
1916
+
1853
1917
  p.note(
1854
- 'cd your-project\nbd init\nopencode\n/swarm "your task"\n\nSkills: Use skills_list to see available skills',
1918
+ 'cd your-project\nswarm init\nopencode\n/swarm "your task"\n\nSkills: Use skills_list to see available skills',
1855
1919
  "Next steps",
1856
1920
  );
1857
1921
 
1858
- p.outro("Setup complete! Run 'swarm doctor' to verify.");
1922
+ p.outro("Run 'swarm doctor' to verify installation.");
1859
1923
  }
1860
1924
 
1861
1925
  async function init() {
1862
1926
  p.intro("swarm init v" + VERSION);
1863
1927
 
1928
+ const projectPath = process.cwd();
1929
+
1864
1930
  const gitDir = existsSync(".git");
1865
1931
  if (!gitDir) {
1866
1932
  p.log.error("Not in a git repository");
@@ -1869,12 +1935,15 @@ async function init() {
1869
1935
  process.exit(1);
1870
1936
  }
1871
1937
 
1938
+ // Check for existing .hive or .beads directories
1939
+ const hiveDir = existsSync(".hive");
1872
1940
  const beadsDir = existsSync(".beads");
1873
- if (beadsDir) {
1874
- p.log.warn("Beads already initialized in this project");
1941
+
1942
+ if (hiveDir) {
1943
+ p.log.warn("Hive already initialized in this project (.hive/ exists)");
1875
1944
 
1876
1945
  const reinit = await p.confirm({
1877
- message: "Re-initialize beads?",
1946
+ message: "Continue anyway?",
1878
1947
  initialValue: false,
1879
1948
  });
1880
1949
 
@@ -1882,25 +1951,54 @@ async function init() {
1882
1951
  p.outro("Aborted");
1883
1952
  process.exit(0);
1884
1953
  }
1954
+ } else if (beadsDir) {
1955
+ // Offer migration from .beads to .hive
1956
+ p.log.warn("Found legacy .beads/ directory");
1957
+
1958
+ const migrate = await p.confirm({
1959
+ message: "Migrate .beads/ to .hive/?",
1960
+ initialValue: true,
1961
+ });
1962
+
1963
+ if (!p.isCancel(migrate) && migrate) {
1964
+ const s = p.spinner();
1965
+ s.start("Migrating .beads/ to .hive/...");
1966
+
1967
+ const result = await migrateBeadsToHive(projectPath);
1968
+
1969
+ if (result.migrated) {
1970
+ s.stop("Migration complete");
1971
+ p.log.success("Renamed .beads/ to .hive/");
1972
+
1973
+ // Merge historic beads if beads.base.jsonl exists
1974
+ const mergeResult = await mergeHistoricBeads(projectPath);
1975
+ if (mergeResult.merged > 0) {
1976
+ p.log.success(`Merged ${mergeResult.merged} historic cells`);
1977
+ }
1978
+ } else {
1979
+ s.stop("Migration skipped: " + result.reason);
1980
+ }
1981
+ }
1885
1982
  }
1886
1983
 
1887
1984
  const s = p.spinner();
1888
- s.start("Initializing beads...");
1889
-
1890
- const success = await runInstall("bd init");
1985
+ s.start("Initializing hive...");
1891
1986
 
1892
- if (success) {
1893
- s.stop("Beads initialized");
1894
- p.log.success("Created .beads/ directory");
1987
+ try {
1988
+ // Create .hive directory using our function (no bd CLI needed)
1989
+ ensureHiveDirectory(projectPath);
1990
+
1991
+ s.stop("Hive initialized");
1992
+ p.log.success("Created .hive/ directory");
1895
1993
 
1896
- const createBead = await p.confirm({
1897
- message: "Create your first bead?",
1994
+ const createCell = await p.confirm({
1995
+ message: "Create your first cell?",
1898
1996
  initialValue: true,
1899
1997
  });
1900
1998
 
1901
- if (!p.isCancel(createBead) && createBead) {
1999
+ if (!p.isCancel(createCell) && createCell) {
1902
2000
  const title = await p.text({
1903
- message: "Bead title:",
2001
+ message: "Cell title:",
1904
2002
  placeholder: "Implement user authentication",
1905
2003
  validate: (v) => (v.length === 0 ? "Title required" : undefined),
1906
2004
  });
@@ -1917,17 +2015,22 @@ async function init() {
1917
2015
  });
1918
2016
 
1919
2017
  if (!p.isCancel(typeResult)) {
1920
- const beadSpinner = p.spinner();
1921
- beadSpinner.start("Creating bead...");
1922
-
1923
- const createSuccess = await runInstall(
1924
- 'bd create --title "' + title + '" --type ' + typeResult,
1925
- );
1926
-
1927
- if (createSuccess) {
1928
- beadSpinner.stop("Bead created");
1929
- } else {
1930
- beadSpinner.stop("Failed to create bead");
2018
+ const cellSpinner = p.spinner();
2019
+ cellSpinner.start("Creating cell...");
2020
+
2021
+ try {
2022
+ // Use HiveAdapter to create the cell (no bd CLI needed)
2023
+ const adapter = await getHiveAdapter(projectPath);
2024
+ const cell = await adapter.createCell(projectPath, {
2025
+ title: title as string,
2026
+ type: typeResult as "feature" | "bug" | "task" | "chore",
2027
+ priority: 2,
2028
+ });
2029
+
2030
+ cellSpinner.stop("Cell created: " + cell.id);
2031
+ } catch (error) {
2032
+ cellSpinner.stop("Failed to create cell");
2033
+ p.log.error(error instanceof Error ? error.message : String(error));
1931
2034
  }
1932
2035
  }
1933
2036
  }
@@ -1953,9 +2056,9 @@ async function init() {
1953
2056
  }
1954
2057
 
1955
2058
  p.outro("Project initialized! Use '/swarm' in OpenCode to get started.");
1956
- } else {
1957
- s.stop("Failed to initialize beads");
1958
- p.log.error("Make sure 'bd' is installed: swarm doctor");
2059
+ } catch (error) {
2060
+ s.stop("Failed to initialize hive");
2061
+ p.log.error(error instanceof Error ? error.message : String(error));
1959
2062
  p.outro("Aborted");
1960
2063
  process.exit(1);
1961
2064
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;AAoCtE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,EAAE,MAqLzB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAe,WAAW,CAAC;AAM3B;;GAEG;AACH,cAAc,WAAW,CAAC;AAE1B;;;;;;;;;;;GAWG;AACH,cAAc,QAAQ,CAAC;AAEvB;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,cAAc,EACd,4BAA4B,EAC5B,4BAA4B,EAC5B,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;GAMG;AACH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EAEjB,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAC;AAMjB;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUX,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,QAAQ,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,SAAS,EACT,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,WAAW,EACX,sBAAsB,EACtB,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9D;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,WAAW,EACX,cAAc,EACd,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,KAAK,EACV,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,4BAA4B,EAC5B,8BAA8B,EAC9B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;GAWG;AACH,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -36161,9 +36161,6 @@ var SwarmPlugin = async (input) => {
36161
36161
  if (toolName === "swarm_complete" && activeAgentMailState) {
36162
36162
  await releaseReservations();
36163
36163
  }
36164
- if (toolName === "hive_close" || toolName === "hive_close") {
36165
- $`bd sync`.quiet().nothrow();
36166
- }
36167
36164
  }
36168
36165
  };
36169
36166
  };
package/dist/plugin.js CHANGED
@@ -35197,9 +35197,6 @@ var SwarmPlugin = async (input) => {
35197
35197
  if (toolName === "swarm_complete" && activeAgentMailState) {
35198
35198
  await releaseReservations();
35199
35199
  }
35200
- if (toolName === "hive_close" || toolName === "hive_close") {
35201
- $`bd sync`.quiet().nothrow();
35202
- }
35203
35200
  }
35204
35201
  };
35205
35202
  };
@@ -73,7 +73,11 @@ async function execTool(
73
73
  );
74
74
  } else if (!result.success && result.error) {
75
75
  // Tool returned an error in JSON format
76
- reject(new Error(result.error.message || "Tool execution failed"));
76
+ // Handle both string errors and object errors with .message
77
+ const errorMsg = typeof result.error === "string"
78
+ ? result.error
79
+ : (result.error.message || "Tool execution failed");
80
+ reject(new Error(errorMsg));
77
81
  } else {
78
82
  resolve(stdout);
79
83
  }
@@ -89,11 +93,11 @@ async function execTool(
89
93
  try {
90
94
  const result = JSON.parse(stdout);
91
95
  if (!result.success && result.error) {
92
- reject(
93
- new Error(
94
- result.error.message || `Tool failed with code ${code}`,
95
- ),
96
- );
96
+ // Handle both string errors and object errors with .message
97
+ const errorMsg = typeof result.error === "string"
98
+ ? result.error
99
+ : (result.error.message || `Tool failed with code ${code}`);
100
+ reject(new Error(errorMsg));
97
101
  } else {
98
102
  reject(
99
103
  new Error(stderr || stdout || `Tool failed with code ${code}`),
@@ -883,15 +887,33 @@ const skills_execute = tool({
883
887
  // Compaction Hook - Swarm Recovery Context
884
888
  // =============================================================================
885
889
 
890
+ /**
891
+ * Detection result with confidence level
892
+ */
893
+ interface SwarmDetection {
894
+ detected: boolean;
895
+ confidence: "high" | "medium" | "low" | "none";
896
+ reasons: string[];
897
+ }
898
+
886
899
  /**
887
900
  * Check for swarm sign - evidence a swarm passed through
888
901
  *
889
- * Like deer scat on a trail, we look for traces:
890
- * - In-progress beads (active work)
891
- * - Open beads with parent_id (subtasks of an epic)
892
- * - Unclosed epics
902
+ * Uses multiple signals with different confidence levels:
903
+ * - HIGH: in_progress cells (active work)
904
+ * - MEDIUM: Open subtasks, unclosed epics, recently updated cells
905
+ * - LOW: Any cells exist
906
+ *
907
+ * Philosophy: Err on the side of continuation.
908
+ * False positive = extra context (low cost)
909
+ * False negative = lost swarm (high cost)
893
910
  */
894
- async function hasSwarmSign(): Promise<boolean> {
911
+ async function detectSwarm(): Promise<SwarmDetection> {
912
+ const reasons: string[] = [];
913
+ let highConfidence = false;
914
+ let mediumConfidence = false;
915
+ let lowConfidence = false;
916
+
895
917
  try {
896
918
  const result = await new Promise<{ exitCode: number; stdout: string }>(
897
919
  (resolve) => {
@@ -909,24 +931,82 @@ async function hasSwarmSign(): Promise<boolean> {
909
931
  },
910
932
  );
911
933
 
912
- if (result.exitCode !== 0) return false;
934
+ if (result.exitCode !== 0) {
935
+ return { detected: false, confidence: "none", reasons: ["hive_query failed"] };
936
+ }
913
937
 
914
- const beads = JSON.parse(result.stdout);
915
- if (!Array.isArray(beads)) return false;
938
+ const cells = JSON.parse(result.stdout);
939
+ if (!Array.isArray(cells) || cells.length === 0) {
940
+ return { detected: false, confidence: "none", reasons: ["no cells found"] };
941
+ }
916
942
 
917
- // Look for swarm sign:
918
- // 1. Any in_progress beads
919
- // 2. Any open beads with a parent (subtasks)
920
- // 3. Any epics that aren't closed
921
- return beads.some(
922
- (b: { status: string; parent_id?: string; type?: string }) =>
923
- b.status === "in_progress" ||
924
- (b.status === "open" && b.parent_id) ||
925
- (b.type === "epic" && b.status !== "closed"),
943
+ // HIGH: Any in_progress cells
944
+ const inProgress = cells.filter(
945
+ (c: { status: string }) => c.status === "in_progress"
946
+ );
947
+ if (inProgress.length > 0) {
948
+ highConfidence = true;
949
+ reasons.push(`${inProgress.length} cells in_progress`);
950
+ }
951
+
952
+ // MEDIUM: Open subtasks (cells with parent_id)
953
+ const subtasks = cells.filter(
954
+ (c: { status: string; parent_id?: string }) =>
955
+ c.status === "open" && c.parent_id
956
+ );
957
+ if (subtasks.length > 0) {
958
+ mediumConfidence = true;
959
+ reasons.push(`${subtasks.length} open subtasks`);
960
+ }
961
+
962
+ // MEDIUM: Unclosed epics
963
+ const openEpics = cells.filter(
964
+ (c: { status: string; type?: string }) =>
965
+ c.type === "epic" && c.status !== "closed"
966
+ );
967
+ if (openEpics.length > 0) {
968
+ mediumConfidence = true;
969
+ reasons.push(`${openEpics.length} unclosed epics`);
970
+ }
971
+
972
+ // MEDIUM: Recently updated cells (last hour)
973
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
974
+ const recentCells = cells.filter(
975
+ (c: { updated_at?: number }) => c.updated_at && c.updated_at > oneHourAgo
926
976
  );
977
+ if (recentCells.length > 0) {
978
+ mediumConfidence = true;
979
+ reasons.push(`${recentCells.length} cells updated in last hour`);
980
+ }
981
+
982
+ // LOW: Any cells exist at all
983
+ if (cells.length > 0) {
984
+ lowConfidence = true;
985
+ reasons.push(`${cells.length} total cells in hive`);
986
+ }
927
987
  } catch {
928
- return false;
988
+ // Detection failed, use fallback
989
+ lowConfidence = true;
990
+ reasons.push("Detection error, using fallback");
929
991
  }
992
+
993
+ // Determine overall confidence
994
+ let confidence: "high" | "medium" | "low" | "none";
995
+ if (highConfidence) {
996
+ confidence = "high";
997
+ } else if (mediumConfidence) {
998
+ confidence = "medium";
999
+ } else if (lowConfidence) {
1000
+ confidence = "low";
1001
+ } else {
1002
+ confidence = "none";
1003
+ }
1004
+
1005
+ return {
1006
+ detected: confidence !== "none",
1007
+ confidence,
1008
+ reasons,
1009
+ };
930
1010
  }
931
1011
 
932
1012
  /**
@@ -992,6 +1072,47 @@ Extract from session context:
992
1072
  **You are not waiting for instructions. You are the coordinator. Coordinate.**
993
1073
  `;
994
1074
 
1075
+ /**
1076
+ * Fallback detection prompt - tells the compactor what to look for
1077
+ *
1078
+ * Used when we can't definitively detect a swarm but want to be safe.
1079
+ * The compactor can check the conversation context for these patterns.
1080
+ */
1081
+ const SWARM_DETECTION_FALLBACK = `## 🐝 Swarm Detection - Check Your Context
1082
+
1083
+ **IMPORTANT:** Before summarizing, check if this session involves an active swarm.
1084
+
1085
+ Look for ANY of these patterns in the conversation:
1086
+
1087
+ ### Tool Calls (definite swarm sign)
1088
+ - \`swarm_decompose\`, \`swarm_spawn_subtask\`, \`swarm_status\`, \`swarm_complete\`
1089
+ - \`swarmmail_init\`, \`swarmmail_reserve\`, \`swarmmail_send\`
1090
+ - \`hive_create_epic\`, \`hive_start\`, \`hive_close\`
1091
+
1092
+ ### IDs and Names
1093
+ - Cell IDs: \`bd-xxx\`, \`bd-xxx.N\` (subtask format)
1094
+ - Agent names: BlueLake, RedMountain, GreenValley, etc.
1095
+ - Epic references: "epic", "subtask", "parent"
1096
+
1097
+ ### Coordination Language
1098
+ - "spawn", "worker", "coordinator"
1099
+ - "reserve", "reservation", "files"
1100
+ - "blocked", "unblock", "dependency"
1101
+ - "progress", "complete", "in_progress"
1102
+
1103
+ ### If You Find Swarm Evidence
1104
+
1105
+ Include this in your summary:
1106
+ 1. Epic ID and title
1107
+ 2. Project path
1108
+ 3. Subtask status (running/blocked/done/pending)
1109
+ 4. Any blockers or issues
1110
+ 5. What should happen next
1111
+
1112
+ **Then tell the resumed session:**
1113
+ "This is an active swarm. Check swarm_status and swarmmail_inbox immediately."
1114
+ `;
1115
+
995
1116
  // Extended hooks type to include experimental compaction hook
996
1117
  type ExtendedHooks = Hooks & {
997
1118
  "experimental.session.compacting"?: (
@@ -1065,15 +1186,23 @@ export const SwarmPlugin: Plugin = async (
1065
1186
  skills_execute,
1066
1187
  },
1067
1188
 
1068
- // Swarm-aware compaction hook - only fires if there's an active swarm
1189
+ // Swarm-aware compaction hook - injects context based on detection confidence
1069
1190
  "experimental.session.compacting": async (
1070
1191
  _input: { sessionID: string },
1071
1192
  output: { context: string[] },
1072
1193
  ) => {
1073
- const hasSign = await hasSwarmSign();
1074
- if (hasSign) {
1075
- output.context.push(SWARM_COMPACTION_CONTEXT);
1194
+ const detection = await detectSwarm();
1195
+
1196
+ if (detection.confidence === "high" || detection.confidence === "medium") {
1197
+ // Definite or probable swarm - inject full context
1198
+ const header = `[Swarm detected: ${detection.reasons.join(", ")}]\n\n`;
1199
+ output.context.push(header + SWARM_COMPACTION_CONTEXT);
1200
+ } else if (detection.confidence === "low") {
1201
+ // Possible swarm - inject fallback detection prompt
1202
+ const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
1203
+ output.context.push(header + SWARM_DETECTION_FALLBACK);
1076
1204
  }
1205
+ // confidence === "none" - no injection, probably not a swarm
1077
1206
  },
1078
1207
  };
1079
1208
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -249,11 +249,9 @@ export const SwarmPlugin: Plugin = async (
249
249
  await releaseReservations();
250
250
  }
251
251
 
252
- // Auto-sync hive after closing (supports both hive_close and legacy hive_close)
253
- if (toolName === "hive_close" || toolName === "hive_close") {
254
- // Trigger async sync without blocking - fire and forget
255
- void $`bd sync`.quiet().nothrow();
256
- }
252
+ // Note: hive_sync should be called explicitly at session end
253
+ // Auto-sync was removed because bd CLI is deprecated
254
+ // The hive_sync tool handles flushing to JSONL and git commit/push
257
255
  },
258
256
  };
259
257
  };