@mcoda/mswarm 0.1.107 → 0.1.109

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.
Files changed (162) hide show
  1. package/README.md +17 -0
  2. package/dist/codali-executor.d.ts.map +1 -1
  3. package/dist/codali-executor.js +17 -3
  4. package/dist/codali-executor.js.map +1 -1
  5. package/dist/runtime.d.ts +16 -5
  6. package/dist/runtime.d.ts.map +1 -1
  7. package/dist/runtime.js +292 -36
  8. package/dist/runtime.js.map +1 -1
  9. package/dist/server.d.ts.map +1 -1
  10. package/dist/server.js +4 -0
  11. package/dist/server.js.map +1 -1
  12. package/dist/vendor/codali/agents/AgentInventory.d.ts +56 -0
  13. package/dist/vendor/codali/agents/AgentInventory.d.ts.map +1 -0
  14. package/dist/vendor/codali/agents/AgentInventory.js +131 -0
  15. package/dist/vendor/codali/agents/RoleResolution.d.ts +49 -0
  16. package/dist/vendor/codali/agents/RoleResolution.d.ts.map +1 -0
  17. package/dist/vendor/codali/agents/RoleResolution.js +166 -0
  18. package/dist/vendor/codali/api/ChatCompletionsAdapter.d.ts +76 -0
  19. package/dist/vendor/codali/api/ChatCompletionsAdapter.d.ts.map +1 -0
  20. package/dist/vendor/codali/api/ChatCompletionsAdapter.js +54 -0
  21. package/dist/vendor/codali/api/CodaliApi.d.ts +78 -0
  22. package/dist/vendor/codali/api/CodaliApi.d.ts.map +1 -0
  23. package/dist/vendor/codali/api/CodaliApi.js +273 -0
  24. package/dist/vendor/codali/api/CodaliServer.d.ts +42 -0
  25. package/dist/vendor/codali/api/CodaliServer.d.ts.map +1 -0
  26. package/dist/vendor/codali/api/CodaliServer.js +118 -0
  27. package/dist/vendor/codali/cli/AskCommand.d.ts +66 -0
  28. package/dist/vendor/codali/cli/AskCommand.d.ts.map +1 -0
  29. package/dist/vendor/codali/cli/AskCommand.js +536 -0
  30. package/dist/vendor/codali/cli/AuthCommand.d.ts +19 -0
  31. package/dist/vendor/codali/cli/AuthCommand.d.ts.map +1 -0
  32. package/dist/vendor/codali/cli/AuthCommand.js +161 -0
  33. package/dist/vendor/codali/cli/ServeCommand.d.ts +5 -0
  34. package/dist/vendor/codali/cli/ServeCommand.d.ts.map +1 -0
  35. package/dist/vendor/codali/cli/ServeCommand.js +87 -0
  36. package/dist/vendor/codali/cli/ToolsCommand.d.ts +22 -0
  37. package/dist/vendor/codali/cli/ToolsCommand.d.ts.map +1 -0
  38. package/dist/vendor/codali/cli/ToolsCommand.js +208 -0
  39. package/dist/vendor/codali/cli.d.ts.map +1 -1
  40. package/dist/vendor/codali/cli.js +29 -1
  41. package/dist/vendor/codali/connectors/http/HttpToolDefinition.d.ts +74 -0
  42. package/dist/vendor/codali/connectors/http/HttpToolDefinition.d.ts.map +1 -0
  43. package/dist/vendor/codali/connectors/http/HttpToolDefinition.js +190 -0
  44. package/dist/vendor/codali/connectors/http/HttpToolSource.d.ts +18 -0
  45. package/dist/vendor/codali/connectors/http/HttpToolSource.d.ts.map +1 -0
  46. package/dist/vendor/codali/connectors/http/HttpToolSource.js +29 -0
  47. package/dist/vendor/codali/connectors/mcp/McpClient.d.ts +132 -0
  48. package/dist/vendor/codali/connectors/mcp/McpClient.d.ts.map +1 -0
  49. package/dist/vendor/codali/connectors/mcp/McpClient.js +215 -0
  50. package/dist/vendor/codali/connectors/mcp/McpServerRegistry.d.ts +75 -0
  51. package/dist/vendor/codali/connectors/mcp/McpServerRegistry.d.ts.map +1 -0
  52. package/dist/vendor/codali/connectors/mcp/McpServerRegistry.js +180 -0
  53. package/dist/vendor/codali/connectors/mcp/McpToolAdapter.d.ts +48 -0
  54. package/dist/vendor/codali/connectors/mcp/McpToolAdapter.d.ts.map +1 -0
  55. package/dist/vendor/codali/connectors/mcp/McpToolAdapter.js +101 -0
  56. package/dist/vendor/codali/connectors/mcp/McpToolSource.d.ts +27 -0
  57. package/dist/vendor/codali/connectors/mcp/McpToolSource.d.ts.map +1 -0
  58. package/dist/vendor/codali/connectors/mcp/McpToolSource.js +33 -0
  59. package/dist/vendor/codali/connectors/media/ImageGenerationTool.d.ts +43 -0
  60. package/dist/vendor/codali/connectors/media/ImageGenerationTool.d.ts.map +1 -0
  61. package/dist/vendor/codali/connectors/media/ImageGenerationTool.js +122 -0
  62. package/dist/vendor/codali/connectors/oauth/DeviceCodeAuth.d.ts +74 -0
  63. package/dist/vendor/codali/connectors/oauth/DeviceCodeAuth.d.ts.map +1 -0
  64. package/dist/vendor/codali/connectors/oauth/DeviceCodeAuth.js +153 -0
  65. package/dist/vendor/codali/docdex/DocdexClient.d.ts +2 -0
  66. package/dist/vendor/codali/docdex/DocdexClient.d.ts.map +1 -1
  67. package/dist/vendor/codali/docdex/DocdexClient.js +4 -0
  68. package/dist/vendor/codali/gateway/CodaliGateway.d.ts +51 -3
  69. package/dist/vendor/codali/gateway/CodaliGateway.d.ts.map +1 -1
  70. package/dist/vendor/codali/gateway/CodaliGateway.js +226 -2
  71. package/dist/vendor/codali/gateway/CodaliGatewayTypes.d.ts +34 -0
  72. package/dist/vendor/codali/gateway/CodaliGatewayTypes.d.ts.map +1 -1
  73. package/dist/vendor/codali/gateway/EvidenceNormalizer.d.ts.map +1 -1
  74. package/dist/vendor/codali/gateway/EvidenceNormalizer.js +90 -1
  75. package/dist/vendor/codali/gateway/Finalizer.d.ts +50 -0
  76. package/dist/vendor/codali/gateway/Finalizer.d.ts.map +1 -0
  77. package/dist/vendor/codali/gateway/Finalizer.js +79 -0
  78. package/dist/vendor/codali/gateway/GatewayPlanner.d.ts +13 -0
  79. package/dist/vendor/codali/gateway/GatewayPlanner.d.ts.map +1 -1
  80. package/dist/vendor/codali/gateway/GatewayPlanner.js +81 -16
  81. package/dist/vendor/codali/gateway/GatewayStateMachine.d.ts.map +1 -1
  82. package/dist/vendor/codali/gateway/GatewayStateMachine.js +6 -0
  83. package/dist/vendor/codali/gateway/GatewayTracer.d.ts +93 -0
  84. package/dist/vendor/codali/gateway/GatewayTracer.d.ts.map +1 -0
  85. package/dist/vendor/codali/gateway/GatewayTracer.js +214 -0
  86. package/dist/vendor/codali/gateway/LocalGatewayProvider.d.ts +46 -0
  87. package/dist/vendor/codali/gateway/LocalGatewayProvider.d.ts.map +1 -0
  88. package/dist/vendor/codali/gateway/LocalGatewayProvider.js +161 -0
  89. package/dist/vendor/codali/gateway/LocalGatewayTaskRunner.d.ts +79 -0
  90. package/dist/vendor/codali/gateway/LocalGatewayTaskRunner.d.ts.map +1 -0
  91. package/dist/vendor/codali/gateway/LocalGatewayTaskRunner.js +354 -0
  92. package/dist/vendor/codali/gateway/RelevanceJudge.d.ts +14 -0
  93. package/dist/vendor/codali/gateway/RelevanceJudge.d.ts.map +1 -0
  94. package/dist/vendor/codali/gateway/RelevanceJudge.js +87 -0
  95. package/dist/vendor/codali/gateway/ResponseSchema.d.ts +32 -0
  96. package/dist/vendor/codali/gateway/ResponseSchema.d.ts.map +1 -0
  97. package/dist/vendor/codali/gateway/ResponseSchema.js +120 -0
  98. package/dist/vendor/codali/gateway/TemporalContext.d.ts +36 -0
  99. package/dist/vendor/codali/gateway/TemporalContext.d.ts.map +1 -0
  100. package/dist/vendor/codali/gateway/TemporalContext.js +115 -0
  101. package/dist/vendor/codali/gateway/ToolCapabilityCompiler.d.ts +5 -0
  102. package/dist/vendor/codali/gateway/ToolCapabilityCompiler.d.ts.map +1 -1
  103. package/dist/vendor/codali/gateway/ToolCapabilityCompiler.js +27 -2
  104. package/dist/vendor/codali/gateway/ToolDescriptorSource.d.ts +6 -0
  105. package/dist/vendor/codali/gateway/ToolDescriptorSource.d.ts.map +1 -0
  106. package/dist/vendor/codali/gateway/ToolDescriptorSource.js +30 -0
  107. package/dist/vendor/codali/gateway/ToolExposure.d.ts +36 -0
  108. package/dist/vendor/codali/gateway/ToolExposure.d.ts.map +1 -0
  109. package/dist/vendor/codali/gateway/ToolExposure.js +154 -0
  110. package/dist/vendor/codali/index.d.ts +57 -0
  111. package/dist/vendor/codali/index.d.ts.map +1 -1
  112. package/dist/vendor/codali/index.js +45 -0
  113. package/dist/vendor/codali/providers/ClaudeCliProvider.d.ts +2 -0
  114. package/dist/vendor/codali/providers/ClaudeCliProvider.d.ts.map +1 -1
  115. package/dist/vendor/codali/providers/ClaudeCliProvider.js +2 -0
  116. package/dist/vendor/codali/providers/CodexCliProvider.d.ts +2 -0
  117. package/dist/vendor/codali/providers/CodexCliProvider.d.ts.map +1 -1
  118. package/dist/vendor/codali/providers/CodexCliProvider.js +2 -0
  119. package/dist/vendor/codali/providers/MswarmWorkerProvider.d.ts +2 -0
  120. package/dist/vendor/codali/providers/MswarmWorkerProvider.d.ts.map +1 -1
  121. package/dist/vendor/codali/providers/MswarmWorkerProvider.js +2 -0
  122. package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts +2 -0
  123. package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts.map +1 -1
  124. package/dist/vendor/codali/providers/OllamaRemoteProvider.js +2 -0
  125. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts +2 -0
  126. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
  127. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.js +2 -0
  128. package/dist/vendor/codali/providers/ProviderTypes.d.ts +13 -0
  129. package/dist/vendor/codali/providers/ProviderTypes.d.ts.map +1 -1
  130. package/dist/vendor/codali/providers/ProviderTypes.js +1 -1
  131. package/dist/vendor/codali/runcontext/CredentialFile.d.ts +12 -0
  132. package/dist/vendor/codali/runcontext/CredentialFile.d.ts.map +1 -0
  133. package/dist/vendor/codali/runcontext/CredentialFile.js +81 -0
  134. package/dist/vendor/codali/runcontext/RunContextResolver.d.ts +126 -0
  135. package/dist/vendor/codali/runcontext/RunContextResolver.d.ts.map +1 -0
  136. package/dist/vendor/codali/runcontext/RunContextResolver.js +389 -0
  137. package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.d.ts +1 -1
  138. package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.d.ts.map +1 -1
  139. package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.js +12 -0
  140. package/dist/vendor/codali/tools/ToolRegistry.d.ts +14 -1
  141. package/dist/vendor/codali/tools/ToolRegistry.d.ts.map +1 -1
  142. package/dist/vendor/codali/tools/ToolRegistry.js +38 -1
  143. package/dist/vendor/codali/tools/ToolTypes.d.ts +35 -0
  144. package/dist/vendor/codali/tools/ToolTypes.d.ts.map +1 -1
  145. package/dist/vendor/codali/tools/ToolTypes.js +21 -0
  146. package/dist/vendor/codali/tools/TruncateResult.d.ts +29 -0
  147. package/dist/vendor/codali/tools/TruncateResult.d.ts.map +1 -0
  148. package/dist/vendor/codali/tools/TruncateResult.js +91 -0
  149. package/dist/vendor/codali/tools/diff/DiffTool.d.ts.map +1 -1
  150. package/dist/vendor/codali/tools/diff/DiffTool.js +2 -0
  151. package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts +21 -1
  152. package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts.map +1 -1
  153. package/dist/vendor/codali/tools/docdex/DocdexTools.js +108 -16
  154. package/dist/vendor/codali/tools/filesystem/FileTools.d.ts.map +1 -1
  155. package/dist/vendor/codali/tools/filesystem/FileTools.js +8 -0
  156. package/dist/vendor/codali/tools/search/SearchTool.d.ts.map +1 -1
  157. package/dist/vendor/codali/tools/search/SearchTool.js +3 -1
  158. package/dist/vendor/codali/tools/shell/ShellTool.d.ts.map +1 -1
  159. package/dist/vendor/codali/tools/shell/ShellTool.js +2 -0
  160. package/package.json +3 -3
  161. package/scripts/stable-diffusion-cpp-auth-guard.py +475 -0
  162. package/scripts/stable-diffusion-cpp-openai-server.py +44 -0
