opencode-swarm-plugin 0.12.4 → 0.12.7

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/index.js CHANGED
@@ -21560,15 +21560,15 @@ var BeadDependencySchema = exports_external.object({
21560
21560
  type: exports_external.enum(["blocks", "blocked-by", "related", "discovered-from"])
21561
21561
  });
21562
21562
  var BeadSchema = exports_external.object({
21563
- id: exports_external.string().regex(/^[a-z0-9-]+-[a-z0-9]+(\.\d+)?$/, "Invalid bead ID format"),
21563
+ id: exports_external.string().regex(/^[a-z0-9]+(-[a-z0-9]+)+(\.\d+)?$/, "Invalid bead ID format"),
21564
21564
  title: exports_external.string().min(1, "Title required"),
21565
21565
  description: exports_external.string().optional().default(""),
21566
21566
  status: BeadStatusSchema.default("open"),
21567
21567
  priority: exports_external.number().int().min(0).max(3).default(2),
21568
21568
  issue_type: BeadTypeSchema.default("task"),
21569
- created_at: exports_external.string(),
21570
- updated_at: exports_external.string().optional(),
21571
- closed_at: exports_external.string().optional(),
21569
+ created_at: exports_external.string().datetime({ offset: true }),
21570
+ updated_at: exports_external.string().datetime({ offset: true }).optional(),
21571
+ closed_at: exports_external.string().datetime({ offset: true }).optional(),
21572
21572
  parent_id: exports_external.string().optional(),
21573
21573
  dependencies: exports_external.array(BeadDependencySchema).optional().default([]),
21574
21574
  metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
@@ -21641,7 +21641,7 @@ var EvaluationSchema = exports_external.object({
21641
21641
  criteria: exports_external.record(exports_external.string(), CriterionEvaluationSchema),
21642
21642
  overall_feedback: exports_external.string(),
21643
21643
  retry_suggestion: exports_external.string().nullable(),
21644
- timestamp: exports_external.string().optional()
21644
+ timestamp: exports_external.string().datetime({ offset: true }).optional()
21645
21645
  });
21646
21646
  var DEFAULT_CRITERIA = [
21647
21647
  "type_safe",
@@ -21659,7 +21659,7 @@ var WeightedEvaluationSchema = exports_external.object({
21659
21659
  criteria: exports_external.record(exports_external.string(), WeightedCriterionEvaluationSchema),
21660
21660
  overall_feedback: exports_external.string(),
21661
21661
  retry_suggestion: exports_external.string().nullable(),
21662
- timestamp: exports_external.string().optional(),
21662
+ timestamp: exports_external.string().datetime({ offset: true }).optional(),
21663
21663
  average_weight: exports_external.number().min(0).max(1).optional(),
21664
21664
  raw_score: exports_external.number().min(0).max(1).optional(),
21665
21665
  weighted_score: exports_external.number().min(0).max(1).optional()
@@ -21732,7 +21732,7 @@ var SwarmSpawnResultSchema = exports_external.object({
21732
21732
  coordinator_name: exports_external.string(),
21733
21733
  thread_id: exports_external.string(),
21734
21734
  agents: exports_external.array(SpawnedAgentSchema),
21735
- started_at: exports_external.string()
21735
+ started_at: exports_external.string().datetime({ offset: true })
21736
21736
  });
21737
21737
  var AgentProgressSchema = exports_external.object({
21738
21738
  bead_id: exports_external.string(),
@@ -21742,7 +21742,7 @@ var AgentProgressSchema = exports_external.object({
21742
21742
  message: exports_external.string().optional(),
21743
21743
  files_touched: exports_external.array(exports_external.string()).optional(),
21744
21744
  blockers: exports_external.array(exports_external.string()).optional(),
21745
- timestamp: exports_external.string()
21745
+ timestamp: exports_external.string().datetime({ offset: true })
21746
21746
  });
21747
21747
  var SwarmStatusSchema = exports_external.object({
21748
21748
  epic_id: exports_external.string(),
@@ -21752,7 +21752,7 @@ var SwarmStatusSchema = exports_external.object({
21752
21752
  failed: exports_external.number().int().min(0),
21753
21753
  blocked: exports_external.number().int().min(0),
21754
21754
  agents: exports_external.array(SpawnedAgentSchema),
21755
- last_update: exports_external.string()
21755
+ last_update: exports_external.string().datetime({ offset: true })
21756
21756
  });
21757
21757
  // src/beads.ts
21758
21758
  class BeadError extends Error {
@@ -21890,18 +21890,32 @@ var beads_create_epic = tool({
21890
21890
  };
21891
21891
  return JSON.stringify(result, null, 2);
21892
21892
  } catch (error45) {
21893
- const rollbackHint = created.map((b) => `bd close ${b.id} --reason "Rollback partial epic"`).join(`
21894
- `);
21895
- const result = {
21896
- success: false,
21897
- epic: created[0] || {},
21898
- subtasks: created.slice(1),
21899
- rollback_hint: rollbackHint
21900
- };
21901
- return JSON.stringify({
21902
- ...result,
21903
- error: error45 instanceof Error ? error45.message : String(error45)
21904
- }, null, 2);
21893
+ const rollbackCommands = [];
21894
+ for (const bead of created) {
21895
+ try {
21896
+ const closeCmd = [
21897
+ "bd",
21898
+ "close",
21899
+ bead.id,
21900
+ "--reason",
21901
+ "Rollback partial epic",
21902
+ "--json"
21903
+ ];
21904
+ await Bun.$`${closeCmd}`.quiet().nothrow();
21905
+ rollbackCommands.push(`bd close ${bead.id} --reason "Rollback partial epic"`);
21906
+ } catch (rollbackError) {
21907
+ console.error(`Failed to rollback bead ${bead.id}:`, rollbackError);
21908
+ }
21909
+ }
21910
+ const errorMsg = error45 instanceof Error ? error45.message : String(error45);
21911
+ const rollbackInfo = rollbackCommands.length > 0 ? `
21912
+
21913
+ Rolled back ${rollbackCommands.length} bead(s):
21914
+ ${rollbackCommands.join(`
21915
+ `)}` : `
21916
+
21917
+ No beads to rollback.`;
21918
+ throw new BeadError(`Epic creation failed: ${errorMsg}${rollbackInfo}`, "beads_create_epic", 1);
21905
21919
  }
21906
21920
  }
21907
21921
  });
@@ -22028,17 +22042,22 @@ var beads_sync = tool({
22028
22042
  },
22029
22043
  async execute(args, ctx) {
22030
22044
  const autoPull = args.auto_pull ?? true;
22045
+ const TIMEOUT_MS = 30000;
22046
+ const withTimeout = async (promise2, timeoutMs, operation) => {
22047
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new BeadError(`Operation timed out after ${timeoutMs}ms`, operation)), timeoutMs));
22048
+ return Promise.race([promise2, timeoutPromise]);
22049
+ };
22031
22050
  if (autoPull) {
22032
- const pullResult = await Bun.$`git pull --rebase`.quiet().nothrow();
22051
+ const pullResult = await withTimeout(Bun.$`git pull --rebase`.quiet().nothrow(), TIMEOUT_MS, "git pull --rebase");
22033
22052
  if (pullResult.exitCode !== 0) {
22034
22053
  throw new BeadError(`Failed to pull: ${pullResult.stderr.toString()}`, "git pull --rebase", pullResult.exitCode);
22035
22054
  }
22036
22055
  }
22037
- const syncResult = await Bun.$`bd sync`.quiet().nothrow();
22056
+ const syncResult = await withTimeout(Bun.$`bd sync`.quiet().nothrow(), TIMEOUT_MS, "bd sync");
22038
22057
  if (syncResult.exitCode !== 0) {
22039
22058
  throw new BeadError(`Failed to sync beads: ${syncResult.stderr.toString()}`, "bd sync", syncResult.exitCode);
22040
22059
  }
22041
- const pushResult = await Bun.$`git push`.quiet().nothrow();
22060
+ const pushResult = await withTimeout(Bun.$`git push`.quiet().nothrow(), TIMEOUT_MS, "git push");
22042
22061
  if (pushResult.exitCode !== 0) {
22043
22062
  throw new BeadError(`Failed to push: ${pushResult.stderr.toString()}`, "git push", pushResult.exitCode);
22044
22063
  }
@@ -22347,9 +22366,11 @@ function getLimitsForEndpoint(endpoint) {
22347
22366
  const upperEndpoint = endpoint.toUpperCase();
22348
22367
  const perMinuteEnv = process.env[`OPENCODE_RATE_LIMIT_${upperEndpoint}_PER_MIN`];
22349
22368
  const perHourEnv = process.env[`OPENCODE_RATE_LIMIT_${upperEndpoint}_PER_HOUR`];
22369
+ const parsedPerMinute = perMinuteEnv ? parseInt(perMinuteEnv, 10) : NaN;
22370
+ const parsedPerHour = perHourEnv ? parseInt(perHourEnv, 10) : NaN;
22350
22371
  return {
22351
- perMinute: perMinuteEnv ? parseInt(perMinuteEnv, 10) : defaults.perMinute,
22352
- perHour: perHourEnv ? parseInt(perHourEnv, 10) : defaults.perHour
22372
+ perMinute: Number.isNaN(parsedPerMinute) ? defaults.perMinute : parsedPerMinute,
22373
+ perHour: Number.isNaN(parsedPerHour) ? defaults.perHour : parsedPerHour
22353
22374
  };
22354
22375
  }
22355
22376
 
@@ -22388,6 +22409,7 @@ class RedisRateLimiter {
22388
22409
  const pipeline = this.redis.pipeline();
22389
22410
  pipeline.zremrangebyscore(key, 0, windowStart);
22390
22411
  pipeline.zcard(key);
22412
+ pipeline.zrange(key, 0, 0, "WITHSCORES");
22391
22413
  const results = await pipeline.exec();
22392
22414
  if (!results) {
22393
22415
  return { allowed: true, remaining: limit, resetAt: now + windowDuration };
@@ -22397,7 +22419,7 @@ class RedisRateLimiter {
22397
22419
  const allowed = count < limit;
22398
22420
  let resetAt = now + windowDuration;
22399
22421
  if (!allowed) {
22400
- const oldest = await this.redis.zrange(key, 0, 0, "WITHSCORES");
22422
+ const oldest = results[2]?.[1] || [];
22401
22423
  if (oldest.length >= 2) {
22402
22424
  const oldestTimestamp = parseInt(oldest[1], 10);
22403
22425
  resetAt = oldestTimestamp + windowDuration;
@@ -22610,8 +22632,8 @@ var RETRY_CONFIG = {
22610
22632
  jitterPercent: 20
22611
22633
  };
22612
22634
  var RECOVERY_CONFIG = {
22613
- failureThreshold: 2,
22614
- restartCooldownMs: 30000,
22635
+ failureThreshold: 1,
22636
+ restartCooldownMs: 1e4,
22615
22637
  enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
22616
22638
  };
22617
22639
  var SESSION_STATE_DIR = process.env.SWARM_STATE_DIR || join2(tmpdir(), "swarm-sessions");
@@ -22638,8 +22660,11 @@ function saveSessionState(sessionID, state) {
22638
22660
  }
22639
22661
  const path = getSessionStatePath(sessionID);
22640
22662
  writeFileSync(path, JSON.stringify(state, null, 2));
22663
+ return true;
22641
22664
  } catch (error45) {
22642
- console.warn(`[agent-mail] Could not save session state: ${error45}`);
22665
+ console.error(`[agent-mail] CRITICAL: Could not save session state: ${error45}`);
22666
+ console.error(`[agent-mail] Session state will not persist across CLI invocations!`);
22667
+ return false;
22643
22668
  }
22644
22669
  }
22645
22670
  var sessionStates = new Map;
@@ -22919,6 +22944,7 @@ async function mcpCall(toolName, args) {
22919
22944
  } catch (error45) {
22920
22945
  lastError = error45 instanceof Error ? error45 : new Error(String(error45));
22921
22946
  consecutiveFailures++;
22947
+ const retryable = isRetryableError(error45);
22922
22948
  if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
22923
22949
  console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
22924
22950
  const healthy = await isServerFunctional();
@@ -22927,12 +22953,14 @@ async function mcpCall(toolName, args) {
22927
22953
  const restarted = await restartServer();
22928
22954
  if (restarted) {
22929
22955
  agentMailAvailable = null;
22930
- attempt--;
22931
- continue;
22956
+ if (retryable) {
22957
+ attempt--;
22958
+ continue;
22959
+ }
22932
22960
  }
22933
22961
  }
22934
22962
  }
22935
- if (!isRetryableError(error45)) {
22963
+ if (!retryable) {
22936
22964
  console.warn(`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`);
22937
22965
  throw lastError;
22938
22966
  }
@@ -22979,24 +23007,57 @@ var agentmail_init = tool({
22979
23007
  fallback: "Swarm will continue without multi-agent coordination. File conflicts possible if multiple agents active."
22980
23008
  }, null, 2);
22981
23009
  }
22982
- const project = await mcpCall("ensure_project", {
22983
- human_key: args.project_path
22984
- });
22985
- const agent = await mcpCall("register_agent", {
22986
- project_key: args.project_path,
22987
- program: "opencode",
22988
- model: "claude-opus-4",
22989
- name: args.agent_name,
22990
- task_description: args.task_description || ""
22991
- });
22992
- const state = {
22993
- projectKey: args.project_path,
22994
- agentName: agent.name,
22995
- reservations: [],
22996
- startedAt: new Date().toISOString()
22997
- };
22998
- setState(ctx.sessionID, state);
22999
- return JSON.stringify({ project, agent, available: true }, null, 2);
23010
+ const MAX_INIT_RETRIES = 3;
23011
+ let lastError = null;
23012
+ for (let attempt = 1;attempt <= MAX_INIT_RETRIES; attempt++) {
23013
+ try {
23014
+ const project = await mcpCall("ensure_project", {
23015
+ human_key: args.project_path
23016
+ });
23017
+ const agent = await mcpCall("register_agent", {
23018
+ project_key: args.project_path,
23019
+ program: "opencode",
23020
+ model: "claude-opus-4",
23021
+ name: args.agent_name,
23022
+ task_description: args.task_description || ""
23023
+ });
23024
+ const state = {
23025
+ projectKey: args.project_path,
23026
+ agentName: agent.name,
23027
+ reservations: [],
23028
+ startedAt: new Date().toISOString()
23029
+ };
23030
+ setState(ctx.sessionID, state);
23031
+ if (attempt > 1) {
23032
+ console.warn(`[agent-mail] Init succeeded on attempt ${attempt} after restart`);
23033
+ }
23034
+ return JSON.stringify({ project, agent, available: true }, null, 2);
23035
+ } catch (error45) {
23036
+ lastError = error45 instanceof Error ? error45 : new Error(String(error45));
23037
+ const isUnexpectedError = lastError.message.toLowerCase().includes("unexpected error");
23038
+ console.warn(`[agent-mail] Init attempt ${attempt}/${MAX_INIT_RETRIES} failed: ${lastError.message}`);
23039
+ if (isUnexpectedError && attempt < MAX_INIT_RETRIES) {
23040
+ console.warn("[agent-mail] Detected 'unexpected error', restarting server...");
23041
+ const restarted = await restartServer();
23042
+ if (restarted) {
23043
+ agentMailAvailable = null;
23044
+ consecutiveFailures = 0;
23045
+ await new Promise((resolve) => setTimeout(resolve, 1000));
23046
+ continue;
23047
+ }
23048
+ }
23049
+ if (!isUnexpectedError) {
23050
+ break;
23051
+ }
23052
+ }
23053
+ }
23054
+ return JSON.stringify({
23055
+ error: `Agent Mail init failed after ${MAX_INIT_RETRIES} attempts`,
23056
+ available: false,
23057
+ lastError: lastError?.message,
23058
+ hint: "Manually restart Agent Mail: pkill -f agent-mail && agent-mail serve",
23059
+ fallback: "Swarm will continue without multi-agent coordination."
23060
+ }, null, 2);
23000
23061
  }
23001
23062
  });
23002
23063
  var agentmail_send = tool({
@@ -23065,12 +23126,12 @@ var agentmail_read_message = tool({
23065
23126
  const messages = await mcpCall("fetch_inbox", {
23066
23127
  project_key: state.projectKey,
23067
23128
  agent_name: state.agentName,
23068
- limit: 1,
23129
+ limit: 50,
23069
23130
  include_bodies: true
23070
23131
  });
23071
23132
  const message = messages.find((m) => m.id === args.message_id);
23072
23133
  if (!message) {
23073
- return `Message ${args.message_id} not found`;
23134
+ return `Message ${args.message_id} not found in recent 50 messages. Try using agentmail_search to locate it.`;
23074
23135
  }
23075
23136
  await recordRateLimitedRequest(state.agentName, "read_message");
23076
23137
  return JSON.stringify(message, null, 2);
@@ -23202,7 +23263,11 @@ var agentmail_health = tool({
23202
23263
  try {
23203
23264
  const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`);
23204
23265
  if (response.ok) {
23205
- return "Agent Mail is running";
23266
+ const functional = await isServerFunctional();
23267
+ if (functional) {
23268
+ return "Agent Mail is running and functional";
23269
+ }
23270
+ return "Agent Mail health OK but MCP not responding - consider restart";
23206
23271
  }
23207
23272
  return `Agent Mail returned status ${response.status}`;
23208
23273
  } catch (error45) {
@@ -23210,6 +23275,41 @@ var agentmail_health = tool({
23210
23275
  }
23211
23276
  }
23212
23277
  });
23278
+ var agentmail_restart = tool({
23279
+ description: "Manually restart Agent Mail server (use when getting 'unexpected error')",
23280
+ args: {
23281
+ force: tool.schema.boolean().optional().describe("Force restart even if server appears healthy (default: false)")
23282
+ },
23283
+ async execute(args) {
23284
+ if (!args.force) {
23285
+ const functional = await isServerFunctional();
23286
+ if (functional) {
23287
+ return JSON.stringify({
23288
+ restarted: false,
23289
+ reason: "Server is functional, no restart needed",
23290
+ hint: "Use force=true to restart anyway"
23291
+ }, null, 2);
23292
+ }
23293
+ }
23294
+ console.warn("[agent-mail] Manual restart requested...");
23295
+ const success2 = await restartServer();
23296
+ agentMailAvailable = null;
23297
+ consecutiveFailures = 0;
23298
+ if (success2) {
23299
+ return JSON.stringify({
23300
+ restarted: true,
23301
+ success: true,
23302
+ message: "Agent Mail server restarted successfully"
23303
+ }, null, 2);
23304
+ }
23305
+ return JSON.stringify({
23306
+ restarted: true,
23307
+ success: false,
23308
+ error: "Restart attempted but server did not come back up",
23309
+ hint: "Check server logs or manually start: agent-mail serve"
23310
+ }, null, 2);
23311
+ }
23312
+ });
23213
23313
  var agentMailTools = {
23214
23314
  agentmail_init,
23215
23315
  agentmail_send,
@@ -23220,7 +23320,8 @@ var agentMailTools = {
23220
23320
  agentmail_release,
23221
23321
  agentmail_ack,
23222
23322
  agentmail_search,
23223
- agentmail_health
23323
+ agentmail_health,
23324
+ agentmail_restart
23224
23325
  };
23225
23326
 
23226
23327
  // src/structured.ts
@@ -23636,6 +23737,24 @@ var CriterionWeightSchema = exports_external.object({
23636
23737
  last_validated: exports_external.string().optional(),
23637
23738
  half_life_days: exports_external.number().positive().default(90)
23638
23739
  });
23740
+ var ErrorTypeSchema = exports_external.enum([
23741
+ "validation",
23742
+ "timeout",
23743
+ "conflict",
23744
+ "tool_failure",
23745
+ "unknown"
23746
+ ]);
23747
+ var ErrorEntrySchema = exports_external.object({
23748
+ id: exports_external.string(),
23749
+ bead_id: exports_external.string(),
23750
+ error_type: ErrorTypeSchema,
23751
+ message: exports_external.string(),
23752
+ stack_trace: exports_external.string().optional(),
23753
+ tool_name: exports_external.string().optional(),
23754
+ timestamp: exports_external.string(),
23755
+ resolved: exports_external.boolean().default(false),
23756
+ context: exports_external.string().optional()
23757
+ });
23639
23758
  var DecompositionStrategySchema = exports_external.enum([
23640
23759
  "file-based",
23641
23760
  "feature-based",
@@ -23725,6 +23844,118 @@ class InMemoryFeedbackStorage {
23725
23844
  }
23726
23845
  }
23727
23846
 
23847
+ class InMemoryErrorStorage {
23848
+ errors = [];
23849
+ async store(entry) {
23850
+ this.errors.push(entry);
23851
+ }
23852
+ async getByBead(beadId) {
23853
+ return this.errors.filter((e) => e.bead_id === beadId);
23854
+ }
23855
+ async getUnresolvedByBead(beadId) {
23856
+ return this.errors.filter((e) => e.bead_id === beadId && !e.resolved);
23857
+ }
23858
+ async markResolved(id) {
23859
+ const error45 = this.errors.find((e) => e.id === id);
23860
+ if (error45) {
23861
+ error45.resolved = true;
23862
+ }
23863
+ }
23864
+ async getAll() {
23865
+ return [...this.errors];
23866
+ }
23867
+ }
23868
+
23869
+ class ErrorAccumulator {
23870
+ storage;
23871
+ constructor(storage) {
23872
+ this.storage = storage ?? new InMemoryErrorStorage;
23873
+ }
23874
+ async recordError(beadId, errorType, message, options) {
23875
+ const entry = {
23876
+ id: `${beadId}-${errorType}-${Date.now()}`,
23877
+ bead_id: beadId,
23878
+ error_type: errorType,
23879
+ message,
23880
+ stack_trace: options?.stack_trace,
23881
+ tool_name: options?.tool_name,
23882
+ timestamp: new Date().toISOString(),
23883
+ resolved: false,
23884
+ context: options?.context
23885
+ };
23886
+ const validated = ErrorEntrySchema.parse(entry);
23887
+ await this.storage.store(validated);
23888
+ return validated;
23889
+ }
23890
+ async getErrors(beadId) {
23891
+ return this.storage.getByBead(beadId);
23892
+ }
23893
+ async getUnresolvedErrors(beadId) {
23894
+ return this.storage.getUnresolvedByBead(beadId);
23895
+ }
23896
+ async resolveError(errorId) {
23897
+ await this.storage.markResolved(errorId);
23898
+ }
23899
+ async getErrorContext(beadId, includeResolved = false) {
23900
+ const errors3 = includeResolved ? await this.getErrors(beadId) : await this.getUnresolvedErrors(beadId);
23901
+ if (errors3.length === 0) {
23902
+ return "";
23903
+ }
23904
+ const byType = errors3.reduce((acc, err) => {
23905
+ const type = err.error_type;
23906
+ if (!acc[type]) {
23907
+ acc[type] = [];
23908
+ }
23909
+ acc[type].push(err);
23910
+ return acc;
23911
+ }, {});
23912
+ const lines = [
23913
+ "## Previous Errors",
23914
+ "",
23915
+ "The following errors were encountered during execution:",
23916
+ ""
23917
+ ];
23918
+ for (const [type, typeErrors] of Object.entries(byType)) {
23919
+ lines.push(`### ${type} (${typeErrors.length} error${typeErrors.length > 1 ? "s" : ""})`);
23920
+ lines.push("");
23921
+ for (const err of typeErrors) {
23922
+ lines.push(`- **${err.message}**`);
23923
+ if (err.context) {
23924
+ lines.push(` - Context: ${err.context}`);
23925
+ }
23926
+ if (err.tool_name) {
23927
+ lines.push(` - Tool: ${err.tool_name}`);
23928
+ }
23929
+ if (err.stack_trace) {
23930
+ lines.push(` - Stack: \`${err.stack_trace.slice(0, 100)}...\``);
23931
+ }
23932
+ lines.push(` - Time: ${new Date(err.timestamp).toLocaleString()}${err.resolved ? " (resolved)" : ""}`);
23933
+ lines.push("");
23934
+ }
23935
+ }
23936
+ lines.push("**Action Required**: Address these errors before proceeding. Consider:");
23937
+ lines.push("- What caused each error?");
23938
+ lines.push("- How can you prevent similar errors?");
23939
+ lines.push("- Are there patterns across error types?");
23940
+ lines.push("");
23941
+ return lines.join(`
23942
+ `);
23943
+ }
23944
+ async getErrorStats(beadId) {
23945
+ const allErrors = await this.getErrors(beadId);
23946
+ const unresolved = await this.getUnresolvedErrors(beadId);
23947
+ const byType = allErrors.reduce((acc, err) => {
23948
+ acc[err.error_type] = (acc[err.error_type] || 0) + 1;
23949
+ return acc;
23950
+ }, {});
23951
+ return {
23952
+ total: allErrors.length,
23953
+ unresolved: unresolved.length,
23954
+ by_type: byType
23955
+ };
23956
+ }
23957
+ }
23958
+
23728
23959
  // src/swarm.ts
23729
23960
  var POSITIVE_MARKERS = [
23730
23961
  "always",
@@ -24135,6 +24366,10 @@ Only modify these files. Need others? Message the coordinator.
24135
24366
  ## Context
24136
24367
  {shared_context}
24137
24368
 
24369
+ {compressed_context}
24370
+
24371
+ {error_context}
24372
+
24138
24373
  ## MANDATORY: Use These Tools
24139
24374
 
24140
24375
  ### Agent Mail - communicate with the swarm
@@ -24168,7 +24403,9 @@ Begin now.`;
24168
24403
  function formatSubtaskPromptV2(params) {
24169
24404
  const fileList = params.files.length > 0 ? params.files.map((f) => `- \`${f}\``).join(`
24170
24405
  `) : "(no specific files - use judgment)";
24171
- 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)");
24406
+ const compressedSection = params.compressed_context ? params.compressed_context : "";
24407
+ const errorSection = params.error_context ? params.error_context : "";
24408
+ 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);
24172
24409
  }
24173
24410
  var EVALUATION_PROMPT = `Evaluate the work completed for this subtask.
24174
24411
 
@@ -24567,6 +24804,13 @@ var swarm_validate_decomposition = tool({
24567
24804
  for (let i = 0;i < validated.subtasks.length; i++) {
24568
24805
  const deps = validated.subtasks[i].dependencies;
24569
24806
  for (const dep of deps) {
24807
+ if (dep < 0 || dep >= validated.subtasks.length) {
24808
+ return JSON.stringify({
24809
+ valid: false,
24810
+ error: `Invalid dependency: subtask ${i} depends on ${dep}, but only ${validated.subtasks.length} subtasks exist (indices 0-${validated.subtasks.length - 1})`,
24811
+ hint: "Dependency index is out of bounds"
24812
+ }, null, 2);
24813
+ }
24570
24814
  if (dep >= i) {
24571
24815
  return JSON.stringify({
24572
24816
  valid: false,
@@ -24867,6 +25111,7 @@ var swarm_record_outcome = tool({
24867
25111
  };
24868
25112
  const validated = OutcomeSignalsSchema.parse(signals);
24869
25113
  const scored = scoreImplicitFeedback(validated, DEFAULT_LEARNING_CONFIG);
25114
+ const errorStats = await globalErrorAccumulator.getErrorStats(args.bead_id);
24870
25115
  const criteriaToScore = args.criteria ?? [
24871
25116
  "type_safe",
24872
25117
  "no_bugs",
@@ -24878,6 +25123,10 @@ var swarm_record_outcome = tool({
24878
25123
  if (args.strategy) {
24879
25124
  event.context = `${event.context || ""} [strategy: ${args.strategy}]`.trim();
24880
25125
  }
25126
+ if (errorStats.total > 0) {
25127
+ const errorSummary = Object.entries(errorStats.by_type).map(([type, count]) => `${type}:${count}`).join(", ");
25128
+ event.context = `${event.context || ""} [errors: ${errorSummary}]`.trim();
25129
+ }
24881
25130
  return event;
24882
25131
  });
24883
25132
  return JSON.stringify({
@@ -24891,13 +25140,16 @@ var swarm_record_outcome = tool({
24891
25140
  }
24892
25141
  },
24893
25142
  feedback_events: feedbackEvents,
25143
+ error_patterns: errorStats,
24894
25144
  summary: {
24895
25145
  feedback_type: scored.type,
24896
25146
  duration_seconds: Math.round(args.duration_ms / 1000),
24897
25147
  error_count: args.error_count ?? 0,
24898
25148
  retry_count: args.retry_count ?? 0,
24899
25149
  success: args.success,
24900
- strategy: args.strategy
25150
+ strategy: args.strategy,
25151
+ accumulated_errors: errorStats.total,
25152
+ unresolved_errors: errorStats.unresolved
24901
25153
  },
24902
25154
  note: "Feedback events should be stored for criterion weight calculation. Use learning.ts functions to apply weights."
24903
25155
  }, null, 2);
@@ -25067,6 +25319,70 @@ var swarm_evaluation_prompt = tool({
25067
25319
  }, null, 2);
25068
25320
  }
25069
25321
  });
25322
+ var globalErrorAccumulator = new ErrorAccumulator;
25323
+ var swarm_accumulate_error = tool({
25324
+ description: "Record an error during subtask execution. Errors feed into retry prompts.",
25325
+ args: {
25326
+ bead_id: tool.schema.string().describe("Bead ID where error occurred"),
25327
+ error_type: tool.schema.enum(["validation", "timeout", "conflict", "tool_failure", "unknown"]).describe("Category of error"),
25328
+ message: tool.schema.string().describe("Human-readable error message"),
25329
+ stack_trace: tool.schema.string().optional().describe("Stack trace for debugging"),
25330
+ tool_name: tool.schema.string().optional().describe("Tool that failed"),
25331
+ context: tool.schema.string().optional().describe("What was happening when error occurred")
25332
+ },
25333
+ async execute(args) {
25334
+ const entry = await globalErrorAccumulator.recordError(args.bead_id, args.error_type, args.message, {
25335
+ stack_trace: args.stack_trace,
25336
+ tool_name: args.tool_name,
25337
+ context: args.context
25338
+ });
25339
+ return JSON.stringify({
25340
+ success: true,
25341
+ error_id: entry.id,
25342
+ bead_id: entry.bead_id,
25343
+ error_type: entry.error_type,
25344
+ message: entry.message,
25345
+ timestamp: entry.timestamp,
25346
+ note: "Error recorded for retry context. Use swarm_get_error_context to retrieve accumulated errors."
25347
+ }, null, 2);
25348
+ }
25349
+ });
25350
+ var swarm_get_error_context = tool({
25351
+ description: "Get accumulated errors for a bead. Returns formatted context for retry prompts.",
25352
+ args: {
25353
+ bead_id: tool.schema.string().describe("Bead ID to get errors for"),
25354
+ include_resolved: tool.schema.boolean().optional().describe("Include resolved errors (default: false)")
25355
+ },
25356
+ async execute(args) {
25357
+ const errorContext = await globalErrorAccumulator.getErrorContext(args.bead_id, args.include_resolved ?? false);
25358
+ const stats = await globalErrorAccumulator.getErrorStats(args.bead_id);
25359
+ return JSON.stringify({
25360
+ bead_id: args.bead_id,
25361
+ error_context: errorContext,
25362
+ stats: {
25363
+ total_errors: stats.total,
25364
+ unresolved: stats.unresolved,
25365
+ by_type: stats.by_type
25366
+ },
25367
+ has_errors: errorContext.length > 0,
25368
+ usage: "Inject error_context into retry prompt using {error_context} placeholder"
25369
+ }, null, 2);
25370
+ }
25371
+ });
25372
+ var swarm_resolve_error = tool({
25373
+ description: "Mark an error as resolved after fixing it. Updates error accumulator state.",
25374
+ args: {
25375
+ error_id: tool.schema.string().describe("Error ID to mark as resolved")
25376
+ },
25377
+ async execute(args) {
25378
+ await globalErrorAccumulator.resolveError(args.error_id);
25379
+ return JSON.stringify({
25380
+ success: true,
25381
+ error_id: args.error_id,
25382
+ resolved: true
25383
+ }, null, 2);
25384
+ }
25385
+ });
25070
25386
  var swarm_init = tool({
25071
25387
  description: "Initialize swarm session and check tool availability. Call at swarm start to see what features are available.",
25072
25388
  args: {
@@ -25128,7 +25444,10 @@ var swarmTools = {
25128
25444
  swarm_subtask_prompt,
25129
25445
  swarm_spawn_subtask,
25130
25446
  swarm_complete_subtask,
25131
- swarm_evaluation_prompt
25447
+ swarm_evaluation_prompt,
25448
+ swarm_accumulate_error,
25449
+ swarm_get_error_context,
25450
+ swarm_resolve_error
25132
25451
  };
25133
25452
  // src/anti-patterns.ts
25134
25453
  var PatternKindSchema = exports_external.enum(["pattern", "anti_pattern"]);
@@ -25232,16 +25551,29 @@ async function resolveSemanticMemoryCommand() {
25232
25551
  return cachedCommand;
25233
25552
  }
25234
25553
  async function execSemanticMemory(args) {
25235
- const cmd = await resolveSemanticMemoryCommand();
25236
- const fullCmd = [...cmd, ...args];
25237
- const proc = Bun.spawn(fullCmd, {
25238
- stdout: "pipe",
25239
- stderr: "pipe"
25240
- });
25241
- const stdout = Buffer.from(await new Response(proc.stdout).arrayBuffer());
25242
- const stderr = Buffer.from(await new Response(proc.stderr).arrayBuffer());
25243
- const exitCode = await proc.exited;
25244
- return { exitCode, stdout, stderr };
25554
+ try {
25555
+ const cmd = await resolveSemanticMemoryCommand();
25556
+ const fullCmd = [...cmd, ...args];
25557
+ const proc = Bun.spawn(fullCmd, {
25558
+ stdout: "pipe",
25559
+ stderr: "pipe"
25560
+ });
25561
+ try {
25562
+ const stdout = Buffer.from(await new Response(proc.stdout).arrayBuffer());
25563
+ const stderr = Buffer.from(await new Response(proc.stderr).arrayBuffer());
25564
+ const exitCode = await proc.exited;
25565
+ return { exitCode, stdout, stderr };
25566
+ } finally {
25567
+ proc.kill();
25568
+ }
25569
+ } catch (error45) {
25570
+ const errorMessage = error45 instanceof Error ? error45.message : String(error45);
25571
+ return {
25572
+ exitCode: 1,
25573
+ stdout: Buffer.from(""),
25574
+ stderr: Buffer.from(`Error executing semantic-memory: ${errorMessage}`)
25575
+ };
25576
+ }
25245
25577
  }
25246
25578
  var DEFAULT_STORAGE_CONFIG = {
25247
25579
  backend: "semantic-memory",
@@ -25503,20 +25835,26 @@ async function createStorageWithFallback(config2 = {}) {
25503
25835
  return new InMemoryStorage;
25504
25836
  }
25505
25837
  var globalStorage = null;
25838
+ var globalStoragePromise = null;
25506
25839
  async function getStorage() {
25507
- if (!globalStorage) {
25508
- globalStorage = await createStorageWithFallback();
25840
+ if (!globalStoragePromise) {
25841
+ globalStoragePromise = createStorageWithFallback().then((storage) => {
25842
+ globalStorage = storage;
25843
+ return storage;
25844
+ });
25509
25845
  }
25510
- return globalStorage;
25846
+ return globalStoragePromise;
25511
25847
  }
25512
25848
  function setStorage(storage) {
25513
25849
  globalStorage = storage;
25850
+ globalStoragePromise = Promise.resolve(storage);
25514
25851
  }
25515
25852
  async function resetStorage() {
25516
25853
  if (globalStorage) {
25517
25854
  await globalStorage.close();
25518
25855
  globalStorage = null;
25519
25856
  }
25857
+ globalStoragePromise = null;
25520
25858
  }
25521
25859
 
25522
25860
  // src/index.ts