opencode-swarm-plugin 0.6.3 → 0.10.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/bun.lock CHANGED
@@ -5,6 +5,7 @@
5
5
  "": {
6
6
  "name": "opencode-swarm-plugin",
7
7
  "dependencies": {
8
+ "@clack/prompts": "^0.11.0",
8
9
  "@opencode-ai/plugin": "^1.0.134",
9
10
  "ioredis": "^5.4.1",
10
11
  "zod": "4.1.8",
@@ -20,6 +21,10 @@
20
21
  },
21
22
  },
22
23
  "packages": {
24
+ "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
25
+
26
+ "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
27
+
23
28
  "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
24
29
 
25
30
  "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
@@ -204,6 +209,8 @@
204
209
 
205
210
  "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
206
211
 
212
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
213
+
207
214
  "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
208
215
 
209
216
  "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
package/dist/index.js CHANGED
@@ -22593,6 +22593,18 @@ async function getRateLimiter() {
22593
22593
  var AGENT_MAIL_URL = "http://127.0.0.1:8765";
22594
22594
  var DEFAULT_TTL_SECONDS = 3600;
22595
22595
  var MAX_INBOX_LIMIT = 5;
22596
+ var RETRY_CONFIG = {
22597
+ maxRetries: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_RETRIES || "3"),
22598
+ baseDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_BASE_DELAY_MS || "100"),
22599
+ maxDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_DELAY_MS || "5000"),
22600
+ timeoutMs: parseInt(process.env.OPENCODE_AGENT_MAIL_TIMEOUT_MS || "10000"),
22601
+ jitterPercent: 20
22602
+ };
22603
+ var RECOVERY_CONFIG = {
22604
+ failureThreshold: 2,
22605
+ restartCooldownMs: 30000,
22606
+ enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
22607
+ };
22596
22608
  var sessionStates = new Map;
22597
22609
 
22598
22610
  class AgentMailError extends Error {
@@ -22639,6 +22651,151 @@ class RateLimitExceededError extends Error {
22639
22651
  this.name = "RateLimitExceededError";
22640
22652
  }
22641
22653
  }