package/dist/runtime.js CHANGED
@@ -62,6 +62,7 @@ const DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 30;
62
62
  const DEFAULT_SELF_HOSTED_NODE_VERSION = "0.1.70";
63
63
  const DEFAULT_REQUEST_TIMEOUT_MS = 10000;
64
64
  const DEFAULT_JOB_TIMEOUT_MS = 3600000;
65
+ const DEFAULT_RESULT_POST_TIMEOUT_MS = 5 * 60000;
65
66
  const DEFAULT_SERVICE_COMMAND_TIMEOUT_MS = 60000;
66
67
  const DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS = 2000;
67
68
  const SELF_HOSTED_RUNTIME_PROTOCOL_VERSION = 1;
@@ -74,6 +75,12 @@ const DEFAULT_MCODA_LIST_ARGS = ["agent", "list", "--json", "--refresh-health"];
74
75
  const DEFAULT_COMMAND_MAX_BUFFER = 16 * 1024 * 1024;
75
76
  const DEFAULT_LOCAL_ARTIFACT_MAX_BYTES = 512 * 1024 * 1024;
76
77
  const DEFAULT_JOB_POLL_WAIT_MS = 2000;
78
+ const RESERVED_JOB_POLL_WAIT_MS = 250;
79
+ const MAX_POLL_EXCLUDED_AGENT_SLUGS = 64;
80
+ const MAX_RELAY_AGENT_SLUG_LENGTH = 255;
81
+ const DEFAULT_RESERVED_PRIORITY_MAX = -1;
82
+ const MIN_RELAY_PRIORITY = -100;
83
+ const MAX_RELAY_PRIORITY = 100;
77
84
  const JOB_POLL_RETRY_BASE_MS = 1000;
