opencode-swarm-plugin 0.28.2 → 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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +109 -0
- package/bin/swarm.test.ts +163 -0
- package/bin/swarm.ts +154 -51
- package/dist/compaction-hook.d.ts +20 -2
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +174 -74
- package/dist/plugin.js +63 -65
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/tool-availability.d.ts.map +1 -1
- package/examples/plugin-wrapper-template.ts +157 -28
- package/package.json +2 -2
- package/src/compaction-hook.test.ts +110 -0
- package/src/compaction-hook.ts +183 -29
- package/src/index.ts +3 -5
- package/src/learning.integration.test.ts +11 -3
- package/src/swarm-orchestrate.ts +76 -79
- package/src/swarm.integration.test.ts +88 -2
- package/src/tool-availability.ts +6 -24
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
isToolAvailable,
|
|
68
68
|
warnMissingTool,
|
|
69
69
|
} from "./tool-availability";
|
|
70
|
+
import { getHiveAdapter } from "./hive";
|
|
70
71
|
import { listSkills } from "./skills";
|
|
71
72
|
import {
|
|
72
73
|
canUseWorktreeIsolation,
|
|
@@ -82,45 +83,34 @@ import {
|
|
|
82
83
|
// ============================================================================
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
|
-
* Query beads for subtasks of an epic
|
|
86
|
+
* Query beads for subtasks of an epic using HiveAdapter (not bd CLI)
|
|
86
87
|
*/
|
|
87
|
-
async function queryEpicSubtasks(epicId: string): Promise<Bead[]> {
|
|
88
|
-
// Check if beads is available
|
|
89
|
-
const beadsAvailable = await isToolAvailable("beads");
|
|
90
|
-
if (!beadsAvailable) {
|
|
91
|
-
warnMissingTool("beads");
|
|
92
|
-
return []; // Return empty - swarm can still function without status tracking
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const result = await Bun.$`bd list --parent ${epicId} --json`
|
|
96
|
-
.quiet()
|
|
97
|
-
.nothrow();
|
|
98
|
-
|
|
99
|
-
if (result.exitCode !== 0) {
|
|
100
|
-
// Don't throw - just return empty and log error prominently
|
|
101
|
-
console.error(
|
|
102
|
-
`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`,
|
|
103
|
-
result.stderr.toString(),
|
|
104
|
-
);
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
|
|
88
|
+
async function queryEpicSubtasks(projectKey: string, epicId: string): Promise<Bead[]> {
|
|
108
89
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
90
|
+
const adapter = await getHiveAdapter(projectKey);
|
|
91
|
+
const cells = await adapter.queryCells(projectKey, { parent_id: epicId });
|
|
92
|
+
// Map Cell (from HiveAdapter) to Bead schema format
|
|
93
|
+
// Cell uses `type` and numeric timestamps, Bead uses `issue_type` and ISO strings
|
|
94
|
+
return cells
|
|
95
|
+
.filter(cell => cell.status !== "tombstone") // Exclude deleted cells
|
|
96
|
+
.map(cell => ({
|
|
97
|
+
id: cell.id,
|
|
98
|
+
title: cell.title,
|
|
99
|
+
description: cell.description || "",
|
|
100
|
+
status: cell.status as "open" | "in_progress" | "blocked" | "closed",
|
|
101
|
+
priority: cell.priority,
|
|
102
|
+
issue_type: cell.type as "bug" | "feature" | "task" | "epic" | "chore",
|
|
103
|
+
created_at: new Date(cell.created_at).toISOString(),
|
|
104
|
+
updated_at: cell.updated_at ? new Date(cell.updated_at).toISOString() : undefined,
|
|
105
|
+
dependencies: [], // Dependencies fetched separately if needed
|
|
106
|
+
metadata: {},
|
|
107
|
+
}));
|
|
111
108
|
} catch (error) {
|
|
112
|
-
if (error instanceof z.ZodError) {
|
|
113
|
-
console.error(
|
|
114
|
-
`[swarm] ERROR: Invalid bead data for epic ${epicId}:`,
|
|
115
|
-
error.message,
|
|
116
|
-
);
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
109
|
console.error(
|
|
120
|
-
`[swarm] ERROR: Failed to
|
|
121
|
-
error,
|
|
110
|
+
`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`,
|
|
111
|
+
error instanceof Error ? error.message : String(error),
|
|
122
112
|
);
|
|
123
|
-
|
|
113
|
+
return [];
|
|
124
114
|
}
|
|
125
115
|
}
|
|
126
116
|
|
|
@@ -758,7 +748,7 @@ export const swarm_status = tool({
|
|
|
758
748
|
},
|
|
759
749
|
async execute(args) {
|
|
760
750
|
// Query subtasks from beads
|
|
761
|
-
const subtasks = await queryEpicSubtasks(args.epic_id);
|
|
751
|
+
const subtasks = await queryEpicSubtasks(args.project_key, args.epic_id);
|
|
762
752
|
|
|
763
753
|
// Count statuses
|
|
764
754
|
const statusCounts = {
|
|
@@ -878,12 +868,16 @@ export const swarm_progress = tool({
|
|
|
878
868
|
// Validate
|
|
879
869
|
const validated = AgentProgressSchema.parse(progress);
|
|
880
870
|
|
|
881
|
-
// Update cell status if needed
|
|
871
|
+
// Update cell status if needed (using HiveAdapter, not bd CLI)
|
|
882
872
|
if (args.status === "blocked" || args.status === "in_progress") {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
.
|
|
886
|
-
.
|
|
873
|
+
try {
|
|
874
|
+
const adapter = await getHiveAdapter(args.project_key);
|
|
875
|
+
const newStatus = args.status === "blocked" ? "blocked" : "in_progress";
|
|
876
|
+
await adapter.changeCellStatus(args.project_key, args.bead_id, newStatus);
|
|
877
|
+
} catch (error) {
|
|
878
|
+
// Non-fatal - log but continue
|
|
879
|
+
console.error(`[swarm] Failed to update cell status: ${error instanceof Error ? error.message : String(error)}`);
|
|
880
|
+
}
|
|
887
881
|
}
|
|
888
882
|
|
|
889
883
|
// Extract epic ID from bead ID (e.g., bd-abc123.1 -> bd-abc123)
|
|
@@ -1152,11 +1146,35 @@ Or use skip_review=true to bypass (not recommended for production work).`,
|
|
|
1152
1146
|
}
|
|
1153
1147
|
|
|
1154
1148
|
try {
|
|
1155
|
-
//
|
|
1156
|
-
// This catches agents who skipped swarmmail_init
|
|
1149
|
+
// Validate bead_id exists and is not already closed (EARLY validation)
|
|
1157
1150
|
const projectKey = args.project_key
|
|
1158
1151
|
.replace(/\//g, "-")
|
|
1159
1152
|
.replace(/\\/g, "-");
|
|
1153
|
+
|
|
1154
|
+
// Use HiveAdapter for validation (not bd CLI)
|
|
1155
|
+
const adapter = await getHiveAdapter(args.project_key);
|
|
1156
|
+
|
|
1157
|
+
// 1. Check if bead exists
|
|
1158
|
+
const cell = await adapter.getCell(projectKey, args.bead_id);
|
|
1159
|
+
if (!cell) {
|
|
1160
|
+
return JSON.stringify({
|
|
1161
|
+
success: false,
|
|
1162
|
+
error: `Bead not found: ${args.bead_id}`,
|
|
1163
|
+
hint: "Check the bead ID is correct. Use hive_query to list open cells.",
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// 2. Check if bead is already closed
|
|
1168
|
+
if (cell.status === "closed") {
|
|
1169
|
+
return JSON.stringify({
|
|
1170
|
+
success: false,
|
|
1171
|
+
error: `Bead already closed: ${args.bead_id}`,
|
|
1172
|
+
hint: "This bead was already completed. No action needed.",
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Verify agent is registered in swarm-mail
|
|
1177
|
+
// This catches agents who skipped swarmmail_init
|
|
1160
1178
|
let agentRegistered = false;
|
|
1161
1179
|
let registrationWarning = "";
|
|
1162
1180
|
|
|
@@ -1303,49 +1321,27 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1303
1321
|
}
|
|
1304
1322
|
}
|
|
1305
1323
|
|
|
1306
|
-
// Close the cell
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
.quiet()
|
|
1312
|
-
.nothrow();
|
|
1313
|
-
|
|
1314
|
-
if (closeResult.exitCode !== 0) {
|
|
1315
|
-
const stderrOutput = closeResult.stderr.toString().trim();
|
|
1316
|
-
const stdoutOutput = closeResult.stdout.toString().trim();
|
|
1317
|
-
|
|
1318
|
-
// Check for common error patterns and provide better guidance
|
|
1319
|
-
const isNoDatabaseError = stderrOutput.includes("no beads database found");
|
|
1320
|
-
const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
|
|
1321
|
-
|
|
1324
|
+
// Close the cell using HiveAdapter (not bd CLI)
|
|
1325
|
+
try {
|
|
1326
|
+
await adapter.closeCell(args.project_key, args.bead_id, args.summary);
|
|
1327
|
+
} catch (closeError) {
|
|
1328
|
+
const errorMessage = closeError instanceof Error ? closeError.message : String(closeError);
|
|
1322
1329
|
return JSON.stringify(
|
|
1323
1330
|
{
|
|
1324
1331
|
success: false,
|
|
1325
1332
|
error: "Failed to close cell",
|
|
1326
|
-
failed_step: "
|
|
1327
|
-
details:
|
|
1333
|
+
failed_step: "closeCell",
|
|
1334
|
+
details: errorMessage,
|
|
1328
1335
|
bead_id: args.bead_id,
|
|
1329
1336
|
project_key: args.project_key,
|
|
1330
1337
|
recovery: {
|
|
1331
|
-
steps:
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
: [
|
|
1339
|
-
`1. Check cell exists: bd show ${args.bead_id}`,
|
|
1340
|
-
`2. Check cell status (might already be closed): hive_query()`,
|
|
1341
|
-
`3. If cell is blocked, unblock first: hive_update(id="${args.bead_id}", status="in_progress")`,
|
|
1342
|
-
`4. Try closing directly: hive_close(id="${args.bead_id}", reason="...")`,
|
|
1343
|
-
],
|
|
1344
|
-
hint: isNoDatabaseError
|
|
1345
|
-
? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.`
|
|
1346
|
-
: isNotFoundError
|
|
1347
|
-
? `Cell "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.`
|
|
1348
|
-
: "If cell is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing.",
|
|
1338
|
+
steps: [
|
|
1339
|
+
`1. Check cell exists: hive_query()`,
|
|
1340
|
+
`2. Check cell status (might already be closed)`,
|
|
1341
|
+
`3. If cell is blocked, unblock first: hive_update(id="${args.bead_id}", status="in_progress")`,
|
|
1342
|
+
`4. Try closing directly: hive_close(id="${args.bead_id}", reason="...")`,
|
|
1343
|
+
],
|
|
1344
|
+
hint: "Cell may already be closed, or the ID is incorrect.",
|
|
1349
1345
|
},
|
|
1350
1346
|
},
|
|
1351
1347
|
null,
|
|
@@ -1645,12 +1641,13 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1645
1641
|
return JSON.stringify(
|
|
1646
1642
|
{
|
|
1647
1643
|
success: false,
|
|
1648
|
-
error: errorMessage
|
|
1644
|
+
error: `swarm_complete failed: ${errorMessage}`,
|
|
1649
1645
|
failed_step: failedStep,
|
|
1650
1646
|
bead_id: args.bead_id,
|
|
1651
1647
|
agent_name: args.agent_name,
|
|
1652
1648
|
coordinator_notified: notificationSent,
|
|
1653
1649
|
stack_trace: errorStack?.slice(0, 500),
|
|
1650
|
+
hint: "Check the error message above. Common issues: bead not found, session not initialized.",
|
|
1654
1651
|
context: {
|
|
1655
1652
|
summary: args.summary,
|
|
1656
1653
|
files_touched: args.files_touched || [],
|
|
@@ -1085,7 +1085,7 @@ describe("Tool Availability", () => {
|
|
|
1085
1085
|
|
|
1086
1086
|
it("checks all tools at once", async () => {
|
|
1087
1087
|
const availability = await checkAllTools();
|
|
1088
|
-
expect(availability.size).toBe(
|
|
1088
|
+
expect(availability.size).toBe(7); // semantic-memory, cass, ubs, hive, beads, swarm-mail, agent-mail
|
|
1089
1089
|
expect(availability.has("semantic-memory")).toBe(true);
|
|
1090
1090
|
expect(availability.has("cass")).toBe(true);
|
|
1091
1091
|
expect(availability.has("ubs")).toBe(true);
|
|
@@ -1420,7 +1420,7 @@ describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
|
|
|
1420
1420
|
it("enforces swarm_complete over manual hive_close", () => {
|
|
1421
1421
|
// Step 9: Use swarm_complete, not hive_close
|
|
1422
1422
|
expect(SUBTASK_PROMPT_V2).toContain("swarm_complete");
|
|
1423
|
-
expect(SUBTASK_PROMPT_V2).toContain("DO NOT manually close the
|
|
1423
|
+
expect(SUBTASK_PROMPT_V2).toContain("DO NOT manually close the cell");
|
|
1424
1424
|
expect(SUBTASK_PROMPT_V2).toContain("Use swarm_complete");
|
|
1425
1425
|
});
|
|
1426
1426
|
});
|
|
@@ -1583,6 +1583,92 @@ describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
|
|
|
1583
1583
|
},
|
|
1584
1584
|
);
|
|
1585
1585
|
|
|
1586
|
+
it.skipIf(!beadsAvailable)(
|
|
1587
|
+
"returns specific error message when bead_id not found",
|
|
1588
|
+
async () => {
|
|
1589
|
+
// Try to complete with a non-existent bead ID
|
|
1590
|
+
const result = await swarm_complete.execute(
|
|
1591
|
+
{
|
|
1592
|
+
project_key: "/tmp/test-bead-not-found",
|
|
1593
|
+
agent_name: "test-agent",
|
|
1594
|
+
bead_id: "bd-totally-fake-xyz123",
|
|
1595
|
+
summary: "This should fail with specific error",
|
|
1596
|
+
skip_verification: true,
|
|
1597
|
+
},
|
|
1598
|
+
mockContext,
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
const parsed = JSON.parse(result);
|
|
1602
|
+
|
|
1603
|
+
// Should return structured error with specific message
|
|
1604
|
+
expect(parsed.success).toBe(false);
|
|
1605
|
+
expect(parsed.error).toBeDefined();
|
|
1606
|
+
// RED: This will fail - we currently get generic "Tool execution failed"
|
|
1607
|
+
// We want the error message to specifically mention the bead was not found
|
|
1608
|
+
expect(
|
|
1609
|
+
parsed.error.toLowerCase().includes("bead not found") ||
|
|
1610
|
+
parsed.error.toLowerCase().includes("not found"),
|
|
1611
|
+
).toBe(true);
|
|
1612
|
+
expect(parsed.bead_id).toBe("bd-totally-fake-xyz123");
|
|
1613
|
+
},
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
it.skipIf(!beadsAvailable)(
|
|
1617
|
+
"returns specific error when project_key is invalid/mismatched",
|
|
1618
|
+
async () => {
|
|
1619
|
+
// Create a real bead first
|
|
1620
|
+
const createResult =
|
|
1621
|
+
await Bun.$`bd create "Test project mismatch" -t task --json`
|
|
1622
|
+
.quiet()
|
|
1623
|
+
.nothrow();
|
|
1624
|
+
|
|
1625
|
+
if (createResult.exitCode !== 0) {
|
|
1626
|
+
console.warn(
|
|
1627
|
+
"Could not create bead:",
|
|
1628
|
+
createResult.stderr.toString(),
|
|
1629
|
+
);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const bead = JSON.parse(createResult.stdout.toString());
|
|
1634
|
+
|
|
1635
|
+
try {
|
|
1636
|
+
// Try to complete with mismatched project_key
|
|
1637
|
+
const result = await swarm_complete.execute(
|
|
1638
|
+
{
|
|
1639
|
+
project_key: "/totally/wrong/project/path",
|
|
1640
|
+
agent_name: "test-agent",
|
|
1641
|
+
bead_id: bead.id,
|
|
1642
|
+
summary: "This should fail with project mismatch",
|
|
1643
|
+
skip_verification: true,
|
|
1644
|
+
},
|
|
1645
|
+
mockContext,
|
|
1646
|
+
);
|
|
1647
|
+
|
|
1648
|
+
const parsed = JSON.parse(result);
|
|
1649
|
+
|
|
1650
|
+
// Should return structured error with specific message about project mismatch
|
|
1651
|
+
expect(parsed.success).toBe(false);
|
|
1652
|
+
expect(parsed.error).toBeDefined();
|
|
1653
|
+
// RED: This will fail - we want specific validation error
|
|
1654
|
+
// Error should mention project mismatch or validation failure
|
|
1655
|
+
const errorLower = parsed.error.toLowerCase();
|
|
1656
|
+
expect(
|
|
1657
|
+
(errorLower.includes("project") &&
|
|
1658
|
+
(errorLower.includes("mismatch") ||
|
|
1659
|
+
errorLower.includes("invalid") ||
|
|
1660
|
+
errorLower.includes("not found"))) ||
|
|
1661
|
+
errorLower.includes("validation"),
|
|
1662
|
+
).toBe(true);
|
|
1663
|
+
} finally {
|
|
1664
|
+
// Clean up
|
|
1665
|
+
await Bun.$`bd close ${bead.id} --reason "Test cleanup"`
|
|
1666
|
+
.quiet()
|
|
1667
|
+
.nothrow();
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
);
|
|
1671
|
+
|
|
1586
1672
|
it.skipIf(!beadsAvailable)(
|
|
1587
1673
|
"includes message_sent status in response",
|
|
1588
1674
|
async () => {
|
package/src/tool-availability.ts
CHANGED
|
@@ -214,31 +214,13 @@ const toolCheckers: Record<ToolName, () => Promise<ToolStatus>> = {
|
|
|
214
214
|
},
|
|
215
215
|
|
|
216
216
|
// DEPRECATED: Use hive instead
|
|
217
|
-
//
|
|
217
|
+
// bd CLI is deprecated - always return false, use HiveAdapter instead
|
|
218
218
|
beads: async () => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
error: "bd command not found",
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
// Just check if bd can run - don't require a repo
|
|
230
|
-
const result = await Bun.$`bd --version`.quiet().nothrow();
|
|
231
|
-
return {
|
|
232
|
-
available: result.exitCode === 0,
|
|
233
|
-
checkedAt: new Date().toISOString(),
|
|
234
|
-
};
|
|
235
|
-
} catch (e) {
|
|
236
|
-
return {
|
|
237
|
-
available: false,
|
|
238
|
-
checkedAt: new Date().toISOString(),
|
|
239
|
-
error: String(e),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
219
|
+
return {
|
|
220
|
+
available: false,
|
|
221
|
+
checkedAt: new Date().toISOString(),
|
|
222
|
+
error: "bd CLI is deprecated - use hive_* tools with HiveAdapter instead",
|
|
223
|
+
};
|
|
242
224
|
},
|
|
243
225
|
|
|
244
226
|
"swarm-mail": async () => {
|