@alfe.ai/gateway 0.1.5 → 0.2.1
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/README.md +5 -0
- package/dist/health.js +207 -66
- package/dist/runtime-upgrade.js +64 -15
- package/dist/src/index.d.ts +12 -0
- package/package.json +15 -7
package/README.md
CHANGED
|
@@ -161,3 +161,8 @@ See `src/protocol.ts` for full type definitions.
|
|
|
161
161
|
|
|
162
162
|
- Dashboard integration management (backend wired, UI in progress)
|
|
163
163
|
- Voice migration to integration model (`packages/openclaw-voice/` exists, not yet full integration)
|
|
164
|
+
|
|
165
|
+
## Links
|
|
166
|
+
|
|
167
|
+
- 🌐 Website: <https://alfe.ai>
|
|
168
|
+
- 📚 Docs: <https://docs.alfe.ai>
|
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";
|
|
@@ -495,6 +495,15 @@ var AuthService = class {
|
|
|
495
495
|
getSubscriptionStatus() {
|
|
496
496
|
return this.client.request(`${this.prefix}/subscription`);
|
|
497
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Mint a short-lived, single-use Clerk sign-in token for the calling user so
|
|
500
|
+
* a native client can hand its logged-in session to the web dashboard
|
|
501
|
+
* (`${dashboard}/handoff?__clerk_ticket=<token>`). Server mints only for the
|
|
502
|
+
* authenticated user — never a caller-supplied id.
|
|
503
|
+
*/
|
|
504
|
+
createHandoffToken() {
|
|
505
|
+
return this.client.request(`${this.prefix}/handoff-token`, { method: "POST" });
|
|
506
|
+
}
|
|
498
507
|
applyForStartup(input) {
|
|
499
508
|
return this.client.request(`${this.prefix}/subscription/apply-startup`, {
|
|
500
509
|
method: "POST",
|
|
@@ -566,10 +575,8 @@ var AuthService = class {
|
|
|
566
575
|
//#region ../../packages-internal/api-client/dist/services/integrations.js
|
|
567
576
|
var IntegrationsService = class {
|
|
568
577
|
client;
|
|
569
|
-
|
|
570
|
-
constructor(client, voiceClient) {
|
|
578
|
+
constructor(client) {
|
|
571
579
|
this.client = client;
|
|
572
|
-
this.voiceClient = voiceClient;
|
|
573
580
|
}
|
|
574
581
|
listScopedInstalls(scope, scopeId) {
|
|
575
582
|
const params = new URLSearchParams({
|
|
@@ -618,8 +625,11 @@ var IntegrationsService = class {
|
|
|
618
625
|
});
|
|
619
626
|
}
|
|
620
627
|
listIntegrations(agentId, options) {
|
|
621
|
-
const
|
|
622
|
-
|
|
628
|
+
const params = new URLSearchParams();
|
|
629
|
+
if (options?.includeInherited) params.set("includeInherited", "true");
|
|
630
|
+
if (options?.effective) params.set("effective", "true");
|
|
631
|
+
const qs = params.toString();
|
|
632
|
+
return this.client.request(`/integrations/agents/${agentId}${qs ? `?${qs}` : ""}`);
|
|
623
633
|
}
|
|
624
634
|
installIntegration(agentId, data) {
|
|
625
635
|
return this.client.request(`/integrations/agents/${agentId}`, {
|
|
@@ -651,10 +661,6 @@ var IntegrationsService = class {
|
|
|
651
661
|
body: JSON.stringify({ agentId })
|
|
652
662
|
});
|
|
653
663
|
}
|
|
654
|
-
listVoices() {
|
|
655
|
-
if (this.voiceClient) return this.voiceClient.request("/api/voices");
|
|
656
|
-
return this.client.request("/integrations/voices");
|
|
657
|
-
}
|
|
658
664
|
getDiscordGuildChannels(guildId) {
|
|
659
665
|
return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels`);
|
|
660
666
|
}
|
|
@@ -4480,7 +4486,17 @@ enumValues({
|
|
|
4480
4486
|
});
|
|
4481
4487
|
enumValues({
|
|
4482
4488
|
OpenClaw: "openclaw",
|
|
4483
|
-
NanoClaw: "nanoclaw"
|
|
4489
|
+
NanoClaw: "nanoclaw",
|
|
4490
|
+
Hermes: "hermes"
|
|
4491
|
+
});
|
|
4492
|
+
const TTS_MODELS = enumValues({
|
|
4493
|
+
ElevenLabsTurbo: "elevenlabs-turbo",
|
|
4494
|
+
ElevenLabsStandard: "elevenlabs-standard"
|
|
4495
|
+
});
|
|
4496
|
+
object({
|
|
4497
|
+
voiceId: string().regex(/^[A-Za-z0-9]{8,64}$/).optional(),
|
|
4498
|
+
ttsModel: _enum(TTS_MODELS).optional(),
|
|
4499
|
+
enabled: boolean().optional()
|
|
4484
4500
|
});
|
|
4485
4501
|
enumValues({
|
|
4486
4502
|
Org: "org",
|
|
@@ -4768,10 +4784,12 @@ const NotificationType = {
|
|
|
4768
4784
|
SubscriptionCancelled: "subscription.cancelled",
|
|
4769
4785
|
SubscriptionPastDue: "subscription.past_due",
|
|
4770
4786
|
BalanceThresholdWarning: "balance.threshold.warning",
|
|
4787
|
+
PlatformTierPriceIncrease: "platform.tier_price_increase",
|
|
4771
4788
|
AgentCreated: "agent.created",
|
|
4772
4789
|
AgentProvisionFailed: "agent.provision.failed",
|
|
4773
4790
|
AgentBillingSuspended: "agent.billing.suspended",
|
|
4774
4791
|
AgentDisconnectedExtended: "agent.disconnected.extended",
|
|
4792
|
+
BrowserTakeoverRequested: "browser_takeover_requested",
|
|
4775
4793
|
InviteCreated: "onboarding.invite.created",
|
|
4776
4794
|
OrgClaimed: "onboarding.org.claimed",
|
|
4777
4795
|
TeamMemberAdded: "team.member.added",
|
|
@@ -4788,7 +4806,7 @@ const RecipientStrategy = {
|
|
|
4788
4806
|
TenantAdmins: "tenant_admins",
|
|
4789
4807
|
SpecificUser: "specific_user"
|
|
4790
4808
|
};
|
|
4791
|
-
NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PaymentFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.TopUpCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AutoRechargeCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AutoRechargeFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.SubscriptionCreated, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.SubscriptionCancelled, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.SubscriptionPastDue, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.BalanceThresholdWarning, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentCreated, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentProvisionFailed, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentBillingSuspended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentDisconnectedExtended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.InviteCreated, NotificationCategory.System, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
|
|
4809
|
+
NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PaymentFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.TopUpCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AutoRechargeCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AutoRechargeFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.SubscriptionCreated, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.SubscriptionCancelled, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.SubscriptionPastDue, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.BalanceThresholdWarning, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PlatformTierPriceIncrease, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AgentCreated, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentProvisionFailed, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentBillingSuspended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentDisconnectedExtended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.BrowserTakeoverRequested, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.InviteCreated, NotificationCategory.System, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
|
|
4792
4810
|
//#endregion
|
|
4793
4811
|
//#region src/config.ts
|
|
4794
4812
|
/**
|
|
@@ -4965,17 +4983,33 @@ async function loadManagedConfig() {
|
|
|
4965
4983
|
agentId: identity.agentId,
|
|
4966
4984
|
orgId: identity.orgId,
|
|
4967
4985
|
runtime: identity.runtime,
|
|
4968
|
-
runtimes: identity.runtime
|
|
4969
|
-
const home = join(homedir(), ".openclaw");
|
|
4970
|
-
return { openclaw: {
|
|
4971
|
-
workspace: home,
|
|
4972
|
-
agentWorkspace: deriveAgentWorkspace("openclaw", home)
|
|
4973
|
-
} };
|
|
4974
|
-
})() : {},
|
|
4986
|
+
runtimes: buildManagedRuntimes(identity.runtime),
|
|
4975
4987
|
autoStartRuntime: true
|
|
4976
4988
|
};
|
|
4977
4989
|
}
|
|
4978
4990
|
/**
|
|
4991
|
+
* Build the managed-mode `runtimes` map for a single resolved runtime.
|
|
4992
|
+
* Returns an empty map for unknown runtimes (the daemon then skips runtime
|
|
4993
|
+
* start rather than crashing).
|
|
4994
|
+
*/
|
|
4995
|
+
function buildManagedRuntimes(runtime) {
|
|
4996
|
+
if (runtime === "openclaw") {
|
|
4997
|
+
const home = join(homedir(), ".openclaw");
|
|
4998
|
+
return { openclaw: {
|
|
4999
|
+
workspace: home,
|
|
5000
|
+
agentWorkspace: deriveAgentWorkspace("openclaw", home)
|
|
5001
|
+
} };
|
|
5002
|
+
}
|
|
5003
|
+
if (runtime === "hermes") {
|
|
5004
|
+
const home = join(homedir(), ".hermes");
|
|
5005
|
+
return { hermes: {
|
|
5006
|
+
workspace: home,
|
|
5007
|
+
agentWorkspace: deriveAgentWorkspace("hermes", home)
|
|
5008
|
+
} };
|
|
5009
|
+
}
|
|
5010
|
+
return {};
|
|
5011
|
+
}
|
|
5012
|
+
/**
|
|
4979
5013
|
* Load full daemon configuration.
|
|
4980
5014
|
* Reads config.toml, validates the API key, resolves agent identity,
|
|
4981
5015
|
* and parses runtime configurations.
|
|
@@ -4994,6 +5028,7 @@ async function loadDaemonConfig() {
|
|
|
4994
5028
|
const identity = await resolveAgentIdentity(alfeConfig.api_key, apiEndpoint);
|
|
4995
5029
|
const gatewayWsUrl = alfeConfig.gateway_url ?? deriveGatewayWsUrl(apiEndpoint);
|
|
4996
5030
|
const runtimes = await loadRuntimeConfigs();
|
|
5031
|
+
const runtime = alfeConfig.runtime ?? identity.runtime;
|
|
4997
5032
|
return {
|
|
4998
5033
|
apiKey: alfeConfig.api_key,
|
|
4999
5034
|
apiEndpoint,
|
|
@@ -5002,7 +5037,7 @@ async function loadDaemonConfig() {
|
|
|
5002
5037
|
pidPath: PID_PATH,
|
|
5003
5038
|
agentId: identity.agentId,
|
|
5004
5039
|
orgId: identity.orgId,
|
|
5005
|
-
runtime
|
|
5040
|
+
runtime,
|
|
5006
5041
|
runtimes,
|
|
5007
5042
|
autoStartRuntime: alfeConfig.auto_start_runtime ?? true
|
|
5008
5043
|
};
|
|
@@ -5029,6 +5064,7 @@ async function fetchAgentConfig(apiKey, apiEndpoint) {
|
|
|
5029
5064
|
const wsResult = await wsResponse.json();
|
|
5030
5065
|
const templateKey = wsResult.data?.templateKey;
|
|
5031
5066
|
const defaultModel = wsResult.data?.defaultModel ?? void 0;
|
|
5067
|
+
const installedFrom = wsResult.data?.installedFrom;
|
|
5032
5068
|
if (!templateKey) {
|
|
5033
5069
|
logger$1.debug("No templateKey in workspace response");
|
|
5034
5070
|
return defaultModel ? {
|
|
@@ -5036,8 +5072,14 @@ async function fetchAgentConfig(apiKey, apiEndpoint) {
|
|
|
5036
5072
|
files: {}
|
|
5037
5073
|
} : null;
|
|
5038
5074
|
}
|
|
5039
|
-
|
|
5040
|
-
const
|
|
5075
|
+
const pinnedVersion = installedFrom?.templateKey === templateKey ? installedFrom.version : void 0;
|
|
5076
|
+
const filesUrl = new URL(`${apiEndpoint}/templates/${encodeURIComponent(templateKey)}/files`);
|
|
5077
|
+
if (pinnedVersion !== void 0) filesUrl.searchParams.set("version", String(pinnedVersion));
|
|
5078
|
+
logger$1.debug({
|
|
5079
|
+
templateKey,
|
|
5080
|
+
version: pinnedVersion
|
|
5081
|
+
}, "Fetching template files...");
|
|
5082
|
+
const filesResponse = await fetch(filesUrl.toString(), {
|
|
5041
5083
|
method: "GET",
|
|
5042
5084
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
5043
5085
|
});
|
|
@@ -5049,16 +5091,18 @@ async function fetchAgentConfig(apiKey, apiEndpoint) {
|
|
|
5049
5091
|
files: {}
|
|
5050
5092
|
};
|
|
5051
5093
|
}
|
|
5052
|
-
const
|
|
5053
|
-
const fileCount = Object.keys(
|
|
5094
|
+
const files = (await filesResponse.json()).data?.files ?? {};
|
|
5095
|
+
const fileCount = Object.keys(files).length;
|
|
5054
5096
|
logger$1.debug({
|
|
5055
5097
|
templateKey,
|
|
5098
|
+
version: pinnedVersion,
|
|
5056
5099
|
fileCount
|
|
5057
5100
|
}, "Template files fetched");
|
|
5058
5101
|
return {
|
|
5059
5102
|
templateKey,
|
|
5060
5103
|
defaultModel,
|
|
5061
|
-
files
|
|
5104
|
+
files,
|
|
5105
|
+
personaFiles: Object.keys(files)
|
|
5062
5106
|
};
|
|
5063
5107
|
} catch (err) {
|
|
5064
5108
|
logger$1.debug({ err: err instanceof Error ? err.message : String(err) }, "fetchAgentConfig failed");
|
|
@@ -21617,6 +21661,10 @@ var RuntimeProcess = class {
|
|
|
21617
21661
|
"--allow-unconfigured"
|
|
21618
21662
|
]
|
|
21619
21663
|
};
|
|
21664
|
+
case "hermes": return {
|
|
21665
|
+
command: "hermes",
|
|
21666
|
+
args: ["gateway", "run"]
|
|
21667
|
+
};
|
|
21620
21668
|
default: throw new Error(`Unsupported runtime: ${this.options.runtime}`);
|
|
21621
21669
|
}
|
|
21622
21670
|
}
|
|
@@ -22195,6 +22243,12 @@ let startedAt;
|
|
|
22195
22243
|
let integrationManager;
|
|
22196
22244
|
let mcpBundler = null;
|
|
22197
22245
|
let mcpManagerRef = null;
|
|
22246
|
+
/**
|
|
22247
|
+
* Hermes-only MCP store consumer (Approach B). Mirrors the runtime-agnostic MCP
|
|
22248
|
+
* store into ~/.hermes/config.yaml. Null for openclaw agents (never constructed)
|
|
22249
|
+
* and for managed/self-hosted runtimes other than hermes.
|
|
22250
|
+
*/
|
|
22251
|
+
let hermesMcpSync = null;
|
|
22198
22252
|
let aiProxyServer = null;
|
|
22199
22253
|
let runtimeProcess = null;
|
|
22200
22254
|
let aiProxyUrl = null;
|
|
@@ -22207,6 +22261,12 @@ let resolvedRuntimeVersion;
|
|
|
22207
22261
|
let upgradingRuntime = false;
|
|
22208
22262
|
let stopPairingApprovalPoller = null;
|
|
22209
22263
|
/**
|
|
22264
|
+
* Module-level handle on the runtime appliers built during start(), so the
|
|
22265
|
+
* module-scoped command handler can route `alfe.config_set` to the active
|
|
22266
|
+
* runtime's applier (mirrors `mcpManagerRef`). Null until start() builds it.
|
|
22267
|
+
*/
|
|
22268
|
+
let runtimeAppliersRef = null;
|
|
22269
|
+
/**
|
|
22210
22270
|
* Resolve the installed @alfe.ai/cli version.
|
|
22211
22271
|
*
|
|
22212
22272
|
* Strategy (in order):
|
|
@@ -22281,15 +22341,44 @@ async function getCliVersion() {
|
|
|
22281
22341
|
logger$1.debug("Could not resolve @alfe.ai/cli version");
|
|
22282
22342
|
}
|
|
22283
22343
|
/**
|
|
22284
|
-
*
|
|
22285
|
-
*
|
|
22344
|
+
* Per-runtime "print installed version" commands. Local to the gateway — we do
|
|
22345
|
+
* NOT import the CLI's `detectRuntime` (`@alfe.ai/gateway` must not depend on
|
|
22346
|
+
* `@alfe.ai/cli`). Unknown runtimes resolve to `undefined` (never throw) so the
|
|
22347
|
+
* connection-status report degrades gracefully instead of crashing.
|
|
22348
|
+
*/
|
|
22349
|
+
const RUNTIME_VERSION_COMMANDS = {
|
|
22350
|
+
openclaw: {
|
|
22351
|
+
command: "openclaw",
|
|
22352
|
+
args: ["--version"]
|
|
22353
|
+
},
|
|
22354
|
+
hermes: {
|
|
22355
|
+
command: "hermes",
|
|
22356
|
+
args: ["version"]
|
|
22357
|
+
}
|
|
22358
|
+
};
|
|
22359
|
+
/**
|
|
22360
|
+
* Pure resolver (exported for tests) — the per-runtime version command, or
|
|
22361
|
+
* `undefined` for an unknown runtime.
|
|
22362
|
+
*/
|
|
22363
|
+
function resolveRuntimeVersionCommand(runtime) {
|
|
22364
|
+
return RUNTIME_VERSION_COMMANDS[runtime];
|
|
22365
|
+
}
|
|
22366
|
+
/**
|
|
22367
|
+
* Resolve the installed runtime version by running the per-runtime version
|
|
22368
|
+
* command and returning the trimmed stdout. An unknown runtime (no command in
|
|
22369
|
+
* the map) returns `undefined` without spawning — it must never throw.
|
|
22286
22370
|
*/
|
|
22287
|
-
async function getRuntimeVersion() {
|
|
22371
|
+
async function getRuntimeVersion(runtime) {
|
|
22372
|
+
const cmd = resolveRuntimeVersionCommand(runtime);
|
|
22373
|
+
if (!cmd) {
|
|
22374
|
+
logger$1.debug({ runtime }, "No version command for runtime — skipping version detection");
|
|
22375
|
+
return;
|
|
22376
|
+
}
|
|
22288
22377
|
try {
|
|
22289
|
-
const { stdout } = await execFileAsync(
|
|
22378
|
+
const { stdout } = await execFileAsync(cmd.command, cmd.args);
|
|
22290
22379
|
return stdout.trim() || void 0;
|
|
22291
22380
|
} catch {
|
|
22292
|
-
logger$1.debug("Could not resolve
|
|
22381
|
+
logger$1.debug({ runtime }, "Could not resolve runtime version");
|
|
22293
22382
|
return;
|
|
22294
22383
|
}
|
|
22295
22384
|
}
|
|
@@ -22437,7 +22526,7 @@ async function startDaemon() {
|
|
|
22437
22526
|
await flushAndExit(1);
|
|
22438
22527
|
}
|
|
22439
22528
|
resolvedCliVersion = await getCliVersion();
|
|
22440
|
-
resolvedRuntimeVersion = await getRuntimeVersion();
|
|
22529
|
+
resolvedRuntimeVersion = await getRuntimeVersion(config.runtime);
|
|
22441
22530
|
logger$1.info({
|
|
22442
22531
|
cliVersion: resolvedCliVersion,
|
|
22443
22532
|
runtimeVersion: resolvedRuntimeVersion
|
|
@@ -22470,7 +22559,14 @@ async function startDaemon() {
|
|
|
22470
22559
|
home: runtimeCfg.workspace,
|
|
22471
22560
|
agentWorkspace: runtimeCfg.agentWorkspace
|
|
22472
22561
|
}, "Registered OpenClaw runtime applier");
|
|
22562
|
+
} else if (name === "hermes") {
|
|
22563
|
+
runtimeAppliers.set(name, new HermesApplier({ home: runtimeCfg.workspace }));
|
|
22564
|
+
logger$1.info({
|
|
22565
|
+
runtime: name,
|
|
22566
|
+
home: runtimeCfg.workspace
|
|
22567
|
+
}, "Registered Hermes runtime applier");
|
|
22473
22568
|
} else logger$1.warn({ runtime: name }, "Unknown runtime type — skipping");
|
|
22569
|
+
runtimeAppliersRef = runtimeAppliers;
|
|
22474
22570
|
const integrationsService = new IntegrationsService(new AlfeApiClient({
|
|
22475
22571
|
apiBaseUrl: config.apiEndpoint,
|
|
22476
22572
|
getToken: () => Promise.resolve(config.apiKey)
|
|
@@ -22485,22 +22581,25 @@ async function startDaemon() {
|
|
|
22485
22581
|
logger: logger$1,
|
|
22486
22582
|
idleTtlMs: 0
|
|
22487
22583
|
}, { connect: defaultConnect });
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
22495
|
-
|
|
22584
|
+
if (config.runtime === "hermes") logger$1.info("Hermes runtime — daemon MCP bundler left idle (store mirrored to config.yaml by HermesMcpSync)");
|
|
22585
|
+
else {
|
|
22586
|
+
await mcpManager.loadIntoBundler(mcpBundler);
|
|
22587
|
+
const warmBundler = (reason) => {
|
|
22588
|
+
if (!mcpBundler) return;
|
|
22589
|
+
mcpBundler.warmup().catch((err) => {
|
|
22590
|
+
logger$1.warn({
|
|
22591
|
+
err: err instanceof Error ? err.message : String(err),
|
|
22592
|
+
reason
|
|
22593
|
+
}, "MCP bundler warmup failed");
|
|
22594
|
+
});
|
|
22595
|
+
};
|
|
22596
|
+
warmBundler("startup");
|
|
22597
|
+
mcpManager.onChange(() => {
|
|
22598
|
+
warmBundler("store change");
|
|
22496
22599
|
});
|
|
22497
|
-
|
|
22498
|
-
|
|
22499
|
-
|
|
22500
|
-
warmBundler("store change");
|
|
22501
|
-
});
|
|
22502
|
-
logger$1.info("MCP bundler attached to manager — warming children");
|
|
22503
|
-
await runOpenclawMcpCleanup({
|
|
22600
|
+
logger$1.info("MCP bundler attached to manager — warming children");
|
|
22601
|
+
}
|
|
22602
|
+
if (config.runtime === "openclaw") await runOpenclawMcpCleanup({
|
|
22504
22603
|
manager: mcpManager,
|
|
22505
22604
|
logger: logger$1
|
|
22506
22605
|
}).catch((err) => {
|
|
@@ -22521,14 +22620,27 @@ async function startDaemon() {
|
|
|
22521
22620
|
});
|
|
22522
22621
|
const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
|
|
22523
22622
|
cloudClient.setIntegrationManager(integrationAdapter);
|
|
22524
|
-
|
|
22623
|
+
const requestRuntimeRestart = () => {
|
|
22525
22624
|
if (runtimeProcess) {
|
|
22526
22625
|
logger$1.info("Runtime state changed — restarting runtime");
|
|
22527
22626
|
runtimeProcess.restart().catch((err) => {
|
|
22528
22627
|
logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
22529
22628
|
});
|
|
22530
22629
|
}
|
|
22531
|
-
}
|
|
22630
|
+
};
|
|
22631
|
+
cloudClient.setRuntimeRestartNeededHandler(requestRuntimeRestart);
|
|
22632
|
+
if (config.runtime === "hermes") {
|
|
22633
|
+
const hermesCfg = config.runtimes[config.runtime];
|
|
22634
|
+
if (hermesCfg) {
|
|
22635
|
+
hermesMcpSync = new HermesMcpSync({
|
|
22636
|
+
manager: mcpManager,
|
|
22637
|
+
home: hermesCfg.workspace,
|
|
22638
|
+
apiKey: config.apiKey,
|
|
22639
|
+
requestRestart: requestRuntimeRestart
|
|
22640
|
+
});
|
|
22641
|
+
hermesMcpSync.start();
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
22532
22644
|
cloudClient.setOnReconciliationComplete(() => {
|
|
22533
22645
|
try {
|
|
22534
22646
|
const activeCommands = integrationManager.getActiveCommands();
|
|
@@ -22583,6 +22695,10 @@ async function startDaemon() {
|
|
|
22583
22695
|
await runtimeProcess.stop();
|
|
22584
22696
|
logger$1.debug("Runtime process stopped");
|
|
22585
22697
|
}
|
|
22698
|
+
if (hermesMcpSync) {
|
|
22699
|
+
hermesMcpSync.stop();
|
|
22700
|
+
hermesMcpSync = null;
|
|
22701
|
+
}
|
|
22586
22702
|
if (mcpBundler) {
|
|
22587
22703
|
logger$1.debug("Stopping MCP bundler...");
|
|
22588
22704
|
await mcpBundler.dispose();
|
|
@@ -22671,17 +22787,28 @@ async function handleCloudCommand(command) {
|
|
|
22671
22787
|
message: "Runtime upgrade already in progress"
|
|
22672
22788
|
}
|
|
22673
22789
|
};
|
|
22674
|
-
const
|
|
22790
|
+
const payload = command.payload;
|
|
22791
|
+
const runtime = config.runtime;
|
|
22792
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.8" : void 0);
|
|
22675
22793
|
upgradingRuntime = true;
|
|
22676
22794
|
setTimeout(() => {
|
|
22677
22795
|
(async () => {
|
|
22678
22796
|
try {
|
|
22679
22797
|
const { upgradeRuntime } = await import("./runtime-upgrade.js");
|
|
22680
|
-
if ((await upgradeRuntime(version, runtimeProcess)).success) {
|
|
22681
|
-
resolvedRuntimeVersion = await getRuntimeVersion();
|
|
22682
|
-
|
|
22683
|
-
logger$1.
|
|
22684
|
-
|
|
22798
|
+
if ((await upgradeRuntime(runtime, version, runtimeProcess)).success) {
|
|
22799
|
+
resolvedRuntimeVersion = await getRuntimeVersion(runtime);
|
|
22800
|
+
const reportedVersion = resolvedRuntimeVersion ?? version;
|
|
22801
|
+
if (!resolvedRuntimeVersion) logger$1.warn({
|
|
22802
|
+
runtime,
|
|
22803
|
+
requestedVersion: version
|
|
22804
|
+
}, "Could not detect runtime version after upgrade");
|
|
22805
|
+
if (reportedVersion) {
|
|
22806
|
+
logger$1.info({
|
|
22807
|
+
runtime,
|
|
22808
|
+
runtimeVersion: reportedVersion
|
|
22809
|
+
}, "Runtime version updated after upgrade");
|
|
22810
|
+
cloudClient.updateRuntimeVersionAndReRegister(reportedVersion);
|
|
22811
|
+
} else logger$1.warn({ runtime }, "No runtime version to report after upgrade — skipping re-register");
|
|
22685
22812
|
}
|
|
22686
22813
|
} catch (err) {
|
|
22687
22814
|
logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Runtime upgrade failed");
|
|
@@ -22696,6 +22823,7 @@ async function handleCloudCommand(command) {
|
|
|
22696
22823
|
status: "ok",
|
|
22697
22824
|
result: {
|
|
22698
22825
|
upgrading: true,
|
|
22826
|
+
runtime,
|
|
22699
22827
|
version
|
|
22700
22828
|
}
|
|
22701
22829
|
};
|
|
@@ -22776,17 +22904,30 @@ async function handleCloudCommand(command) {
|
|
|
22776
22904
|
message: "alfe.config_set requires key and value"
|
|
22777
22905
|
}
|
|
22778
22906
|
};
|
|
22907
|
+
const runtime = config.runtime;
|
|
22908
|
+
const applier = runtimeAppliersRef?.get(runtime);
|
|
22909
|
+
if (!applier || typeof applier.setConfigRaw !== "function") {
|
|
22910
|
+
const message = `No runtime applier supports config_set for runtime "${runtime}"`;
|
|
22911
|
+
logger$1.warn({
|
|
22912
|
+
runtime,
|
|
22913
|
+
key
|
|
22914
|
+
}, message);
|
|
22915
|
+
return {
|
|
22916
|
+
type: "COMMAND_ACK",
|
|
22917
|
+
commandId: command.commandId,
|
|
22918
|
+
status: "error",
|
|
22919
|
+
result: {
|
|
22920
|
+
code: "CONFIG_SET_UNSUPPORTED",
|
|
22921
|
+
message
|
|
22922
|
+
}
|
|
22923
|
+
};
|
|
22924
|
+
}
|
|
22779
22925
|
try {
|
|
22780
|
-
await
|
|
22781
|
-
"config",
|
|
22782
|
-
"set",
|
|
22783
|
-
key,
|
|
22784
|
-
value
|
|
22785
|
-
], { timeout: 1e4 });
|
|
22926
|
+
await applier.setConfigRaw(key, value);
|
|
22786
22927
|
logger$1.info({
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
}, "Applied config via
|
|
22928
|
+
runtime,
|
|
22929
|
+
key
|
|
22930
|
+
}, "Applied config via runtime applier setConfigRaw");
|
|
22790
22931
|
return {
|
|
22791
22932
|
type: "COMMAND_ACK",
|
|
22792
22933
|
commandId: command.commandId,
|
|
@@ -22800,9 +22941,9 @@ async function handleCloudCommand(command) {
|
|
|
22800
22941
|
const message = err instanceof Error ? err.message : String(err);
|
|
22801
22942
|
logger$1.error({
|
|
22802
22943
|
err: message,
|
|
22803
|
-
|
|
22804
|
-
|
|
22805
|
-
}, "Failed to apply config via
|
|
22944
|
+
runtime,
|
|
22945
|
+
key
|
|
22946
|
+
}, "Failed to apply config via runtime applier");
|
|
22806
22947
|
return {
|
|
22807
22948
|
type: "COMMAND_ACK",
|
|
22808
22949
|
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/dist/src/index.d.ts
CHANGED
|
@@ -60,6 +60,18 @@ interface AgentWorkspaceConfig {
|
|
|
60
60
|
templateKey?: string;
|
|
61
61
|
defaultModel?: string;
|
|
62
62
|
files: Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Filenames within `files` that are agent-owned persona files (SOUL.md,
|
|
65
|
+
* IDENTITY.md, AGENTS.md, USER.md, BOOTSTRAP.md, …). The consumer
|
|
66
|
+
* (`writeWorkspaceFiles`) seeds these ONLY IF ABSENT so an agent's own edits
|
|
67
|
+
* — restored by the openclaw-sync backup on a fresh VM — are never clobbered
|
|
68
|
+
* by a setup-time re-seed. Filenames NOT in this set keep overwrite-on-write
|
|
69
|
+
* semantics. The templates `/files` endpoint returns persona files
|
|
70
|
+
* exclusively today, so `fetchAgentConfig` populates this with every key; the
|
|
71
|
+
* field is threaded explicitly rather than assumed so a future workspace-file
|
|
72
|
+
* source can mix persona (seed-if-missing) and non-persona (overwrite) files.
|
|
73
|
+
*/
|
|
74
|
+
personaFiles?: string[];
|
|
63
75
|
}
|
|
64
76
|
declare const ALFE_DIR: string;
|
|
65
77
|
declare const SOCKET_PATH: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,14 +22,22 @@
|
|
|
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.
|
|
30
|
-
"@alfe.ai/mcp-bundler": "^0.2.
|
|
25
|
+
"@alfe.ai/agent-api-client": "^0.4.0",
|
|
26
|
+
"@alfe.ai/ai-proxy-local": "^0.0.12",
|
|
27
|
+
"@alfe.ai/config": "^0.2.0",
|
|
28
|
+
"@alfe.ai/integration-manifest": "^0.3.1",
|
|
29
|
+
"@alfe.ai/integrations": "^0.2.4",
|
|
30
|
+
"@alfe.ai/mcp-bundler": "^0.2.2"
|
|
31
31
|
},
|
|
32
32
|
"license": "UNLICENSED",
|
|
33
|
+
"homepage": "https://alfe.ai",
|
|
34
|
+
"author": "Alfe (https://alfe.ai)",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"alfe",
|
|
37
|
+
"ai-agents",
|
|
38
|
+
"agent",
|
|
39
|
+
"llm"
|
|
40
|
+
],
|
|
33
41
|
"scripts": {
|
|
34
42
|
"build": "tsdown",
|
|
35
43
|
"dev": "tsdown --watch",
|