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/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 swarm:progress to report status updates
13836
- 3. **Blocked**: If you hit a blocker, report it - don't spin
13837
- 4. **Complete**: Use swarm:complete when done - it handles:
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 swarm:complete, evaluate your work:
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
- ## Communication
14125
+ ## Planning Your Work
13853
14126
 
13854
- To message other agents or the coordinator:
13855
- \`\`\`
13856
- agent-mail:send(
13857
- to: ["coordinator_name" or other agent],
13858
- subject: "Brief subject",
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
- throw new SwarmError(`Failed to query subtasks: ${result.stderr.toString()}`, "query_subtasks");
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
- throw new SwarmError(`Invalid bead data: ${error45.message}`, "query_subtasks", error45.issues);
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.1.0",
4
- "description": "OpenCode plugin for type-safe swarm coordination with Agent Mail integration",
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,