@mcoda/mswarm 0.1.81 → 0.1.86
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/codali-executor.d.ts.map +1 -1
- package/dist/codali-executor.js +17 -16
- package/dist/codali-executor.js.map +1 -1
- package/dist/runtime.d.ts +16 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +186 -13
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +89 -13
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/runtime.js
CHANGED
|
@@ -3,8 +3,10 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { cpus, freemem, hostname, homedir, loadavg, platform, totalmem, userInfo } from "node:os";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
6
|
+
import { isIP } from "node:net";
|
|
7
|
+
import { GlobalRepository } from "@mcoda/db";
|
|
6
8
|
import { MswarmCodaliExecutor } from "./codali-executor.js";
|
|
7
|
-
import { MSWARM_CAPABILITY_SCHEMA_VERSION, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
9
|
+
import { MSWARM_CAPABILITY_SCHEMA_VERSION, CryptoHelper, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
8
10
|
const DEFAULT_GATEWAY_BASE_URL = "http://127.0.0.1:8080";
|
|
9
11
|
const DEFAULT_SETUP_GATEWAY_BASE_URL = "https://api.mswarm.org";
|
|
10
12
|
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
@@ -25,7 +27,7 @@ const DEFAULT_MCODA_BIN = "mcoda";
|
|
|
25
27
|
const DEFAULT_MCODA_LIST_ARGS = ["agent", "list", "--json", "--refresh-health"];
|
|
26
28
|
const DEFAULT_COMMAND_MAX_BUFFER = 16 * 1024 * 1024;
|
|
27
29
|
const DEFAULT_LOCAL_ARTIFACT_MAX_BYTES = 512 * 1024 * 1024;
|
|
28
|
-
const DEFAULT_JOB_POLL_WAIT_MS =
|
|
30
|
+
const DEFAULT_JOB_POLL_WAIT_MS = 2000;
|
|
29
31
|
const DEFAULT_SELF_HOSTED_JOBS_POLL_PATH = "/v1/swarm/self-hosted/node/jobs/poll";
|
|
30
32
|
const DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE = "/v1/swarm/self-hosted/node/jobs/:jobId/start";
|
|
31
33
|
const DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE = "/v1/swarm/self-hosted/node/jobs/:jobId/events";
|
|
@@ -354,6 +356,90 @@ function parseList(value) {
|
|
|
354
356
|
.map((entry) => entry.trim())
|
|
355
357
|
.filter(Boolean);
|
|
356
358
|
}
|
|
359
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
360
|
+
const DOMAIN_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
361
|
+
function normalizeSelfHostedDomainClient(value) {
|
|
362
|
+
const normalized = value.trim().toLowerCase().replace(/\.+$/g, "");
|
|
363
|
+
if (!normalized || normalized.length > 253 || normalized.includes("..")) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const labels = normalized.split(".");
|
|
367
|
+
if (!labels.every((label) => DOMAIN_LABEL_PATTERN.test(label))) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return normalized;
|
|
371
|
+
}
|
|
372
|
+
export function normalizeSelfHostedNodeClientIdentity(value) {
|
|
373
|
+
if (isRecord(value)) {
|
|
374
|
+
const kind = optionalText(value.kind)?.toLowerCase();
|
|
375
|
+
const rawValue = optionalText(value.value);
|
|
376
|
+
const normalized = normalizeSelfHostedNodeClientIdentity(rawValue);
|
|
377
|
+
if (!normalized) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (kind && kind !== normalized.kind) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const addedAt = optionalText(value.added_at);
|
|
384
|
+
return addedAt ? { ...normalized, added_at: addedAt } : normalized;
|
|
385
|
+
}
|
|
386
|
+
const raw = optionalText(value);
|
|
387
|
+
if (!raw) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
const trimmed = raw.trim();
|
|
391
|
+
if (isIP(trimmed) !== 0) {
|
|
392
|
+
return { kind: "ip", value: trimmed.toLowerCase() };
|
|
393
|
+
}
|
|
394
|
+
if (UUID_PATTERN.test(trimmed)) {
|
|
395
|
+
return { kind: "uuid", value: trimmed.toLowerCase() };
|
|
396
|
+
}
|
|
397
|
+
const domain = normalizeSelfHostedDomainClient(trimmed);
|
|
398
|
+
if (domain) {
|
|
399
|
+
return { kind: "domain", value: domain };
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
export function normalizeSelfHostedNodeClientAllowlist(value) {
|
|
404
|
+
const entries = Array.isArray(value)
|
|
405
|
+
? value
|
|
406
|
+
: typeof value === "string"
|
|
407
|
+
? parseList(value)
|
|
408
|
+
: value == null || value === false
|
|
409
|
+
? []
|
|
410
|
+
: [value];
|
|
411
|
+
const byKey = new Map();
|
|
412
|
+
for (const entry of entries) {
|
|
413
|
+
const identity = normalizeSelfHostedNodeClientIdentity(entry);
|
|
414
|
+
if (!identity) {
|
|
415
|
+
throw new Error(typeof entry === "string"
|
|
416
|
+
? `Invalid mswarm node client identifier: ${entry}`
|
|
417
|
+
: "Invalid mswarm node client identifier");
|
|
418
|
+
}
|
|
419
|
+
byKey.set(`${identity.kind}:${identity.value}`, identity);
|
|
420
|
+
}
|
|
421
|
+
return [...byKey.values()];
|
|
422
|
+
}
|
|
423
|
+
export function addSelfHostedNodeClients(current, additions, now = new Date().toISOString()) {
|
|
424
|
+
const byKey = new Map();
|
|
425
|
+
for (const entry of current) {
|
|
426
|
+
const normalized = normalizeSelfHostedNodeClientIdentity(entry);
|
|
427
|
+
if (normalized) {
|
|
428
|
+
byKey.set(`${normalized.kind}:${normalized.value}`, normalized);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
for (const entry of additions) {
|
|
432
|
+
byKey.set(`${entry.kind}:${entry.value}`, { ...entry, added_at: entry.added_at || now });
|
|
433
|
+
}
|
|
434
|
+
return [...byKey.values()];
|
|
435
|
+
}
|
|
436
|
+
export function removeSelfHostedNodeClients(current, removals) {
|
|
437
|
+
const removeKeys = new Set(removals.map((entry) => `${entry.kind}:${entry.value}`));
|
|
438
|
+
return current
|
|
439
|
+
.map((entry) => normalizeSelfHostedNodeClientIdentity(entry))
|
|
440
|
+
.filter((entry) => Boolean(entry))
|
|
441
|
+
.filter((entry) => !removeKeys.has(`${entry.kind}:${entry.value}`));
|
|
442
|
+
}
|
|
357
443
|
function parseArgs(value, fallback) {
|
|
358
444
|
const parsed = parseList(value);
|
|
359
445
|
return parsed.length > 0 ? parsed : fallback;
|
|
@@ -679,6 +765,13 @@ class HttpStatusError extends Error {
|
|
|
679
765
|
function isHttpStatusError(error, status) {
|
|
680
766
|
return error instanceof HttpStatusError && (status === undefined || error.status === status);
|
|
681
767
|
}
|
|
768
|
+
const RELAY_RESULT_POST_RETRY_DELAYS_MS = [250, 1000, 2500];
|
|
769
|
+
function runtimeErrorMessage(error) {
|
|
770
|
+
return error instanceof Error ? error.message : String(error);
|
|
771
|
+
}
|
|
772
|
+
async function sleep(ms) {
|
|
773
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
774
|
+
}
|
|
682
775
|
async function fetchJson(fetchImpl, url, init, timeoutMs) {
|
|
683
776
|
const controller = new AbortController();
|
|
684
777
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -1266,7 +1359,10 @@ export async function readSelfHostedNodeConfig(env = process.env) {
|
|
|
1266
1359
|
hardwareTelemetryEnabled: parseBoolean(env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY_ENABLED ?? env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY, state.hardware_telemetry_enabled === true),
|
|
1267
1360
|
exposeAllModels: resolveDaemonExposeAllModels(env, state),
|
|
1268
1361
|
modelAllowlist: parseList(env.MSWARM_SELF_HOSTED_MODEL_ALLOWLIST || state.model_allowlist),
|
|
1269
|
-
modelBlocklist: parseList(env.MSWARM_SELF_HOSTED_MODEL_BLOCKLIST || state.model_blocklist)
|
|
1362
|
+
modelBlocklist: parseList(env.MSWARM_SELF_HOSTED_MODEL_BLOCKLIST || state.model_blocklist),
|
|
1363
|
+
clientAllowlist: normalizeSelfHostedNodeClientAllowlist(env.MSWARM_SELF_HOSTED_CLIENT_ALLOWLIST ||
|
|
1364
|
+
env.MSWARM_SELF_HOSTED_CLIENTS ||
|
|
1365
|
+
state.client_allowlist)
|
|
1270
1366
|
};
|
|
1271
1367
|
}
|
|
1272
1368
|
export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = process.env) {
|
|
@@ -1293,6 +1389,10 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
|
|
|
1293
1389
|
DEFAULT_OLLAMA_BASE_URL;
|
|
1294
1390
|
const allowlist = parseList(options.allow || env.MSWARM_SELF_HOSTED_MODEL_ALLOWLIST);
|
|
1295
1391
|
const blocklist = parseList(options.block || env.MSWARM_SELF_HOSTED_MODEL_BLOCKLIST);
|
|
1392
|
+
const clientAllowlist = normalizeSelfHostedNodeClientAllowlist(options.clients ||
|
|
1393
|
+
options.client ||
|
|
1394
|
+
env.MSWARM_SELF_HOSTED_CLIENT_ALLOWLIST ||
|
|
1395
|
+
env.MSWARM_SELF_HOSTED_CLIENTS);
|
|
1296
1396
|
const packageNodeVersion = await readPackageNodeVersion();
|
|
1297
1397
|
const maxConcurrentJobs = parsePositiveInteger(options["max-concurrent-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS, 1);
|
|
1298
1398
|
const maxConcurrentLlmJobs = parsePositiveInteger(options["max-concurrent-llm-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, maxConcurrentJobs);
|
|
@@ -1332,6 +1432,7 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
|
|
|
1332
1432
|
exposeAllModels: resolveOwnerSetupExposeAllModels(options, env),
|
|
1333
1433
|
modelAllowlist: allowlist,
|
|
1334
1434
|
modelBlocklist: blocklist,
|
|
1435
|
+
clientAllowlist,
|
|
1335
1436
|
start: options.start === true
|
|
1336
1437
|
};
|
|
1337
1438
|
}
|
|
@@ -1668,6 +1769,20 @@ function configText(config, ...keys) {
|
|
|
1668
1769
|
}
|
|
1669
1770
|
return undefined;
|
|
1670
1771
|
}
|
|
1772
|
+
async function defaultMcodaAgentAuthResolver(agent) {
|
|
1773
|
+
const agentId = optionalText(agent.id);
|
|
1774
|
+
if (!agentId) {
|
|
1775
|
+
return undefined;
|
|
1776
|
+
}
|
|
1777
|
+
const repo = await GlobalRepository.create();
|
|
1778
|
+
try {
|
|
1779
|
+
const secret = await repo.getAgentAuthSecret(agentId);
|
|
1780
|
+
return secret?.encryptedSecret ? CryptoHelper.decryptSecret(secret.encryptedSecret) : undefined;
|
|
1781
|
+
}
|
|
1782
|
+
finally {
|
|
1783
|
+
await repo.close();
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1671
1786
|
function mcodaAgentDefaultModel(agent) {
|
|
1672
1787
|
return (optionalText(agent.defaultModel) ||
|
|
1673
1788
|
optionalText(agent.default_model) ||
|
|
@@ -1683,18 +1798,35 @@ function resolveCodaliProviderForAgent(agent) {
|
|
|
1683
1798
|
if (["ollama-remote", "ollama-cli", "ollama", "local-model"].includes(adapter || "")) {
|
|
1684
1799
|
return "ollama-remote";
|
|
1685
1800
|
}
|
|
1686
|
-
if (adapter === "openai" ||
|
|
1801
|
+
if (adapter === "openai" ||
|
|
1802
|
+
adapter === "openai-api" ||
|
|
1803
|
+
adapter === "openai-compatible" ||
|
|
1804
|
+
adapter === "openai-cli") {
|
|
1687
1805
|
return "openai-compatible";
|
|
1688
1806
|
}
|
|
1689
1807
|
if (adapter === "codex-cli")
|
|
1690
1808
|
return "codex-cli";
|
|
1691
1809
|
return adapter || undefined;
|
|
1692
1810
|
}
|
|
1811
|
+
function mcodaAgentRequiresApiKey(agent, mapped) {
|
|
1812
|
+
if (mapped.provider !== "openai-compatible") {
|
|
1813
|
+
return false;
|
|
1814
|
+
}
|
|
1815
|
+
if (mapped.localRunner) {
|
|
1816
|
+
return mapped.authMode === "bearer";
|
|
1817
|
+
}
|
|
1818
|
+
const adapter = optionalText(agent.adapter) || "";
|
|
1819
|
+
if (["openai", "openai-api", "openai-compatible"].includes(adapter)) {
|
|
1820
|
+
return mapped.authMode !== "none";
|
|
1821
|
+
}
|
|
1822
|
+
return mapped.authMode === "bearer";
|
|
1823
|
+
}
|
|
1693
1824
|
function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
1694
1825
|
const adapter = optionalText(agent.adapter) || "unknown";
|
|
1695
1826
|
const model = mcodaAgentDefaultModel(agent) || fallbackSlug;
|
|
1696
1827
|
const config = agent.config ?? null;
|
|
1697
1828
|
const localRunner = normalizeMcodaLocalRunnerConfig(adapter, config);
|
|
1829
|
+
const configAuthMode = normalizeLocalRunnerAuthMode(config?.authMode);
|
|
1698
1830
|
const supportsTools = optionalBoolean(agent.supportsTools, agent.supports_tools, localRunner?.supportsTools) === true;
|
|
1699
1831
|
return {
|
|
1700
1832
|
slug: optionalText(agent.slug) || fallbackSlug,
|
|
@@ -1709,7 +1841,7 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1709
1841
|
: configText(config, "apiKey", "api_key"),
|
|
1710
1842
|
localRunner,
|
|
1711
1843
|
runnerKind: localRunner?.runnerKind,
|
|
1712
|
-
authMode: localRunner?.authMode,
|
|
1844
|
+
authMode: localRunner?.authMode ?? configAuthMode,
|
|
1713
1845
|
dummyBearerToken: localRunner?.dummyBearerToken,
|
|
1714
1846
|
headers: localRunner?.headers,
|
|
1715
1847
|
extraBody: localRunner?.extraBody,
|
|
@@ -1747,6 +1879,7 @@ const DOCDEX_JOB_ERROR_CODES = new Set([
|
|
|
1747
1879
|
]);
|
|
1748
1880
|
const PRE_START_JOB_ERROR_CODES = new Set([
|
|
1749
1881
|
"selected_agent_unavailable",
|
|
1882
|
+
"selected_agent_auth_unavailable",
|
|
1750
1883
|
"selected_agent_unhealthy",
|
|
1751
1884
|
"validation_failed",
|
|
1752
1885
|
"docdex_context_missing",
|
|
@@ -3436,6 +3569,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3436
3569
|
args: config.mcodaListArgs,
|
|
3437
3570
|
timeoutMs: config.requestTimeoutMs
|
|
3438
3571
|
});
|
|
3572
|
+
this.mcodaAgentAuthResolver = deps?.mcodaAgentAuthResolver || defaultMcodaAgentAuthResolver;
|
|
3439
3573
|
this.mcodaExecutor =
|
|
3440
3574
|
deps?.mcodaExecutor ||
|
|
3441
3575
|
new McodaLocalAgentExecutor({
|
|
@@ -3626,6 +3760,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3626
3760
|
expose_all_models: setupConfig.exposeAllModels,
|
|
3627
3761
|
model_allowlist: setupConfig.modelAllowlist,
|
|
3628
3762
|
model_blocklist: setupConfig.modelBlocklist,
|
|
3763
|
+
client_allowlist: setupConfig.clientAllowlist,
|
|
3629
3764
|
heartbeat_interval_seconds: setupConfig.heartbeatIntervalSeconds,
|
|
3630
3765
|
max_concurrent_jobs: setupConfig.maxConcurrentJobs,
|
|
3631
3766
|
max_concurrent_llm_jobs: setupConfig.maxConcurrentLlmJobs,
|
|
@@ -3687,7 +3822,8 @@ export class SelfHostedNodeRuntime {
|
|
|
3687
3822
|
expose_all_models: setupConfig.exposeAllModels,
|
|
3688
3823
|
exposure_policy: setupConfig.exposeAllModels ? "all" : "none",
|
|
3689
3824
|
model_allowlist: setupConfig.modelAllowlist,
|
|
3690
|
-
model_blocklist: setupConfig.modelBlocklist
|
|
3825
|
+
model_blocklist: setupConfig.modelBlocklist,
|
|
3826
|
+
client_allowlist: setupConfig.clientAllowlist
|
|
3691
3827
|
};
|
|
3692
3828
|
await writeSelfHostedNodeState(setupConfig.statePath, state);
|
|
3693
3829
|
await writeSelfHostedRuntimeToken(setupConfig.runtimeTokenPath, runtimeToken);
|
|
@@ -3740,7 +3876,8 @@ export class SelfHostedNodeRuntime {
|
|
|
3740
3876
|
hardwareTelemetryEnabled: setupConfig.hardwareTelemetryEnabled,
|
|
3741
3877
|
exposeAllModels: setupConfig.exposeAllModels,
|
|
3742
3878
|
modelAllowlist: setupConfig.modelAllowlist,
|
|
3743
|
-
modelBlocklist: setupConfig.modelBlocklist
|
|
3879
|
+
modelBlocklist: setupConfig.modelBlocklist,
|
|
3880
|
+
clientAllowlist: setupConfig.clientAllowlist
|
|
3744
3881
|
}, { ...deps, gateway: runtimeGateway });
|
|
3745
3882
|
const once = await runtime.runOnce();
|
|
3746
3883
|
return {
|
|
@@ -3869,7 +4006,8 @@ export class SelfHostedNodeRuntime {
|
|
|
3869
4006
|
expose_all_models: this.config.exposeAllModels,
|
|
3870
4007
|
exposure_policy: this.config.exposeAllModels ? "all" : "none",
|
|
3871
4008
|
model_allowlist: this.config.modelAllowlist,
|
|
3872
|
-
model_blocklist: this.config.modelBlocklist
|
|
4009
|
+
model_blocklist: this.config.modelBlocklist,
|
|
4010
|
+
client_allowlist: this.config.clientAllowlist
|
|
3873
4011
|
};
|
|
3874
4012
|
await writeSelfHostedNodeState(this.config.statePath, nextState);
|
|
3875
4013
|
await writeSelfHostedRuntimeToken(this.config.runtimeTokenPath, runtimeToken);
|
|
@@ -3903,7 +4041,21 @@ export class SelfHostedNodeRuntime {
|
|
|
3903
4041
|
if (mapped.health_status && mapped.health_status !== "healthy" && mapped.health_status !== "unknown") {
|
|
3904
4042
|
throw new SelfHostedPreStartJobError("selected_agent_unhealthy", `selected local mcoda agent ${selected} is ${mapped.health_status}`);
|
|
3905
4043
|
}
|
|
3906
|
-
|
|
4044
|
+
const codaliAgent = mapMcodaAgentToCodaliAgent(agent, selected);
|
|
4045
|
+
if (!codaliAgent.apiKey && mcodaAgentRequiresApiKey(agent, codaliAgent)) {
|
|
4046
|
+
let apiKey;
|
|
4047
|
+
try {
|
|
4048
|
+
apiKey = optionalText(await this.mcodaAgentAuthResolver(agent)) || undefined;
|
|
4049
|
+
}
|
|
4050
|
+
catch {
|
|
4051
|
+
throw new SelfHostedPreStartJobError("selected_agent_auth_unavailable", `selected local mcoda agent ${selected} auth could not be loaded`);
|
|
4052
|
+
}
|
|
4053
|
+
if (!apiKey) {
|
|
4054
|
+
throw new SelfHostedPreStartJobError("selected_agent_auth_unavailable", `selected local mcoda agent ${selected} requires an API key; run "mcoda agent auth set ${selected}" on this node`);
|
|
4055
|
+
}
|
|
4056
|
+
codaliAgent.apiKey = apiKey;
|
|
4057
|
+
}
|
|
4058
|
+
return codaliAgent;
|
|
3907
4059
|
}
|
|
3908
4060
|
async executeGenericJob(envelope, options = {}) {
|
|
3909
4061
|
const startedAt = Date.now();
|
|
@@ -4416,6 +4568,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4416
4568
|
model_count: models.length,
|
|
4417
4569
|
exposed_model_count: exposedModelCount
|
|
4418
4570
|
},
|
|
4571
|
+
client_allowlist: this.config.clientAllowlist,
|
|
4419
4572
|
models,
|
|
4420
4573
|
capabilities: capabilityPayload,
|
|
4421
4574
|
...(loadTelemetry.hardware_pressure ? { hardware_pressure: loadTelemetry.hardware_pressure } : {})
|
|
@@ -4576,10 +4729,28 @@ export class SelfHostedNodeRuntime {
|
|
|
4576
4729
|
}
|
|
4577
4730
|
: (({ stream_events: _streamEvents, ...rest }) => rest)(result)
|
|
4578
4731
|
: result;
|
|
4579
|
-
|
|
4580
|
-
|
|
4732
|
+
let resultPostError = null;
|
|
4733
|
+
for (let attempt = 0; attempt <= RELAY_RESULT_POST_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
4734
|
+
try {
|
|
4735
|
+
await this.gateway.postJobResult(enrollment.runtimeToken, job.job_id, this.jobResultPayload(postedResult));
|
|
4736
|
+
resultPostError = null;
|
|
4737
|
+
break;
|
|
4738
|
+
}
|
|
4739
|
+
catch (error) {
|
|
4740
|
+
resultPostError = error;
|
|
4741
|
+
const attemptNumber = attempt + 1;
|
|
4742
|
+
const maxAttempts = RELAY_RESULT_POST_RETRY_DELAYS_MS.length + 1;
|
|
4743
|
+
const retryDelayMs = RELAY_RESULT_POST_RETRY_DELAYS_MS[attempt];
|
|
4744
|
+
if (retryDelayMs === undefined) {
|
|
4745
|
+
break;
|
|
4746
|
+
}
|
|
4747
|
+
console.warn(`[mswarm] self-hosted result post failed for ${job.job_id} ` +
|
|
4748
|
+
`(attempt ${attemptNumber}/${maxAttempts}): ${runtimeErrorMessage(error)}`);
|
|
4749
|
+
await sleep(retryDelayMs);
|
|
4750
|
+
}
|
|
4581
4751
|
}
|
|
4582
|
-
|
|
4752
|
+
if (resultPostError) {
|
|
4753
|
+
const error = resultPostError;
|
|
4583
4754
|
const mismatch = this.lifecycleProtocolMismatch("result", error);
|
|
4584
4755
|
if (mismatch) {
|
|
4585
4756
|
await this.markLifecycleProtocolDegraded(mismatch);
|
|
@@ -4632,7 +4803,9 @@ export class SelfHostedNodeRuntime {
|
|
|
4632
4803
|
return;
|
|
4633
4804
|
polling = true;
|
|
4634
4805
|
void this.pollAndExecuteJob()
|
|
4635
|
-
.catch(() =>
|
|
4806
|
+
.catch((error) => {
|
|
4807
|
+
console.error(`[mswarm] self-hosted relay poll failed: ${runtimeErrorMessage(error)}`);
|
|
4808
|
+
})
|
|
4636
4809
|
.finally(() => {
|
|
4637
4810
|
polling = false;
|
|
4638
4811
|
if (!stopped) {
|