opencode-swarm-plugin 0.1.0 → 0.2.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 +308 -0
- package/README.md +157 -11
- package/dist/index.js +804 -23
- package/dist/plugin.js +443 -23
- package/package.json +7 -3
- package/src/agent-mail.ts +46 -3
- package/src/index.ts +60 -0
- package/src/learning.integration.test.ts +326 -1
- package/src/storage.integration.test.ts +341 -0
- package/src/storage.ts +679 -0
- package/src/swarm.integration.test.ts +194 -3
- package/src/swarm.ts +185 -32
- package/src/tool-availability.ts +389 -0
package/dist/plugin.js
CHANGED
|
@@ -12879,6 +12879,216 @@ var beadsTools = {
|
|
|
12879
12879
|
beads_link_thread
|
|
12880
12880
|
};
|
|
12881
12881
|
|
|
12882
|
+
// src/tool-availability.ts
|
|
12883
|
+
var toolCache = new Map;
|
|
12884
|
+
var warningsLogged = new Set;
|
|
12885
|
+
async function commandExists(cmd) {
|
|
12886
|
+
try {
|
|
12887
|
+
const result = await Bun.$`which ${cmd}`.quiet().nothrow();
|
|
12888
|
+
return result.exitCode === 0;
|
|
12889
|
+
} catch {
|
|
12890
|
+
return false;
|
|
12891
|
+
}
|
|
12892
|
+
}
|
|
12893
|
+
async function urlReachable(url2, timeoutMs = 1000) {
|
|
12894
|
+
try {
|
|
12895
|
+
const controller = new AbortController;
|
|
12896
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
12897
|
+
const response = await fetch(url2, {
|
|
12898
|
+
method: "HEAD",
|
|
12899
|
+
signal: controller.signal
|
|
12900
|
+
});
|
|
12901
|
+
clearTimeout(timeout);
|
|
12902
|
+
return response.ok;
|
|
12903
|
+
} catch {
|
|
12904
|
+
return false;
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
var toolCheckers = {
|
|
12908
|
+
"semantic-memory": async () => {
|
|
12909
|
+
const nativeExists = await commandExists("semantic-memory");
|
|
12910
|
+
if (nativeExists) {
|
|
12911
|
+
try {
|
|
12912
|
+
const result = await Bun.$`semantic-memory stats`.quiet().nothrow();
|
|
12913
|
+
return {
|
|
12914
|
+
available: result.exitCode === 0,
|
|
12915
|
+
checkedAt: new Date().toISOString(),
|
|
12916
|
+
version: "native"
|
|
12917
|
+
};
|
|
12918
|
+
} catch (e) {
|
|
12919
|
+
return {
|
|
12920
|
+
available: false,
|
|
12921
|
+
checkedAt: new Date().toISOString(),
|
|
12922
|
+
error: String(e)
|
|
12923
|
+
};
|
|
12924
|
+
}
|
|
12925
|
+
}
|
|
12926
|
+
try {
|
|
12927
|
+
const proc = Bun.spawn(["bunx", "semantic-memory", "stats"], {
|
|
12928
|
+
stdout: "pipe",
|
|
12929
|
+
stderr: "pipe"
|
|
12930
|
+
});
|
|
12931
|
+
const timeout = setTimeout(() => proc.kill(), 1e4);
|
|
12932
|
+
const exitCode = await proc.exited;
|
|
12933
|
+
clearTimeout(timeout);
|
|
12934
|
+
return {
|
|
12935
|
+
available: exitCode === 0,
|
|
12936
|
+
checkedAt: new Date().toISOString(),
|
|
12937
|
+
version: "bunx"
|
|
12938
|
+
};
|
|
12939
|
+
} catch (e) {
|
|
12940
|
+
return {
|
|
12941
|
+
available: false,
|
|
12942
|
+
checkedAt: new Date().toISOString(),
|
|
12943
|
+
error: String(e)
|
|
12944
|
+
};
|
|
12945
|
+
}
|
|
12946
|
+
},
|
|
12947
|
+
cass: async () => {
|
|
12948
|
+
const exists = await commandExists("cass");
|
|
12949
|
+
if (!exists) {
|
|
12950
|
+
return {
|
|
12951
|
+
available: false,
|
|
12952
|
+
checkedAt: new Date().toISOString(),
|
|
12953
|
+
error: "cass command not found"
|
|
12954
|
+
};
|
|
12955
|
+
}
|
|
12956
|
+
try {
|
|
12957
|
+
const result = await Bun.$`cass health`.quiet().nothrow();
|
|
12958
|
+
return {
|
|
12959
|
+
available: result.exitCode === 0,
|
|
12960
|
+
checkedAt: new Date().toISOString()
|
|
12961
|
+
};
|
|
12962
|
+
} catch (e) {
|
|
12963
|
+
return {
|
|
12964
|
+
available: false,
|
|
12965
|
+
checkedAt: new Date().toISOString(),
|
|
12966
|
+
error: String(e)
|
|
12967
|
+
};
|
|
12968
|
+
}
|
|
12969
|
+
},
|
|
12970
|
+
ubs: async () => {
|
|
12971
|
+
const exists = await commandExists("ubs");
|
|
12972
|
+
if (!exists) {
|
|
12973
|
+
return {
|
|
12974
|
+
available: false,
|
|
12975
|
+
checkedAt: new Date().toISOString(),
|
|
12976
|
+
error: "ubs command not found"
|
|
12977
|
+
};
|
|
12978
|
+
}
|
|
12979
|
+
try {
|
|
12980
|
+
const result = await Bun.$`ubs doctor`.quiet().nothrow();
|
|
12981
|
+
return {
|
|
12982
|
+
available: result.exitCode === 0,
|
|
12983
|
+
checkedAt: new Date().toISOString()
|
|
12984
|
+
};
|
|
12985
|
+
} catch (e) {
|
|
12986
|
+
return {
|
|
12987
|
+
available: false,
|
|
12988
|
+
checkedAt: new Date().toISOString(),
|
|
12989
|
+
error: String(e)
|
|
12990
|
+
};
|
|
12991
|
+
}
|
|
12992
|
+
},
|
|
12993
|
+
beads: async () => {
|
|
12994
|
+
const exists = await commandExists("bd");
|
|
12995
|
+
if (!exists) {
|
|
12996
|
+
return {
|
|
12997
|
+
available: false,
|
|
12998
|
+
checkedAt: new Date().toISOString(),
|
|
12999
|
+
error: "bd command not found"
|
|
13000
|
+
};
|
|
13001
|
+
}
|
|
13002
|
+
try {
|
|
13003
|
+
const result = await Bun.$`bd --version`.quiet().nothrow();
|
|
13004
|
+
return {
|
|
13005
|
+
available: result.exitCode === 0,
|
|
13006
|
+
checkedAt: new Date().toISOString()
|
|
13007
|
+
};
|
|
13008
|
+
} catch (e) {
|
|
13009
|
+
return {
|
|
13010
|
+
available: false,
|
|
13011
|
+
checkedAt: new Date().toISOString(),
|
|
13012
|
+
error: String(e)
|
|
13013
|
+
};
|
|
13014
|
+
}
|
|
13015
|
+
},
|
|
13016
|
+
"agent-mail": async () => {
|
|
13017
|
+
const reachable = await urlReachable("http://127.0.0.1:8765/health/liveness");
|
|
13018
|
+
return {
|
|
13019
|
+
available: reachable,
|
|
13020
|
+
checkedAt: new Date().toISOString(),
|
|
13021
|
+
error: reachable ? undefined : "Agent Mail server not reachable at :8765"
|
|
13022
|
+
};
|
|
13023
|
+
}
|
|
13024
|
+
};
|
|
13025
|
+
var fallbackBehaviors = {
|
|
13026
|
+
"semantic-memory": "Learning data stored in-memory only (lost on session end)",
|
|
13027
|
+
cass: "Decomposition proceeds without historical context from past sessions",
|
|
13028
|
+
ubs: "Subtask completion skips bug scanning - manual review recommended",
|
|
13029
|
+
beads: "Swarm cannot track issues - task coordination will be less reliable",
|
|
13030
|
+
"agent-mail": "Multi-agent coordination disabled - file conflicts possible if multiple agents active"
|
|
13031
|
+
};
|
|
13032
|
+
async function checkTool(tool3) {
|
|
13033
|
+
const cached2 = toolCache.get(tool3);
|
|
13034
|
+
if (cached2) {
|
|
13035
|
+
return cached2;
|
|
13036
|
+
}
|
|
13037
|
+
const checker = toolCheckers[tool3];
|
|
13038
|
+
const status = await checker();
|
|
13039
|
+
toolCache.set(tool3, status);
|
|
13040
|
+
return status;
|
|
13041
|
+
}
|
|
13042
|
+
async function isToolAvailable(tool3) {
|
|
13043
|
+
const status = await checkTool(tool3);
|
|
13044
|
+
return status.available;
|
|
13045
|
+
}
|
|
13046
|
+
async function getToolAvailability(tool3) {
|
|
13047
|
+
const status = await checkTool(tool3);
|
|
13048
|
+
return {
|
|
13049
|
+
tool: tool3,
|
|
13050
|
+
status,
|
|
13051
|
+
fallbackBehavior: fallbackBehaviors[tool3]
|
|
13052
|
+
};
|
|
13053
|
+
}
|
|
13054
|
+
async function checkAllTools() {
|
|
13055
|
+
const tools = [
|
|
13056
|
+
"semantic-memory",
|
|
13057
|
+
"cass",
|
|
13058
|
+
"ubs",
|
|
13059
|
+
"beads",
|
|
13060
|
+
"agent-mail"
|
|
13061
|
+
];
|
|
13062
|
+
const results = new Map;
|
|
13063
|
+
const checks3 = await Promise.all(tools.map(async (tool3) => ({
|
|
13064
|
+
tool: tool3,
|
|
13065
|
+
availability: await getToolAvailability(tool3)
|
|
13066
|
+
})));
|
|
13067
|
+
for (const { tool: tool3, availability } of checks3) {
|
|
13068
|
+
results.set(tool3, availability);
|
|
13069
|
+
}
|
|
13070
|
+
return results;
|
|
13071
|
+
}
|
|
13072
|
+
function warnMissingTool(tool3) {
|
|
13073
|
+
if (warningsLogged.has(tool3)) {
|
|
13074
|
+
return;
|
|
13075
|
+
}
|
|
13076
|
+
warningsLogged.add(tool3);
|
|
13077
|
+
const fallback = fallbackBehaviors[tool3];
|
|
13078
|
+
console.warn(`[swarm] ${tool3} not available: ${fallback}`);
|
|
13079
|
+
}
|
|
13080
|
+
function formatToolAvailability(availability) {
|
|
13081
|
+
const lines = ["Tool Availability:"];
|
|
13082
|
+
for (const [tool3, info] of availability) {
|
|
13083
|
+
const status = info.status.available ? "\u2713" : "\u2717";
|
|
13084
|
+
const version2 = info.status.version ? ` (${info.status.version})` : "";
|
|
13085
|
+
const fallback = info.status.available ? "" : ` \u2192 ${info.fallbackBehavior}`;
|
|
13086
|
+
lines.push(` ${status} ${tool3}${version2}${fallback}`);
|
|
13087
|
+
}
|
|
13088
|
+
return lines.join(`
|
|
13089
|
+
`);
|
|
13090
|
+
}
|
|
13091
|
+
|
|
12882
13092
|
// src/agent-mail.ts
|
|
12883
13093
|
var AGENT_MAIL_URL = "http://127.0.0.1:8765";
|
|
12884
13094
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
@@ -12913,6 +13123,14 @@ class FileReservationConflictError extends Error {
|
|
|
12913
13123
|
this.name = "FileReservationConflictError";
|
|
12914
13124
|
}
|
|
12915
13125
|
}
|
|
13126
|
+
var agentMailAvailable = null;
|
|
13127
|
+
async function checkAgentMailAvailable() {
|
|
13128
|
+
if (agentMailAvailable !== null) {
|
|
13129
|
+
return agentMailAvailable;
|
|
13130
|
+
}
|
|
13131
|
+
agentMailAvailable = await isToolAvailable("agent-mail");
|
|
13132
|
+
return agentMailAvailable;
|
|
13133
|
+
}
|
|
12916
13134
|
async function mcpCall(toolName, args) {
|
|
12917
13135
|
const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
|
|
12918
13136
|
method: "POST",
|
|
@@ -12962,6 +13180,16 @@ var agentmail_init = tool({
|
|
|
12962
13180
|
task_description: tool.schema.string().optional().describe("Description of current task")
|
|
12963
13181
|
},
|
|
12964
13182
|
async execute(args, ctx) {
|
|
13183
|
+
const available = await checkAgentMailAvailable();
|
|
13184
|
+
if (!available) {
|
|
13185
|
+
warnMissingTool("agent-mail");
|
|
13186
|
+
return JSON.stringify({
|
|
13187
|
+
error: "Agent Mail server not available",
|
|
13188
|
+
available: false,
|
|
13189
|
+
hint: "Start Agent Mail with: agent-mail serve",
|
|
13190
|
+
fallback: "Swarm will continue without multi-agent coordination. File conflicts possible if multiple agents active."
|
|
13191
|
+
}, null, 2);
|
|
13192
|
+
}
|
|
12965
13193
|
const project = await mcpCall("ensure_project", {
|
|
12966
13194
|
human_key: args.project_path
|
|
12967
13195
|
});
|
|
@@ -12979,7 +13207,7 @@ var agentmail_init = tool({
|
|
|
12979
13207
|
startedAt: new Date().toISOString()
|
|
12980
13208
|
};
|
|
12981
13209
|
setState(ctx.sessionID, state);
|
|
12982
|
-
return JSON.stringify({ project, agent }, null, 2);
|
|
13210
|
+
return JSON.stringify({ project, agent, available: true }, null, 2);
|
|
12983
13211
|
}
|
|
12984
13212
|
});
|
|
12985
13213
|
var agentmail_send = tool({
|
|
@@ -13762,6 +13990,18 @@ var DECOMPOSITION_PROMPT = `You are decomposing a task into parallelizable subta
|
|
|
13762
13990
|
|
|
13763
13991
|
{context_section}
|
|
13764
13992
|
|
|
13993
|
+
## MANDATORY: Beads Issue Tracking
|
|
13994
|
+
|
|
13995
|
+
**Every subtask MUST become a bead.** This is non-negotiable.
|
|
13996
|
+
|
|
13997
|
+
After decomposition, the coordinator will:
|
|
13998
|
+
1. Create an epic bead for the overall task
|
|
13999
|
+
2. Create child beads for each subtask
|
|
14000
|
+
3. Track progress through bead status updates
|
|
14001
|
+
4. Close beads with summaries when complete
|
|
14002
|
+
|
|
14003
|
+
Agents MUST update their bead status as they work. No silent progress.
|
|
14004
|
+
|
|
13765
14005
|
## Requirements
|
|
13766
14006
|
|
|
13767
14007
|
1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
|
|
@@ -13769,6 +14009,7 @@ var DECOMPOSITION_PROMPT = `You are decomposing a task into parallelizable subta
|
|
|
13769
14009
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
13770
14010
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
13771
14011
|
5. **Estimate complexity** - 1 (trivial) to 5 (complex)
|
|
14012
|
+
6. **Plan aggressively** - break down more than you think necessary, smaller is better
|
|
13772
14013
|
|
|
13773
14014
|
## Response Format
|
|
13774
14015
|
|
|
@@ -13795,10 +14036,12 @@ Respond with a JSON object matching this schema:
|
|
|
13795
14036
|
|
|
13796
14037
|
## Guidelines
|
|
13797
14038
|
|
|
14039
|
+
- **Plan aggressively** - when in doubt, split further. 3 small tasks > 1 medium task
|
|
13798
14040
|
- **Prefer smaller, focused subtasks** over large complex ones
|
|
13799
14041
|
- **Include test files** in the same subtask as the code they test
|
|
13800
14042
|
- **Consider shared types** - if multiple files share types, handle that first
|
|
13801
14043
|
- **Think about imports** - changes to exported APIs affect downstream files
|
|
14044
|
+
- **Explicit > implicit** - spell out what each subtask should do, don't assume
|
|
13802
14045
|
|
|
13803
14046
|
## File Assignment Examples
|
|
13804
14047
|
|
|
@@ -13829,19 +14072,49 @@ send a message to the coordinator requesting the change.
|
|
|
13829
14072
|
## Shared Context
|
|
13830
14073
|
{shared_context}
|
|
13831
14074
|
|
|
14075
|
+
## MANDATORY: Beads Tracking
|
|
14076
|
+
|
|
14077
|
+
You MUST keep your bead updated as you work:
|
|
14078
|
+
|
|
14079
|
+
1. **Your bead is already in_progress** - don't change this unless blocked
|
|
14080
|
+
2. **If blocked**: \`bd update {bead_id} --status blocked\` and message coordinator
|
|
14081
|
+
3. **When done**: Use \`swarm_complete\` - it closes your bead automatically
|
|
14082
|
+
4. **Discovered issues**: Create new beads with \`bd create "issue" -t bug\`
|
|
14083
|
+
|
|
14084
|
+
**Never work silently.** Your bead status is how the swarm tracks progress.
|
|
14085
|
+
|
|
14086
|
+
## MANDATORY: Agent Mail Communication
|
|
14087
|
+
|
|
14088
|
+
You MUST communicate with other agents:
|
|
14089
|
+
|
|
14090
|
+
1. **Report progress** every significant milestone (not just at the end)
|
|
14091
|
+
2. **Ask questions** if requirements are unclear - don't guess
|
|
14092
|
+
3. **Announce blockers** immediately - don't spin trying to fix alone
|
|
14093
|
+
4. **Coordinate on shared concerns** - if you see something affecting other agents, say so
|
|
14094
|
+
|
|
14095
|
+
Use Agent Mail for all communication:
|
|
14096
|
+
\`\`\`
|
|
14097
|
+
agentmail_send(
|
|
14098
|
+
to: ["coordinator" or specific agent],
|
|
14099
|
+
subject: "Brief subject",
|
|
14100
|
+
body: "Message content",
|
|
14101
|
+
thread_id: "{epic_id}"
|
|
14102
|
+
)
|
|
14103
|
+
\`\`\`
|
|
14104
|
+
|
|
13832
14105
|
## Coordination Protocol
|
|
13833
14106
|
|
|
13834
14107
|
1. **Start**: Your bead is already marked in_progress
|
|
13835
|
-
2. **Progress**: Use
|
|
13836
|
-
3. **Blocked**:
|
|
13837
|
-
4. **Complete**: Use
|
|
14108
|
+
2. **Progress**: Use swarm_progress to report status updates
|
|
14109
|
+
3. **Blocked**: Report immediately via Agent Mail - don't spin
|
|
14110
|
+
4. **Complete**: Use swarm_complete when done - it handles:
|
|
13838
14111
|
- Closing your bead with a summary
|
|
13839
14112
|
- Releasing file reservations
|
|
13840
14113
|
- Notifying the coordinator
|
|
13841
14114
|
|
|
13842
14115
|
## Self-Evaluation
|
|
13843
14116
|
|
|
13844
|
-
Before calling
|
|
14117
|
+
Before calling swarm_complete, evaluate your work:
|
|
13845
14118
|
- Type safety: Does it compile without errors?
|
|
13846
14119
|
- No obvious bugs: Did you handle edge cases?
|
|
13847
14120
|
- Follows patterns: Does it match existing code style?
|
|
@@ -13849,17 +14122,13 @@ Before calling swarm:complete, evaluate your work:
|
|
|
13849
14122
|
|
|
13850
14123
|
If evaluation fails, fix the issues before completing.
|
|
13851
14124
|
|
|
13852
|
-
##
|
|
14125
|
+
## Planning Your Work
|
|
13853
14126
|
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
body: "Message content",
|
|
13860
|
-
thread_id: "{epic_id}"
|
|
13861
|
-
)
|
|
13862
|
-
\`\`\`
|
|
14127
|
+
Before writing code:
|
|
14128
|
+
1. **Read the files** you're assigned to understand current state
|
|
14129
|
+
2. **Plan your approach** - what changes, in what order?
|
|
14130
|
+
3. **Identify risks** - what could go wrong? What dependencies?
|
|
14131
|
+
4. **Communicate your plan** via Agent Mail if non-trivial
|
|
13863
14132
|
|
|
13864
14133
|
Begin work on your subtask now.`;
|
|
13865
14134
|
var EVALUATION_PROMPT = `Evaluate the work completed for this subtask.
|
|
@@ -13926,21 +14195,32 @@ function formatEvaluationPrompt(params) {
|
|
|
13926
14195
|
return EVALUATION_PROMPT.replace("{bead_id}", params.bead_id).replace("{subtask_title}", params.subtask_title).replace("{files_touched}", filesList || "(no files recorded)");
|
|
13927
14196
|
}
|
|
13928
14197
|
async function queryEpicSubtasks(epicId) {
|
|
14198
|
+
const beadsAvailable = await isToolAvailable("beads");
|
|
14199
|
+
if (!beadsAvailable) {
|
|
14200
|
+
warnMissingTool("beads");
|
|
14201
|
+
return [];
|
|
14202
|
+
}
|
|
13929
14203
|
const result = await Bun.$`bd list --parent ${epicId} --json`.quiet().nothrow();
|
|
13930
14204
|
if (result.exitCode !== 0) {
|
|
13931
|
-
|
|
14205
|
+
console.warn(`[swarm] Failed to query subtasks: ${result.stderr.toString()}`);
|
|
14206
|
+
return [];
|
|
13932
14207
|
}
|
|
13933
14208
|
try {
|
|
13934
14209
|
const parsed = JSON.parse(result.stdout.toString());
|
|
13935
14210
|
return exports_external.array(BeadSchema).parse(parsed);
|
|
13936
14211
|
} catch (error45) {
|
|
13937
14212
|
if (error45 instanceof exports_external.ZodError) {
|
|
13938
|
-
|
|
14213
|
+
console.warn(`[swarm] Invalid bead data: ${error45.message}`);
|
|
14214
|
+
return [];
|
|
13939
14215
|
}
|
|
13940
14216
|
throw error45;
|
|
13941
14217
|
}
|
|
13942
14218
|
}
|
|
13943
14219
|
async function querySwarmMessages(projectKey, threadId) {
|
|
14220
|
+
const agentMailAvailable2 = await isToolAvailable("agent-mail");
|
|
14221
|
+
if (!agentMailAvailable2) {
|
|
14222
|
+
return 0;
|
|
14223
|
+
}
|
|
13944
14224
|
try {
|
|
13945
14225
|
const summary = await mcpCall("summarize_thread", {
|
|
13946
14226
|
project_key: projectKey,
|
|
@@ -13969,11 +14249,13 @@ ${progress.blockers.map((b) => `- ${b}`).join(`
|
|
|
13969
14249
|
`);
|
|
13970
14250
|
}
|
|
13971
14251
|
async function queryCassHistory(task, limit = 3) {
|
|
14252
|
+
const cassAvailable = await isToolAvailable("cass");
|
|
14253
|
+
if (!cassAvailable) {
|
|
14254
|
+
warnMissingTool("cass");
|
|
14255
|
+
return null;
|
|
14256
|
+
}
|
|
13972
14257
|
try {
|
|
13973
14258
|
const result = await Bun.$`cass search ${task} --limit ${limit} --json`.quiet().nothrow();
|
|
13974
|
-
if (result.exitCode === 127) {
|
|
13975
|
-
return null;
|
|
13976
|
-
}
|
|
13977
14259
|
if (result.exitCode !== 0) {
|
|
13978
14260
|
return null;
|
|
13979
14261
|
}
|
|
@@ -14233,11 +14515,13 @@ async function runUbsScan(files) {
|
|
|
14233
14515
|
if (files.length === 0) {
|
|
14234
14516
|
return null;
|
|
14235
14517
|
}
|
|
14518
|
+
const ubsAvailable = await isToolAvailable("ubs");
|
|
14519
|
+
if (!ubsAvailable) {
|
|
14520
|
+
warnMissingTool("ubs");
|
|
14521
|
+
return null;
|
|
14522
|
+
}
|
|
14236
14523
|
try {
|
|
14237
14524
|
const result = await Bun.$`ubs scan ${files.join(" ")} --json`.quiet().nothrow();
|
|
14238
|
-
if (result.exitCode === 127) {
|
|
14239
|
-
return null;
|
|
14240
|
-
}
|
|
14241
14525
|
const output = result.stdout.toString();
|
|
14242
14526
|
if (!output.trim()) {
|
|
14243
14527
|
return {
|
|
@@ -14468,7 +14752,56 @@ var swarm_evaluation_prompt = tool({
|
|
|
14468
14752
|
}, null, 2);
|
|
14469
14753
|
}
|
|
14470
14754
|
});
|
|
14755
|
+
var swarm_init = tool({
|
|
14756
|
+
description: "Initialize swarm session and check tool availability. Call at swarm start to see what features are available.",
|
|
14757
|
+
args: {
|
|
14758
|
+
project_path: tool.schema.string().optional().describe("Project path (for Agent Mail init)")
|
|
14759
|
+
},
|
|
14760
|
+
async execute(args) {
|
|
14761
|
+
const availability = await checkAllTools();
|
|
14762
|
+
const report = formatToolAvailability(availability);
|
|
14763
|
+
const beadsAvailable = availability.get("beads")?.status.available ?? false;
|
|
14764
|
+
const agentMailAvailable2 = availability.get("agent-mail")?.status.available ?? false;
|
|
14765
|
+
const warnings = [];
|
|
14766
|
+
const degradedFeatures = [];
|
|
14767
|
+
if (!beadsAvailable) {
|
|
14768
|
+
warnings.push("\u26A0\uFE0F beads (bd) not available - issue tracking disabled, swarm coordination will be limited");
|
|
14769
|
+
degradedFeatures.push("issue tracking", "progress persistence");
|
|
14770
|
+
}
|
|
14771
|
+
if (!agentMailAvailable2) {
|
|
14772
|
+
warnings.push("\u26A0\uFE0F agent-mail not available - multi-agent communication disabled");
|
|
14773
|
+
degradedFeatures.push("agent communication", "file reservations");
|
|
14774
|
+
}
|
|
14775
|
+
if (!availability.get("cass")?.status.available) {
|
|
14776
|
+
degradedFeatures.push("historical context from past sessions");
|
|
14777
|
+
}
|
|
14778
|
+
if (!availability.get("ubs")?.status.available) {
|
|
14779
|
+
degradedFeatures.push("pre-completion bug scanning");
|
|
14780
|
+
}
|
|
14781
|
+
if (!availability.get("semantic-memory")?.status.available) {
|
|
14782
|
+
degradedFeatures.push("persistent learning (using in-memory fallback)");
|
|
14783
|
+
}
|
|
14784
|
+
return JSON.stringify({
|
|
14785
|
+
ready: true,
|
|
14786
|
+
tool_availability: Object.fromEntries(Array.from(availability.entries()).map(([k, v]) => [
|
|
14787
|
+
k,
|
|
14788
|
+
{
|
|
14789
|
+
available: v.status.available,
|
|
14790
|
+
fallback: v.status.available ? null : v.fallbackBehavior
|
|
14791
|
+
}
|
|
14792
|
+
])),
|
|
14793
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
14794
|
+
degraded_features: degradedFeatures.length > 0 ? degradedFeatures : undefined,
|
|
14795
|
+
recommendations: {
|
|
14796
|
+
beads: beadsAvailable ? "\u2713 Use beads for all task tracking" : "Install beads: npm i -g @joelhooks/beads",
|
|
14797
|
+
agent_mail: agentMailAvailable2 ? "\u2713 Use Agent Mail for coordination" : "Start Agent Mail: agent-mail serve"
|
|
14798
|
+
},
|
|
14799
|
+
report
|
|
14800
|
+
}, null, 2);
|
|
14801
|
+
}
|
|
14802
|
+
});
|
|
14471
14803
|
var swarmTools = {
|
|
14804
|
+
swarm_init,
|
|
14472
14805
|
swarm_decompose,
|
|
14473
14806
|
swarm_validate_decomposition,
|
|
14474
14807
|
swarm_status,
|
|
@@ -14478,6 +14811,93 @@ var swarmTools = {
|
|
|
14478
14811
|
swarm_subtask_prompt,
|
|
14479
14812
|
swarm_evaluation_prompt
|
|
14480
14813
|
};
|
|
14814
|
+
// src/anti-patterns.ts
|
|
14815
|
+
var PatternKindSchema = exports_external.enum(["pattern", "anti_pattern"]);
|
|
14816
|
+
var DecompositionPatternSchema = exports_external.object({
|
|
14817
|
+
id: exports_external.string(),
|
|
14818
|
+
content: exports_external.string(),
|
|
14819
|
+
kind: PatternKindSchema,
|
|
14820
|
+
is_negative: exports_external.boolean(),
|
|
14821
|
+
success_count: exports_external.number().int().min(0).default(0),
|
|
14822
|
+
failure_count: exports_external.number().int().min(0).default(0),
|
|
14823
|
+
created_at: exports_external.string(),
|
|
14824
|
+
updated_at: exports_external.string(),
|
|
14825
|
+
reason: exports_external.string().optional(),
|
|
14826
|
+
tags: exports_external.array(exports_external.string()).default([]),
|
|
14827
|
+
example_beads: exports_external.array(exports_external.string()).default([])
|
|
14828
|
+
});
|
|
14829
|
+
var PatternInversionResultSchema = exports_external.object({
|
|
14830
|
+
original: DecompositionPatternSchema,
|
|
14831
|
+
inverted: DecompositionPatternSchema,
|
|
14832
|
+
reason: exports_external.string()
|
|
14833
|
+
});
|
|
14834
|
+
class InMemoryPatternStorage {
|
|
14835
|
+
patterns = new Map;
|
|
14836
|
+
async store(pattern) {
|
|
14837
|
+
this.patterns.set(pattern.id, pattern);
|
|
14838
|
+
}
|
|
14839
|
+
async get(id) {
|
|
14840
|
+
return this.patterns.get(id) ?? null;
|
|
14841
|
+
}
|
|
14842
|
+
async getAll() {
|
|
14843
|
+
return Array.from(this.patterns.values());
|
|
14844
|
+
}
|
|
14845
|
+
async getAntiPatterns() {
|
|
14846
|
+
return Array.from(this.patterns.values()).filter((p) => p.kind === "anti_pattern");
|
|
14847
|
+
}
|
|
14848
|
+
async getByTag(tag) {
|
|
14849
|
+
return Array.from(this.patterns.values()).filter((p) => p.tags.includes(tag));
|
|
14850
|
+
}
|
|
14851
|
+
async findByContent(content) {
|
|
14852
|
+
const lower = content.toLowerCase();
|
|
14853
|
+
return Array.from(this.patterns.values()).filter((p) => p.content.toLowerCase().includes(lower));
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
|
|
14857
|
+
// src/pattern-maturity.ts
|
|
14858
|
+
var MaturityStateSchema = exports_external.enum([
|
|
14859
|
+
"candidate",
|
|
14860
|
+
"established",
|
|
14861
|
+
"proven",
|
|
14862
|
+
"deprecated"
|
|
14863
|
+
]);
|
|
14864
|
+
var PatternMaturitySchema = exports_external.object({
|
|
14865
|
+
pattern_id: exports_external.string(),
|
|
14866
|
+
state: MaturityStateSchema,
|
|
14867
|
+
helpful_count: exports_external.number().int().min(0),
|
|
14868
|
+
harmful_count: exports_external.number().int().min(0),
|
|
14869
|
+
last_validated: exports_external.string(),
|
|
14870
|
+
promoted_at: exports_external.string().optional(),
|
|
14871
|
+
deprecated_at: exports_external.string().optional()
|
|
14872
|
+
});
|
|
14873
|
+
var MaturityFeedbackSchema = exports_external.object({
|
|
14874
|
+
pattern_id: exports_external.string(),
|
|
14875
|
+
type: exports_external.enum(["helpful", "harmful"]),
|
|
14876
|
+
timestamp: exports_external.string(),
|
|
14877
|
+
weight: exports_external.number().min(0).max(1).default(1)
|
|
14878
|
+
});
|
|
14879
|
+
class InMemoryMaturityStorage {
|
|
14880
|
+
maturities = new Map;
|
|
14881
|
+
feedback = [];
|
|
14882
|
+
async store(maturity) {
|
|
14883
|
+
this.maturities.set(maturity.pattern_id, maturity);
|
|
14884
|
+
}
|
|
14885
|
+
async get(patternId) {
|
|
14886
|
+
return this.maturities.get(patternId) ?? null;
|
|
14887
|
+
}
|
|
14888
|
+
async getAll() {
|
|
14889
|
+
return Array.from(this.maturities.values());
|
|
14890
|
+
}
|
|
14891
|
+
async getByState(state) {
|
|
14892
|
+
return Array.from(this.maturities.values()).filter((m) => m.state === state);
|
|
14893
|
+
}
|
|
14894
|
+
async storeFeedback(feedback) {
|
|
14895
|
+
this.feedback.push(feedback);
|
|
14896
|
+
}
|
|
14897
|
+
async getFeedback(patternId) {
|
|
14898
|
+
return this.feedback.filter((f) => f.pattern_id === patternId);
|
|
14899
|
+
}
|
|
14900
|
+
}
|
|
14481
14901
|
|
|
14482
14902
|
// src/index.ts
|
|
14483
14903
|
var SwarmPlugin = async (input) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -41,7 +41,11 @@
|
|
|
41
41
|
"swarm",
|
|
42
42
|
"agent-mail",
|
|
43
43
|
"beads",
|
|
44
|
-
"multi-agent"
|
|
44
|
+
"multi-agent",
|
|
45
|
+
"ai-agents",
|
|
46
|
+
"machine-learning",
|
|
47
|
+
"task-decomposition",
|
|
48
|
+
"coordination"
|
|
45
49
|
],
|
|
46
50
|
"author": "Joel Hooks",
|
|
47
51
|
"license": "MIT",
|
package/src/agent-mail.ts
CHANGED
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
* - fetch_inbox ALWAYS limits to 5 messages max
|
|
10
10
|
* - Use summarize_thread instead of fetching all messages
|
|
11
11
|
* - Auto-release reservations when tasks complete
|
|
12
|
+
*
|
|
13
|
+
* GRACEFUL DEGRADATION:
|
|
14
|
+
* - If Agent Mail server is not running, tools return helpful error messages
|
|
15
|
+
* - Swarm can still function without Agent Mail (just no coordination)
|
|
12
16
|
*/
|
|
13
17
|
import { tool } from "@opencode-ai/plugin";
|
|
14
18
|
import { z } from "zod";
|
|
19
|
+
import { isToolAvailable, warnMissingTool } from "./tool-availability";
|
|
15
20
|
|
|
16
21
|
// ============================================================================
|
|
17
22
|
// Configuration
|
|
@@ -163,13 +168,35 @@ interface MCPToolResult<T = unknown> {
|
|
|
163
168
|
isError?: boolean;
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
/** Cached availability check result */
|
|
172
|
+
let agentMailAvailable: boolean | null = null;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if Agent Mail server is available (cached)
|
|
176
|
+
*/
|
|
177
|
+
async function checkAgentMailAvailable(): Promise<boolean> {
|
|
178
|
+
if (agentMailAvailable !== null) {
|
|
179
|
+
return agentMailAvailable;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
agentMailAvailable = await isToolAvailable("agent-mail");
|
|
183
|
+
return agentMailAvailable;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reset availability cache (for testing)
|
|
188
|
+
*/
|
|
189
|
+
export function resetAgentMailCache(): void {
|
|
190
|
+
agentMailAvailable = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
/**
|
|
167
194
|
* Call an Agent Mail MCP tool
|
|
168
195
|
*
|
|
169
196
|
* Handles both direct results (mock server) and wrapped results (real server).
|
|
170
197
|
* Real Agent Mail returns: { content: [...], structuredContent: {...} }
|
|
171
198
|
*/
|
|
172
|
-
async function mcpCall<T>(
|
|
199
|
+
export async function mcpCall<T>(
|
|
173
200
|
toolName: string,
|
|
174
201
|
args: Record<string, unknown>,
|
|
175
202
|
): Promise<T> {
|
|
@@ -281,6 +308,23 @@ export const agentmail_init = tool({
|
|
|
281
308
|
.describe("Description of current task"),
|
|
282
309
|
},
|
|
283
310
|
async execute(args, ctx) {
|
|
311
|
+
// Check if Agent Mail is available
|
|
312
|
+
const available = await checkAgentMailAvailable();
|
|
313
|
+
if (!available) {
|
|
314
|
+
warnMissingTool("agent-mail");
|
|
315
|
+
return JSON.stringify(
|
|
316
|
+
{
|
|
317
|
+
error: "Agent Mail server not available",
|
|
318
|
+
available: false,
|
|
319
|
+
hint: "Start Agent Mail with: agent-mail serve",
|
|
320
|
+
fallback:
|
|
321
|
+
"Swarm will continue without multi-agent coordination. File conflicts possible if multiple agents active.",
|
|
322
|
+
},
|
|
323
|
+
null,
|
|
324
|
+
2,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
284
328
|
// 1. Ensure project exists
|
|
285
329
|
const project = await mcpCall<ProjectInfo>("ensure_project", {
|
|
286
330
|
human_key: args.project_path,
|
|
@@ -304,7 +348,7 @@ export const agentmail_init = tool({
|
|
|
304
348
|
};
|
|
305
349
|
setState(ctx.sessionID, state);
|
|
306
350
|
|
|
307
|
-
return JSON.stringify({ project, agent }, null, 2);
|
|
351
|
+
return JSON.stringify({ project, agent, available: true }, null, 2);
|
|
308
352
|
},
|
|
309
353
|
});
|
|
310
354
|
|
|
@@ -654,7 +698,6 @@ export const agentMailTools = {
|
|
|
654
698
|
// ============================================================================
|
|
655
699
|
|
|
656
700
|
export {
|
|
657
|
-
mcpCall,
|
|
658
701
|
requireState,
|
|
659
702
|
setState,
|
|
660
703
|
getState,
|