78
85
  const JOB_POLL_RETRY_MAX_MS = 60000;
79
86
  /**
@@ -284,6 +291,33 @@ const LOCAL_RUNNER_RESPONSE_FORMAT_ALIASES = {
284
291
  function optionalText(value) {
285
292
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
286
293
  }
294
+ function relayJobAgentAliases(job) {
295
+ const aliases = [job.agent_slug, job.source_agent_slug, job.remote_slug]
296
+ .map(optionalText)
297
+ .map((value) => value
298
+ ?.toLowerCase()
299
+ .replace(/[^a-z0-9]+/g, "-")
300
+ .replace(/-+/g, "-")
301
+ .replace(/^-|-$/g, "") || null)
302
+ .filter((value) => typeof value === "string" && value.length > 0 && value.length <= MAX_RELAY_AGENT_SLUG_LENGTH);
303
+ return [...new Set(aliases)];
304
+ }
305
+ function boundedRelayAgentAliases(activeAliases) {
306
+ const aliases = [];
307
+ const seen = new Set();
308
+ for (const group of activeAliases) {
309
+ for (const alias of group) {
310
+ if (seen.has(alias))
311
+ continue;
312
+ seen.add(alias);
313
+ aliases.push(alias);
314
+ if (aliases.length >= MAX_POLL_EXCLUDED_AGENT_SLUGS) {
315
+ return aliases;
316
+ }
317
+ }
318
+ }
319
+ return aliases;
320
+ }
287
321
  function isRecord(value) {
288
322
  return typeof value === "object" && value !== null && !Array.isArray(value);
289
323
  }
@@ -588,6 +622,39 @@ function parsePositiveInteger(value, fallback) {
588
622
  }
589
623
  return Math.floor(parsed);
590
624
  }
625
+ function parseNonNegativeInteger(value, fallback) {
626
+ const parsed = value === undefined || value === null || value === ""
627
+ ? fallback
628
+ : typeof value === "number"
629
+ ? value
630
+ : typeof value === "string"
631
+ ? Number(value)
632
+ : Number.NaN;
633
+ if (!Number.isInteger(parsed) || parsed < 0) {
634
+ throw new Error("MSWARM_SELF_HOSTED_RESERVED_LLM_JOBS must be a non-negative integer");
635
+ }
636
+ return parsed;
637
+ }
638
+ function parseRelayPriority(value, fallback) {
639
+ const parsed = value === undefined || value === null || value === ""
640
+ ? fallback
641
+ : typeof value === "number"
642
+ ? value
643
+ : typeof value === "string"
644
+ ? Number(value)
645
+ : Number.NaN;
646
+ if (!Number.isInteger(parsed) || parsed < MIN_RELAY_PRIORITY || parsed > MAX_RELAY_PRIORITY) {
647
+ throw new Error(`MSWARM_SELF_HOSTED_RESERVED_PRIORITY_MAX must be an integer from ${MIN_RELAY_PRIORITY} to ${MAX_RELAY_PRIORITY}`);
648
+ }
649
+ return parsed;
650
+ }
651
+ function assertReservedLlmCapacity(input) {
652
+ const totalLlmJobs = input.maxConcurrentLlmJobs + input.reservedLlmJobs;
653
+ if (totalLlmJobs > input.maxConcurrentJobs) {
654
+ throw new Error("MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS must be greater than or equal to " +
655
+ "MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS plus MSWARM_SELF_HOSTED_RESERVED_LLM_JOBS");
656
+ }
657
+ }
591
658
  function parseBoolean(value, fallback) {
592
659
  if (typeof value === "boolean")
593
660
  return value;
@@ -1280,6 +1347,8 @@ function serviceEnvironment(config, env, homeDir) {
1280
1347
  MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS: String(config.jobTimeoutMs),
1281
1348
  MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS: String(config.maxConcurrentJobs || 1),
1282
1349
  MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS: String(config.maxConcurrentLlmJobs || config.maxConcurrentJobs || 1),
1350
+ MSWARM_SELF_HOSTED_RESERVED_LLM_JOBS: String(config.reservedLlmJobs ?? 0),
1351
+ MSWARM_SELF_HOSTED_RESERVED_PRIORITY_MAX: String(config.reservedPriorityMax ?? DEFAULT_RESERVED_PRIORITY_MAX),
1283
1352
  MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED: config.genericJobsEnabled ? "true" : "false",
1284
1353
  MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS: String(config.genericJobTimeoutMs),
1285
1354
  MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY: String(config.genericJobMaxConcurrency),
@@ -1695,6 +1764,9 @@ export async function readSelfHostedNodeConfig(env = process.env) {
1695
1764
  const packageNodeVersion = await readPackageNodeVersion();
1696
1765
  const maxConcurrentJobs = parsePositiveInteger(env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS, state.max_concurrent_jobs || 1);
1697
1766
  const maxConcurrentLlmJobs = parsePositiveInteger(env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, state.max_concurrent_llm_jobs || maxConcurrentJobs);
1767
+ const reservedLlmJobs = parseNonNegativeInteger(env.MSWARM_SELF_HOSTED_RESERVED_LLM_JOBS, state.reserved_llm_jobs ?? 0);
1768
+ const reservedPriorityMax = parseRelayPriority(env.MSWARM_SELF_HOSTED_RESERVED_PRIORITY_MAX, state.reserved_priority_max ?? DEFAULT_RESERVED_PRIORITY_MAX);
1769
+ assertReservedLlmCapacity({ maxConcurrentJobs, maxConcurrentLlmJobs, reservedLlmJobs });
1698
1770
  return {
1699
1771
  gatewayBaseUrl: trimTrailingSlash(gatewayBaseUrl),
1700
1772
  jobsPollPath: optionalText(env.MSWARM_SELF_HOSTED_JOBS_POLL_PATH) ||
@@ -1738,6 +1810,8 @@ export async function readSelfHostedNodeConfig(env = process.env) {
1738
1810
  jobTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS, state.job_timeout_ms || DEFAULT_JOB_TIMEOUT_MS),
1739
1811
  maxConcurrentJobs,
1740
1812
  maxConcurrentLlmJobs,
1813
+ reservedLlmJobs,
1814
+ reservedPriorityMax,
1741
1815
  genericJobsEnabled: parseBoolean(env.MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED ?? env.MSWARM_SELF_HOSTED_GENERIC_JOBS, state.generic_jobs_enabled === true),
1742
1816
  genericJobTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS, state.generic_job_timeout_ms || state.job_timeout_ms || DEFAULT_JOB_TIMEOUT_MS),
1743
1817
  genericJobMaxConcurrency: parsePositiveInteger(env.MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY, state.generic_job_max_concurrency || 1),
@@ -1787,6 +1861,9 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
1787
1861
  const packageNodeVersion = await readPackageNodeVersion();
1788
1862
  const maxConcurrentJobs = parsePositiveInteger(options["max-concurrent-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS, 1);
1789
1863
  const maxConcurrentLlmJobs = parsePositiveInteger(options["max-concurrent-llm-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, maxConcurrentJobs);
1864
+ const reservedLlmJobs = parseNonNegativeInteger(options["reserved-llm-jobs"] ?? env.MSWARM_SELF_HOSTED_RESERVED_LLM_JOBS, 0);
1865
+ const reservedPriorityMax = parseRelayPriority(options["reserved-priority-max"] ?? env.MSWARM_SELF_HOSTED_RESERVED_PRIORITY_MAX, DEFAULT_RESERVED_PRIORITY_MAX);
1866
+ assertReservedLlmCapacity({ maxConcurrentJobs, maxConcurrentLlmJobs, reservedLlmJobs });
1790
1867
  return {
1791
1868
  apiKey,
1792
1869
  handle: handle || undefined,
@@ -1812,6 +1889,8 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
1812
1889
  jobTimeoutMs: parsePositiveInteger(options["job-timeout-ms"] || env.MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS, DEFAULT_JOB_TIMEOUT_MS),
1813
1890
  maxConcurrentJobs,
1814
1891
  maxConcurrentLlmJobs,
1892
+ reservedLlmJobs,
1893
+ reservedPriorityMax,
1815
1894
  genericJobsEnabled: parseBoolean(options["enable-generic-jobs"] || env.MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED || env.MSWARM_SELF_HOSTED_GENERIC_JOBS, false),
1816
1895
  genericJobTimeoutMs: parsePositiveInteger(options["generic-job-timeout-ms"] || env.MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS, DEFAULT_JOB_TIMEOUT_MS),
1817
1896
  genericJobMaxConcurrency: parsePositiveInteger(options["generic-job-max-concurrency"] || env.MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY, 1),
@@ -2203,6 +2282,41 @@ function mcodaAgentDefaultModel(agent) {
2203
2282
  optionalText(agent.models?.find((model) => model.isDefault === true || model.is_default === true)?.model_name) ||
2204
2283
  null);
2205
2284
  }
2285
+ /**
2286
+ * The agent's base URL, but only when it names a runner on this machine.
2287
+ *
2288
+ * The catalogue publishes a mirror of each self-hosted agent back to the node,
2289
+ * and that mirror's base URL is the gateway. Serving a job from it sends the
2290
+ * request back out instead of to the model running here, so a loopback address
2291
+ * is what distinguishes the real runner from its reflection.
2292
+ */
2293
+ function mcodaAgentLocalRunnerBaseUrl(agent) {
2294
+ const config = isRecord(agent.config) ? agent.config : {};
2295
+ const localRunner = isRecord(config.localRunner) ? config.localRunner : {};
2296
+ const baseUrl = readConfigString({ ...localRunner, ...config }, [
2297
+ "baseUrl",
2298
+ "base_url",
2299
+ "endpoint",
2300
+ "apiBaseUrl",
2301
+ "api_base_url"
2302
+ ]);
2303
+ if (!baseUrl) {
2304
+ return null;
2305
+ }
2306
+ let host;
2307
+ try {
2308
+ host = new URL(baseUrl).hostname.toLowerCase();
2309
+ }
2310
+ catch {
2311
+ return null;
2312
+ }
2313
+ const isLoopback = host === "localhost" ||
2314
+ host === "::1" ||
2315
+ host === "0.0.0.0" ||
2316
+ host.endsWith(".localhost") ||
2317
+ /^127\./.test(host);
2318
+ return isLoopback ? baseUrl : null;
2319
+ }
2206
2320
  function resolveCodaliProviderForAgent(agent) {
2207
2321
  const adapter = optionalText(agent.adapter);
2208
2322
  if (isLocalOpenAiCompatibleAdapter(adapter)) {
@@ -4100,6 +4214,7 @@ export class MswarmSelfHostedNodeClient {
4100
4214
  this.jobsResultPathTemplate = lifecyclePath(input.jobsResultPathTemplate, DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE);
4101
4215
  this.fetchImpl = input.fetchImpl || fetch;
4102
4216
  this.timeoutMs = input.timeoutMs || DEFAULT_REQUEST_TIMEOUT_MS;
4217
+ this.resultTimeoutMs = Math.max(this.timeoutMs, input.resultTimeoutMs || DEFAULT_RESULT_POST_TIMEOUT_MS);
4103
4218
  }
4104
4219
  lifecycleEndpoint(kind) {
4105
4220
  const path = kind === "poll"
@@ -4214,7 +4329,7 @@ export class MswarmSelfHostedNodeClient {
4214
4329
  authorization: `Bearer ${runtimeToken}`
4215
4330
  },
4216
4331
  body: JSON.stringify(payload)
4217
- }, this.timeoutMs);
4332
+ }, this.resultTimeoutMs);
4218
4333
  }
