opencode-swarm-plugin 0.12.30 → 0.13.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/.beads/issues.jsonl +204 -10
- package/.opencode/skills/tdd/SKILL.md +182 -0
- package/README.md +165 -17
- package/bin/swarm.ts +120 -31
- package/bun.lock +23 -0
- package/dist/index.js +4020 -438
- package/dist/pglite.data +0 -0
- package/dist/pglite.wasm +0 -0
- package/dist/plugin.js +4008 -514
- package/examples/commands/swarm.md +114 -19
- package/examples/skills/beads-workflow/SKILL.md +75 -28
- package/examples/skills/swarm-coordination/SKILL.md +92 -1
- package/global-skills/testing-patterns/SKILL.md +430 -0
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +586 -0
- package/package.json +11 -5
- package/src/index.ts +44 -5
- package/src/streams/agent-mail.test.ts +777 -0
- package/src/streams/agent-mail.ts +535 -0
- package/src/streams/debug.test.ts +500 -0
- package/src/streams/debug.ts +629 -0
- package/src/streams/effect/ask.integration.test.ts +314 -0
- package/src/streams/effect/ask.ts +202 -0
- package/src/streams/effect/cursor.integration.test.ts +418 -0
- package/src/streams/effect/cursor.ts +288 -0
- package/src/streams/effect/deferred.test.ts +357 -0
- package/src/streams/effect/deferred.ts +445 -0
- package/src/streams/effect/index.ts +17 -0
- package/src/streams/effect/layers.ts +73 -0
- package/src/streams/effect/lock.test.ts +385 -0
- package/src/streams/effect/lock.ts +399 -0
- package/src/streams/effect/mailbox.test.ts +260 -0
- package/src/streams/effect/mailbox.ts +318 -0
- package/src/streams/events.test.ts +628 -0
- package/src/streams/events.ts +214 -0
- package/src/streams/index.test.ts +229 -0
- package/src/streams/index.ts +492 -0
- package/src/streams/migrations.test.ts +355 -0
- package/src/streams/migrations.ts +269 -0
- package/src/streams/projections.test.ts +611 -0
- package/src/streams/projections.ts +302 -0
- package/src/streams/store.integration.test.ts +548 -0
- package/src/streams/store.ts +546 -0
- package/src/streams/swarm-mail.ts +552 -0
- package/src/swarm-mail.integration.test.ts +970 -0
- package/src/swarm-mail.ts +739 -0
- package/src/swarm.ts +84 -59
- package/src/tool-availability.ts +35 -2
- package/global-skills/mcp-tool-authoring/SKILL.md +0 -695
package/src/swarm.ts
CHANGED
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
type SpawnedAgent,
|
|
26
26
|
type Bead,
|
|
27
27
|
} from "./schemas";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
sendSwarmMessage,
|
|
30
|
+
getSwarmInbox,
|
|
31
|
+
readSwarmMessage,
|
|
32
|
+
releaseSwarmFiles,
|
|
33
|
+
} from "./streams/swarm-mail";
|
|
29
34
|
import {
|
|
30
35
|
OutcomeSignalsSchema,
|
|
31
36
|
DecompositionStrategySchema,
|
|
@@ -959,15 +964,19 @@ async function querySwarmMessages(
|
|
|
959
964
|
}
|
|
960
965
|
|
|
961
966
|
try {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
llm_mode: false, // Just need the count
|
|
967
|
+
// Use embedded swarm-mail inbox to count messages in thread
|
|
968
|
+
const inbox = await getSwarmInbox({
|
|
969
|
+
projectPath: projectKey,
|
|
970
|
+
agentName: "coordinator", // Dummy agent name for thread query
|
|
971
|
+
limit: 5,
|
|
972
|
+
includeBodies: false,
|
|
969
973
|
});
|
|
970
|
-
|
|
974
|
+
|
|
975
|
+
// Count messages that match the thread ID
|
|
976
|
+
const threadMessages = inbox.messages.filter(
|
|
977
|
+
(m) => m.thread_id === threadId,
|
|
978
|
+
);
|
|
979
|
+
return threadMessages.length;
|
|
971
980
|
} catch (error) {
|
|
972
981
|
// Thread might not exist yet, or query failed
|
|
973
982
|
console.warn(
|
|
@@ -1312,9 +1321,10 @@ export const swarm_plan_prompt = tool({
|
|
|
1312
1321
|
|
|
1313
1322
|
// Fetch skills context
|
|
1314
1323
|
let skillsContext = "";
|
|
1315
|
-
let skillsInfo: { included: boolean; count?: number; relevant?: string[] } =
|
|
1316
|
-
|
|
1317
|
-
|
|
1324
|
+
let skillsInfo: { included: boolean; count?: number; relevant?: string[] } =
|
|
1325
|
+
{
|
|
1326
|
+
included: false,
|
|
1327
|
+
};
|
|
1318
1328
|
|
|
1319
1329
|
if (args.include_skills !== false) {
|
|
1320
1330
|
const allSkills = await listSkills();
|
|
@@ -1769,15 +1779,14 @@ export const swarm_progress = tool({
|
|
|
1769
1779
|
? args.bead_id.split(".")[0]
|
|
1770
1780
|
: args.bead_id;
|
|
1771
1781
|
|
|
1772
|
-
// Send progress message to thread
|
|
1773
|
-
await
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
to: [], // Coordinator will pick it up from thread
|
|
1782
|
+
// Send progress message to thread using embedded swarm-mail
|
|
1783
|
+
await sendSwarmMessage({
|
|
1784
|
+
projectPath: args.project_key,
|
|
1785
|
+
fromAgent: args.agent_name,
|
|
1786
|
+
toAgents: [], // Coordinator will pick it up from thread
|
|
1778
1787
|
subject: `Progress: ${args.bead_id} - ${args.status}`,
|
|
1779
|
-
|
|
1780
|
-
|
|
1788
|
+
body: formatProgressMessage(validated),
|
|
1789
|
+
threadId: epicId,
|
|
1781
1790
|
importance: args.status === "blocked" ? "high" : "normal",
|
|
1782
1791
|
});
|
|
1783
1792
|
|
|
@@ -1895,6 +1904,12 @@ export const swarm_broadcast = tool({
|
|
|
1895
1904
|
description:
|
|
1896
1905
|
"Broadcast context update to all agents working on the same epic",
|
|
1897
1906
|
args: {
|
|
1907
|
+
project_path: tool.schema
|
|
1908
|
+
.string()
|
|
1909
|
+
.describe("Absolute path to project root"),
|
|
1910
|
+
agent_name: tool.schema
|
|
1911
|
+
.string()
|
|
1912
|
+
.describe("Name of the agent broadcasting the message"),
|
|
1898
1913
|
epic_id: tool.schema.string().describe("Epic ID (e.g., bd-abc123)"),
|
|
1899
1914
|
message: tool.schema
|
|
1900
1915
|
.string()
|
|
@@ -1909,18 +1924,14 @@ export const swarm_broadcast = tool({
|
|
|
1909
1924
|
.describe("Files this context relates to"),
|
|
1910
1925
|
},
|
|
1911
1926
|
async execute(args, ctx) {
|
|
1912
|
-
// Get agent state - requires prior initialization
|
|
1913
|
-
const state = requireState(ctx.sessionID);
|
|
1914
|
-
|
|
1915
1927
|
// Extract bead_id from context if available (for traceability)
|
|
1916
|
-
// In the swarm flow, ctx might have the current bead being worked on
|
|
1917
1928
|
const beadId = (ctx as { beadId?: string }).beadId || "unknown";
|
|
1918
1929
|
|
|
1919
1930
|
// Format the broadcast message
|
|
1920
1931
|
const body = [
|
|
1921
1932
|
`## Context Update`,
|
|
1922
1933
|
"",
|
|
1923
|
-
`**From**: ${
|
|
1934
|
+
`**From**: ${args.agent_name} (${beadId})`,
|
|
1924
1935
|
`**Priority**: ${args.importance.toUpperCase()}`,
|
|
1925
1936
|
"",
|
|
1926
1937
|
args.message,
|
|
@@ -1940,25 +1951,23 @@ export const swarm_broadcast = tool({
|
|
|
1940
1951
|
? "high"
|
|
1941
1952
|
: "normal";
|
|
1942
1953
|
|
|
1943
|
-
// Send as broadcast to thread
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
body_md: body,
|
|
1952
|
-
thread_id: args.epic_id,
|
|
1954
|
+
// Send as broadcast to thread using embedded swarm-mail
|
|
1955
|
+
await sendSwarmMessage({
|
|
1956
|
+
projectPath: args.project_path,
|
|
1957
|
+
fromAgent: args.agent_name,
|
|
1958
|
+
toAgents: [], // Broadcast to thread
|
|
1959
|
+
subject: `[${args.importance.toUpperCase()}] Context update from ${args.agent_name}`,
|
|
1960
|
+
body,
|
|
1961
|
+
threadId: args.epic_id,
|
|
1953
1962
|
importance: mailImportance,
|
|
1954
|
-
|
|
1963
|
+
ackRequired: args.importance === "blocker",
|
|
1955
1964
|
});
|
|
1956
1965
|
|
|
1957
1966
|
return JSON.stringify(
|
|
1958
1967
|
{
|
|
1959
1968
|
broadcast: true,
|
|
1960
1969
|
epic_id: args.epic_id,
|
|
1961
|
-
from:
|
|
1970
|
+
from: args.agent_name,
|
|
1962
1971
|
bead_id: beadId,
|
|
1963
1972
|
importance: args.importance,
|
|
1964
1973
|
recipients: "all agents in epic",
|
|
@@ -2070,16 +2079,15 @@ export const swarm_complete = tool({
|
|
|
2070
2079
|
);
|
|
2071
2080
|
}
|
|
2072
2081
|
|
|
2073
|
-
// Release file reservations for this agent
|
|
2074
|
-
// Uses auto-reinit wrapper to handle server restarts - this was the original
|
|
2075
|
-
// failure point that prompted the self-healing implementation
|
|
2082
|
+
// Release file reservations for this agent using embedded swarm-mail
|
|
2076
2083
|
try {
|
|
2077
|
-
await
|
|
2078
|
-
|
|
2079
|
-
|
|
2084
|
+
await releaseSwarmFiles({
|
|
2085
|
+
projectPath: args.project_key,
|
|
2086
|
+
agentName: args.agent_name,
|
|
2087
|
+
// Release all reservations for this agent
|
|
2080
2088
|
});
|
|
2081
2089
|
} catch (error) {
|
|
2082
|
-
//
|
|
2090
|
+
// Release might fail (e.g., no reservations existed)
|
|
2083
2091
|
// This is non-fatal - log and continue
|
|
2084
2092
|
console.warn(
|
|
2085
2093
|
`[swarm] Failed to release file reservations for ${args.agent_name}:`,
|
|
@@ -2092,7 +2100,7 @@ export const swarm_complete = tool({
|
|
|
2092
2100
|
? args.bead_id.split(".")[0]
|
|
2093
2101
|
: args.bead_id;
|
|
2094
2102
|
|
|
2095
|
-
// Send completion message
|
|
2103
|
+
// Send completion message using embedded swarm-mail
|
|
2096
2104
|
const completionBody = [
|
|
2097
2105
|
`## Subtask Complete: ${args.bead_id}`,
|
|
2098
2106
|
"",
|
|
@@ -2108,14 +2116,13 @@ export const swarm_complete = tool({
|
|
|
2108
2116
|
.filter(Boolean)
|
|
2109
2117
|
.join("\n");
|
|
2110
2118
|
|
|
2111
|
-
await
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
to: [], // Thread broadcast
|
|
2119
|
+
await sendSwarmMessage({
|
|
2120
|
+
projectPath: args.project_key,
|
|
2121
|
+
fromAgent: args.agent_name,
|
|
2122
|
+
toAgents: [], // Thread broadcast
|
|
2116
2123
|
subject: `Complete: ${args.bead_id}`,
|
|
2117
|
-
|
|
2118
|
-
|
|
2124
|
+
body: completionBody,
|
|
2125
|
+
threadId: epicId,
|
|
2119
2126
|
importance: "normal",
|
|
2120
2127
|
});
|
|
2121
2128
|
|
|
@@ -2650,7 +2657,14 @@ This tool helps you formalize learnings into a skill that future agents can disc
|
|
|
2650
2657
|
.string()
|
|
2651
2658
|
.describe("Brief summary of what was learned (1-2 sentences)"),
|
|
2652
2659
|
pattern_type: tool.schema
|
|
2653
|
-
.enum([
|
|
2660
|
+
.enum([
|
|
2661
|
+
"code-pattern",
|
|
2662
|
+
"best-practice",
|
|
2663
|
+
"gotcha",
|
|
2664
|
+
"tool-usage",
|
|
2665
|
+
"domain-knowledge",
|
|
2666
|
+
"workflow",
|
|
2667
|
+
])
|
|
2654
2668
|
.describe("Category of the learning"),
|
|
2655
2669
|
details: tool.schema
|
|
2656
2670
|
.string()
|
|
@@ -2669,7 +2683,9 @@ This tool helps you formalize learnings into a skill that future agents can disc
|
|
|
2669
2683
|
create_skill: tool.schema
|
|
2670
2684
|
.boolean()
|
|
2671
2685
|
.optional()
|
|
2672
|
-
.describe(
|
|
2686
|
+
.describe(
|
|
2687
|
+
"Create a skill from this learning (default: false, just document)",
|
|
2688
|
+
),
|
|
2673
2689
|
skill_name: tool.schema
|
|
2674
2690
|
.string()
|
|
2675
2691
|
.regex(/^[a-z0-9-]+$/)
|
|
@@ -2737,7 +2753,8 @@ ${args.files_context && args.files_context.length > 0 ? `## Reference Files\n\n$
|
|
|
2737
2753
|
error: `Skill '${args.skill_name}' already exists`,
|
|
2738
2754
|
existing_path: existing.path,
|
|
2739
2755
|
learning: learning,
|
|
2740
|
-
suggestion:
|
|
2756
|
+
suggestion:
|
|
2757
|
+
"Use skills_update to add to existing skill, or choose a different name",
|
|
2741
2758
|
},
|
|
2742
2759
|
null,
|
|
2743
2760
|
2,
|
|
@@ -2745,7 +2762,12 @@ ${args.files_context && args.files_context.length > 0 ? `## Reference Files\n\n$
|
|
|
2745
2762
|
}
|
|
2746
2763
|
|
|
2747
2764
|
// Create skill directory and file
|
|
2748
|
-
const skillDir = join(
|
|
2765
|
+
const skillDir = join(
|
|
2766
|
+
process.cwd(),
|
|
2767
|
+
".opencode",
|
|
2768
|
+
"skills",
|
|
2769
|
+
args.skill_name,
|
|
2770
|
+
);
|
|
2749
2771
|
const skillPath = join(skillDir, "SKILL.md");
|
|
2750
2772
|
|
|
2751
2773
|
const frontmatter = [
|
|
@@ -2798,8 +2820,10 @@ ${args.files_context && args.files_context.length > 0 ? `## Reference Files\n\n$
|
|
|
2798
2820
|
success: true,
|
|
2799
2821
|
skill_created: false,
|
|
2800
2822
|
learning: learning,
|
|
2801
|
-
message:
|
|
2802
|
-
|
|
2823
|
+
message:
|
|
2824
|
+
"Learning documented. Use create_skill=true to persist as a skill for future agents.",
|
|
2825
|
+
suggested_skill_name:
|
|
2826
|
+
args.skill_name ||
|
|
2803
2827
|
args.summary
|
|
2804
2828
|
.toLowerCase()
|
|
2805
2829
|
.replace(/[^a-z0-9\s-]/g, "")
|
|
@@ -3029,7 +3053,8 @@ export const swarm_init = tool({
|
|
|
3029
3053
|
if (availableSkills.length > 0) {
|
|
3030
3054
|
skillsGuidance = `Found ${availableSkills.length} skill(s). Use skills_list to see details, skills_use to activate.`;
|
|
3031
3055
|
} else {
|
|
3032
|
-
skillsGuidance =
|
|
3056
|
+
skillsGuidance =
|
|
3057
|
+
"No skills found. Add skills to .opencode/skills/ or .claude/skills/ for specialized guidance.";
|
|
3033
3058
|
}
|
|
3034
3059
|
|
|
3035
3060
|
return JSON.stringify(
|
package/src/tool-availability.ts
CHANGED
|
@@ -9,14 +9,18 @@
|
|
|
9
9
|
* - cass: Cross-agent session search for historical context
|
|
10
10
|
* - ubs: Universal bug scanner for pre-commit checks
|
|
11
11
|
* - beads (bd): Git-backed issue tracking
|
|
12
|
-
* -
|
|
12
|
+
* - swarm-mail: Embedded multi-agent coordination (PGLite-based)
|
|
13
|
+
* - agent-mail: DEPRECATED - Legacy MCP server (use swarm-mail instead)
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
import { checkSwarmHealth } from "./streams/swarm-mail";
|
|
17
|
+
|
|
15
18
|
export type ToolName =
|
|
16
19
|
| "semantic-memory"
|
|
17
20
|
| "cass"
|
|
18
21
|
| "ubs"
|
|
19
22
|
| "beads"
|
|
23
|
+
| "swarm-mail"
|
|
20
24
|
| "agent-mail";
|
|
21
25
|
|
|
22
26
|
export interface ToolStatus {
|
|
@@ -199,6 +203,32 @@ const toolCheckers: Record<ToolName, () => Promise<ToolStatus>> = {
|
|
|
199
203
|
}
|
|
200
204
|
},
|
|
201
205
|
|
|
206
|
+
"swarm-mail": async () => {
|
|
207
|
+
try {
|
|
208
|
+
// Note: checkSwarmHealth() accepts optional projectPath parameter.
|
|
209
|
+
// For tool availability checking, we call it without args to check global health.
|
|
210
|
+
// This is intentional - we're verifying the embedded Swarm Mail system is functional,
|
|
211
|
+
// not checking health for a specific project.
|
|
212
|
+
const healthResult = await checkSwarmHealth();
|
|
213
|
+
return {
|
|
214
|
+
available: healthResult.healthy,
|
|
215
|
+
checkedAt: new Date().toISOString(),
|
|
216
|
+
error: healthResult.healthy
|
|
217
|
+
? undefined
|
|
218
|
+
: "Swarm Mail database not healthy",
|
|
219
|
+
version: "embedded",
|
|
220
|
+
};
|
|
221
|
+
} catch (e) {
|
|
222
|
+
return {
|
|
223
|
+
available: false,
|
|
224
|
+
checkedAt: new Date().toISOString(),
|
|
225
|
+
error: String(e),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// DEPRECATED: Use swarm-mail instead
|
|
231
|
+
// Kept for backward compatibility only
|
|
202
232
|
"agent-mail": async () => {
|
|
203
233
|
const reachable = await urlReachable(
|
|
204
234
|
"http://127.0.0.1:8765/health/liveness",
|
|
@@ -220,8 +250,10 @@ const fallbackBehaviors: Record<ToolName, string> = {
|
|
|
220
250
|
cass: "Decomposition proceeds without historical context from past sessions",
|
|
221
251
|
ubs: "Subtask completion skips bug scanning - manual review recommended",
|
|
222
252
|
beads: "Swarm cannot track issues - task coordination will be less reliable",
|
|
223
|
-
"
|
|
253
|
+
"swarm-mail":
|
|
224
254
|
"Multi-agent coordination disabled - file conflicts possible if multiple agents active",
|
|
255
|
+
"agent-mail":
|
|
256
|
+
"DEPRECATED: Use swarm-mail instead. Legacy MCP server mode - file conflicts possible if multiple agents active",
|
|
225
257
|
};
|
|
226
258
|
|
|
227
259
|
/**
|
|
@@ -276,6 +308,7 @@ export async function checkAllTools(): Promise<
|
|
|
276
308
|
"cass",
|
|
277
309
|
"ubs",
|
|
278
310
|
"beads",
|
|
311
|
+
"swarm-mail",
|
|
279
312
|
"agent-mail",
|
|
280
313
|
];
|
|
281
314
|
|