@alfe.ai/gateway 0.1.4 → 0.2.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/dist/health.js +262 -59
- package/dist/runtime-upgrade.js +64 -15
- package/package.json +6 -6
package/dist/health.js
CHANGED
|
@@ -12,7 +12,7 @@ import crypto from "crypto";
|
|
|
12
12
|
import { parse } from "smol-toml";
|
|
13
13
|
import WebSocket from "ws";
|
|
14
14
|
import { createConnection, createServer } from "node:net";
|
|
15
|
-
import { IntegrationManager, IntegrationManagerAdapter, McpApplier, OpenClawApplier } from "@alfe.ai/integrations";
|
|
15
|
+
import { HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, OpenClawApplier } from "@alfe.ai/integrations";
|
|
16
16
|
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
17
17
|
import { Manager, McpBundler, defaultConnect } from "@alfe.ai/mcp-bundler";
|
|
18
18
|
import stream, { Readable } from "stream";
|
|
@@ -109,6 +109,8 @@ const ID_PREFIXES = {
|
|
|
109
109
|
identityVerification: "ivf",
|
|
110
110
|
role: "role",
|
|
111
111
|
directGrant: "dgr",
|
|
112
|
+
revision: "rev",
|
|
113
|
+
fact: "kfc",
|
|
112
114
|
oauthConnection: "con",
|
|
113
115
|
channel: "chn",
|
|
114
116
|
oauthConnectionRequest: "crq",
|
|
@@ -1292,6 +1294,7 @@ const string$1 = (params) => {
|
|
|
1292
1294
|
};
|
|
1293
1295
|
const integer = /^-?\d+$/;
|
|
1294
1296
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
1297
|
+
const boolean$1 = /^(?:true|false)$/i;
|
|
1295
1298
|
const lowercase = /^[^A-Z]*$/;
|
|
1296
1299
|
const uppercase = /^[^a-z]*$/;
|
|
1297
1300
|
//#endregion
|
|
@@ -2066,6 +2069,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
|
|
|
2066
2069
|
$ZodCheckNumberFormat.init(inst, def);
|
|
2067
2070
|
$ZodNumber.init(inst, def);
|
|
2068
2071
|
});
|
|
2072
|
+
const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
2073
|
+
$ZodType.init(inst, def);
|
|
2074
|
+
inst._zod.pattern = boolean$1;
|
|
2075
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
2076
|
+
if (def.coerce) try {
|
|
2077
|
+
payload.value = Boolean(payload.value);
|
|
2078
|
+
} catch (_) {}
|
|
2079
|
+
const input = payload.value;
|
|
2080
|
+
if (typeof input === "boolean") return payload;
|
|
2081
|
+
payload.issues.push({
|
|
2082
|
+
expected: "boolean",
|
|
2083
|
+
code: "invalid_type",
|
|
2084
|
+
input,
|
|
2085
|
+
inst
|
|
2086
|
+
});
|
|
2087
|
+
return payload;
|
|
2088
|
+
};
|
|
2089
|
+
});
|
|
2069
2090
|
const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
2070
2091
|
$ZodType.init(inst, def);
|
|
2071
2092
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3046,6 +3067,13 @@ function _int(Class, params) {
|
|
|
3046
3067
|
});
|
|
3047
3068
|
}
|
|
3048
3069
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3070
|
+
function _boolean(Class, params) {
|
|
3071
|
+
return new Class({
|
|
3072
|
+
type: "boolean",
|
|
3073
|
+
...normalizeParams(params)
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3049
3077
|
function _unknown(Class) {
|
|
3050
3078
|
return new Class({ type: "unknown" });
|
|
3051
3079
|
}
|
|
@@ -3588,6 +3616,9 @@ const numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
3588
3616
|
}
|
|
3589
3617
|
if (typeof multipleOf === "number") json.multipleOf = multipleOf;
|
|
3590
3618
|
};
|
|
3619
|
+
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
3620
|
+
json.type = "boolean";
|
|
3621
|
+
};
|
|
3591
3622
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
3592
3623
|
json.not = {};
|
|
3593
3624
|
};
|
|
@@ -4063,6 +4094,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
|
|
|
4063
4094
|
function int(params) {
|
|
4064
4095
|
return /* @__PURE__ */ _int(ZodNumberFormat, params);
|
|
4065
4096
|
}
|
|
4097
|
+
const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
4098
|
+
$ZodBoolean.init(inst, def);
|
|
4099
|
+
ZodType.init(inst, def);
|
|
4100
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4101
|
+
});
|
|
4102
|
+
function boolean(params) {
|
|
4103
|
+
return /* @__PURE__ */ _boolean(ZodBoolean, params);
|
|
4104
|
+
}
|
|
4066
4105
|
const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
4067
4106
|
$ZodUnknown.init(inst, def);
|
|
4068
4107
|
ZodType.init(inst, def);
|
|
@@ -4391,7 +4430,10 @@ enumValues({
|
|
|
4391
4430
|
Month: "month",
|
|
4392
4431
|
Year: "year"
|
|
4393
4432
|
});
|
|
4394
|
-
object({
|
|
4433
|
+
object({
|
|
4434
|
+
gracePeriodDays: number().int().positive().optional(),
|
|
4435
|
+
launchDiscountEnabled: boolean().optional()
|
|
4436
|
+
});
|
|
4395
4437
|
enumValues({
|
|
4396
4438
|
ManagedHosting: "managed-hosting",
|
|
4397
4439
|
PlatformTier: "platform-tier",
|
|
@@ -4399,12 +4441,13 @@ enumValues({
|
|
|
4399
4441
|
Other: "other"
|
|
4400
4442
|
});
|
|
4401
4443
|
enumValues({
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4444
|
+
IndividualLite: "individual_lite",
|
|
4445
|
+
IndividualNormal: "individual_normal",
|
|
4446
|
+
IndividualPro: "individual_pro",
|
|
4447
|
+
IndividualMax: "individual_max",
|
|
4448
|
+
OrgFree: "org_free",
|
|
4449
|
+
OrgProfessional: "org_professional",
|
|
4450
|
+
OrgEnterprise: "org_enterprise"
|
|
4408
4451
|
});
|
|
4409
4452
|
enumValues({
|
|
4410
4453
|
Pending: "pending",
|
|
@@ -4437,7 +4480,8 @@ enumValues({
|
|
|
4437
4480
|
});
|
|
4438
4481
|
enumValues({
|
|
4439
4482
|
OpenClaw: "openclaw",
|
|
4440
|
-
NanoClaw: "nanoclaw"
|
|
4483
|
+
NanoClaw: "nanoclaw",
|
|
4484
|
+
Hermes: "hermes"
|
|
4441
4485
|
});
|
|
4442
4486
|
enumValues({
|
|
4443
4487
|
Org: "org",
|
|
@@ -4922,17 +4966,33 @@ async function loadManagedConfig() {
|
|
|
4922
4966
|
agentId: identity.agentId,
|
|
4923
4967
|
orgId: identity.orgId,
|
|
4924
4968
|
runtime: identity.runtime,
|
|
4925
|
-
runtimes: identity.runtime
|
|
4926
|
-
const home = join(homedir(), ".openclaw");
|
|
4927
|
-
return { openclaw: {
|
|
4928
|
-
workspace: home,
|
|
4929
|
-
agentWorkspace: deriveAgentWorkspace("openclaw", home)
|
|
4930
|
-
} };
|
|
4931
|
-
})() : {},
|
|
4969
|
+
runtimes: buildManagedRuntimes(identity.runtime),
|
|
4932
4970
|
autoStartRuntime: true
|
|
4933
4971
|
};
|
|
4934
4972
|
}
|
|
4935
4973
|
/**
|
|
4974
|
+
* Build the managed-mode `runtimes` map for a single resolved runtime.
|
|
4975
|
+
* Returns an empty map for unknown runtimes (the daemon then skips runtime
|
|
4976
|
+
* start rather than crashing).
|
|
4977
|
+
*/
|
|
4978
|
+
function buildManagedRuntimes(runtime) {
|
|
4979
|
+
if (runtime === "openclaw") {
|
|
4980
|
+
const home = join(homedir(), ".openclaw");
|
|
4981
|
+
return { openclaw: {
|
|
4982
|
+
workspace: home,
|
|
4983
|
+
agentWorkspace: deriveAgentWorkspace("openclaw", home)
|
|
4984
|
+
} };
|
|
4985
|
+
}
|
|
4986
|
+
if (runtime === "hermes") {
|
|
4987
|
+
const home = join(homedir(), ".hermes");
|
|
4988
|
+
return { hermes: {
|
|
4989
|
+
workspace: home,
|
|
4990
|
+
agentWorkspace: deriveAgentWorkspace("hermes", home)
|
|
4991
|
+
} };
|
|
4992
|
+
}
|
|
4993
|
+
return {};
|
|
4994
|
+
}
|
|
4995
|
+
/**
|
|
4936
4996
|
* Load full daemon configuration.
|
|
4937
4997
|
* Reads config.toml, validates the API key, resolves agent identity,
|
|
4938
4998
|
* and parses runtime configurations.
|
|
@@ -4951,6 +5011,7 @@ async function loadDaemonConfig() {
|
|
|
4951
5011
|
const identity = await resolveAgentIdentity(alfeConfig.api_key, apiEndpoint);
|
|
4952
5012
|
const gatewayWsUrl = alfeConfig.gateway_url ?? deriveGatewayWsUrl(apiEndpoint);
|
|
4953
5013
|
const runtimes = await loadRuntimeConfigs();
|
|
5014
|
+
const runtime = alfeConfig.runtime ?? identity.runtime;
|
|
4954
5015
|
return {
|
|
4955
5016
|
apiKey: alfeConfig.api_key,
|
|
4956
5017
|
apiEndpoint,
|
|
@@ -4959,7 +5020,7 @@ async function loadDaemonConfig() {
|
|
|
4959
5020
|
pidPath: PID_PATH,
|
|
4960
5021
|
agentId: identity.agentId,
|
|
4961
5022
|
orgId: identity.orgId,
|
|
4962
|
-
runtime
|
|
5023
|
+
runtime,
|
|
4963
5024
|
runtimes,
|
|
4964
5025
|
autoStartRuntime: alfeConfig.auto_start_runtime ?? true
|
|
4965
5026
|
};
|
|
@@ -5172,7 +5233,18 @@ const PROTOCOL_VERSION = 1;
|
|
|
5172
5233
|
//#endregion
|
|
5173
5234
|
//#region src/reconciliation.ts
|
|
5174
5235
|
const log$2 = logger$1.child({ component: "Reconciliation" });
|
|
5236
|
+
/**
|
|
5237
|
+
* How many times reconcile will re-attempt activation of an intact integration
|
|
5238
|
+
* stuck in `error` (with no reinstall requested) before giving up and waiting
|
|
5239
|
+
* for a manual reinstall. Each reconcile pass = one cloud DESIRED_STATE push,
|
|
5240
|
+
* so these attempts are spread across pushes, not a tight loop. This lets a
|
|
5241
|
+
* transient activation failure (e.g. an `openclaw config set` racing a runtime
|
|
5242
|
+
* hot-reload) self-heal without the user clicking Reinstall.
|
|
5243
|
+
*/
|
|
5244
|
+
const MAX_ERROR_ACTIVATE_ATTEMPTS = 3;
|
|
5175
5245
|
var ReconciliationEngine = class {
|
|
5246
|
+
/** Consecutive re-activation attempts for an intact error-state integration, keyed by id. */
|
|
5247
|
+
activateAttempts = /* @__PURE__ */ new Map();
|
|
5176
5248
|
constructor(manager) {
|
|
5177
5249
|
this.manager = manager;
|
|
5178
5250
|
}
|
|
@@ -5236,6 +5308,7 @@ var ReconciliationEngine = class {
|
|
|
5236
5308
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5237
5309
|
log$2.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5238
5310
|
this.manager.resetReinstallAttempts(id);
|
|
5311
|
+
this.activateAttempts.delete(id);
|
|
5239
5312
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
5240
5313
|
report.installed.push(id);
|
|
5241
5314
|
report.activated.push(id);
|
|
@@ -5267,6 +5340,7 @@ var ReconciliationEngine = class {
|
|
|
5267
5340
|
try {
|
|
5268
5341
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
5269
5342
|
this.manager.resetReinstallAttempts(id);
|
|
5343
|
+
this.activateAttempts.delete(id);
|
|
5270
5344
|
report.installed.push(id);
|
|
5271
5345
|
report.activated.push(id);
|
|
5272
5346
|
report.results.push({
|
|
@@ -5290,7 +5364,37 @@ var ReconciliationEngine = class {
|
|
|
5290
5364
|
}
|
|
5291
5365
|
return;
|
|
5292
5366
|
}
|
|
5293
|
-
|
|
5367
|
+
const activateAttempts = this.activateAttempts.get(id) ?? 0;
|
|
5368
|
+
if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
|
|
5369
|
+
this.activateAttempts.set(id, activateAttempts + 1);
|
|
5370
|
+
log$2.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
|
|
5371
|
+
try {
|
|
5372
|
+
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5373
|
+
this.activateAttempts.delete(id);
|
|
5374
|
+
report.activated.push(id);
|
|
5375
|
+
report.results.push({
|
|
5376
|
+
integrationId: id,
|
|
5377
|
+
action: "activated",
|
|
5378
|
+
actualStatus: "active"
|
|
5379
|
+
});
|
|
5380
|
+
return;
|
|
5381
|
+
} catch (reactivateErr) {
|
|
5382
|
+
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
5383
|
+
log$2.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
5384
|
+
report.errors.push({
|
|
5385
|
+
integrationId: id,
|
|
5386
|
+
error: reactivateMsg
|
|
5387
|
+
});
|
|
5388
|
+
report.results.push({
|
|
5389
|
+
integrationId: id,
|
|
5390
|
+
action: "error",
|
|
5391
|
+
actualStatus: "error",
|
|
5392
|
+
errorMessage: reactivateMsg
|
|
5393
|
+
});
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
log$2.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
5294
5398
|
report.errors.push({
|
|
5295
5399
|
integrationId: id,
|
|
5296
5400
|
error: "Integration is in error state"
|
|
@@ -5317,6 +5421,7 @@ var ReconciliationEngine = class {
|
|
|
5317
5421
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5318
5422
|
log$2.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5319
5423
|
this.manager.resetReinstallAttempts(id);
|
|
5424
|
+
this.activateAttempts.delete(id);
|
|
5320
5425
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
5321
5426
|
report.installed.push(id);
|
|
5322
5427
|
report.activated.push(id);
|
|
@@ -5327,6 +5432,7 @@ var ReconciliationEngine = class {
|
|
|
5327
5432
|
});
|
|
5328
5433
|
return;
|
|
5329
5434
|
}
|
|
5435
|
+
this.activateAttempts.delete(id);
|
|
5330
5436
|
report.results.push({
|
|
5331
5437
|
integrationId: id,
|
|
5332
5438
|
action: "up_to_date",
|
|
@@ -21529,6 +21635,10 @@ var RuntimeProcess = class {
|
|
|
21529
21635
|
"--allow-unconfigured"
|
|
21530
21636
|
]
|
|
21531
21637
|
};
|
|
21638
|
+
case "hermes": return {
|
|
21639
|
+
command: "hermes",
|
|
21640
|
+
args: ["gateway", "run"]
|
|
21641
|
+
};
|
|
21532
21642
|
default: throw new Error(`Unsupported runtime: ${this.options.runtime}`);
|
|
21533
21643
|
}
|
|
21534
21644
|
}
|
|
@@ -22107,6 +22217,12 @@ let startedAt;
|
|
|
22107
22217
|
let integrationManager;
|
|
22108
22218
|
let mcpBundler = null;
|
|
22109
22219
|
let mcpManagerRef = null;
|
|
22220
|
+
/**
|
|
22221
|
+
* Hermes-only MCP store consumer (Approach B). Mirrors the runtime-agnostic MCP
|
|
22222
|
+
* store into ~/.hermes/config.yaml. Null for openclaw agents (never constructed)
|
|
22223
|
+
* and for managed/self-hosted runtimes other than hermes.
|
|
22224
|
+
*/
|
|
22225
|
+
let hermesMcpSync = null;
|
|
22110
22226
|
let aiProxyServer = null;
|
|
22111
22227
|
let runtimeProcess = null;
|
|
22112
22228
|
let aiProxyUrl = null;
|
|
@@ -22119,6 +22235,12 @@ let resolvedRuntimeVersion;
|
|
|
22119
22235
|
let upgradingRuntime = false;
|
|
22120
22236
|
let stopPairingApprovalPoller = null;
|
|
22121
22237
|
/**
|
|
22238
|
+
* Module-level handle on the runtime appliers built during start(), so the
|
|
22239
|
+
* module-scoped command handler can route `alfe.config_set` to the active
|
|
22240
|
+
* runtime's applier (mirrors `mcpManagerRef`). Null until start() builds it.
|
|
22241
|
+
*/
|
|
22242
|
+
let runtimeAppliersRef = null;
|
|
22243
|
+
/**
|
|
22122
22244
|
* Resolve the installed @alfe.ai/cli version.
|
|
22123
22245
|
*
|
|
22124
22246
|
* Strategy (in order):
|
|
@@ -22193,15 +22315,44 @@ async function getCliVersion() {
|
|
|
22193
22315
|
logger$1.debug("Could not resolve @alfe.ai/cli version");
|
|
22194
22316
|
}
|
|
22195
22317
|
/**
|
|
22196
|
-
*
|
|
22197
|
-
*
|
|
22318
|
+
* Per-runtime "print installed version" commands. Local to the gateway — we do
|
|
22319
|
+
* NOT import the CLI's `detectRuntime` (`@alfe.ai/gateway` must not depend on
|
|
22320
|
+
* `@alfe.ai/cli`). Unknown runtimes resolve to `undefined` (never throw) so the
|
|
22321
|
+
* connection-status report degrades gracefully instead of crashing.
|
|
22322
|
+
*/
|
|
22323
|
+
const RUNTIME_VERSION_COMMANDS = {
|
|
22324
|
+
openclaw: {
|
|
22325
|
+
command: "openclaw",
|
|
22326
|
+
args: ["--version"]
|
|
22327
|
+
},
|
|
22328
|
+
hermes: {
|
|
22329
|
+
command: "hermes",
|
|
22330
|
+
args: ["version"]
|
|
22331
|
+
}
|
|
22332
|
+
};
|
|
22333
|
+
/**
|
|
22334
|
+
* Pure resolver (exported for tests) — the per-runtime version command, or
|
|
22335
|
+
* `undefined` for an unknown runtime.
|
|
22336
|
+
*/
|
|
22337
|
+
function resolveRuntimeVersionCommand(runtime) {
|
|
22338
|
+
return RUNTIME_VERSION_COMMANDS[runtime];
|
|
22339
|
+
}
|
|
22340
|
+
/**
|
|
22341
|
+
* Resolve the installed runtime version by running the per-runtime version
|
|
22342
|
+
* command and returning the trimmed stdout. An unknown runtime (no command in
|
|
22343
|
+
* the map) returns `undefined` without spawning — it must never throw.
|
|
22198
22344
|
*/
|
|
22199
|
-
async function getRuntimeVersion() {
|
|
22345
|
+
async function getRuntimeVersion(runtime) {
|
|
22346
|
+
const cmd = resolveRuntimeVersionCommand(runtime);
|
|
22347
|
+
if (!cmd) {
|
|
22348
|
+
logger$1.debug({ runtime }, "No version command for runtime — skipping version detection");
|
|
22349
|
+
return;
|
|
22350
|
+
}
|
|
22200
22351
|
try {
|
|
22201
|
-
const { stdout } = await execFileAsync(
|
|
22352
|
+
const { stdout } = await execFileAsync(cmd.command, cmd.args);
|
|
22202
22353
|
return stdout.trim() || void 0;
|
|
22203
22354
|
} catch {
|
|
22204
|
-
logger$1.debug("Could not resolve
|
|
22355
|
+
logger$1.debug({ runtime }, "Could not resolve runtime version");
|
|
22205
22356
|
return;
|
|
22206
22357
|
}
|
|
22207
22358
|
}
|
|
@@ -22349,7 +22500,7 @@ async function startDaemon() {
|
|
|
22349
22500
|
await flushAndExit(1);
|
|
22350
22501
|
}
|
|
22351
22502
|
resolvedCliVersion = await getCliVersion();
|
|
22352
|
-
resolvedRuntimeVersion = await getRuntimeVersion();
|
|
22503
|
+
resolvedRuntimeVersion = await getRuntimeVersion(config.runtime);
|
|
22353
22504
|
logger$1.info({
|
|
22354
22505
|
cliVersion: resolvedCliVersion,
|
|
22355
22506
|
runtimeVersion: resolvedRuntimeVersion
|
|
@@ -22382,7 +22533,14 @@ async function startDaemon() {
|
|
|
22382
22533
|
home: runtimeCfg.workspace,
|
|
22383
22534
|
agentWorkspace: runtimeCfg.agentWorkspace
|
|
22384
22535
|
}, "Registered OpenClaw runtime applier");
|
|
22536
|
+
} else if (name === "hermes") {
|
|
22537
|
+
runtimeAppliers.set(name, new HermesApplier({ home: runtimeCfg.workspace }));
|
|
22538
|
+
logger$1.info({
|
|
22539
|
+
runtime: name,
|
|
22540
|
+
home: runtimeCfg.workspace
|
|
22541
|
+
}, "Registered Hermes runtime applier");
|
|
22385
22542
|
} else logger$1.warn({ runtime: name }, "Unknown runtime type — skipping");
|
|
22543
|
+
runtimeAppliersRef = runtimeAppliers;
|
|
22386
22544
|
const integrationsService = new IntegrationsService(new AlfeApiClient({
|
|
22387
22545
|
apiBaseUrl: config.apiEndpoint,
|
|
22388
22546
|
getToken: () => Promise.resolve(config.apiKey)
|
|
@@ -22397,22 +22555,25 @@ async function startDaemon() {
|
|
|
22397
22555
|
logger: logger$1,
|
|
22398
22556
|
idleTtlMs: 0
|
|
22399
22557
|
}, { connect: defaultConnect });
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22558
|
+
if (config.runtime === "hermes") logger$1.info("Hermes runtime — daemon MCP bundler left idle (store mirrored to config.yaml by HermesMcpSync)");
|
|
22559
|
+
else {
|
|
22560
|
+
await mcpManager.loadIntoBundler(mcpBundler);
|
|
22561
|
+
const warmBundler = (reason) => {
|
|
22562
|
+
if (!mcpBundler) return;
|
|
22563
|
+
mcpBundler.warmup().catch((err) => {
|
|
22564
|
+
logger$1.warn({
|
|
22565
|
+
err: err instanceof Error ? err.message : String(err),
|
|
22566
|
+
reason
|
|
22567
|
+
}, "MCP bundler warmup failed");
|
|
22568
|
+
});
|
|
22569
|
+
};
|
|
22570
|
+
warmBundler("startup");
|
|
22571
|
+
mcpManager.onChange(() => {
|
|
22572
|
+
warmBundler("store change");
|
|
22408
22573
|
});
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
warmBundler("store change");
|
|
22413
|
-
});
|
|
22414
|
-
logger$1.info("MCP bundler attached to manager — warming children");
|
|
22415
|
-
await runOpenclawMcpCleanup({
|
|
22574
|
+
logger$1.info("MCP bundler attached to manager — warming children");
|
|
22575
|
+
}
|
|
22576
|
+
if (config.runtime === "openclaw") await runOpenclawMcpCleanup({
|
|
22416
22577
|
manager: mcpManager,
|
|
22417
22578
|
logger: logger$1
|
|
22418
22579
|
}).catch((err) => {
|
|
@@ -22433,14 +22594,27 @@ async function startDaemon() {
|
|
|
22433
22594
|
});
|
|
22434
22595
|
const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
|
|
22435
22596
|
cloudClient.setIntegrationManager(integrationAdapter);
|
|
22436
|
-
|
|
22597
|
+
const requestRuntimeRestart = () => {
|
|
22437
22598
|
if (runtimeProcess) {
|
|
22438
22599
|
logger$1.info("Runtime state changed — restarting runtime");
|
|
22439
22600
|
runtimeProcess.restart().catch((err) => {
|
|
22440
22601
|
logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
22441
22602
|
});
|
|
22442
22603
|
}
|
|
22443
|
-
}
|
|
22604
|
+
};
|
|
22605
|
+
cloudClient.setRuntimeRestartNeededHandler(requestRuntimeRestart);
|
|
22606
|
+
if (config.runtime === "hermes") {
|
|
22607
|
+
const hermesCfg = config.runtimes[config.runtime];
|
|
22608
|
+
if (hermesCfg) {
|
|
22609
|
+
hermesMcpSync = new HermesMcpSync({
|
|
22610
|
+
manager: mcpManager,
|
|
22611
|
+
home: hermesCfg.workspace,
|
|
22612
|
+
apiKey: config.apiKey,
|
|
22613
|
+
requestRestart: requestRuntimeRestart
|
|
22614
|
+
});
|
|
22615
|
+
hermesMcpSync.start();
|
|
22616
|
+
}
|
|
22617
|
+
}
|
|
22444
22618
|
cloudClient.setOnReconciliationComplete(() => {
|
|
22445
22619
|
try {
|
|
22446
22620
|
const activeCommands = integrationManager.getActiveCommands();
|
|
@@ -22495,6 +22669,10 @@ async function startDaemon() {
|
|
|
22495
22669
|
await runtimeProcess.stop();
|
|
22496
22670
|
logger$1.debug("Runtime process stopped");
|
|
22497
22671
|
}
|
|
22672
|
+
if (hermesMcpSync) {
|
|
22673
|
+
hermesMcpSync.stop();
|
|
22674
|
+
hermesMcpSync = null;
|
|
22675
|
+
}
|
|
22498
22676
|
if (mcpBundler) {
|
|
22499
22677
|
logger$1.debug("Stopping MCP bundler...");
|
|
22500
22678
|
await mcpBundler.dispose();
|
|
@@ -22583,17 +22761,28 @@ async function handleCloudCommand(command) {
|
|
|
22583
22761
|
message: "Runtime upgrade already in progress"
|
|
22584
22762
|
}
|
|
22585
22763
|
};
|
|
22586
|
-
const
|
|
22764
|
+
const payload = command.payload;
|
|
22765
|
+
const runtime = config.runtime;
|
|
22766
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.8" : void 0);
|
|
22587
22767
|
upgradingRuntime = true;
|
|
22588
22768
|
setTimeout(() => {
|
|
22589
22769
|
(async () => {
|
|
22590
22770
|
try {
|
|
22591
22771
|
const { upgradeRuntime } = await import("./runtime-upgrade.js");
|
|
22592
|
-
if ((await upgradeRuntime(version, runtimeProcess)).success) {
|
|
22593
|
-
resolvedRuntimeVersion = await getRuntimeVersion();
|
|
22594
|
-
|
|
22595
|
-
logger$1.
|
|
22596
|
-
|
|
22772
|
+
if ((await upgradeRuntime(runtime, version, runtimeProcess)).success) {
|
|
22773
|
+
resolvedRuntimeVersion = await getRuntimeVersion(runtime);
|
|
22774
|
+
const reportedVersion = resolvedRuntimeVersion ?? version;
|
|
22775
|
+
if (!resolvedRuntimeVersion) logger$1.warn({
|
|
22776
|
+
runtime,
|
|
22777
|
+
requestedVersion: version
|
|
22778
|
+
}, "Could not detect runtime version after upgrade");
|
|
22779
|
+
if (reportedVersion) {
|
|
22780
|
+
logger$1.info({
|
|
22781
|
+
runtime,
|
|
22782
|
+
runtimeVersion: reportedVersion
|
|
22783
|
+
}, "Runtime version updated after upgrade");
|
|
22784
|
+
cloudClient.updateRuntimeVersionAndReRegister(reportedVersion);
|
|
22785
|
+
} else logger$1.warn({ runtime }, "No runtime version to report after upgrade — skipping re-register");
|
|
22597
22786
|
}
|
|
22598
22787
|
} catch (err) {
|
|
22599
22788
|
logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Runtime upgrade failed");
|
|
@@ -22608,6 +22797,7 @@ async function handleCloudCommand(command) {
|
|
|
22608
22797
|
status: "ok",
|
|
22609
22798
|
result: {
|
|
22610
22799
|
upgrading: true,
|
|
22800
|
+
runtime,
|
|
22611
22801
|
version
|
|
22612
22802
|
}
|
|
22613
22803
|
};
|
|
@@ -22688,17 +22878,30 @@ async function handleCloudCommand(command) {
|
|
|
22688
22878
|
message: "alfe.config_set requires key and value"
|
|
22689
22879
|
}
|
|
22690
22880
|
};
|
|
22881
|
+
const runtime = config.runtime;
|
|
22882
|
+
const applier = runtimeAppliersRef?.get(runtime);
|
|
22883
|
+
if (!applier || typeof applier.setConfigRaw !== "function") {
|
|
22884
|
+
const message = `No runtime applier supports config_set for runtime "${runtime}"`;
|
|
22885
|
+
logger$1.warn({
|
|
22886
|
+
runtime,
|
|
22887
|
+
key
|
|
22888
|
+
}, message);
|
|
22889
|
+
return {
|
|
22890
|
+
type: "COMMAND_ACK",
|
|
22891
|
+
commandId: command.commandId,
|
|
22892
|
+
status: "error",
|
|
22893
|
+
result: {
|
|
22894
|
+
code: "CONFIG_SET_UNSUPPORTED",
|
|
22895
|
+
message
|
|
22896
|
+
}
|
|
22897
|
+
};
|
|
22898
|
+
}
|
|
22691
22899
|
try {
|
|
22692
|
-
await
|
|
22693
|
-
"config",
|
|
22694
|
-
"set",
|
|
22695
|
-
key,
|
|
22696
|
-
value
|
|
22697
|
-
], { timeout: 1e4 });
|
|
22900
|
+
await applier.setConfigRaw(key, value);
|
|
22698
22901
|
logger$1.info({
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
}, "Applied config via
|
|
22902
|
+
runtime,
|
|
22903
|
+
key
|
|
22904
|
+
}, "Applied config via runtime applier setConfigRaw");
|
|
22702
22905
|
return {
|
|
22703
22906
|
type: "COMMAND_ACK",
|
|
22704
22907
|
commandId: command.commandId,
|
|
@@ -22712,9 +22915,9 @@ async function handleCloudCommand(command) {
|
|
|
22712
22915
|
const message = err instanceof Error ? err.message : String(err);
|
|
22713
22916
|
logger$1.error({
|
|
22714
22917
|
err: message,
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
}, "Failed to apply config via
|
|
22918
|
+
runtime,
|
|
22919
|
+
key
|
|
22920
|
+
}, "Failed to apply config via runtime applier");
|
|
22718
22921
|
return {
|
|
22719
22922
|
type: "COMMAND_ACK",
|
|
22720
22923
|
commandId: command.commandId,
|
package/dist/runtime-upgrade.js
CHANGED
|
@@ -3,40 +3,87 @@ import { execFile } from "node:child_process";
|
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
//#region src/runtime-upgrade.ts
|
|
5
5
|
/**
|
|
6
|
-
* Runtime upgrade —
|
|
6
|
+
* Runtime upgrade — install/update the runtime binary then cycle the RuntimeProcess.
|
|
7
7
|
*
|
|
8
8
|
* Unlike CLI upgrade (which exits the daemon for systemd restart),
|
|
9
9
|
* runtime upgrade keeps the daemon running and just restarts the child process.
|
|
10
10
|
*
|
|
11
|
-
* Flow:
|
|
11
|
+
* Flow (generic across runtimes):
|
|
12
12
|
* 1. Stop the RuntimeProcess child (SIGTERM + grace period)
|
|
13
|
-
* 2.
|
|
13
|
+
* 2. Run the per-runtime upgrade command (see `resolveUpgradeCommand`)
|
|
14
14
|
* 3. Restart the RuntimeProcess child
|
|
15
15
|
*
|
|
16
|
-
* On
|
|
16
|
+
* On install failure, the runtime is restarted on the old version.
|
|
17
17
|
*/
|
|
18
18
|
const execFileAsync = promisify(execFile);
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the per-runtime upgrade command.
|
|
21
|
+
*
|
|
22
|
+
* - `openclaw`: `npm install -g openclaw@<version>` — version-pinned (unchanged).
|
|
23
|
+
* - `hermes`: `hermes update --yes` — Hermes self-updates from its own channel.
|
|
24
|
+
* A pinned version does not map to a Hermes CLI flag, so the
|
|
25
|
+
* `version` arg is intentionally ignored on this branch.
|
|
26
|
+
*
|
|
27
|
+
* Returns `undefined` for an unknown runtime (or openclaw with no version),
|
|
28
|
+
* which the caller treats as a failed upgrade (restart on the old version).
|
|
29
|
+
*/
|
|
30
|
+
function resolveUpgradeCommand(runtime, version) {
|
|
31
|
+
switch (runtime) {
|
|
32
|
+
case "openclaw":
|
|
33
|
+
if (!version) return void 0;
|
|
34
|
+
return {
|
|
35
|
+
command: "npm",
|
|
36
|
+
args: [
|
|
37
|
+
"install",
|
|
38
|
+
"-g",
|
|
39
|
+
`openclaw@${version}`
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
case "hermes": return {
|
|
43
|
+
command: "hermes",
|
|
44
|
+
args: ["update", "--yes"]
|
|
45
|
+
};
|
|
46
|
+
default: return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function upgradeRuntime(runtime, version, runtimeProcess) {
|
|
50
|
+
const cmd = resolveUpgradeCommand(runtime, version);
|
|
51
|
+
if (!cmd) {
|
|
52
|
+
const message = `No upgrade command for runtime '${runtime}'`;
|
|
53
|
+
logger.error({
|
|
54
|
+
runtime,
|
|
55
|
+
version
|
|
56
|
+
}, message);
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
runtime,
|
|
60
|
+
version,
|
|
61
|
+
error: message
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
logger.info({
|
|
65
|
+
runtime,
|
|
66
|
+
version
|
|
67
|
+
}, "Upgrading runtime...");
|
|
21
68
|
if (runtimeProcess) {
|
|
22
69
|
logger.info("Stopping runtime for upgrade...");
|
|
23
70
|
await runtimeProcess.stop();
|
|
24
71
|
}
|
|
25
72
|
try {
|
|
26
|
-
const { stdout, stderr } = await execFileAsync(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
logger.info({ version }, "OpenClaw runtime upgraded successfully");
|
|
73
|
+
const { stdout, stderr } = await execFileAsync(cmd.command, cmd.args, { timeout: 12e4 });
|
|
74
|
+
if (stdout) logger.debug({ stdout: stdout.trim() }, "runtime upgrade stdout");
|
|
75
|
+
if (stderr) logger.debug({ stderr: stderr.trim() }, "runtime upgrade stderr");
|
|
76
|
+
logger.info({
|
|
77
|
+
runtime,
|
|
78
|
+
version
|
|
79
|
+
}, "Runtime upgraded successfully");
|
|
34
80
|
} catch (err) {
|
|
35
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
36
82
|
logger.error({
|
|
37
83
|
err: message,
|
|
84
|
+
runtime,
|
|
38
85
|
version
|
|
39
|
-
}, "Runtime upgrade
|
|
86
|
+
}, "Runtime upgrade install command failed");
|
|
40
87
|
if (runtimeProcess) try {
|
|
41
88
|
logger.info("Restarting runtime on previous version after failed upgrade...");
|
|
42
89
|
await runtimeProcess.restart();
|
|
@@ -46,6 +93,7 @@ async function upgradeRuntime(version, runtimeProcess) {
|
|
|
46
93
|
}
|
|
47
94
|
return {
|
|
48
95
|
success: false,
|
|
96
|
+
runtime,
|
|
49
97
|
version,
|
|
50
98
|
error: message
|
|
51
99
|
};
|
|
@@ -56,6 +104,7 @@ async function upgradeRuntime(version, runtimeProcess) {
|
|
|
56
104
|
}
|
|
57
105
|
return {
|
|
58
106
|
success: true,
|
|
107
|
+
runtime,
|
|
59
108
|
version
|
|
60
109
|
};
|
|
61
110
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"pino-roll": "^1.2.0",
|
|
23
23
|
"smol-toml": ">=1.6.1",
|
|
24
24
|
"ws": "^8.18.0",
|
|
25
|
-
"@alfe.ai/agent-api-client": "^0.
|
|
26
|
-
"@alfe.ai/ai-proxy-local": "^0.0.
|
|
27
|
-
"@alfe.ai/config": "^0.0
|
|
28
|
-
"@alfe.ai/integration-manifest": "^0.
|
|
29
|
-
"@alfe.ai/integrations": "^0.
|
|
25
|
+
"@alfe.ai/agent-api-client": "^0.3.0",
|
|
26
|
+
"@alfe.ai/ai-proxy-local": "^0.0.11",
|
|
27
|
+
"@alfe.ai/config": "^0.1.0",
|
|
28
|
+
"@alfe.ai/integration-manifest": "^0.3.0",
|
|
29
|
+
"@alfe.ai/integrations": "^0.2.0",
|
|
30
30
|
"@alfe.ai/mcp-bundler": "^0.2.1"
|
|
31
31
|
},
|
|
32
32
|
"license": "UNLICENSED",
|