4219
4334
  async postJobStart(runtimeToken, jobId, payload) {
4220
4335
  return fetchJson(this.fetchImpl, this.lifecycleUrl("start", jobId), {
@@ -4246,6 +4361,12 @@ export class SelfHostedNodeRuntime {
4246
4361
  this.latencySamplesMs = [];
4247
4362
  this.recentFailures = [];
4248
4363
  this.lifecyclePollingDisabled = false;
4364
+ assertReservedLlmCapacity({
4365
+ maxConcurrentJobs: Math.max(1, Math.floor(config.maxConcurrentJobs || 1)),
4366
+ maxConcurrentLlmJobs: Math.max(1, Math.floor(config.maxConcurrentLlmJobs || config.maxConcurrentJobs || 1)),
4367
+ reservedLlmJobs: parseNonNegativeInteger(config.reservedLlmJobs, 0)
4368
+ });
4369
+ parseRelayPriority(config.reservedPriorityMax, DEFAULT_RESERVED_PRIORITY_MAX);
4249
4370
  this.config = config;
4250
4371
  this.fetchImpl = deps?.fetchImpl || fetch;
4251
4372
  this.useDedicatedGenerativeDispatcher = deps?.fetchImpl === undefined;
@@ -4258,7 +4379,8 @@ export class SelfHostedNodeRuntime {
4258
4379
  jobsEventsPathTemplate: config.jobsEventsPathTemplate,
4259
4380
  jobsResultPathTemplate: config.jobsResultPathTemplate,
4260
4381
  fetchImpl: this.fetchImpl,
4261
- timeoutMs: config.requestTimeoutMs
4382
+ timeoutMs: config.requestTimeoutMs,
4383
+ resultTimeoutMs: Math.min(config.jobTimeoutMs, DEFAULT_RESULT_POST_TIMEOUT_MS)
4262
4384
  });
4263
4385
  this.mcoda =
4264
4386
  deps?.mcoda ||
@@ -4448,7 +4570,8 @@ export class SelfHostedNodeRuntime {
4448
4570
  new MswarmSelfHostedNodeClient({
4449
4571
  gatewayBaseUrl: setupConfig.gatewayBaseUrl,
4450
4572
  fetchImpl: deps?.fetchImpl,
4451
- timeoutMs: setupConfig.requestTimeoutMs
4573
+ timeoutMs: setupConfig.requestTimeoutMs,
4574
+ resultTimeoutMs: Math.min(setupConfig.jobTimeoutMs, DEFAULT_RESULT_POST_TIMEOUT_MS)
4452
4575
  });
4453
4576
  const machineId = await readOrCreateSelfHostedMachineId(setupConfig.machineIdPath);
4454
4577
  const machineFingerprint = machineFingerprintFromId(machineId);
@@ -4517,6 +4640,8 @@ export class SelfHostedNodeRuntime {
4517
4640
  job_timeout_ms: setupConfig.jobTimeoutMs,
4518
4641
  max_concurrent_jobs: setupConfig.maxConcurrentJobs,
4519
4642
  max_concurrent_llm_jobs: setupConfig.maxConcurrentLlmJobs,
4643
+ reserved_llm_jobs: setupConfig.reservedLlmJobs,
4644
+ reserved_priority_max: setupConfig.reservedPriorityMax,
4520
4645
  generic_jobs_enabled: setupConfig.genericJobsEnabled,
4521
4646
  generic_job_timeout_ms: setupConfig.genericJobTimeoutMs,
4522
4647
  generic_job_max_concurrency: setupConfig.genericJobMaxConcurrency,
@@ -4541,7 +4666,8 @@ export class SelfHostedNodeRuntime {
4541
4666
  jobsEventsPathTemplate,
4542
4667
  jobsResultPathTemplate,
4543
4668
  fetchImpl: deps?.fetchImpl,
4544
- timeoutMs: setupConfig.requestTimeoutMs
4669
+ timeoutMs: setupConfig.requestTimeoutMs,
4670
+ resultTimeoutMs: Math.min(setupConfig.jobTimeoutMs, DEFAULT_RESULT_POST_TIMEOUT_MS)
4545
4671
  });
4546
4672
  const runtime = new SelfHostedNodeRuntime({
4547
4673
  gatewayBaseUrl: trimTrailingSlash(relayGatewayBaseUrl),
@@ -4572,6 +4698,8 @@ export class SelfHostedNodeRuntime {
4572
4698
  jobTimeoutMs: setupConfig.jobTimeoutMs,
4573
4699
  maxConcurrentJobs: setupConfig.maxConcurrentJobs,
4574
4700
  maxConcurrentLlmJobs: setupConfig.maxConcurrentLlmJobs,
4701
+ reservedLlmJobs: setupConfig.reservedLlmJobs,
4702
+ reservedPriorityMax: setupConfig.reservedPriorityMax,
4575
4703
  genericJobsEnabled: setupConfig.genericJobsEnabled,
4576
4704
  genericJobTimeoutMs: setupConfig.genericJobTimeoutMs,
4577
4705
  genericJobMaxConcurrency: setupConfig.genericJobMaxConcurrency,
@@ -4702,6 +4830,8 @@ export class SelfHostedNodeRuntime {
4702
4830
  job_timeout_ms: this.config.jobTimeoutMs,
4703
4831
  max_concurrent_jobs: this.config.maxConcurrentJobs,
4704
4832
  max_concurrent_llm_jobs: this.config.maxConcurrentLlmJobs,
4833
+ reserved_llm_jobs: this.config.reservedLlmJobs ?? 0,
4834
+ reserved_priority_max: this.config.reservedPriorityMax ?? DEFAULT_RESERVED_PRIORITY_MAX,
4705
4835
  generic_jobs_enabled: this.config.genericJobsEnabled,
4706
4836
  generic_job_timeout_ms: this.config.genericJobTimeoutMs,
4707
4837
  generic_job_max_concurrency: this.config.genericJobMaxConcurrency,
@@ -4730,7 +4860,16 @@ export class SelfHostedNodeRuntime {
4730
4860
  async executeOpenAiToolPassthrough(job, agent) {
4731
4861
  const baseUrl = optionalText(agent.baseUrl);
4732
4862
  if (!baseUrl) {
4733
- throw new SelfHostedPreStartJobError("selected_agent_unavailable", "agent has no local runner base URL for OpenAI tool pass-through");
4863
+ /*
4864
+ * Name the agent. "agent has no local runner base URL" alone cannot be
4865
+ * acted on: a node holds several entries per model - the local runner and
4866
+ * the gateway's mirror of it - and this says nothing about which one was
4867
+ * resolved or whether it carried a config at all.
4868
+ */
4869
+ throw new SelfHostedPreStartJobError("selected_agent_unavailable", `agent has no local runner base URL for OpenAI tool pass-through ` +
4870
+ `(slug=${optionalText(agent.slug) || "?"} adapter=${optionalText(agent.adapter) || "?"} ` +
4871
+ `runnerKind=${optionalText(agent.runnerKind) || "?"} ` +
4872
+ `localRunner=${agent.localRunner ? "yes" : "no"} model=${optionalText(agent.model) || "?"})`);
4734
4873
  }
4735
4874
  const headers = { "content-type": "application/json" };
4736
4875
  const bearer = agent.authMode === "bearer"
@@ -5123,7 +5262,7 @@ export class SelfHostedNodeRuntime {
5123
5262
  }
5124
5263
  const rawAgents = await this.mcoda.listRawAgents();
5125
5264
  const strictSelectedAgent = selectedSourceAgentSlug || selectedAgentSlug;
5126
- const agent = rawAgents.find((entry) => {
5265
+ const matches = rawAgents.filter((entry) => {
5127
5266
  const slug = optionalText(entry.slug);
5128
5267
  if (strictSelectedAgent) {
5129
5268
  return slug === strictSelectedAgent;
@@ -5131,6 +5270,15 @@ export class SelfHostedNodeRuntime {
5131
5270
  const defaultModel = mcodaAgentDefaultModel(entry);
5132
5271
  return slug === selected || defaultModel === selected;
5133
5272
  });
5273
+ /*
5274
+ * A node carries two entries for the same model: the local runner, and the
5275
+ * gateway mirror that the catalogue publishes back to it. The mirror's base
5276
+ * URL is the gateway itself, so serving a job from it sends the request back
5277
+ * out to the gateway instead of to the model sitting on this machine - which
5278
+ * is why OpenAI tool pass-through reported having no local runner to call.
5279
+ * Take the entry that actually points at a runner here.
5280
+ */
5281
+ const agent = matches.find((entry) => Boolean(mcodaAgentLocalRunnerBaseUrl(entry))) ?? matches[0];
5134
5282
  if (!agent) {
5135
5283
  throw new SelfHostedPreStartJobError("selected_agent_unavailable", `selected local mcoda agent ${selected} is not available on this node`);
5136
5284
  }
@@ -5853,11 +6001,11 @@ export class SelfHostedNodeRuntime {
5853
6001
  });
5854
6002
  return { count: models.filter((model) => model.exposed !== false).length, response };
5855
6003
  }
5856
- async pollAndExecuteJob(waitMs = DEFAULT_JOB_POLL_WAIT_MS) {
6004
+ async pollRelayJob(waitMs = DEFAULT_JOB_POLL_WAIT_MS, options = {}) {
5857
6005
  const lifecycleState = await this.readLifecycleState();
5858
6006
  if (this.lifecyclePollingDisabled || this.isLifecycleProtocolDegradedState(lifecycleState)) {
5859
6007
  this.lifecyclePollingDisabled = true;
5860
- return { executed: false, status: "failed" };
6008
+ return { claim: null, status: "failed" };
5861
6009
  }
5862
6010
  const enrollment = await this.ensureEnrolled();
5863
6011
  const pollCapacity = this.buildLoadTelemetry({ models: [] });
@@ -5871,7 +6019,17 @@ export class SelfHostedNodeRuntime {
5871
6019
  max_jobs: pollCapacity.max_concurrency,
5872
6020
  max_concurrency: pollCapacity.max_concurrency,
5873
6021
  free_slots: pollCapacity.free_slots,
5874
- drain_mode: pollCapacity.drain_mode
6022
+ drain_mode: pollCapacity.drain_mode,
6023
+ ...(options.excludedAgentSlugs !== undefined
6024
+ ? {
6025
+ excluded_agent_slugs: boundedRelayAgentAliases([
6026
+ options.excludedAgentSlugs
6027
+ ])
6028
+ }
6029
+ : {}),
6030
+ ...(options.maxRequestedPriority !== undefined
6031
+ ? { max_requested_priority: options.maxRequestedPriority }
6032
+ : {})
5875
6033
  },
5876
6034
  wait_ms: waitMs
5877
6035
  });
@@ -5880,14 +6038,24 @@ export class SelfHostedNodeRuntime {
5880
6038
  const mismatch = this.lifecycleProtocolMismatch("poll", error);
5881
6039
  if (mismatch) {
5882
6040
  await this.markLifecycleProtocolDegraded(mismatch);
5883
- return { executed: false, status: "failed" };
6041
+ return { claim: null, status: "failed" };
5884
6042
  }
5885
6043
  throw error;
5886
6044
  }
5887
6045
  const job = response.job || null;
5888
6046
  if (!job) {
5889
- return { executed: false };
6047
+ return { claim: null };
5890
6048
  }
6049
+ return {
6050
+ claim: {
6051
+ runtimeToken: enrollment.runtimeToken,
6052
+ job,
6053
+ attachedMswarmApiKey: optionalText(response.attached_mswarm_api_key) || undefined
6054
+ }
6055
+ };
6056
+ }
6057
+ async executeRelayJobClaim(claim) {
6058
+ const { job, runtimeToken } = claim;
5891
6059
  const chatStreaming = (normalizeSelfHostedGenerativeOperationName(job.operation) ?? "chat.completions") ===
5892
6060
  "chat.completions" &&
5893
6061
  job.openai_request.stream === true;
@@ -5900,7 +6068,7 @@ export class SelfHostedNodeRuntime {
5900
6068
  }
5901
6069
  const stream_events = pendingStreamEvents.splice(0, pendingStreamEvents.length);
5902
6070
  try {
5903
- await this.gateway.postJobEvents(enrollment.runtimeToken, job.job_id, {
6071
+ await this.gateway.postJobEvents(runtimeToken, job.job_id, {
5904
6072
  node_id: this.config.nodeId,
5905
6073
  stream_events
5906
6074
  });
@@ -5917,10 +6085,10 @@ export class SelfHostedNodeRuntime {
5917
6085
  }
5918
6086
  };
5919
6087
  let result = await this.executeJob(job, {
5920
- attachedMswarmApiKey: optionalText(response.attached_mswarm_api_key) || undefined,
6088
+ attachedMswarmApiKey: claim.attachedMswarmApiKey,
5921
6089
  onStarted: async (event) => {
5922
6090
  try {
5923
- await this.gateway.postJobStart(enrollment.runtimeToken, job.job_id, {
6091
+ await this.gateway.postJobStart(runtimeToken, job.job_id, {
5924
6092
  node_id: this.config.nodeId,
5925
6093
  agent_slug: event.agent_slug || job.agent_slug,
5926
6094
  source_agent_slug: event.source_agent_slug || job.source_agent_slug || null,
@@ -5975,7 +6143,7 @@ export class SelfHostedNodeRuntime {
5975
6143
  let resultPostError = null;
5976
6144
  for (let attempt = 0; attempt <= RELAY_RESULT_POST_RETRY_DELAYS_MS.length; attempt += 1) {
5977
6145
  try {
5978
- await this.gateway.postJobResult(enrollment.runtimeToken, job.job_id, this.jobResultPayload(postedResult));
6146
+ await this.gateway.postJobResult(runtimeToken, job.job_id, this.jobResultPayload(postedResult));
5979
6147
  resultPostError = null;
5980
6148
  break;
5981
6149
  }
@@ -6003,6 +6171,13 @@ export class SelfHostedNodeRuntime {
6003
6171
  }
6004
6172
  return { executed: true, job_id: job.job_id, status: result.status };
6005
6173
  }
6174
+ async pollAndExecuteJob(waitMs = DEFAULT_JOB_POLL_WAIT_MS) {
6175
+ const polled = await this.pollRelayJob(waitMs);
6176
+ if (!polled.claim) {
6177
+ return { executed: false, ...(polled.status ? { status: polled.status } : {}) };
6178
+ }
6179
+ return this.executeRelayJobClaim(polled.claim);
6180
+ }
6006
6181
  async doctor() {
6007
6182
  const checks = [];
6008
6183
  checks.push({ name: "config", ok: Boolean(this.config.nodeId), message: this.config.nodeId || "missing node id" });
@@ -6041,19 +6216,33 @@ export class SelfHostedNodeRuntime {
6041
6216
  let stopped = false;
6042
6217
  let timer = null;
6043
6218
  let pollTimer = null;
6219
+ let pollTimerDueAt = 0;
6044
6220
  let polling = false;
6045
- let pollFailureStreak = 0;
6221
+ let relayPollFailureStreak = 0;
6222
+ let relayExecutionFailureStreak = 0;
6223
+ let activeRelayExecutions = 0;
6224
+ let activeRegularRelayExecutions = 0;
6225
+ let relayExecutionSequence = 0;
6226
+ const activeRelayAgentAliases = new Map();
6046
6227
  let revoked = false;
6228
+ const regularRelayExecutionLimit = Math.max(1, Math.floor(this.config.maxConcurrentLlmJobs || this.config.maxConcurrentJobs || 1));
6229
+ const reservedRelayExecutionLimit = parseNonNegativeInteger(this.config.reservedLlmJobs, 0);
6230
+ const reservedPriorityMax = parseRelayPriority(this.config.reservedPriorityMax, DEFAULT_RESERVED_PRIORITY_MAX);
6231
+ const relayExecutionLimit = regularRelayExecutionLimit + reservedRelayExecutionLimit;
6232
+ const clearPollTimer = () => {
6233
+ if (pollTimer) {
6234
+ clearTimeout(pollTimer);
6235
+ pollTimer = null;
6236
+ }
6237
+ pollTimerDueAt = 0;
6238
+ };
6047
6239
  const halt = () => {
6048
6240
  stopped = true;
6049
6241
  if (timer) {
6050
6242
  clearTimeout(timer);
6051
6243
  timer = null;
6052
6244
  }
6053
- if (pollTimer) {
6054
- clearTimeout(pollTimer);
6055
- pollTimer = null;
6056
- }
6245
+ clearPollTimer();
6057
6246
  };
6058
6247
  // 410 means the gateway has retired this node's credentials: it was removed there.
6059
6248
  // Retrying at full rate achieves nothing but a hot loop and a flooded journal, so
@@ -6067,10 +6256,7 @@ export class SelfHostedNodeRuntime {
6067
6256
  if (!isHttpStatusError(error, 410)) {
6068
6257
  return false;
6069
6258
  }
6070
- if (pollTimer) {
6071
- clearTimeout(pollTimer);
6072
- pollTimer = null;
6073
- }
6259
+ clearPollTimer();
6074
6260
  if (!revoked) {
6075
6261
  revoked = true;
6076
6262
  console.error(`[mswarm] this node was removed at the gateway (node ${this.config.nodeId}). ` +
@@ -6081,42 +6267,112 @@ export class SelfHostedNodeRuntime {
6081
6267
  return true;
6082
6268
  };
6083
6269
  const noteAlive = () => {
6084
- if (!revoked) {
6270
+ const recoveredFromRevocation = revoked;
6271
+ const relayPollingIsBackedOff = (relayPollFailureStreak > 0 || relayExecutionFailureStreak > 0) && !polling;
6272
+ if (!recoveredFromRevocation && !relayPollingIsBackedOff) {
6085
6273
  return;
6086
6274
  }
6087
- revoked = false;
6088
- console.error(`[mswarm] node ${this.config.nodeId} is accepted at the gateway again; resuming normal operation.`);
6089
- poll();
6275
+ if (recoveredFromRevocation) {
6276
+ revoked = false;
6277
+ console.error(`[mswarm] node ${this.config.nodeId} is accepted at the gateway again; resuming normal operation.`);
6278
+ }
6279
+ clearPollTimer();
6280
+ relayPollFailureStreak = 0;
6281
+ relayExecutionFailureStreak = 0;
6282
+ schedulePoll(0);
6283
+ };
6284
+ const currentRelayRetryDelayMs = () => Math.max(pollRetryDelayMs(relayPollFailureStreak), pollRetryDelayMs(relayExecutionFailureStreak));
6285
+ const schedulePoll = (delayMs) => {
6286
+ if (stopped ||
6287
+ polling ||
6288
+ revoked ||
6289
+ this.lifecyclePollingDisabled ||
6290
+ this.config.relayMode === "direct" ||
6291
+ activeRelayExecutions >= relayExecutionLimit) {
6292
+ return;
6293
+ }
6294
+ const dueAt = Date.now() + Math.max(0, delayMs);
6295
+ if (pollTimer && pollTimerDueAt >= dueAt) {
6296
+ return;
6297
+ }
6298
+ clearPollTimer();
6299
+ pollTimerDueAt = dueAt;
6300
+ pollTimer = setTimeout(() => {
6301
+ pollTimer = null;
6302
+ pollTimerDueAt = 0;
6303
+ poll();
6304
+ }, Math.max(0, dueAt - Date.now()));
6090
6305
  };
6091
6306
  const poll = () => {
6092
6307
  if (stopped ||
6093
6308
  polling ||
6094
6309
  revoked ||
6095
6310
  this.lifecyclePollingDisabled ||
6096
- this.config.relayMode === "direct") {
6311
+ this.config.relayMode === "direct" ||
6312
+ activeRelayExecutions >= relayExecutionLimit) {
6097
6313
  return;
6098
6314
  }
6099
6315
  polling = true;
6100
- void this.pollAndExecuteJob()
6101
- .then(() => {
6102
- pollFailureStreak = 0;
6316
+ const reservedOnly = reservedRelayExecutionLimit > 0 &&
6317
+ activeRegularRelayExecutions >= regularRelayExecutionLimit &&
6318
+ activeRelayExecutions < relayExecutionLimit;
6319
+ const excludedAgentSlugs = boundedRelayAgentAliases(activeRelayAgentAliases.values());
6320
+ void this.pollRelayJob(reservedOnly ? RESERVED_JOB_POLL_WAIT_MS : DEFAULT_JOB_POLL_WAIT_MS, {
6321
+ ...(reservedRelayExecutionLimit > 0 ? { excludedAgentSlugs } : {}),
6322
+ ...(reservedOnly ? { maxRequestedPriority: reservedPriorityMax } : {})
6323
+ })
6324
+ .then((polled) => {
6325
+ relayPollFailureStreak = 0;
6326
+ const claim = polled.claim;
6327
+ if (!claim) {
6328
+ return;
6329
+ }
6330
+ const regularLane = !reservedOnly;
6331
+ const executionId = ++relayExecutionSequence;
6332
+ activeRelayExecutions += 1;
6333
+ if (regularLane) {
6334
+ activeRegularRelayExecutions += 1;
6335
+ }
6336
+ activeRelayAgentAliases.set(executionId, relayJobAgentAliases(claim.job));
6337
+ void this.executeRelayJobClaim(claim)
6338
+ .then(() => {
6339
+ relayExecutionFailureStreak = 0;
6340
+ })
6341
+ .catch((error) => {
6342
+ if (noteRevoked(error)) {
6343
+ return;
6344
+ }
6345
+ relayExecutionFailureStreak += 1;
6346
+ console.error(`[mswarm] self-hosted relay job ${claim.job.job_id} failed: ${runtimeErrorMessage(error)}`);
6347
+ })
6348
+ .finally(() => {
6349
+ activeRelayExecutions = Math.max(0, activeRelayExecutions - 1);
6350
+ if (regularLane) {
6351
+ activeRegularRelayExecutions = Math.max(0, activeRegularRelayExecutions - 1);
6352
+ }
6353
+ activeRelayAgentAliases.delete(executionId);
6354
+ schedulePoll(currentRelayRetryDelayMs());
6355
+ });
6103
6356
  })
6104
6357
  .catch((error) => {
6105
6358
  if (noteRevoked(error)) {
6106
6359
  return;
6107
6360
  }
6108
- pollFailureStreak += 1;
6361
+ relayPollFailureStreak += 1;
6109
6362
  console.error(`[mswarm] self-hosted relay poll failed: ${runtimeErrorMessage(error)}`);
6110
6363
  })
6111
6364
  .finally(() => {
6112
6365
  polling = false;
6113
- if (stopped || revoked) {
6366
+ if (stopped ||
6367
+ revoked ||
6368
+ this.lifecyclePollingDisabled ||
6369
+ activeRelayExecutions >= relayExecutionLimit) {
6114
6370
  return;
6115
6371
  }
6116
6372
  // A healthy poll long-polls at the gateway, so re-entering immediately is
6117
6373
  // correct. A failing one returns at once, so without this the loop spins as
6118
6374
  // fast as the network allows.
6119
- pollTimer = setTimeout(poll, pollRetryDelayMs(pollFailureStreak));
6375
+ schedulePoll(currentRelayRetryDelayMs());
6120
6376
  });
6121
6377
  };
6122
6378
  const schedule = () => {
@@ -6138,7 +6394,7 @@ export class SelfHostedNodeRuntime {
6138
6394
  })
6139
6395
  .finally(() => {
6140
6396
  schedule();
6141
- poll();
6397
+ schedulePoll(0);
6142
6398
  });
6143
6399
  return { stop: halt };
6144
6400
  }