opencode-swarm-plugin 0.12.6 → 0.12.8

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
@@ -22606,8 +22606,8 @@ var RETRY_CONFIG = {
22606
22606
  jitterPercent: 20
22607
22607
  };
22608
22608
  var RECOVERY_CONFIG = {
22609
- failureThreshold: 2,
22610
- restartCooldownMs: 30000,
22609
+ failureThreshold: 1,
22610
+ restartCooldownMs: 1e4,
22611
22611
  enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
22612
22612
  };
22613
22613
  var SESSION_STATE_DIR = process.env.SWARM_STATE_DIR || join2(tmpdir(), "swarm-sessions");
@@ -22905,6 +22905,7 @@ async function mcpCallOnce(toolName, args) {
22905
22905
  }
22906
22906
  async function mcpCall(toolName, args) {
22907
22907
  let lastError = null;
22908
+ let restartAttempted = false;
22908
22909
  for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
22909
22910
  if (attempt > 0) {
22910
22911
  const delay = calculateBackoffDelay(attempt);
@@ -22917,13 +22918,28 @@ async function mcpCall(toolName, args) {
22917
22918
  return result;
22918
22919
  } catch (error45) {
22919
22920
  lastError = error45 instanceof Error ? error45 : new Error(String(error45));
22921
+ const errorMessage = lastError.message.toLowerCase();
22920
22922
  consecutiveFailures++;
22921
22923
  const retryable = isRetryableError(error45);
22922
- if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
22924
+ const isUnexpectedError = errorMessage.includes("unexpected error");
22925
+ if (isUnexpectedError && !restartAttempted && RECOVERY_CONFIG.enabled) {
22926
+ console.warn(`[agent-mail] "${toolName}" got unexpected error, restarting server immediately...`);
22927
+ restartAttempted = true;
22928
+ const restarted = await restartServer();
22929
+ if (restarted) {
22930
+ agentMailAvailable = null;
22931
+ consecutiveFailures = 0;
22932
+ await new Promise((resolve) => setTimeout(resolve, 1000));
22933
+ attempt--;
22934
+ continue;
22935
+ }
22936
+ }
22937
+ if (!isUnexpectedError && consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled && !restartAttempted) {
22923
22938
  console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
22924
22939
  const healthy = await isServerFunctional();
22925
22940
  if (!healthy) {
22926
22941
  console.warn("[agent-mail] Server unhealthy, attempting restart...");
22942
+ restartAttempted = true;
22927
22943
  const restarted = await restartServer();
22928
22944
  if (restarted) {
22929
22945
  agentMailAvailable = null;
@@ -22981,24 +22997,57 @@ var agentmail_init = tool({
22981
22997
  fallback: "Swarm will continue without multi-agent coordination. File conflicts possible if multiple agents active."
22982
22998
  }, null, 2);
22983
22999
  }
22984
- const project = await mcpCall("ensure_project", {
22985
- human_key: args.project_path
22986
- });
22987
- const agent = await mcpCall("register_agent", {
22988
- project_key: args.project_path,
22989
- program: "opencode",
22990
- model: "claude-opus-4",
22991
- name: args.agent_name,
22992
- task_description: args.task_description || ""
22993
- });
22994
- const state = {
22995
- projectKey: args.project_path,
22996
- agentName: agent.name,
22997
- reservations: [],
22998
- startedAt: new Date().toISOString()
22999
- };
23000
- setState(ctx.sessionID, state);
23001
- return JSON.stringify({ project, agent, available: true }, null, 2);
23000
+ const MAX_INIT_RETRIES = 3;
23001
+ let lastError = null;
23002
+ for (let attempt = 1;attempt <= MAX_INIT_RETRIES; attempt++) {
23003
+ try {
23004
+ const project = await mcpCall("ensure_project", {
23005
+ human_key: args.project_path
23006
+ });
23007
+ const agent = await mcpCall("register_agent", {
23008
+ project_key: args.project_path,
23009
+ program: "opencode",
23010
+ model: "claude-opus-4",
23011
+ name: args.agent_name,
23012
+ task_description: args.task_description || ""
23013
+ });
23014
+ const state = {
23015
+ projectKey: args.project_path,
23016
+ agentName: agent.name,
23017
+ reservations: [],
23018
+ startedAt: new Date().toISOString()
23019
+ };
23020
+ setState(ctx.sessionID, state);
23021
+ if (attempt > 1) {
23022
+ console.warn(`[agent-mail] Init succeeded on attempt ${attempt} after restart`);
23023
+ }
23024
+ return JSON.stringify({ project, agent, available: true }, null, 2);
23025
+ } catch (error45) {
23026
+ lastError = error45 instanceof Error ? error45 : new Error(String(error45));
23027
+ const isUnexpectedError = lastError.message.toLowerCase().includes("unexpected error");
23028
+ console.warn(`[agent-mail] Init attempt ${attempt}/${MAX_INIT_RETRIES} failed: ${lastError.message}`);
23029
+ if (isUnexpectedError && attempt < MAX_INIT_RETRIES) {
23030
+ console.warn("[agent-mail] Detected 'unexpected error', restarting server...");
23031
+ const restarted = await restartServer();
23032
+ if (restarted) {
23033
+ agentMailAvailable = null;
23034
+ consecutiveFailures = 0;
23035
+ await new Promise((resolve) => setTimeout(resolve, 1000));
23036
+ continue;
23037
+ }
23038
+ }
23039
+ if (!isUnexpectedError) {
23040
+ break;
23041
+ }
23042
+ }
23043
+ }
23044
+ return JSON.stringify({
23045
+ error: `Agent Mail init failed after ${MAX_INIT_RETRIES} attempts`,
23046
+ available: false,
23047
+ lastError: lastError?.message,
23048
+ hint: "Manually restart Agent Mail: pkill -f agent-mail && agent-mail serve",
23049
+ fallback: "Swarm will continue without multi-agent coordination."
23050
+ }, null, 2);
23002
23051
  }
23003
23052
  });
23004
23053
  var agentmail_send = tool({
@@ -23204,7 +23253,11 @@ var agentmail_health = tool({
23204
23253
  try {
23205
23254
  const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`);
23206
23255
  if (response.ok) {
23207
- return "Agent Mail is running";
23256
+ const functional = await isServerFunctional();
23257
+ if (functional) {
23258
+ return "Agent Mail is running and functional";
23259
+ }
23260
+ return "Agent Mail health OK but MCP not responding - consider restart";
23208
23261
  }
23209
23262
  return `Agent Mail returned status ${response.status}`;
23210
23263
  } catch (error45) {
@@ -23212,6 +23265,41 @@ var agentmail_health = tool({
23212
23265
  }
23213
23266
  }
23214
23267
  });
23268
+ var agentmail_restart = tool({
23269
+ description: "Manually restart Agent Mail server (use when getting 'unexpected error')",
23270
+ args: {
23271
+ force: tool.schema.boolean().optional().describe("Force restart even if server appears healthy (default: false)")
23272
+ },
23273
+ async execute(args) {
23274
+ if (!args.force) {
23275
+ const functional = await isServerFunctional();
23276
+ if (functional) {
23277
+ return JSON.stringify({
23278
+ restarted: false,
23279
+ reason: "Server is functional, no restart needed",
23280
+ hint: "Use force=true to restart anyway"
23281
+ }, null, 2);
23282
+ }
23283
+ }
23284
+ console.warn("[agent-mail] Manual restart requested...");
23285
+ const success2 = await restartServer();
23286
+ agentMailAvailable = null;
23287
+ consecutiveFailures = 0;
23288
+ if (success2) {
23289
+ return JSON.stringify({
23290
+ restarted: true,
23291
+ success: true,
23292
+ message: "Agent Mail server restarted successfully"
23293
+ }, null, 2);
23294
+ }
23295
+ return JSON.stringify({
23296
+ restarted: true,
23297
+ success: false,
23298
+ error: "Restart attempted but server did not come back up",
23299
+ hint: "Check server logs or manually start: agent-mail serve"
23300
+ }, null, 2);
23301
+ }
23302
+ });
23215
23303
  var agentMailTools = {
23216
23304
  agentmail_init,
23217
23305
  agentmail_send,
@@ -23222,7 +23310,8 @@ var agentMailTools = {
23222
23310
  agentmail_release,
23223
23311
  agentmail_ack,
23224
23312
  agentmail_search,
23225
- agentmail_health
23313
+ agentmail_health,
23314
+ agentmail_restart
23226
23315
  };
23227
23316
 
23228
23317
  // src/structured.ts
@@ -23638,6 +23727,24 @@ var CriterionWeightSchema = exports_external.object({
23638
23727
  last_validated: exports_external.string().optional(),
23639
23728
  half_life_days: exports_external.number().positive().default(90)
23640
23729
  });
23730
+ var ErrorTypeSchema = exports_external.enum([
23731
+ "validation",
23732
+ "timeout",
23733
+ "conflict",
23734
+ "tool_failure",
23735
+ "unknown"
23736
+ ]);
23737
+ var ErrorEntrySchema = exports_external.object({
23738
+ id: exports_external.string(),
23739
+ bead_id: exports_external.string(),
23740
+ error_type: ErrorTypeSchema,
23741
+ message: exports_external.string(),
23742
+ stack_trace: exports_external.string().optional(),
23743
+ tool_name: exports_external.string().optional(),
23744
+ timestamp: exports_external.string(),
23745
+ resolved: exports_external.boolean().default(false),
23746
+ context: exports_external.string().optional()
23747
+ });
23641
23748
  var DecompositionStrategySchema = exports_external.enum([
23642
23749
  "file-based",
23643
23750
  "feature-based",
@@ -23711,6 +23818,117 @@ function outcomeToFeedback(outcome, criterion) {
23711
23818
  raw_value: outcome.decayed_value
23712
23819
  };
23713
23820
  }
23821
+ class InMemoryErrorStorage {
23822
+ errors = [];
23823
+ async store(entry) {
23824
+ this.errors.push(entry);
23825
+ }
23826
+ async getByBead(beadId) {
23827
+ return this.errors.filter((e) => e.bead_id === beadId);
23828
+ }
23829
+ async getUnresolvedByBead(beadId) {
23830
+ return this.errors.filter((e) => e.bead_id === beadId && !e.resolved);
23831
+ }
23832
+ async markResolved(id) {
23833
+ const error45 = this.errors.find((e) => e.id === id);
23834
+ if (error45) {
23835
+ error45.resolved = true;
23836
+ }
23837
+ }
23838
+ async getAll() {
23839
+ return [...this.errors];
23840
+ }
23841
+ }
23842
+
23843
+ class ErrorAccumulator {
23844
+ storage;
23845
+ constructor(storage) {
23846
+ this.storage = storage ?? new InMemoryErrorStorage;
23847
+ }
23848
+ async recordError(beadId, errorType, message, options) {
23849
+ const entry = {
23850
+ id: `${beadId}-${errorType}-${Date.now()}`,
23851
+ bead_id: beadId,
23852
+ error_type: errorType,
23853
+ message,
23854
+ stack_trace: options?.stack_trace,
23855
+ tool_name: options?.tool_name,
23856
+ timestamp: new Date().toISOString(),
23857
+ resolved: false,
23858
+ context: options?.context
23859
+ };
23860
+ const validated = ErrorEntrySchema.parse(entry);
23861
+ await this.storage.store(validated);
23862
+ return validated;
23863
+ }
23864
+ async getErrors(beadId) {
23865
+ return this.storage.getByBead(beadId);
23866
+ }
23867
+ async getUnresolvedErrors(beadId) {
23868
+ return this.storage.getUnresolvedByBead(beadId);
23869
+ }
23870
+ async resolveError(errorId) {
23871
+ await this.storage.markResolved(errorId);
23872
+ }
23873
+ async getErrorContext(beadId, includeResolved = false) {
23874
+ const errors3 = includeResolved ? await this.getErrors(beadId) : await this.getUnresolvedErrors(beadId);
23875
+ if (errors3.length === 0) {
23876
+ return "";
23877
+ }
23878
+ const byType = errors3.reduce((acc, err) => {
23879
+ const type = err.error_type;
23880
+ if (!acc[type]) {
23881
+ acc[type] = [];
23882
+ }
23883
+ acc[type].push(err);
23884
+ return acc;
23885
+ }, {});
23886
+ const lines = [
23887
+ "## Previous Errors",
23888
+ "",
23889
+ "The following errors were encountered during execution:",
23890
+ ""
23891
+ ];
23892
+ for (const [type, typeErrors] of Object.entries(byType)) {
23893
+ lines.push(`### ${type} (${typeErrors.length} error${typeErrors.length > 1 ? "s" : ""})`);
23894
+ lines.push("");
23895
+ for (const err of typeErrors) {
23896
+ lines.push(`- **${err.message}**`);
23897
+ if (err.context) {
23898
+ lines.push(` - Context: ${err.context}`);
23899
+ }
23900
+ if (err.tool_name) {
23901
+ lines.push(` - Tool: ${err.tool_name}`);
23902
+ }
23903
+ if (err.stack_trace) {
23904
+ lines.push(` - Stack: \`${err.stack_trace.slice(0, 100)}...\``);
23905
+ }
23906
+ lines.push(` - Time: ${new Date(err.timestamp).toLocaleString()}${err.resolved ? " (resolved)" : ""}`);
23907
+ lines.push("");
23908
+ }
23909
+ }
23910
+ lines.push("**Action Required**: Address these errors before proceeding. Consider:");
23911
+ lines.push("- What caused each error?");
23912
+ lines.push("- How can you prevent similar errors?");
23913
+ lines.push("- Are there patterns across error types?");
23914
+ lines.push("");
23915
+ return lines.join(`
23916
+ `);
23917
+ }
23918
+ async getErrorStats(beadId) {
23919
+ const allErrors = await this.getErrors(beadId);
23920
+ const unresolved = await this.getUnresolvedErrors(beadId);
23921
+ const byType = allErrors.reduce((acc, err) => {
23922
+ acc[err.error_type] = (acc[err.error_type] || 0) + 1;
23923
+ return acc;
23924
+ }, {});
23925
+ return {
23926
+ total: allErrors.length,
23927
+ unresolved: unresolved.length,
23928
+ by_type: byType
23929
+ };
23930
+ }
23931
+ }
23714
23932
 
23715
23933
  // src/swarm.ts
23716
23934
  var POSITIVE_MARKERS = [
@@ -24122,6 +24340,10 @@ Only modify these files. Need others? Message the coordinator.
24122
24340
  ## Context
24123
24341
  {shared_context}
24124
24342
 
24343
+ {compressed_context}
24344
+
24345
+ {error_context}
24346
+
24125
24347
  ## MANDATORY: Use These Tools
24126
24348
 
24127
24349
  ### Agent Mail - communicate with the swarm
@@ -24155,7 +24377,9 @@ Begin now.`;
24155
24377
  function formatSubtaskPromptV2(params) {
24156
24378
  const fileList = params.files.length > 0 ? params.files.map((f) => `- \`${f}\``).join(`
24157
24379
  `) : "(no specific files - use judgment)";
24158
- return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id).replace(/{epic_id}/g, params.epic_id).replace("{subtask_title}", params.subtask_title).replace("{subtask_description}", params.subtask_description || "(see title)").replace("{file_list}", fileList).replace("{shared_context}", params.shared_context || "(none)");
24380
+ const compressedSection = params.compressed_context ? params.compressed_context : "";
24381
+ const errorSection = params.error_context ? params.error_context : "";
24382
+ return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id).replace(/{epic_id}/g, params.epic_id).replace("{subtask_title}", params.subtask_title).replace("{subtask_description}", params.subtask_description || "(see title)").replace("{file_list}", fileList).replace("{shared_context}", params.shared_context || "(none)").replace("{compressed_context}", compressedSection).replace("{error_context}", errorSection);
24159
24383
  }
24160
24384
  var EVALUATION_PROMPT = `Evaluate the work completed for this subtask.
24161
24385
 
@@ -24853,6 +25077,7 @@ var swarm_record_outcome = tool({
24853
25077
  };
24854
25078
  const validated = OutcomeSignalsSchema.parse(signals);
24855
25079
  const scored = scoreImplicitFeedback(validated, DEFAULT_LEARNING_CONFIG);
25080
+ const errorStats = await globalErrorAccumulator.getErrorStats(args.bead_id);
24856
25081
  const criteriaToScore = args.criteria ?? [
24857
25082
  "type_safe",
24858
25083
  "no_bugs",
@@ -24864,6 +25089,10 @@ var swarm_record_outcome = tool({
24864
25089
  if (args.strategy) {
24865
25090
  event.context = `${event.context || ""} [strategy: ${args.strategy}]`.trim();
24866
25091
  }
25092
+ if (errorStats.total > 0) {
25093
+ const errorSummary = Object.entries(errorStats.by_type).map(([type, count]) => `${type}:${count}`).join(", ");
25094
+ event.context = `${event.context || ""} [errors: ${errorSummary}]`.trim();
25095
+ }
24867
25096
  return event;
24868
25097
  });
24869
25098
  return JSON.stringify({
@@ -24877,13 +25106,16 @@ var swarm_record_outcome = tool({
24877
25106
  }
24878
25107
  },
24879
25108
  feedback_events: feedbackEvents,
25109
+ error_patterns: errorStats,
24880
25110
  summary: {
24881
25111
  feedback_type: scored.type,
24882
25112
  duration_seconds: Math.round(args.duration_ms / 1000),
24883
25113
  error_count: args.error_count ?? 0,
24884
25114
  retry_count: args.retry_count ?? 0,
24885
25115
  success: args.success,
24886
- strategy: args.strategy
25116
+ strategy: args.strategy,
25117
+ accumulated_errors: errorStats.total,
25118
+ unresolved_errors: errorStats.unresolved
24887
25119
  },
24888
25120
  note: "Feedback events should be stored for criterion weight calculation. Use learning.ts functions to apply weights."
24889
25121
  }, null, 2);
@@ -25053,6 +25285,70 @@ var swarm_evaluation_prompt = tool({
25053
25285
  }, null, 2);
25054
25286
  }
25055
25287
  });
25288
+ var globalErrorAccumulator = new ErrorAccumulator;
25289
+ var swarm_accumulate_error = tool({
25290
+ description: "Record an error during subtask execution. Errors feed into retry prompts.",
25291
+ args: {
25292
+ bead_id: tool.schema.string().describe("Bead ID where error occurred"),
25293
+ error_type: tool.schema.enum(["validation", "timeout", "conflict", "tool_failure", "unknown"]).describe("Category of error"),
25294
+ message: tool.schema.string().describe("Human-readable error message"),
25295
+ stack_trace: tool.schema.string().optional().describe("Stack trace for debugging"),
25296
+ tool_name: tool.schema.string().optional().describe("Tool that failed"),
25297
+ context: tool.schema.string().optional().describe("What was happening when error occurred")
25298
+ },
25299
+ async execute(args) {
25300
+ const entry = await globalErrorAccumulator.recordError(args.bead_id, args.error_type, args.message, {
25301
+ stack_trace: args.stack_trace,
25302
+ tool_name: args.tool_name,
25303
+ context: args.context
25304
+ });
25305
+ return JSON.stringify({
25306
+ success: true,
25307
+ error_id: entry.id,
25308
+ bead_id: entry.bead_id,
25309
+ error_type: entry.error_type,
25310
+ message: entry.message,
25311
+ timestamp: entry.timestamp,
25312
+ note: "Error recorded for retry context. Use swarm_get_error_context to retrieve accumulated errors."
25313
+ }, null, 2);
25314
+ }
25315
+ });
25316
+ var swarm_get_error_context = tool({
25317
+ description: "Get accumulated errors for a bead. Returns formatted context for retry prompts.",
25318
+ args: {
25319
+ bead_id: tool.schema.string().describe("Bead ID to get errors for"),
25320
+ include_resolved: tool.schema.boolean().optional().describe("Include resolved errors (default: false)")
25321
+ },
25322
+ async execute(args) {
25323
+ const errorContext = await globalErrorAccumulator.getErrorContext(args.bead_id, args.include_resolved ?? false);
25324
+ const stats = await globalErrorAccumulator.getErrorStats(args.bead_id);
25325
+ return JSON.stringify({
25326
+ bead_id: args.bead_id,
25327
+ error_context: errorContext,
25328
+ stats: {
25329
+ total_errors: stats.total,
25330
+ unresolved: stats.unresolved,
25331
+ by_type: stats.by_type
25332
+ },
25333
+ has_errors: errorContext.length > 0,
25334
+ usage: "Inject error_context into retry prompt using {error_context} placeholder"
25335
+ }, null, 2);
25336
+ }
25337
+ });
25338
+ var swarm_resolve_error = tool({
25339
+ description: "Mark an error as resolved after fixing it. Updates error accumulator state.",
25340
+ args: {
25341
+ error_id: tool.schema.string().describe("Error ID to mark as resolved")
25342
+ },
25343
+ async execute(args) {
25344
+ await globalErrorAccumulator.resolveError(args.error_id);
25345
+ return JSON.stringify({
25346
+ success: true,
25347
+ error_id: args.error_id,
25348
+ resolved: true
25349
+ }, null, 2);
25350
+ }
25351
+ });
25056
25352
  var swarm_init = tool({
25057
25353
  description: "Initialize swarm session and check tool availability. Call at swarm start to see what features are available.",
25058
25354
  args: {
@@ -25114,7 +25410,10 @@ var swarmTools = {
25114
25410
  swarm_subtask_prompt,
25115
25411
  swarm_spawn_subtask,
25116
25412
  swarm_complete_subtask,
25117
- swarm_evaluation_prompt
25413
+ swarm_evaluation_prompt,
25414
+ swarm_accumulate_error,
25415
+ swarm_get_error_context,
25416
+ swarm_resolve_error
25118
25417
  };
25119
25418
  // src/anti-patterns.ts
25120
25419
  var PatternKindSchema = exports_external.enum(["pattern", "anti_pattern"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",