22654
+ var consecutiveFailures = 0;
22655
+ var lastRestartAttempt = 0;
22656
+ var isRestarting = false;
22657
+ async function isServerHealthy() {
22658
+ try {
22659
+ const controller = new AbortController;
22660
+ const timeout = setTimeout(() => controller.abort(), 3000);
22661
+ const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`, {
22662
+ signal: controller.signal
22663
+ });
22664
+ clearTimeout(timeout);
22665
+ return response.ok;
22666
+ } catch {
22667
+ return false;
22668
+ }
22669
+ }
22670
+ async function isServerFunctional() {
22671
+ try {
22672
+ const controller = new AbortController;
22673
+ const timeout = setTimeout(() => controller.abort(), 5000);
22674
+ const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22675
+ method: "POST",
22676
+ headers: { "Content-Type": "application/json" },
22677
+ body: JSON.stringify({
22678
+ jsonrpc: "2.0",
22679
+ id: "health-test",
22680
+ method: "tools/call",
22681
+ params: { name: "health_check", arguments: {} }
22682
+ }),
22683
+ signal: controller.signal
22684
+ });
22685
+ clearTimeout(timeout);
22686
+ if (!response.ok)
22687
+ return false;
22688
+ const json2 = await response.json();
22689
+ if (json2.result?.isError)
22690
+ return false;
22691
+ return true;
22692
+ } catch {
22693
+ return false;
22694
+ }
22695
+ }
22696
+ async function restartServer() {
22697
+ if (!RECOVERY_CONFIG.enabled) {
22698
+ console.warn("[agent-mail] Auto-restart disabled via OPENCODE_AGENT_MAIL_AUTO_RESTART=false");
22699
+ return false;
22700
+ }
22701
+ if (isRestarting) {
22702
+ console.warn("[agent-mail] Restart already in progress");
22703
+ return false;
22704
+ }
22705
+ const now = Date.now();
22706
+ if (now - lastRestartAttempt < RECOVERY_CONFIG.restartCooldownMs) {
22707
+ const waitSec = Math.ceil((RECOVERY_CONFIG.restartCooldownMs - (now - lastRestartAttempt)) / 1000);
22708
+ console.warn(`[agent-mail] Restart cooldown active, wait ${waitSec}s`);
22709
+ return false;
22710
+ }
22711
+ isRestarting = true;
22712
+ lastRestartAttempt = now;
22713
+ try {
22714
+ console.warn("[agent-mail] Attempting server restart...");
22715
+ const findProc = Bun.spawn(["lsof", "-i", ":8765", "-t"], {
22716
+ stdout: "pipe",
22717
+ stderr: "pipe"
22718
+ });
22719
+ const findOutput = await new Response(findProc.stdout).text();
22720
+ await findProc.exited;
22721
+ const pids = findOutput.trim().split(`
22722
+ `).filter(Boolean);
22723
+ if (pids.length > 0) {
22724
+ for (const pid of pids) {
22725
+ console.warn(`[agent-mail] Killing process ${pid}`);
22726
+ Bun.spawn(["kill", pid]);
22727
+ }
22728
+ await new Promise((resolve) => setTimeout(resolve, 2000));
22729
+ }
22730
+ const possiblePaths = [
22731
+ `${process.env.HOME}/Code/Dicklesworthstone/mcp_agent_mail`,
22732
+ `${process.env.HOME}/.local/share/agent-mail`,
22733
+ `${process.env.HOME}/mcp_agent_mail`
22734
+ ];
22735
+ let serverDir = null;
22736
+ for (const path of possiblePaths) {
22737
+ try {
22738
+ const stat = await Bun.file(`${path}/pyproject.toml`).exists();
22739
+ if (stat) {
22740
+ serverDir = path;
22741
+ break;
22742
+ }
22743
+ } catch {
22744
+ continue;
22745
+ }
22746
+ }
22747
+ if (!serverDir) {
22748
+ console.error("[agent-mail] Could not find agent-mail installation directory");
22749
+ return false;
22750
+ }
22751
+ console.warn(`[agent-mail] Starting server from ${serverDir}`);
22752
+ Bun.spawn(["python", "-m", "mcp_agent_mail.cli", "serve-http"], {
22753
+ cwd: serverDir,
22754
+ stdout: "ignore",
22755
+ stderr: "ignore",
22756
+ detached: true
22757
+ });
22758
+ for (let i = 0;i < 10; i++) {
22759
+ await new Promise((resolve) => setTimeout(resolve, 1000));
22760
+ if (await isServerHealthy()) {
22761
+ console.warn("[agent-mail] Server restarted successfully");
22762
+ consecutiveFailures = 0;
22763
+ return true;
22764
+ }
22765
+ }
22766
+ console.error("[agent-mail] Server failed to start after restart");
22767
+ return false;
22768
+ } catch (error45) {
22769
+ console.error("[agent-mail] Restart failed:", error45);
22770
+ return false;
22771
+ } finally {
22772
+ isRestarting = false;
22773
+ }
22774
+ }
22775
+ function calculateBackoffDelay(attempt) {
22776
+ if (attempt === 0)
22777
+ return 0;
22778
+ const exponentialDelay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1);
22779
+ const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);
22780
+ const jitterRange = cappedDelay * (RETRY_CONFIG.jitterPercent / 100);
22781
+ const jitter = (Math.random() * 2 - 1) * jitterRange;
22782
+ return Math.round(cappedDelay + jitter);
22783
+ }
22784
+ function isRetryableError(error45) {
22785
+ if (error45 instanceof Error) {
22786
+ const message = error45.message.toLowerCase();
22787
+ if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket") || message.includes("network") || message.includes("timeout") || message.includes("aborted")) {
22788
+ return true;
22789
+ }
22790
+ if (error45 instanceof AgentMailError && error45.code) {
22791
+ return error45.code === 502 || error45.code === 503 || error45.code === 504;
22792
+ }
22793
+ if (message.includes("unexpected error")) {
22794
+ return true;
22795
+ }
22796
+ }
22797
+ return false;
22798
+ }
22642
22799
  var agentMailAvailable = null;
22643
22800
  async function checkAgentMailAvailable() {
22644
22801
  if (agentMailAvailable !== null) {
@@ -22670,36 +22827,85 @@ async function recordRateLimitedRequest(agentName, endpoint) {
22670
22827
  }
22671
22828
  await rateLimiter.recordRequest(agentName, endpoint);
22672
22829
  }
22673
- async function mcpCall(toolName, args) {
22674
- const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22675
- method: "POST",
22676
- headers: { "Content-Type": "application/json" },
22677
- body: JSON.stringify({
22678
- jsonrpc: "2.0",
22679
- id: crypto.randomUUID(),
22680
- method: "tools/call",
22681
- params: { name: toolName, arguments: args }
22682
- })
22683
- });
22684
- if (!response.ok) {
22685
- throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName);
22686
- }
22687
- const json2 = await response.json();
22688
- if (json2.error) {
22689
- throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
22830
+ async function mcpCallOnce(toolName, args) {
22831
+ const controller = new AbortController;
22832
+ const timeout = setTimeout(() => controller.abort(), RETRY_CONFIG.timeoutMs);
22833
+ try {
22834
+ const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22835
+ method: "POST",
22836
+ headers: { "Content-Type": "application/json" },
22837
+ body: JSON.stringify({
22838
+ jsonrpc: "2.0",
22839
+ id: crypto.randomUUID(),
22840
+ method: "tools/call",
22841
+ params: { name: toolName, arguments: args }
22842
+ }),
22843
+ signal: controller.signal
22844
+ });
22845
+ clearTimeout(timeout);
22846
+ if (!response.ok) {
22847
+ throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName, response.status);
22848
+ }
22849
+ const json2 = await response.json();
22850
+ if (json2.error) {
22851
+ throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
22852
+ }
22853
+ const result = json2.result;
22854
+ if (result && typeof result === "object") {
22855
+ const wrapped = result;
22856
+ if (wrapped.isError) {
22857
+ const errorText = wrapped.content?.[0]?.text || "Unknown error";
22858
+ throw new AgentMailError(errorText, toolName);
22859
+ }
22860
+ if ("structuredContent" in wrapped) {
22861
+ return wrapped.structuredContent;
22862
+ }
22863
+ }
22864
+ return result;
22865
+ } catch (error45) {
22866
+ clearTimeout(timeout);
22867
+ throw error45;
22690
22868
  }
22691
- const result = json2.result;
22692
- if (result && typeof result === "object") {
22693
- const wrapped = result;
22694
- if (wrapped.isError) {
22695
- const errorText = wrapped.content?.[0]?.text || "Unknown error";
22696
- throw new AgentMailError(errorText, toolName);
22869
+ }
22870
+ async function mcpCall(toolName, args) {
22871
+ let lastError = null;
22872
+ for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
22873
+ if (attempt > 0) {
22874
+ const delay = calculateBackoffDelay(attempt);
22875
+ console.warn(`[agent-mail] Retry ${attempt}/${RETRY_CONFIG.maxRetries} for ${toolName} after ${delay}ms`);
22876
+ await new Promise((resolve) => setTimeout(resolve, delay));
22697
22877
  }
22698
- if ("structuredContent" in wrapped) {
22699
- return wrapped.structuredContent;
22878
+ try {
22879
+ const result = await mcpCallOnce(toolName, args);
22880
+ consecutiveFailures = 0;
22881
+ return result;
22882
+ } catch (error45) {
22883
+ lastError = error45 instanceof Error ? error45 : new Error(String(error45));
22884
+ consecutiveFailures++;
22885
+ if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
22886
+ console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
22887
+ const healthy = await isServerFunctional();
22888
+ if (!healthy) {
22889
+ console.warn("[agent-mail] Server unhealthy, attempting restart...");
22890
+ const restarted = await restartServer();
22891
+ if (restarted) {
22892
+ agentMailAvailable = null;
22893
+ attempt--;
22894
+ continue;
22895
+ }
22896
+ }
22897
+ }
22898
+ if (!isRetryableError(error45)) {
22899
+ console.warn(`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`);
22900
+ throw lastError;
22901
+ }
22902
+ if (attempt === RETRY_CONFIG.maxRetries) {
22903
+ console.error(`[agent-mail] All ${RETRY_CONFIG.maxRetries} retries exhausted for ${toolName}`);
22904
+ throw lastError;
22905
+ }
22700
22906
  }
22701
22907
  }
22702
- return result;
22908
+ throw lastError || new Error("Unknown error in mcpCall");
22703
22909
  }
22704
22910
  function requireState(sessionID) {
22705
22911
  const state = sessionStates.get(sessionID);
package/dist/plugin.js CHANGED
@@ -22567,6 +22567,18 @@ async function getRateLimiter() {
22567
22567
  var AGENT_MAIL_URL = "http://127.0.0.1:8765";
22568
22568
  var DEFAULT_TTL_SECONDS = 3600;
22569
22569
  var MAX_INBOX_LIMIT = 5;
22570
+ var RETRY_CONFIG = {
22571
+ maxRetries: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_RETRIES || "3"),
22572
+ baseDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_BASE_DELAY_MS || "100"),
22573
+ maxDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_DELAY_MS || "5000"),
22574
+ timeoutMs: parseInt(process.env.OPENCODE_AGENT_MAIL_TIMEOUT_MS || "10000"),
22575
+ jitterPercent: 20
22576
+ };
22577
+ var RECOVERY_CONFIG = {
22578
+ failureThreshold: 2,
22579
+ restartCooldownMs: 30000,
22580
+ enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
22581
+ };
22570
22582
  var sessionStates = new Map;
22571
22583
 
22572
22584
  class AgentMailError extends Error {
@@ -22613,6 +22625,151 @@ class RateLimitExceededError extends Error {
22613
22625
  this.name = "RateLimitExceededError";
22614
22626
  }
22615
22627
  }
22628
+ var consecutiveFailures = 0;
22629
+ var lastRestartAttempt = 0;
22630
+ var isRestarting = false;
22631
+ async function isServerHealthy() {
22632
+ try {
22633
+ const controller = new AbortController;
22634
+ const timeout = setTimeout(() => controller.abort(), 3000);
22635
+ const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`, {
22636
+ signal: controller.signal
22637
+ });
22638
+ clearTimeout(timeout);
22639
+ return response.ok;
22640
+ } catch {
22641
+ return false;
22642
+ }
22643
+ }
22644
+ async function isServerFunctional() {
22645
+ try {
22646
+ const controller = new AbortController;
22647
+ const timeout = setTimeout(() => controller.abort(), 5000);
22648
+ const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22649
+ method: "POST",
22650
+ headers: { "Content-Type": "application/json" },
22651
+ body: JSON.stringify({
22652
+ jsonrpc: "2.0",
22653
+ id: "health-test",
22654
+ method: "tools/call",
22655
+ params: { name: "health_check", arguments: {} }
22656
+ }),
22657
+ signal: controller.signal
22658
+ });
22659
+ clearTimeout(timeout);
22660
+ if (!response.ok)
22661
+ return false;
22662
+ const json2 = await response.json();
22663
+ if (json2.result?.isError)
22664
+ return false;
22665
+ return true;
22666
+ } catch {
22667
+ return false;
22668
+ }
22669
+ }
22670
+ async function restartServer() {
22671
+ if (!RECOVERY_CONFIG.enabled) {
22672
+ console.warn("[agent-mail] Auto-restart disabled via OPENCODE_AGENT_MAIL_AUTO_RESTART=false");
22673
+ return false;
22674
+ }
22675
+ if (isRestarting) {
22676
+ console.warn("[agent-mail] Restart already in progress");
22677
+ return false;
22678
+ }
22679
+ const now = Date.now();
22680
+ if (now - lastRestartAttempt < RECOVERY_CONFIG.restartCooldownMs) {
22681
+ const waitSec = Math.ceil((RECOVERY_CONFIG.restartCooldownMs - (now - lastRestartAttempt)) / 1000);
22682
+ console.warn(`[agent-mail] Restart cooldown active, wait ${waitSec}s`);
22683
+ return false;
22684
+ }
22685
+ isRestarting = true;
22686
+ lastRestartAttempt = now;
22687
+ try {
22688
+ console.warn("[agent-mail] Attempting server restart...");
22689
+ const findProc = Bun.spawn(["lsof", "-i", ":8765", "-t"], {
22690
+ stdout: "pipe",
22691
+ stderr: "pipe"
22692
+ });
22693
+ const findOutput = await new Response(findProc.stdout).text();
22694
+ await findProc.exited;
22695
+ const pids = findOutput.trim().split(`
22696
+ `).filter(Boolean);
22697
+ if (pids.length > 0) {
22698
+ for (const pid of pids) {
22699
+ console.warn(`[agent-mail] Killing process ${pid}`);
22700
+ Bun.spawn(["kill", pid]);
22701
+ }
22702
+ await new Promise((resolve) => setTimeout(resolve, 2000));
22703
+ }
22704
+ const possiblePaths = [
22705
+ `${process.env.HOME}/Code/Dicklesworthstone/mcp_agent_mail`,
22706
+ `${process.env.HOME}/.local/share/agent-mail`,
22707
+ `${process.env.HOME}/mcp_agent_mail`
22708
+ ];
22709
+ let serverDir = null;
22710
+ for (const path of possiblePaths) {
22711
+ try {
22712
+ const stat = await Bun.file(`${path}/pyproject.toml`).exists();
22713
+ if (stat) {
22714
+ serverDir = path;
22715
+ break;
22716
+ }
22717
+ } catch {
22718
+ continue;
22719
+ }
22720
+ }
22721
+ if (!serverDir) {
22722
+ console.error("[agent-mail] Could not find agent-mail installation directory");
22723
+ return false;
22724
+ }
22725
+ console.warn(`[agent-mail] Starting server from ${serverDir}`);
22726
+ Bun.spawn(["python", "-m", "mcp_agent_mail.cli", "serve-http"], {
22727
+ cwd: serverDir,
22728
+ stdout: "ignore",
22729
+ stderr: "ignore",
22730
+ detached: true
22731
+ });
22732
+ for (let i = 0;i < 10; i++) {
22733
+ await new Promise((resolve) => setTimeout(resolve, 1000));
22734
+ if (await isServerHealthy()) {
22735
+ console.warn("[agent-mail] Server restarted successfully");
22736
+ consecutiveFailures = 0;
22737
+ return true;
22738
+ }
22739
+ }
22740
+ console.error("[agent-mail] Server failed to start after restart");
22741
+ return false;
22742
+ } catch (error45) {
22743
+ console.error("[agent-mail] Restart failed:", error45);
22744
+ return false;
22745
+ } finally {
22746
+ isRestarting = false;
22747
+ }
22748
+ }
22749
+ function calculateBackoffDelay(attempt) {
22750
+ if (attempt === 0)
22751
+ return 0;
22752
+ const exponentialDelay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1);
22753
+ const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);
22754
+ const jitterRange = cappedDelay * (RETRY_CONFIG.jitterPercent / 100);
22755
+ const jitter = (Math.random() * 2 - 1) * jitterRange;
22756
+ return Math.round(cappedDelay + jitter);
22757
+ }
22758
+ function isRetryableError(error45) {
22759
+ if (error45 instanceof Error) {
22760
+ const message = error45.message.toLowerCase();
22761
+ if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket") || message.includes("network") || message.includes("timeout") || message.includes("aborted")) {
22762
+ return true;
22763
+ }
22764
+ if (error45 instanceof AgentMailError && error45.code) {
22765
+ return error45.code === 502 || error45.code === 503 || error45.code === 504;
22766
+ }
22767
+ if (message.includes("unexpected error")) {
22768
+ return true;
22769
+ }
22770
+ }
22771
+ return false;
22772
+ }
22616
22773
  var agentMailAvailable = null;
22617
22774
  async function checkAgentMailAvailable() {
22618
22775
  if (agentMailAvailable !== null) {
@@ -22644,36 +22801,85 @@ async function recordRateLimitedRequest(agentName, endpoint) {
22644
22801
  }
22645
22802
  await rateLimiter.recordRequest(agentName, endpoint);
22646
22803
  }
22647
- async function mcpCall(toolName, args) {
22648
- const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22649
- method: "POST",
22650
- headers: { "Content-Type": "application/json" },
22651
- body: JSON.stringify({
22652
- jsonrpc: "2.0",
22653
- id: crypto.randomUUID(),
22654
- method: "tools/call",
22655
- params: { name: toolName, arguments: args }
22656
- })
22657
- });
22658
- if (!response.ok) {
22659
- throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName);
22660
- }
22661
- const json2 = await response.json();
22662
- if (json2.error) {
22663
- throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
22804
+ async function mcpCallOnce(toolName, args) {
22805
+ const controller = new AbortController;
22806
+ const timeout = setTimeout(() => controller.abort(), RETRY_CONFIG.timeoutMs);
22807
+ try {
22808
+ const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
22809
+ method: "POST",
22810
+ headers: { "Content-Type": "application/json" },
22811
+ body: JSON.stringify({
22812
+ jsonrpc: "2.0",
22813
+ id: crypto.randomUUID(),
22814
+ method: "tools/call",
22815
+ params: { name: toolName, arguments: args }
22816
+ }),
22817
+ signal: controller.signal
22818
+ });
22819
+ clearTimeout(timeout);
22820
+ if (!response.ok) {
22821
+ throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName, response.status);
22822
+ }
22823
+ const json2 = await response.json();
22824
+ if (json2.error) {
22825
+ throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
22826
+ }
22827
+ const result = json2.result;
22828
+ if (result && typeof result === "object") {
22829
+ const wrapped = result;
22830
+ if (wrapped.isError) {
22831
+ const errorText = wrapped.content?.[0]?.text || "Unknown error";
22832
+ throw new AgentMailError(errorText, toolName);
22833
+ }
22834
+ if ("structuredContent" in wrapped) {
22835
+ return wrapped.structuredContent;
22836
+ }
22837
+ }
22838
+ return result;
22839
+ } catch (error45) {
22840
+ clearTimeout(timeout);
22841
+ throw error45;
22664
22842
  }
22665
- const result = json2.result;
22666
- if (result && typeof result === "object") {
22667
- const wrapped = result;
22668
- if (wrapped.isError) {
22669
- const errorText = wrapped.content?.[0]?.text || "Unknown error";
22670
- throw new AgentMailError(errorText, toolName);
22843
+ }
22844
+ async function mcpCall(toolName, args) {
22845
+ let lastError = null;
22846
+ for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
22847
+ if (attempt > 0) {
22848
+ const delay = calculateBackoffDelay(attempt);
22849
+ console.warn(`[agent-mail] Retry ${attempt}/${RETRY_CONFIG.maxRetries} for ${toolName} after ${delay}ms`);
22850
+ await new Promise((resolve) => setTimeout(resolve, delay));
22671
22851
  }
22672
- if ("structuredContent" in wrapped) {
22673
- return wrapped.structuredContent;
22852
+ try {
22853
+ const result = await mcpCallOnce(toolName, args);
22854
+ consecutiveFailures = 0;
22855
+ return result;
22856
+ } catch (error45) {
22857
+ lastError = error45 instanceof Error ? error45 : new Error(String(error45));
22858
+ consecutiveFailures++;
22859
+ if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
22860
+ console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
22861
+ const healthy = await isServerFunctional();
22862
+ if (!healthy) {
22863
+ console.warn("[agent-mail] Server unhealthy, attempting restart...");
22864
+ const restarted = await restartServer();
22865
+ if (restarted) {
22866
+ agentMailAvailable = null;
22867
+ attempt--;
22868
+ continue;
22869
+ }
22870
+ }
22871
+ }
22872
+ if (!isRetryableError(error45)) {
22873
+ console.warn(`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`);
22874
+ throw lastError;
22875
+ }
22876
+ if (attempt === RETRY_CONFIG.maxRetries) {
22877
+ console.error(`[agent-mail] All ${RETRY_CONFIG.maxRetries} retries exhausted for ${toolName}`);
22878
+ throw lastError;
22879
+ }
22674
22880
  }
22675
22881
  }
22676
- return result;
22882
+ throw lastError || new Error("Unknown error in mcpCall");
22677
22883
  }
22678
22884
  function requireState(sessionID) {
22679
22885
  const state = sessionStates.get(sessionID);
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.6.3",
3
+ "version": "0.10.0",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "opencode-swarm-setup": "./scripts/setup.ts"
9
+ "swarm": "./bin/swarm.ts"
10
10
  },
11
11
  "exports": {
12
12
  ".": {
@@ -25,6 +25,7 @@
25
25
  "clean": "rm -rf dist"
26
26
  },
27
27
  "dependencies": {
28
+ "@clack/prompts": "^0.11.0",
28
29
  "@opencode-ai/plugin": "^1.0.134",
29
30
  "ioredis": "^5.4.1",
30
31
  "zod": "4.1.8"