@alfe.ai/gateway 0.9.5 → 0.9.6
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 +36 -37
- package/dist/health.js +412 -153
- package/dist/src/index.d.ts +1 -7
- package/dist/upgrade.js +8 -5
- package/package.json +7 -7
package/dist/health.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as logger$1 } from "./logger.js";
|
|
2
2
|
import { a as captureFatal, c as captureRuntimeCrash, d as initAgentSentry, f as setAgentContext, l as captureRuntimeErrorOutput, o as captureIntegrationFailure, s as captureMcpFailure, u as flushSentry } from "./sentry.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { chmod, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import pino from "pino";
|
|
@@ -12,9 +12,9 @@ import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
|
12
12
|
import { parse } from "smol-toml";
|
|
13
13
|
import WebSocket from "ws";
|
|
14
14
|
import { execFile, execSync, spawn } from "node:child_process";
|
|
15
|
-
import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
|
|
16
|
-
import { Manager, McpBundler, defaultConnect } from "@alfe.ai/mcp-bundler";
|
|
17
15
|
import { promisify } from "node:util";
|
|
16
|
+
import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
|
|
17
|
+
import { Manager, McpBundler, defaultConnect, serverLaunchFingerprint } from "@alfe.ai/mcp-bundler";
|
|
18
18
|
import { createConnection, createServer } from "node:net";
|
|
19
19
|
import stream, { Readable } from "stream";
|
|
20
20
|
import util, { format } from "util";
|
|
@@ -94,7 +94,7 @@ const PINNED_OPENCLAW_VERSION = "2026.6.11";
|
|
|
94
94
|
* `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
|
|
95
95
|
* keep in sync.
|
|
96
96
|
*/
|
|
97
|
-
const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.
|
|
97
|
+
const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.6";
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region ../../packages-internal/ids/dist/prefixes.js
|
|
100
100
|
const ID_PREFIXES = {
|
|
@@ -110,6 +110,7 @@ const ID_PREFIXES = {
|
|
|
110
110
|
promoCode: "prc",
|
|
111
111
|
promoRedemption: "prr",
|
|
112
112
|
pendingPromo: "pdp",
|
|
113
|
+
remoteSession: "rms",
|
|
113
114
|
webhook: "whk",
|
|
114
115
|
webhookDelivery: "wdl",
|
|
115
116
|
onboardingSession: "obs",
|
|
@@ -298,7 +299,7 @@ var AlfeApiClient = class {
|
|
|
298
299
|
getToken;
|
|
299
300
|
onAuthFailure;
|
|
300
301
|
constructor(options) {
|
|
301
|
-
this.apiBaseUrl = options.apiBaseUrl;
|
|
302
|
+
this.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
302
303
|
this.getToken = options.getToken;
|
|
303
304
|
this.onAuthFailure = options.onAuthFailure;
|
|
304
305
|
}
|
|
@@ -315,7 +316,7 @@ var AlfeApiClient = class {
|
|
|
315
316
|
try {
|
|
316
317
|
const url = `${this.apiBaseUrl}${path}`;
|
|
317
318
|
const headers = new Headers(options?.headers);
|
|
318
|
-
headers.set("Content-Type", "application/json");
|
|
319
|
+
if (typeof options?.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
319
320
|
headers.set("x-correlation-id", correlationId());
|
|
320
321
|
if (!skipAuth) {
|
|
321
322
|
const token = await this.getToken();
|
|
@@ -347,15 +348,24 @@ var AlfeApiClient = class {
|
|
|
347
348
|
}
|
|
348
349
|
};
|
|
349
350
|
}
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
const text = res.status === 204 ? "" : await res.text();
|
|
352
|
+
let body;
|
|
353
|
+
if (text) try {
|
|
354
|
+
body = JSON.parse(text);
|
|
355
|
+
} catch {
|
|
356
|
+
body = text;
|
|
357
|
+
}
|
|
358
|
+
if (!res.ok) {
|
|
359
|
+
const errBody = body;
|
|
360
|
+
return {
|
|
354
361
|
ok: false,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
362
|
+
result: {
|
|
363
|
+
ok: false,
|
|
364
|
+
error: (typeof errBody === "object" ? errBody.message : void 0) ?? (typeof body === "string" ? body : `API error: ${String(res.status)}`),
|
|
365
|
+
status: res.status
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
359
369
|
return {
|
|
360
370
|
ok: true,
|
|
361
371
|
res,
|
|
@@ -378,6 +388,10 @@ var AlfeApiClient = class {
|
|
|
378
388
|
async request(path, options) {
|
|
379
389
|
const result = await this._fetch(path, options);
|
|
380
390
|
if (!result.ok) return result.result;
|
|
391
|
+
if (result.body === void 0) return {
|
|
392
|
+
ok: true,
|
|
393
|
+
data: void 0
|
|
394
|
+
};
|
|
381
395
|
return {
|
|
382
396
|
ok: true,
|
|
383
397
|
data: result.body.data
|
|
@@ -392,6 +406,10 @@ var AlfeApiClient = class {
|
|
|
392
406
|
async publicRequest(path, options) {
|
|
393
407
|
const result = await this._fetch(path, options, true);
|
|
394
408
|
if (!result.ok) return result.result;
|
|
409
|
+
if (result.body === void 0) return {
|
|
410
|
+
ok: true,
|
|
411
|
+
data: void 0
|
|
412
|
+
};
|
|
395
413
|
return {
|
|
396
414
|
ok: true,
|
|
397
415
|
data: result.body.data
|
|
@@ -422,7 +440,9 @@ var AlfeApiClient = class {
|
|
|
422
440
|
* (both need `readonly [string, ...string[]]`).
|
|
423
441
|
*/
|
|
424
442
|
function enumValues(obj) {
|
|
425
|
-
|
|
443
|
+
const values = Object.values(obj);
|
|
444
|
+
if (values.length === 0) throw new TypeError("enumValues requires at least one value");
|
|
445
|
+
return values;
|
|
426
446
|
}
|
|
427
447
|
Object.freeze({ status: "aborted" });
|
|
428
448
|
function $constructor(name, initializer, params) {
|
|
@@ -4105,6 +4125,12 @@ function refine(fn, _params = {}) {
|
|
|
4105
4125
|
function superRefine(fn) {
|
|
4106
4126
|
return /* @__PURE__ */ _superRefine(fn);
|
|
4107
4127
|
}
|
|
4128
|
+
//#endregion
|
|
4129
|
+
//#region ../../packages-internal/types/dist/lib/usd.js
|
|
4130
|
+
function isWholeUsdCentAmount(amountUsd) {
|
|
4131
|
+
const roundedCents = Math.round(amountUsd * 100);
|
|
4132
|
+
return Math.abs(amountUsd - roundedCents / 100) < 1e-9;
|
|
4133
|
+
}
|
|
4108
4134
|
enumValues({
|
|
4109
4135
|
Active: "active",
|
|
4110
4136
|
PastDue: "past_due",
|
|
@@ -4133,6 +4159,7 @@ enumValues({
|
|
|
4133
4159
|
Month: "month",
|
|
4134
4160
|
Year: "year"
|
|
4135
4161
|
});
|
|
4162
|
+
number().min(0).max(1e6).refine(isWholeUsdCentAmount, "Stripe price amounts cannot contain fractional cents");
|
|
4136
4163
|
object({
|
|
4137
4164
|
gracePeriodDays: number().int().positive().optional(),
|
|
4138
4165
|
launchDiscountEnabled: boolean().optional()
|
|
@@ -4268,6 +4295,7 @@ enumValues({
|
|
|
4268
4295
|
Completed: "completed",
|
|
4269
4296
|
Failed: "failed"
|
|
4270
4297
|
});
|
|
4298
|
+
number().min(.01).max(1e6).refine(isWholeUsdCentAmount, "discountAmountUsd cannot contain fractional cents");
|
|
4271
4299
|
enumValues({
|
|
4272
4300
|
BalanceCredit: "balance_credit",
|
|
4273
4301
|
SubscriptionDiscount: "subscription_discount",
|
|
@@ -4448,7 +4476,7 @@ _enum(XAI_MODELS);
|
|
|
4448
4476
|
_enum(ZHIPU_MODELS);
|
|
4449
4477
|
_enum(MOONSHOT_MODELS);
|
|
4450
4478
|
_enum(QWEN_MODELS);
|
|
4451
|
-
string().min(1);
|
|
4479
|
+
string().trim().min(1);
|
|
4452
4480
|
AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini35Flash, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, MistralModel.MagistralMedium, MistralModel.MagistralSmall, MistralModel.DevstralMedium, XAIModel.Grok45, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
|
|
4453
4481
|
AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini35Flash, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, MistralModel.MagistralMedium, MistralModel.MagistralSmall, MistralModel.DevstralMedium, XAIModel.Grok45, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
|
|
4454
4482
|
enumValues({
|
|
@@ -4553,7 +4581,7 @@ const RecipientStrategy = {
|
|
|
4553
4581
|
TenantAdmins: "tenant_admins",
|
|
4554
4582
|
SpecificUser: "specific_user"
|
|
4555
4583
|
};
|
|
4556
|
-
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.StartupGrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.GrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.EnterpriseGrantEnded, 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.
|
|
4584
|
+
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.StartupGrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.GrantEnded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.EnterpriseGrantEnded, 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.TenantAdmins, 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;
|
|
4557
4585
|
//#endregion
|
|
4558
4586
|
//#region ../../packages-internal/api-client/dist/services/auth.js
|
|
4559
4587
|
var AuthService = class {
|
|
@@ -4578,7 +4606,7 @@ var AuthService = class {
|
|
|
4578
4606
|
});
|
|
4579
4607
|
}
|
|
4580
4608
|
deleteToken(tokenId) {
|
|
4581
|
-
return this.client.request(`${this.prefix}/tokens/${tokenId}`, { method: "DELETE" });
|
|
4609
|
+
return this.client.request(`${this.prefix}/tokens/${encodeURIComponent(tokenId)}`, { method: "DELETE" });
|
|
4582
4610
|
}
|
|
4583
4611
|
/**
|
|
4584
4612
|
* Start a device-code flow. Called by the CLI when the user runs
|
|
@@ -4798,6 +4826,12 @@ var IntegrationsService = class {
|
|
|
4798
4826
|
constructor(client) {
|
|
4799
4827
|
this.client = client;
|
|
4800
4828
|
}
|
|
4829
|
+
agentIntegrationsPath(agentId) {
|
|
4830
|
+
return `/integrations/agents/${encodeURIComponent(agentId)}`;
|
|
4831
|
+
}
|
|
4832
|
+
agentIntegrationPath(agentId, integrationId) {
|
|
4833
|
+
return `${this.agentIntegrationsPath(agentId)}/${encodeURIComponent(integrationId)}`;
|
|
4834
|
+
}
|
|
4801
4835
|
listScopedInstalls(scope, scopeId) {
|
|
4802
4836
|
const params = new URLSearchParams({
|
|
4803
4837
|
scope,
|
|
@@ -4846,31 +4880,31 @@ var IntegrationsService = class {
|
|
|
4846
4880
|
}
|
|
4847
4881
|
listIntegrations(agentId, options) {
|
|
4848
4882
|
const params = new URLSearchParams();
|
|
4849
|
-
if (options?.includeInherited) params.set("includeInherited",
|
|
4850
|
-
if (options?.effective) params.set("effective",
|
|
4883
|
+
if (options?.includeInherited !== void 0) params.set("includeInherited", String(options.includeInherited));
|
|
4884
|
+
if (options?.effective !== void 0) params.set("effective", String(options.effective));
|
|
4851
4885
|
const qs = params.toString();
|
|
4852
|
-
return this.client.request(
|
|
4886
|
+
return this.client.request(`${this.agentIntegrationsPath(agentId)}${qs ? `?${qs}` : ""}`);
|
|
4853
4887
|
}
|
|
4854
4888
|
installIntegration(agentId, data) {
|
|
4855
|
-
return this.client.request(
|
|
4889
|
+
return this.client.request(this.agentIntegrationsPath(agentId), {
|
|
4856
4890
|
method: "POST",
|
|
4857
4891
|
body: JSON.stringify(data)
|
|
4858
4892
|
});
|
|
4859
4893
|
}
|
|
4860
4894
|
getIntegrationConfig(agentId, integrationId) {
|
|
4861
|
-
return this.client.request(
|
|
4895
|
+
return this.client.request(`${this.agentIntegrationPath(agentId, integrationId)}/config`);
|
|
4862
4896
|
}
|
|
4863
4897
|
updateIntegration(agentId, integrationId, data) {
|
|
4864
|
-
return this.client.request(
|
|
4898
|
+
return this.client.request(this.agentIntegrationPath(agentId, integrationId), {
|
|
4865
4899
|
method: "PATCH",
|
|
4866
4900
|
body: JSON.stringify(data)
|
|
4867
4901
|
});
|
|
4868
4902
|
}
|
|
4869
4903
|
removeIntegration(agentId, integrationId) {
|
|
4870
|
-
return this.client.request(
|
|
4904
|
+
return this.client.request(this.agentIntegrationPath(agentId, integrationId), { method: "DELETE" });
|
|
4871
4905
|
}
|
|
4872
4906
|
reinstallIntegration(agentId, integrationId) {
|
|
4873
|
-
return this.client.request(
|
|
4907
|
+
return this.client.request(`${this.agentIntegrationPath(agentId, integrationId)}/reinstall`, { method: "POST" });
|
|
4874
4908
|
}
|
|
4875
4909
|
/**
|
|
4876
4910
|
* Upgrade an integration to the registry's latest version via the fast,
|
|
@@ -4879,7 +4913,7 @@ var IntegrationsService = class {
|
|
|
4879
4913
|
* {@link reinstallIntegration}, which is the destructive repair path.
|
|
4880
4914
|
*/
|
|
4881
4915
|
upgradeIntegration(agentId, integrationId) {
|
|
4882
|
-
return this.client.request(
|
|
4916
|
+
return this.client.request(`${this.agentIntegrationPath(agentId, integrationId)}/upgrade`, { method: "POST" });
|
|
4883
4917
|
}
|
|
4884
4918
|
getRegistry() {
|
|
4885
4919
|
return this.client.request("/integrations/registry");
|
|
@@ -4890,17 +4924,16 @@ var IntegrationsService = class {
|
|
|
4890
4924
|
body: JSON.stringify({ agentId })
|
|
4891
4925
|
});
|
|
4892
4926
|
}
|
|
4893
|
-
getDiscordGuildChannels(guildId) {
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
listMobileNumbers() {
|
|
4897
|
-
return this.client.request("/mobile/numbers");
|
|
4927
|
+
getDiscordGuildChannels(agentId, guildId) {
|
|
4928
|
+
const query = new URLSearchParams({ agentId });
|
|
4929
|
+
return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels?${query}`);
|
|
4898
4930
|
}
|
|
4899
4931
|
searchMobileNumbers(country, query) {
|
|
4900
4932
|
const params = new URLSearchParams();
|
|
4901
|
-
if (country) params.set("country", country);
|
|
4902
|
-
if (query) params.set("query", query);
|
|
4903
|
-
|
|
4933
|
+
if (country !== void 0) params.set("country", country);
|
|
4934
|
+
if (query !== void 0) params.set("query", query);
|
|
4935
|
+
const qs = params.toString();
|
|
4936
|
+
return this.client.request(`/mobile/numbers/search${qs ? `?${qs}` : ""}`);
|
|
4904
4937
|
}
|
|
4905
4938
|
assignMobileNumber(agentId, phoneNumber, countryCode) {
|
|
4906
4939
|
return this.client.request("/mobile/numbers/assign", {
|
|
@@ -4921,10 +4954,6 @@ var IntegrationsService = class {
|
|
|
4921
4954
|
getMobileNumber(agentId) {
|
|
4922
4955
|
return this.client.request(`/mobile/numbers?agentId=${encodeURIComponent(agentId)}`);
|
|
4923
4956
|
}
|
|
4924
|
-
disconnectGoogle(agentId, email) {
|
|
4925
|
-
const query = email ? `?email=${encodeURIComponent(email)}` : "";
|
|
4926
|
-
return this.client.request(`/google/agents/${encodeURIComponent(agentId)}/account${query}`, { method: "DELETE" });
|
|
4927
|
-
}
|
|
4928
4957
|
getAtlassianSites(agentId) {
|
|
4929
4958
|
return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`);
|
|
4930
4959
|
}
|
|
@@ -4966,10 +4995,7 @@ const PID_PATH = join(ALFE_DIR, "gateway.pid");
|
|
|
4966
4995
|
* Returns agentId (derived from tokenId) and orgId (tenantId).
|
|
4967
4996
|
*/
|
|
4968
4997
|
async function resolveAgentIdentity(apiKey, apiEndpoint) {
|
|
4969
|
-
logger$1.debug({
|
|
4970
|
-
apiEndpoint,
|
|
4971
|
-
keyPrefix: apiKey.slice(0, 8) + "..."
|
|
4972
|
-
}, "Resolving agent identity...");
|
|
4998
|
+
logger$1.debug({ apiEndpoint }, "Resolving agent identity...");
|
|
4973
4999
|
const auth = new AuthService(new AlfeApiClient({
|
|
4974
5000
|
apiBaseUrl: apiEndpoint,
|
|
4975
5001
|
getToken: () => Promise.resolve(apiKey)
|
|
@@ -5065,21 +5091,32 @@ function deriveAgentWorkspace(runtime, home) {
|
|
|
5065
5091
|
if (runtime === "claude-code") return join(home, "workspace");
|
|
5066
5092
|
return home;
|
|
5067
5093
|
}
|
|
5094
|
+
const UNSAFE_RECORD_KEYS = new Set([
|
|
5095
|
+
"__proto__",
|
|
5096
|
+
"constructor",
|
|
5097
|
+
"prototype"
|
|
5098
|
+
]);
|
|
5099
|
+
function isRecord$1(value) {
|
|
5100
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5101
|
+
}
|
|
5102
|
+
function parseRuntimeConfigs(parsed) {
|
|
5103
|
+
if (!isRecord$1(parsed) || !isRecord$1(parsed.runtimes)) return {};
|
|
5104
|
+
const result = {};
|
|
5105
|
+
for (const [name, candidate] of Object.entries(parsed.runtimes)) {
|
|
5106
|
+
if (name.length === 0 || name.length > 128 || UNSAFE_RECORD_KEYS.has(name) || !isRecord$1(candidate) || typeof candidate.workspace !== "string" || candidate.workspace.length === 0 || candidate.workspace.length > 4096) continue;
|
|
5107
|
+
const workspace = candidate.workspace.startsWith("~/") ? join(homedir(), candidate.workspace.slice(2)) : candidate.workspace;
|
|
5108
|
+
result[name] = {
|
|
5109
|
+
workspace,
|
|
5110
|
+
agentWorkspace: deriveAgentWorkspace(name, workspace)
|
|
5111
|
+
};
|
|
5112
|
+
}
|
|
5113
|
+
return result;
|
|
5114
|
+
}
|
|
5068
5115
|
async function loadRuntimeConfigs() {
|
|
5069
5116
|
const configPath = join(ALFE_DIR, "config.toml");
|
|
5070
5117
|
if (!existsSync(configPath)) return {};
|
|
5071
5118
|
try {
|
|
5072
|
-
|
|
5073
|
-
if (!runtimes) return {};
|
|
5074
|
-
const result = {};
|
|
5075
|
-
for (const [name, cfg] of Object.entries(runtimes)) if (typeof cfg.workspace === "string") {
|
|
5076
|
-
const workspace = cfg.workspace.startsWith("~/") ? join(homedir(), cfg.workspace.slice(2)) : cfg.workspace;
|
|
5077
|
-
result[name] = {
|
|
5078
|
-
workspace,
|
|
5079
|
-
agentWorkspace: deriveAgentWorkspace(name, workspace)
|
|
5080
|
-
};
|
|
5081
|
-
}
|
|
5082
|
-
return result;
|
|
5119
|
+
return parseRuntimeConfigs(parse(await readFile(configPath, "utf-8")));
|
|
5083
5120
|
} catch {
|
|
5084
5121
|
return {};
|
|
5085
5122
|
}
|
|
@@ -5255,6 +5292,8 @@ async function fetchAgentConfig(apiKey, apiEndpoint) {
|
|
|
5255
5292
|
}
|
|
5256
5293
|
//#endregion
|
|
5257
5294
|
//#region src/protocol.ts
|
|
5295
|
+
/** Maximum size of one newline-delimited local IPC message. */
|
|
5296
|
+
const MAX_IPC_MESSAGE_BYTES = 1024 * 1024;
|
|
5258
5297
|
/**
|
|
5259
5298
|
* Map a cloud command name to an IPC method name.
|
|
5260
5299
|
* Cloud commands use dot-notation matching IPC methods.
|
|
@@ -5353,23 +5392,56 @@ function parseMessage(raw) {
|
|
|
5353
5392
|
return null;
|
|
5354
5393
|
}
|
|
5355
5394
|
}
|
|
5395
|
+
const MAX_DESIRED_INTEGRATIONS = 4096;
|
|
5396
|
+
const MAX_CONFIG_VALUES = 512;
|
|
5397
|
+
function isRecord(value) {
|
|
5398
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5399
|
+
}
|
|
5400
|
+
function isNonEmptyString(value) {
|
|
5401
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
5402
|
+
}
|
|
5403
|
+
function isDesiredCustomSource(value) {
|
|
5404
|
+
if (!isRecord(value)) return false;
|
|
5405
|
+
return isNonEmptyString(value.connectionId) && isNonEmptyString(value.manifestRepo) && isNonEmptyString(value.manifestPath) && isNonEmptyString(value.manifestRef) && isNonEmptyString(value.manifestSha) && isNonEmptyString(value.manifestCommit) && isRecord(value.manifest);
|
|
5406
|
+
}
|
|
5407
|
+
function isDesiredConfig(value) {
|
|
5408
|
+
if (!isRecord(value)) return false;
|
|
5409
|
+
if (!Number.isSafeInteger(value.version) || value.version < 0) return false;
|
|
5410
|
+
if (!isRecord(value.values)) return false;
|
|
5411
|
+
const entries = Object.entries(value.values);
|
|
5412
|
+
return entries.length <= MAX_CONFIG_VALUES && entries.every(([key, configValue]) => key.trim().length > 0 && (typeof configValue === "string" || configValue === null));
|
|
5413
|
+
}
|
|
5414
|
+
function isDesiredIntegration(value) {
|
|
5415
|
+
if (!isRecord(value)) return false;
|
|
5416
|
+
return isNonEmptyString(value.integrationId) && typeof value.version === "string" && (value.desiredStatus === "active" || value.desiredStatus === "removed") && (value.reinstallRequestedAt === void 0 || typeof value.reinstallRequestedAt === "string") && (value.customSource === void 0 || isDesiredCustomSource(value.customSource));
|
|
5417
|
+
}
|
|
5356
5418
|
/**
|
|
5357
5419
|
* Type guard: is this a cloud COMMAND message?
|
|
5358
5420
|
*/
|
|
5359
5421
|
function isCloudCommand(msg) {
|
|
5360
|
-
|
|
5422
|
+
if (!isRecord(msg)) return false;
|
|
5423
|
+
return msg.type === "COMMAND" && isNonEmptyString(msg.commandId) && isNonEmptyString(msg.agentId) && isNonEmptyString(msg.command);
|
|
5361
5424
|
}
|
|
5362
5425
|
/**
|
|
5363
5426
|
* Type guard: is this a cloud SERVICE_ACK message?
|
|
5364
5427
|
*/
|
|
5365
5428
|
function isCloudServiceAck(msg) {
|
|
5366
|
-
return
|
|
5429
|
+
return isRecord(msg) && msg.type === "SERVICE_ACK" && (msg.status === "ok" || msg.status === "error");
|
|
5367
5430
|
}
|
|
5368
5431
|
/**
|
|
5369
5432
|
* Type guard: is this a cloud DESIRED_STATE message?
|
|
5370
5433
|
*/
|
|
5371
5434
|
function isCloudDesiredState(msg) {
|
|
5372
|
-
|
|
5435
|
+
if (!isRecord(msg) || msg.type !== "DESIRED_STATE" || !Array.isArray(msg.integrations)) return false;
|
|
5436
|
+
if (msg.integrations.length > MAX_DESIRED_INTEGRATIONS) return false;
|
|
5437
|
+
if (msg.config !== void 0 && !isDesiredConfig(msg.config)) return false;
|
|
5438
|
+
const integrationIds = /* @__PURE__ */ new Set();
|
|
5439
|
+
for (const integration of msg.integrations) {
|
|
5440
|
+
if (!isDesiredIntegration(integration)) return false;
|
|
5441
|
+
if (integrationIds.has(integration.integrationId)) return false;
|
|
5442
|
+
integrationIds.add(integration.integrationId);
|
|
5443
|
+
}
|
|
5444
|
+
return true;
|
|
5373
5445
|
}
|
|
5374
5446
|
/**
|
|
5375
5447
|
* Create a RECONCILIATION_REPORT message.
|
|
@@ -5402,7 +5474,7 @@ function isCloudPing(msg) {
|
|
|
5402
5474
|
* Type guard: is this an IPC request?
|
|
5403
5475
|
*/
|
|
5404
5476
|
function isIPCRequest(msg) {
|
|
5405
|
-
return
|
|
5477
|
+
return isRecord(msg) && msg.type === "req" && isNonEmptyString(msg.id) && isNonEmptyString(msg.method) && isRecord(msg.params);
|
|
5406
5478
|
}
|
|
5407
5479
|
/**
|
|
5408
5480
|
* Type guard: is this an IPC response?
|
|
@@ -5558,7 +5630,20 @@ var ReconciliationEngine = class {
|
|
|
5558
5630
|
localIntegrations = await this.manager.getInstalledIntegrations();
|
|
5559
5631
|
} catch (err) {
|
|
5560
5632
|
log$5.error({ err }, "Failed to get local integrations");
|
|
5561
|
-
|
|
5633
|
+
captureIntegrationFailure("_reconciliation", "read_local_state", err);
|
|
5634
|
+
const errorMessage = "Failed to read local integration state; reconciliation skipped";
|
|
5635
|
+
const affectedIds = desiredIntegrations.length > 0 ? desiredIntegrations.map((desired) => desired.integrationId) : ["*"];
|
|
5636
|
+
report.errors.push(...affectedIds.map((integrationId) => ({
|
|
5637
|
+
integrationId,
|
|
5638
|
+
error: errorMessage
|
|
5639
|
+
})));
|
|
5640
|
+
report.results.push(...desiredIntegrations.map((desired) => ({
|
|
5641
|
+
integrationId: desired.integrationId,
|
|
5642
|
+
action: "error",
|
|
5643
|
+
actualStatus: "unknown",
|
|
5644
|
+
errorMessage
|
|
5645
|
+
})));
|
|
5646
|
+
return report;
|
|
5562
5647
|
}
|
|
5563
5648
|
const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
|
|
5564
5649
|
const desiredMap = new Map(desiredIntegrations.map((d) => [d.integrationId, d]));
|
|
@@ -5913,6 +5998,7 @@ var CloudClient = class {
|
|
|
5913
5998
|
closed = false;
|
|
5914
5999
|
registered = false;
|
|
5915
6000
|
pingTimer = null;
|
|
6001
|
+
reconnectTimer = null;
|
|
5916
6002
|
lastPong = 0;
|
|
5917
6003
|
config;
|
|
5918
6004
|
onCommand = null;
|
|
@@ -5985,6 +6071,7 @@ var CloudClient = class {
|
|
|
5985
6071
|
agentId: this.config.agentId
|
|
5986
6072
|
}, "Cloud client starting...");
|
|
5987
6073
|
this.closed = false;
|
|
6074
|
+
if (this.ws || this.reconnectTimer) return;
|
|
5988
6075
|
this.doConnect();
|
|
5989
6076
|
}
|
|
5990
6077
|
/**
|
|
@@ -5994,12 +6081,14 @@ var CloudClient = class {
|
|
|
5994
6081
|
logger$1.debug("Cloud client stopping...");
|
|
5995
6082
|
this.closed = true;
|
|
5996
6083
|
this.stopPingTimer();
|
|
6084
|
+
this.stopReconnectTimer();
|
|
5997
6085
|
if (this.ws) {
|
|
5998
|
-
|
|
6086
|
+
const ws = this.ws;
|
|
6087
|
+
this.ws = null;
|
|
6088
|
+
logger$1.debug({ readyState: ws.readyState }, "Cloud: closing WebSocket");
|
|
5999
6089
|
try {
|
|
6000
|
-
|
|
6090
|
+
ws.close(1e3, "Daemon shutting down");
|
|
6001
6091
|
} catch {}
|
|
6002
|
-
this.ws = null;
|
|
6003
6092
|
}
|
|
6004
6093
|
this.registered = false;
|
|
6005
6094
|
this.reconciling = false;
|
|
@@ -6034,35 +6123,45 @@ var CloudClient = class {
|
|
|
6034
6123
|
logger$1.debug("Cloud: doConnect skipped — client is closed");
|
|
6035
6124
|
return;
|
|
6036
6125
|
}
|
|
6126
|
+
if (this.ws) {
|
|
6127
|
+
logger$1.debug("Cloud: doConnect skipped — connection already exists");
|
|
6128
|
+
return;
|
|
6129
|
+
}
|
|
6037
6130
|
logger$1.info({
|
|
6038
6131
|
url: this.config.wsUrl,
|
|
6039
6132
|
backoffMs: this.backoffMs
|
|
6040
6133
|
}, "Connecting to cloud gateway...");
|
|
6041
|
-
logger$1.debug({
|
|
6042
|
-
|
|
6043
|
-
keyPrefix: this.config.apiKey.slice(0, 12) + "..."
|
|
6044
|
-
}, "Cloud: connection details");
|
|
6045
|
-
this.ws = new WebSocket(this.config.wsUrl, {
|
|
6134
|
+
logger$1.debug({ agentId: this.config.agentId }, "Cloud: connection details");
|
|
6135
|
+
const ws = new WebSocket(this.config.wsUrl, {
|
|
6046
6136
|
headers: { authorization: `Bearer ${this.config.apiKey}` },
|
|
6047
6137
|
maxPayload: 10 * 1024 * 1024,
|
|
6048
6138
|
handshakeTimeout: 1e4
|
|
6049
6139
|
});
|
|
6050
|
-
this.ws
|
|
6140
|
+
this.ws = ws;
|
|
6141
|
+
ws.on("open", () => {
|
|
6142
|
+
if (this.ws !== ws) return;
|
|
6051
6143
|
logger$1.info("Cloud WebSocket connected");
|
|
6052
|
-
logger$1.debug({ readyState:
|
|
6144
|
+
logger$1.debug({ readyState: ws.readyState }, "Cloud: WebSocket open, sending registration...");
|
|
6053
6145
|
this.backoffMs = 1e3;
|
|
6054
6146
|
this.sendRegister();
|
|
6055
6147
|
});
|
|
6056
|
-
|
|
6148
|
+
ws.on("message", (data) => {
|
|
6149
|
+
if (this.ws !== ws) return;
|
|
6057
6150
|
const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
|
|
6058
6151
|
logger$1.debug({ size: text.length }, "Cloud: received message");
|
|
6059
6152
|
this.handleMessage(text);
|
|
6060
6153
|
});
|
|
6061
|
-
|
|
6154
|
+
ws.on("ping", () => {
|
|
6155
|
+
if (this.ws !== ws) return;
|
|
6062
6156
|
logger$1.debug("Cloud: received ping, sending pong");
|
|
6063
|
-
|
|
6157
|
+
ws.pong();
|
|
6064
6158
|
});
|
|
6065
|
-
|
|
6159
|
+
ws.on("close", (code, reason) => {
|
|
6160
|
+
if (this.ws !== ws) {
|
|
6161
|
+
logger$1.debug("Cloud: ignoring close from superseded WebSocket");
|
|
6162
|
+
return;
|
|
6163
|
+
}
|
|
6164
|
+
this.ws = null;
|
|
6066
6165
|
logger$1.warn({
|
|
6067
6166
|
code,
|
|
6068
6167
|
reason: reason.toString()
|
|
@@ -6072,7 +6171,8 @@ var CloudClient = class {
|
|
|
6072
6171
|
this.onConnectionChange?.(false);
|
|
6073
6172
|
this.scheduleReconnect();
|
|
6074
6173
|
});
|
|
6075
|
-
|
|
6174
|
+
ws.on("error", (err) => {
|
|
6175
|
+
if (this.ws !== ws) return;
|
|
6076
6176
|
logger$1.error({
|
|
6077
6177
|
err: err.message,
|
|
6078
6178
|
url: this.config.wsUrl
|
|
@@ -6119,10 +6219,16 @@ var CloudClient = class {
|
|
|
6119
6219
|
if (ack.status === "ok") {
|
|
6120
6220
|
logger$1.info("Cloud: registered successfully ✅");
|
|
6121
6221
|
this.registered = true;
|
|
6222
|
+
this.lastPong = Date.now();
|
|
6122
6223
|
this.startPingTimer();
|
|
6123
6224
|
this.onConnectionChange?.(true);
|
|
6225
|
+
} else if (ack.retryable === false) {
|
|
6226
|
+
logger$1.error({ message: ack.message }, "Cloud: registration rejected — not retrying");
|
|
6227
|
+
this.closed = true;
|
|
6228
|
+
this.stopReconnectTimer();
|
|
6229
|
+
this.ws?.close(1008, "Registration rejected");
|
|
6124
6230
|
} else {
|
|
6125
|
-
logger$1.error({ message: ack.message }, "Cloud: registration failed");
|
|
6231
|
+
logger$1.error({ message: ack.message }, "Cloud: registration failed — will retry");
|
|
6126
6232
|
this.ws?.close(1008, "Registration rejected");
|
|
6127
6233
|
}
|
|
6128
6234
|
}
|
|
@@ -6252,16 +6358,27 @@ var CloudClient = class {
|
|
|
6252
6358
|
logger$1.debug("Cloud: reconnect skipped — client is closed");
|
|
6253
6359
|
return;
|
|
6254
6360
|
}
|
|
6361
|
+
if (this.reconnectTimer) {
|
|
6362
|
+
logger$1.debug("Cloud: reconnect already scheduled");
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
6255
6365
|
const delay = this.backoffMs;
|
|
6256
6366
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
6257
6367
|
logger$1.info({
|
|
6258
6368
|
delayMs: delay,
|
|
6259
6369
|
nextBackoffMs: this.backoffMs
|
|
6260
6370
|
}, "Cloud: scheduling reconnect...");
|
|
6261
|
-
setTimeout(() => {
|
|
6371
|
+
this.reconnectTimer = setTimeout(() => {
|
|
6372
|
+
this.reconnectTimer = null;
|
|
6262
6373
|
this.doConnect();
|
|
6263
6374
|
}, delay);
|
|
6264
6375
|
}
|
|
6376
|
+
stopReconnectTimer() {
|
|
6377
|
+
if (this.reconnectTimer) {
|
|
6378
|
+
clearTimeout(this.reconnectTimer);
|
|
6379
|
+
this.reconnectTimer = null;
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6265
6382
|
};
|
|
6266
6383
|
//#endregion
|
|
6267
6384
|
//#region src/config-reconciler.ts
|
|
@@ -6353,11 +6470,15 @@ var ConfigReconciler = class {
|
|
|
6353
6470
|
//#endregion
|
|
6354
6471
|
//#region src/command-queue.ts
|
|
6355
6472
|
const DEFAULT_TTL_MS$1 = 300 * 1e3;
|
|
6473
|
+
const DEFAULT_MAX_PER_SERVICE = 100;
|
|
6474
|
+
const DEFAULT_MAX_TOTAL = 1e3;
|
|
6356
6475
|
var CommandQueue = class {
|
|
6357
6476
|
queues = /* @__PURE__ */ new Map();
|
|
6358
6477
|
ttlMs;
|
|
6359
6478
|
gcTimer = null;
|
|
6360
|
-
constructor(ttlMs = DEFAULT_TTL_MS$1) {
|
|
6479
|
+
constructor(ttlMs = DEFAULT_TTL_MS$1, maxPerService = DEFAULT_MAX_PER_SERVICE, maxTotal = DEFAULT_MAX_TOTAL) {
|
|
6480
|
+
this.maxPerService = maxPerService;
|
|
6481
|
+
this.maxTotal = maxTotal;
|
|
6361
6482
|
this.ttlMs = ttlMs;
|
|
6362
6483
|
}
|
|
6363
6484
|
/**
|
|
@@ -6380,16 +6501,22 @@ var CommandQueue = class {
|
|
|
6380
6501
|
* Enqueue a command for a specific service.
|
|
6381
6502
|
*/
|
|
6382
6503
|
enqueue(serviceId, request, commandId) {
|
|
6504
|
+
this.purgeExpired();
|
|
6383
6505
|
let queue = this.queues.get(serviceId);
|
|
6384
6506
|
if (!queue) {
|
|
6385
6507
|
queue = [];
|
|
6386
6508
|
this.queues.set(serviceId, queue);
|
|
6387
6509
|
}
|
|
6510
|
+
if (queue.length >= this.maxPerService || this.totalPending() >= this.maxTotal) {
|
|
6511
|
+
if (queue.length === 0) this.queues.delete(serviceId);
|
|
6512
|
+
return false;
|
|
6513
|
+
}
|
|
6388
6514
|
queue.push({
|
|
6389
6515
|
request,
|
|
6390
6516
|
queuedAt: Date.now(),
|
|
6391
6517
|
commandId
|
|
6392
6518
|
});
|
|
6519
|
+
return true;
|
|
6393
6520
|
}
|
|
6394
6521
|
/**
|
|
6395
6522
|
* Drain all pending (non-expired) commands for a service.
|
|
@@ -6458,6 +6585,13 @@ var CommandQueue = class {
|
|
|
6458
6585
|
*/
|
|
6459
6586
|
const LAUNCHD_LABEL = "ai.alfe.gateway";
|
|
6460
6587
|
const SYSTEMD_SERVICE = "alfe-gateway";
|
|
6588
|
+
const MANAGED_ENV_VARS = [
|
|
6589
|
+
"ALFE_MANAGED",
|
|
6590
|
+
"ALFE_API_KEY",
|
|
6591
|
+
"LOG_LEVEL",
|
|
6592
|
+
"ALFE_CLI_VERSION"
|
|
6593
|
+
];
|
|
6594
|
+
const execFileAsync$3 = promisify(execFile);
|
|
6461
6595
|
/**
|
|
6462
6596
|
* On-disk path for the boot-time self-heal guard script (Linux only).
|
|
6463
6597
|
* Written next to the systemd unit at setup time and invoked via
|
|
@@ -6472,6 +6606,21 @@ function isRootUser() {
|
|
|
6472
6606
|
function getSystemdSystemServicePath() {
|
|
6473
6607
|
return `/etc/systemd/system/${SYSTEMD_SERVICE}.service`;
|
|
6474
6608
|
}
|
|
6609
|
+
function getManagedEnvironmentPath() {
|
|
6610
|
+
return isRootUser() ? "/etc/alfe/gateway.env" : join(homedir(), ".alfe", "gateway.env");
|
|
6611
|
+
}
|
|
6612
|
+
function escapeEnvironmentFileValue(value) {
|
|
6613
|
+
if (/[\0\r\n]/.test(value)) throw new Error("Managed environment values cannot contain NUL or newlines");
|
|
6614
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
6615
|
+
}
|
|
6616
|
+
/** @internal exported for unit tests; not re-exported from the barrel. */
|
|
6617
|
+
function generateManagedEnvironmentFile() {
|
|
6618
|
+
const lines = MANAGED_ENV_VARS.flatMap((key) => {
|
|
6619
|
+
const value = process.env[key];
|
|
6620
|
+
return value === void 0 ? [] : [`${key}="${escapeEnvironmentFileValue(value)}"`];
|
|
6621
|
+
});
|
|
6622
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
6623
|
+
}
|
|
6475
6624
|
function getLaunchdPlistPath() {
|
|
6476
6625
|
return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
6477
6626
|
}
|
|
@@ -6566,7 +6715,7 @@ function generateLaunchdPlist() {
|
|
|
6566
6715
|
*/
|
|
6567
6716
|
/** @internal exported for unit tests; not re-exported from the barrel. */
|
|
6568
6717
|
function generateGuardScript() {
|
|
6569
|
-
const
|
|
6718
|
+
const exactVersion = process.env.ALFE_CLI_VERSION?.match(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/)?.[0];
|
|
6570
6719
|
return `#!/bin/sh
|
|
6571
6720
|
# Alfe CLI boot-time self-heal guard. Auto-generated by 'alfe setup' — do not edit.
|
|
6572
6721
|
# Repairs an interrupted 'npm install -g @alfe.ai/cli' before the daemon starts,
|
|
@@ -6574,7 +6723,7 @@ function generateGuardScript() {
|
|
|
6574
6723
|
# Must never wedge boot: every failure path logs and exits 0.
|
|
6575
6724
|
set -u
|
|
6576
6725
|
|
|
6577
|
-
TARGET='${
|
|
6726
|
+
TARGET='${exactVersion ? `@alfe.ai/cli@${exactVersion}` : "@alfe.ai/cli@latest"}'
|
|
6578
6727
|
log() { echo "[alfe-cli-guard] $*" >&2; }
|
|
6579
6728
|
|
|
6580
6729
|
# Resolve the alfe bin and its real target (dist/index.js). readlink -f follows
|
|
@@ -6638,12 +6787,7 @@ exit 0
|
|
|
6638
6787
|
function generateSystemdUnit() {
|
|
6639
6788
|
const alfeBin = getAlfeBinPath();
|
|
6640
6789
|
const root = isRootUser();
|
|
6641
|
-
const
|
|
6642
|
-
"ALFE_MANAGED",
|
|
6643
|
-
"ALFE_API_KEY",
|
|
6644
|
-
"LOG_LEVEL",
|
|
6645
|
-
"ALFE_CLI_VERSION"
|
|
6646
|
-
].filter((key) => process.env[key]).map((key) => `Environment=${key}=${process.env[key] ?? ""}`).join("\n");
|
|
6790
|
+
const managedEnvironmentPath = getManagedEnvironmentPath();
|
|
6647
6791
|
return `[Unit]
|
|
6648
6792
|
Description=Alfe Gateway Daemon
|
|
6649
6793
|
After=network-online.target
|
|
@@ -6662,7 +6806,7 @@ RestartSec=10
|
|
|
6662
6806
|
# SIGKILL when the stop timeout expires.
|
|
6663
6807
|
KillMode=mixed
|
|
6664
6808
|
Environment=NODE_ENV=production${root ? "\nEnvironment=HOME=/root\nWorkingDirectory=/root" : ""}
|
|
6665
|
-
|
|
6809
|
+
EnvironmentFile=-${managedEnvironmentPath}
|
|
6666
6810
|
|
|
6667
6811
|
[Install]
|
|
6668
6812
|
WantedBy=${root ? "multi-user.target" : "default.target"}`;
|
|
@@ -6680,6 +6824,19 @@ async function writeGuardScript() {
|
|
|
6680
6824
|
});
|
|
6681
6825
|
logger$1.info({ path: guardPath }, "Wrote CLI self-heal guard script");
|
|
6682
6826
|
}
|
|
6827
|
+
async function writeManagedEnvironmentFile() {
|
|
6828
|
+
const environmentPath = getManagedEnvironmentPath();
|
|
6829
|
+
await mkdir(dirname(environmentPath), {
|
|
6830
|
+
recursive: true,
|
|
6831
|
+
mode: 448
|
|
6832
|
+
});
|
|
6833
|
+
await writeFile(environmentPath, generateManagedEnvironmentFile(), {
|
|
6834
|
+
encoding: "utf-8",
|
|
6835
|
+
mode: 384
|
|
6836
|
+
});
|
|
6837
|
+
await chmod(environmentPath, 384);
|
|
6838
|
+
logger$1.info({ path: environmentPath }, "Wrote managed environment file");
|
|
6839
|
+
}
|
|
6683
6840
|
/**
|
|
6684
6841
|
* Install the service unit for the current platform.
|
|
6685
6842
|
*/
|
|
@@ -6743,6 +6900,7 @@ async function installSystemd() {
|
|
|
6743
6900
|
const ctl = root ? "systemctl" : "systemctl --user";
|
|
6744
6901
|
if (!root) await mkdir(dir, { recursive: true });
|
|
6745
6902
|
await writeGuardScript();
|
|
6903
|
+
await writeManagedEnvironmentFile();
|
|
6746
6904
|
await writeFile(unitPath, generateSystemdUnit(), "utf-8");
|
|
6747
6905
|
logger$1.info({
|
|
6748
6906
|
path: unitPath,
|
|
@@ -6768,6 +6926,9 @@ async function uninstallSystemd() {
|
|
|
6768
6926
|
try {
|
|
6769
6927
|
await unlink(getGuardScriptPath());
|
|
6770
6928
|
} catch {}
|
|
6929
|
+
try {
|
|
6930
|
+
await unlink(getManagedEnvironmentPath());
|
|
6931
|
+
} catch {}
|
|
6771
6932
|
try {
|
|
6772
6933
|
execSync(`${ctl} daemon-reload`, { stdio: "pipe" });
|
|
6773
6934
|
} catch {}
|
|
@@ -6862,6 +7023,26 @@ function isServiceInstalled() {
|
|
|
6862
7023
|
async function writePidFile() {
|
|
6863
7024
|
await writeFile(PID_PATH, String(process.pid), "utf-8");
|
|
6864
7025
|
}
|
|
7026
|
+
/** @internal exported for unit tests; not re-exported from the barrel. */
|
|
7027
|
+
function isGatewayDaemonCommand(command) {
|
|
7028
|
+
const normalized = command.replaceAll("\0", " ").replace(/\s+/g, " ").trim();
|
|
7029
|
+
const hasGatewayEntrypoint = /(?:^|[\s/])(?:alfe-gateway|gateway(?:\.js)?)(?:\s|$)/.test(normalized) || /(?:^|\s)alfe(?:\s|$)/.test(normalized) && /(?:^|\s)gateway(?:\s|$)/.test(normalized);
|
|
7030
|
+
const hasDaemonVerb = /(?:^|\s)(?:daemon|start|restart)(?:\s|$)/.test(normalized);
|
|
7031
|
+
return hasGatewayEntrypoint && hasDaemonVerb;
|
|
7032
|
+
}
|
|
7033
|
+
async function isGatewayDaemonProcess(pid) {
|
|
7034
|
+
try {
|
|
7035
|
+
const { stdout } = await execFileAsync$3("ps", [
|
|
7036
|
+
"-p",
|
|
7037
|
+
String(pid),
|
|
7038
|
+
"-o",
|
|
7039
|
+
"command="
|
|
7040
|
+
], { timeout: 2e3 });
|
|
7041
|
+
return isGatewayDaemonCommand(stdout);
|
|
7042
|
+
} catch {
|
|
7043
|
+
return false;
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
6865
7046
|
/**
|
|
6866
7047
|
* Remove the PID file.
|
|
6867
7048
|
*/
|
|
@@ -6877,15 +7058,18 @@ async function removePidFile() {
|
|
|
6877
7058
|
async function checkExistingDaemon() {
|
|
6878
7059
|
if (!existsSync(PID_PATH)) return null;
|
|
6879
7060
|
try {
|
|
6880
|
-
const
|
|
6881
|
-
const pid =
|
|
6882
|
-
if (
|
|
7061
|
+
const trimmedPid = (await readFile(PID_PATH, "utf-8")).trim();
|
|
7062
|
+
const pid = /^\d+$/.test(trimmedPid) ? Number(trimmedPid) : NaN;
|
|
7063
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) {
|
|
6883
7064
|
await removePidFile();
|
|
6884
7065
|
return null;
|
|
6885
7066
|
}
|
|
6886
7067
|
try {
|
|
6887
7068
|
process.kill(pid, 0);
|
|
6888
|
-
return pid;
|
|
7069
|
+
if (await isGatewayDaemonProcess(pid)) return pid;
|
|
7070
|
+
logger$1.warn({ pid }, "Ignoring stale daemon PID owned by another process");
|
|
7071
|
+
await removePidFile();
|
|
7072
|
+
return null;
|
|
6889
7073
|
} catch {
|
|
6890
7074
|
await removePidFile();
|
|
6891
7075
|
return null;
|
|
@@ -6911,7 +7095,8 @@ async function stopExistingDaemon() {
|
|
|
6911
7095
|
return true;
|
|
6912
7096
|
}
|
|
6913
7097
|
}
|
|
6914
|
-
process.kill(pid, "SIGKILL");
|
|
7098
|
+
if (await isGatewayDaemonProcess(pid)) process.kill(pid, "SIGKILL");
|
|
7099
|
+
else logger$1.warn({ pid }, "Skipping SIGKILL because daemon PID ownership changed");
|
|
6915
7100
|
await removePidFile();
|
|
6916
7101
|
return true;
|
|
6917
7102
|
} catch {
|
|
@@ -6937,7 +7122,8 @@ var IPCServer = class {
|
|
|
6937
7122
|
connections = /* @__PURE__ */ new Map();
|
|
6938
7123
|
socketPath;
|
|
6939
7124
|
requestHandler = null;
|
|
6940
|
-
constructor(socketPath) {
|
|
7125
|
+
constructor(socketPath, daemonVersion = "unknown") {
|
|
7126
|
+
this.daemonVersion = daemonVersion;
|
|
6941
7127
|
this.socketPath = socketPath;
|
|
6942
7128
|
}
|
|
6943
7129
|
/**
|
|
@@ -7112,6 +7298,7 @@ var IPCServer = class {
|
|
|
7112
7298
|
socket.on("data", (data) => {
|
|
7113
7299
|
conn.buffer += data.toString();
|
|
7114
7300
|
this.processBuffer(conn);
|
|
7301
|
+
if (Buffer.byteLength(conn.buffer, "utf8") > 1048576) this.closeOversizedConnection(conn);
|
|
7115
7302
|
});
|
|
7116
7303
|
socket.on("close", () => {
|
|
7117
7304
|
logger$1.info({
|
|
@@ -7141,8 +7328,13 @@ var IPCServer = class {
|
|
|
7141
7328
|
processBuffer(conn) {
|
|
7142
7329
|
let newlineIdx;
|
|
7143
7330
|
while ((newlineIdx = conn.buffer.indexOf("\n")) !== -1) {
|
|
7144
|
-
const
|
|
7331
|
+
const rawLine = conn.buffer.slice(0, newlineIdx);
|
|
7145
7332
|
conn.buffer = conn.buffer.slice(newlineIdx + 1);
|
|
7333
|
+
if (Buffer.byteLength(rawLine, "utf8") > 1048576) {
|
|
7334
|
+
this.closeOversizedConnection(conn);
|
|
7335
|
+
return;
|
|
7336
|
+
}
|
|
7337
|
+
const line = rawLine.trim();
|
|
7146
7338
|
if (!line) continue;
|
|
7147
7339
|
let parsed;
|
|
7148
7340
|
try {
|
|
@@ -7165,6 +7357,14 @@ var IPCServer = class {
|
|
|
7165
7357
|
}, "IPC: unhandled message");
|
|
7166
7358
|
}
|
|
7167
7359
|
}
|
|
7360
|
+
closeOversizedConnection(conn) {
|
|
7361
|
+
logger$1.warn({
|
|
7362
|
+
connId: conn.id,
|
|
7363
|
+
maxBytes: MAX_IPC_MESSAGE_BYTES
|
|
7364
|
+
}, "IPC: message exceeded size limit — closing connection");
|
|
7365
|
+
conn.buffer = "";
|
|
7366
|
+
conn.socket.destroy();
|
|
7367
|
+
}
|
|
7168
7368
|
handlePluginResponse(conn, response) {
|
|
7169
7369
|
const pending = conn.pending.get(response.id);
|
|
7170
7370
|
if (!pending) return;
|
|
@@ -7190,8 +7390,11 @@ var IPCServer = class {
|
|
|
7190
7390
|
}
|
|
7191
7391
|
handleRegister(conn, request) {
|
|
7192
7392
|
const { name, version, protocolVersion, capabilities, pid } = request.params;
|
|
7193
|
-
|
|
7194
|
-
|
|
7393
|
+
const validCapabilities = capabilities === void 0 || Array.isArray(capabilities) && capabilities.length <= 256 && capabilities.every((capability) => typeof capability === "string" && capability.trim().length > 0 && capability.length <= 256);
|
|
7394
|
+
const validPid = pid === void 0 || Number.isSafeInteger(pid) && pid > 0;
|
|
7395
|
+
const validProtocolVersion = protocolVersion === void 0 || Number.isSafeInteger(protocolVersion);
|
|
7396
|
+
if (typeof name !== "string" || name.trim().length === 0 || name.length > 256 || typeof version !== "string" || version.trim().length === 0 || version.length > 256 || !validCapabilities || !validPid || !validProtocolVersion) {
|
|
7397
|
+
this.sendResponse(conn, createIPCError(request.id, "INVALID_REGISTER", "Invalid plugin registration fields"));
|
|
7195
7398
|
return;
|
|
7196
7399
|
}
|
|
7197
7400
|
if (protocolVersion !== void 0 && protocolVersion !== 1) {
|
|
@@ -7228,7 +7431,7 @@ var IPCServer = class {
|
|
|
7228
7431
|
name,
|
|
7229
7432
|
version,
|
|
7230
7433
|
protocolVersion: protocolVersion ?? 1,
|
|
7231
|
-
capabilities: capabilities
|
|
7434
|
+
capabilities: capabilities ? [...capabilities] : [],
|
|
7232
7435
|
pid: pid ?? null,
|
|
7233
7436
|
connectedAt: Date.now(),
|
|
7234
7437
|
lastSeen: Date.now()
|
|
@@ -7241,7 +7444,7 @@ var IPCServer = class {
|
|
|
7241
7444
|
}, "IPC: plugin registered");
|
|
7242
7445
|
this.sendResponse(conn, createIPCResponse(request.id, {
|
|
7243
7446
|
status: "registered",
|
|
7244
|
-
daemonVersion:
|
|
7447
|
+
daemonVersion: this.daemonVersion,
|
|
7245
7448
|
protocolVersion: 1
|
|
7246
7449
|
}));
|
|
7247
7450
|
}
|
|
@@ -7462,9 +7665,9 @@ async function startAiProxy(apiKey) {
|
|
|
7462
7665
|
* here is fatal — plugins have no other path to the daemon — so this flushes
|
|
7463
7666
|
* logs and exits instead of returning.
|
|
7464
7667
|
*/
|
|
7465
|
-
async function startIpcServer(socketPath, requestHandler) {
|
|
7668
|
+
async function startIpcServer(socketPath, requestHandler, daemonVersion) {
|
|
7466
7669
|
logger$1.debug({ socketPath }, "Starting IPC server...");
|
|
7467
|
-
const ipcServer = new IPCServer(socketPath);
|
|
7670
|
+
const ipcServer = new IPCServer(socketPath, daemonVersion);
|
|
7468
7671
|
ipcServer.setRequestHandler(requestHandler);
|
|
7469
7672
|
try {
|
|
7470
7673
|
await ipcServer.start();
|
|
@@ -7545,7 +7748,8 @@ function handleMcpListServers(manager) {
|
|
|
7545
7748
|
ok: true,
|
|
7546
7749
|
payload: { servers: manager.listServers().map(({ id, entry }) => ({
|
|
7547
7750
|
id,
|
|
7548
|
-
entry,
|
|
7751
|
+
entry: toPublicServerEntry(entry),
|
|
7752
|
+
fingerprint: serverLaunchFingerprint(entry),
|
|
7549
7753
|
status: byName.get(id) ?? {
|
|
7550
7754
|
name: id,
|
|
7551
7755
|
connected: false,
|
|
@@ -7555,6 +7759,14 @@ function handleMcpListServers(manager) {
|
|
|
7555
7759
|
})) }
|
|
7556
7760
|
};
|
|
7557
7761
|
}
|
|
7762
|
+
function toPublicServerEntry(entry) {
|
|
7763
|
+
return {
|
|
7764
|
+
owner: entry.owner,
|
|
7765
|
+
addedAt: entry.addedAt,
|
|
7766
|
+
transport: entry.transport,
|
|
7767
|
+
...entry.version !== void 0 ? { version: entry.version } : {}
|
|
7768
|
+
};
|
|
7769
|
+
}
|
|
7558
7770
|
/**
|
|
7559
7771
|
* How long the add-confirm probe waits for the freshly-added server to
|
|
7560
7772
|
* connect before replying. This is effectively the whole budget for
|
|
@@ -23114,7 +23326,7 @@ var RuntimeProcess = class {
|
|
|
23114
23326
|
* Start the runtime process.
|
|
23115
23327
|
*/
|
|
23116
23328
|
start() {
|
|
23117
|
-
if (this.stopped) return;
|
|
23329
|
+
if (this.stopped || this.child !== null) return;
|
|
23118
23330
|
const { command, args } = this.resolveCommand();
|
|
23119
23331
|
this.lastStartTime = Date.now();
|
|
23120
23332
|
log$3.info({
|
|
@@ -23123,7 +23335,7 @@ var RuntimeProcess = class {
|
|
|
23123
23335
|
args,
|
|
23124
23336
|
workspace: this.options.workspace
|
|
23125
23337
|
}, "Starting runtime process");
|
|
23126
|
-
|
|
23338
|
+
const child = spawn(command, args, {
|
|
23127
23339
|
cwd: this.options.workspace,
|
|
23128
23340
|
env: {
|
|
23129
23341
|
...process.env,
|
|
@@ -23135,7 +23347,9 @@ var RuntimeProcess = class {
|
|
|
23135
23347
|
"pipe"
|
|
23136
23348
|
]
|
|
23137
23349
|
});
|
|
23138
|
-
this.child
|
|
23350
|
+
this.child = child;
|
|
23351
|
+
let terminalHandled = false;
|
|
23352
|
+
child.stdout.on("data", (data) => {
|
|
23139
23353
|
const lines = data.toString().trim().split("\n");
|
|
23140
23354
|
for (const line of lines) {
|
|
23141
23355
|
log$3.info({
|
|
@@ -23145,7 +23359,7 @@ var RuntimeProcess = class {
|
|
|
23145
23359
|
this.observeLine("stdout", line);
|
|
23146
23360
|
}
|
|
23147
23361
|
});
|
|
23148
|
-
|
|
23362
|
+
child.stderr.on("data", (data) => {
|
|
23149
23363
|
const lines = data.toString().trim().split("\n");
|
|
23150
23364
|
for (const line of lines) {
|
|
23151
23365
|
log$3.warn({
|
|
@@ -23155,8 +23369,14 @@ var RuntimeProcess = class {
|
|
|
23155
23369
|
this.observeLine("stderr", line);
|
|
23156
23370
|
}
|
|
23157
23371
|
});
|
|
23158
|
-
|
|
23159
|
-
|
|
23372
|
+
const handleTermination = (code, signal, spawnError) => {
|
|
23373
|
+
if (terminalHandled) return;
|
|
23374
|
+
terminalHandled = true;
|
|
23375
|
+
if (this.child === child) this.child = null;
|
|
23376
|
+
if (spawnError) log$3.error({
|
|
23377
|
+
runtime: this.options.runtime,
|
|
23378
|
+
err: spawnError.message
|
|
23379
|
+
}, "Runtime process error");
|
|
23160
23380
|
if (this.stopped) {
|
|
23161
23381
|
log$3.info({
|
|
23162
23382
|
runtime: this.options.runtime,
|
|
@@ -23165,7 +23385,7 @@ var RuntimeProcess = class {
|
|
|
23165
23385
|
}, "Runtime stopped (expected)");
|
|
23166
23386
|
return;
|
|
23167
23387
|
}
|
|
23168
|
-
if (code === 0 && signal == null) {
|
|
23388
|
+
if (!spawnError && code === 0 && signal == null) {
|
|
23169
23389
|
log$3.info({
|
|
23170
23390
|
runtime: this.options.runtime,
|
|
23171
23391
|
code
|
|
@@ -23180,6 +23400,7 @@ var RuntimeProcess = class {
|
|
|
23180
23400
|
runtime: this.options.runtime,
|
|
23181
23401
|
code,
|
|
23182
23402
|
signal,
|
|
23403
|
+
spawnError: spawnError?.message,
|
|
23183
23404
|
backoffMs: this.backoffMs
|
|
23184
23405
|
}, "Runtime crashed — scheduling restart with backoff");
|
|
23185
23406
|
const uptime = Date.now() - this.lastStartTime;
|
|
@@ -23195,7 +23416,8 @@ var RuntimeProcess = class {
|
|
|
23195
23416
|
signal,
|
|
23196
23417
|
uptimeMs: uptime,
|
|
23197
23418
|
recentOutput: this.ringBuffer.snapshot(),
|
|
23198
|
-
crashesSuppressed: crash.suppressedCount
|
|
23419
|
+
crashesSuppressed: crash.suppressedCount,
|
|
23420
|
+
...spawnError ? { spawnError } : {}
|
|
23199
23421
|
});
|
|
23200
23422
|
else log$3.debug({
|
|
23201
23423
|
runtime: this.options.runtime,
|
|
@@ -23207,23 +23429,12 @@ var RuntimeProcess = class {
|
|
|
23207
23429
|
this.start();
|
|
23208
23430
|
}, this.backoffMs);
|
|
23209
23431
|
this.backoffMs = Math.min(this.backoffMs * 2, BACKOFF_MAX_MS);
|
|
23432
|
+
};
|
|
23433
|
+
child.on("exit", (code, signal) => {
|
|
23434
|
+
handleTermination(code, signal);
|
|
23210
23435
|
});
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
runtime: this.options.runtime,
|
|
23214
|
-
err: err.message
|
|
23215
|
-
}, "Runtime process error");
|
|
23216
|
-
if (this.stopped) return;
|
|
23217
|
-
const crash = this.throttle.allowCrashCapture(Date.now() - this.lastStartTime);
|
|
23218
|
-
if (crash.allow) captureRuntimeCrash({
|
|
23219
|
-
runtime: this.options.runtime,
|
|
23220
|
-
code: null,
|
|
23221
|
-
signal: null,
|
|
23222
|
-
uptimeMs: Date.now() - this.lastStartTime,
|
|
23223
|
-
recentOutput: this.ringBuffer.snapshot(),
|
|
23224
|
-
crashesSuppressed: crash.suppressedCount,
|
|
23225
|
-
spawnError: err
|
|
23226
|
-
});
|
|
23436
|
+
child.on("error", (err) => {
|
|
23437
|
+
handleTermination(null, null, err);
|
|
23227
23438
|
});
|
|
23228
23439
|
}
|
|
23229
23440
|
/**
|
|
@@ -23292,14 +23503,19 @@ var RuntimeProcess = class {
|
|
|
23292
23503
|
const child = this.child;
|
|
23293
23504
|
if (!child) return;
|
|
23294
23505
|
return new Promise((resolve) => {
|
|
23506
|
+
let settled = false;
|
|
23507
|
+
const finish = () => {
|
|
23508
|
+
if (settled) return;
|
|
23509
|
+
settled = true;
|
|
23510
|
+
clearTimeout(killTimer);
|
|
23511
|
+
resolve();
|
|
23512
|
+
};
|
|
23295
23513
|
const killTimer = setTimeout(() => {
|
|
23296
23514
|
log$3.warn({ runtime: this.options.runtime }, "Runtime did not exit in time — sending SIGKILL");
|
|
23297
23515
|
child.kill("SIGKILL");
|
|
23298
23516
|
}, 5e3);
|
|
23299
|
-
child.
|
|
23300
|
-
|
|
23301
|
-
resolve();
|
|
23302
|
-
});
|
|
23517
|
+
child.once("exit", finish);
|
|
23518
|
+
child.once("error", finish);
|
|
23303
23519
|
child.kill("SIGTERM");
|
|
23304
23520
|
});
|
|
23305
23521
|
}
|
|
@@ -23471,6 +23687,8 @@ var IpcTurnActivityProbe = class {
|
|
|
23471
23687
|
* via ESM dynamic import on first use and cached until the registry is cleared.
|
|
23472
23688
|
*/
|
|
23473
23689
|
const log$1 = createLogger("CommandRegistry");
|
|
23690
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 3e4;
|
|
23691
|
+
const MAX_COMMAND_TIMEOUT_MS = 1800 * 1e3;
|
|
23474
23692
|
var CommandRegistry = class {
|
|
23475
23693
|
commands = /* @__PURE__ */ new Map();
|
|
23476
23694
|
version = 0;
|
|
@@ -23478,7 +23696,7 @@ var CommandRegistry = class {
|
|
|
23478
23696
|
* Register a command from an integration.
|
|
23479
23697
|
* Rejects duplicate command names — first registration wins.
|
|
23480
23698
|
*/
|
|
23481
|
-
register(integrationId, name, handlerPath, method = "handle", timeoutMs =
|
|
23699
|
+
register(integrationId, name, handlerPath, method = "handle", timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS) {
|
|
23482
23700
|
const existing = this.commands.get(name);
|
|
23483
23701
|
if (existing) {
|
|
23484
23702
|
log$1.warn({
|
|
@@ -23496,12 +23714,13 @@ var CommandRegistry = class {
|
|
|
23496
23714
|
}, "Handler file does not exist — skipping registration");
|
|
23497
23715
|
return;
|
|
23498
23716
|
}
|
|
23717
|
+
const boundedTimeoutMs = Number.isSafeInteger(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_COMMAND_TIMEOUT_MS) : DEFAULT_COMMAND_TIMEOUT_MS;
|
|
23499
23718
|
this.commands.set(name, {
|
|
23500
23719
|
commandName: name,
|
|
23501
23720
|
integrationId,
|
|
23502
23721
|
handlerPath,
|
|
23503
23722
|
handlerMethod: method,
|
|
23504
|
-
timeoutMs,
|
|
23723
|
+
timeoutMs: boundedTimeoutMs,
|
|
23505
23724
|
handler: null
|
|
23506
23725
|
});
|
|
23507
23726
|
log$1.info({
|
|
@@ -23509,7 +23728,7 @@ var CommandRegistry = class {
|
|
|
23509
23728
|
integrationId,
|
|
23510
23729
|
handlerPath,
|
|
23511
23730
|
method,
|
|
23512
|
-
timeoutMs
|
|
23731
|
+
timeoutMs: boundedTimeoutMs
|
|
23513
23732
|
}, "Registered command");
|
|
23514
23733
|
}
|
|
23515
23734
|
/**
|
|
@@ -23569,9 +23788,10 @@ var CommandRegistry = class {
|
|
|
23569
23788
|
}
|
|
23570
23789
|
};
|
|
23571
23790
|
}
|
|
23791
|
+
let timeout;
|
|
23572
23792
|
try {
|
|
23573
23793
|
return await Promise.race([entry.handler(payload, context), new Promise((_, reject) => {
|
|
23574
|
-
setTimeout(() => {
|
|
23794
|
+
timeout = setTimeout(() => {
|
|
23575
23795
|
reject(/* @__PURE__ */ new Error(`Command "${name}" timed out after ${String(entry.timeoutMs)}ms`));
|
|
23576
23796
|
}, entry.timeoutMs);
|
|
23577
23797
|
})]);
|
|
@@ -23588,6 +23808,8 @@ var CommandRegistry = class {
|
|
|
23588
23808
|
message
|
|
23589
23809
|
}
|
|
23590
23810
|
};
|
|
23811
|
+
} finally {
|
|
23812
|
+
if (timeout) clearTimeout(timeout);
|
|
23591
23813
|
}
|
|
23592
23814
|
}
|
|
23593
23815
|
/**
|
|
@@ -23637,13 +23859,18 @@ var CommandDedupe = class {
|
|
|
23637
23859
|
duplicate: true
|
|
23638
23860
|
};
|
|
23639
23861
|
const promise = exec();
|
|
23640
|
-
|
|
23862
|
+
const entry = {
|
|
23641
23863
|
insertedAt: this.now(),
|
|
23642
|
-
promise
|
|
23643
|
-
|
|
23864
|
+
promise,
|
|
23865
|
+
settled: false
|
|
23866
|
+
};
|
|
23867
|
+
this.entries.set(commandId, entry);
|
|
23644
23868
|
this.evictOverCap();
|
|
23645
|
-
promise.
|
|
23646
|
-
|
|
23869
|
+
promise.then(() => {
|
|
23870
|
+
entry.settled = true;
|
|
23871
|
+
this.evictOverCap();
|
|
23872
|
+
}, () => {
|
|
23873
|
+
if (this.entries.get(commandId) === entry) this.entries.delete(commandId);
|
|
23647
23874
|
});
|
|
23648
23875
|
return {
|
|
23649
23876
|
ack: await promise,
|
|
@@ -23658,7 +23885,7 @@ var CommandDedupe = class {
|
|
|
23658
23885
|
has(commandId) {
|
|
23659
23886
|
const e = this.entries.get(commandId);
|
|
23660
23887
|
if (!e) return false;
|
|
23661
|
-
if (this.now() - e.insertedAt > this.ttlMs) {
|
|
23888
|
+
if (e.settled && this.now() - e.insertedAt > this.ttlMs) {
|
|
23662
23889
|
this.entries.delete(commandId);
|
|
23663
23890
|
return false;
|
|
23664
23891
|
}
|
|
@@ -23666,13 +23893,13 @@ var CommandDedupe = class {
|
|
|
23666
23893
|
}
|
|
23667
23894
|
evictExpired() {
|
|
23668
23895
|
const cutoff = this.now() - this.ttlMs;
|
|
23669
|
-
for (const [id, entry] of this.entries) if (entry.insertedAt <= cutoff) this.entries.delete(id);
|
|
23896
|
+
for (const [id, entry] of this.entries) if (entry.settled && entry.insertedAt <= cutoff) this.entries.delete(id);
|
|
23670
23897
|
}
|
|
23671
23898
|
evictOverCap() {
|
|
23672
23899
|
while (this.entries.size > this.maxEntries) {
|
|
23673
|
-
const
|
|
23674
|
-
if (
|
|
23675
|
-
this.entries.delete(
|
|
23900
|
+
const oldestSettled = Array.from(this.entries).find(([, entry]) => entry.settled);
|
|
23901
|
+
if (!oldestSettled) break;
|
|
23902
|
+
this.entries.delete(oldestSettled[0]);
|
|
23676
23903
|
}
|
|
23677
23904
|
}
|
|
23678
23905
|
};
|
|
@@ -24126,6 +24353,8 @@ let stopPairingApprovalPoller = null;
|
|
|
24126
24353
|
* runtime's applier (mirrors `mcpManagerRef`). Null until start() builds it.
|
|
24127
24354
|
*/
|
|
24128
24355
|
let runtimeAppliersRef = null;
|
|
24356
|
+
const LEGACY_CONFIG_SET_KEYS = new Set(["agents.defaults.model"]);
|
|
24357
|
+
const MAX_LEGACY_CONFIG_VALUE_LENGTH = 16 * 1024;
|
|
24129
24358
|
async function startDaemon() {
|
|
24130
24359
|
startedAt = Date.now();
|
|
24131
24360
|
const managed = isManagedMode();
|
|
@@ -24133,9 +24362,10 @@ async function startDaemon() {
|
|
|
24133
24362
|
managed,
|
|
24134
24363
|
pid: process.pid
|
|
24135
24364
|
}, "Starting Alfe Gateway Daemon...");
|
|
24365
|
+
resolvedCliVersion = await getCliVersion();
|
|
24136
24366
|
await initAgentSentry({
|
|
24137
24367
|
surface: "daemon",
|
|
24138
|
-
release:
|
|
24368
|
+
release: resolvedCliVersion
|
|
24139
24369
|
});
|
|
24140
24370
|
let fatalExiting = false;
|
|
24141
24371
|
process.on("uncaughtException", (err) => {
|
|
@@ -24189,9 +24419,8 @@ async function startDaemon() {
|
|
|
24189
24419
|
aiProxyServer = aiProxy.server;
|
|
24190
24420
|
aiProxyUrl = aiProxy.url;
|
|
24191
24421
|
aiProxyRunning = aiProxy.running;
|
|
24192
|
-
ipcServer = await startIpcServer(config.socketPath, handlePluginRequest);
|
|
24422
|
+
ipcServer = await startIpcServer(config.socketPath, handlePluginRequest, resolvedCliVersion);
|
|
24193
24423
|
turnActivityProbe = new IpcTurnActivityProbe(() => ipcServer);
|
|
24194
|
-
resolvedCliVersion = await getCliVersion();
|
|
24195
24424
|
resolvedRuntimeVersion = await getRuntimeVersion(config.runtime);
|
|
24196
24425
|
logger$1.info({
|
|
24197
24426
|
cliVersion: resolvedCliVersion,
|
|
@@ -24547,7 +24776,7 @@ async function executeCloudCommand(command) {
|
|
|
24547
24776
|
};
|
|
24548
24777
|
const payload = command.payload;
|
|
24549
24778
|
const runtime = config.runtime;
|
|
24550
|
-
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : runtime === "claude-code" ? "0.1.
|
|
24779
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : runtime === "claude-code" ? "0.1.6" : void 0);
|
|
24551
24780
|
upgradingRuntime = true;
|
|
24552
24781
|
setTimeout(() => {
|
|
24553
24782
|
(async () => {
|
|
@@ -24662,6 +24891,15 @@ async function executeCloudCommand(command) {
|
|
|
24662
24891
|
message: "alfe.config_set requires key and value"
|
|
24663
24892
|
}
|
|
24664
24893
|
};
|
|
24894
|
+
if (!LEGACY_CONFIG_SET_KEYS.has(key) || value.length > MAX_LEGACY_CONFIG_VALUE_LENGTH) return {
|
|
24895
|
+
type: "COMMAND_ACK",
|
|
24896
|
+
commandId: command.commandId,
|
|
24897
|
+
status: "error",
|
|
24898
|
+
result: {
|
|
24899
|
+
code: "CONFIG_KEY_UNSUPPORTED",
|
|
24900
|
+
message: "Config key is not supported by the legacy command path"
|
|
24901
|
+
}
|
|
24902
|
+
};
|
|
24665
24903
|
const runtime = config.runtime;
|
|
24666
24904
|
const applier = runtimeAppliersRef?.get(runtime);
|
|
24667
24905
|
if (!applier || typeof applier.setConfigRaw !== "function") {
|
|
@@ -24692,7 +24930,7 @@ async function executeCloudCommand(command) {
|
|
|
24692
24930
|
status: "ok",
|
|
24693
24931
|
result: {
|
|
24694
24932
|
key,
|
|
24695
|
-
|
|
24933
|
+
applied: true
|
|
24696
24934
|
}
|
|
24697
24935
|
};
|
|
24698
24936
|
} catch (err) {
|
|
@@ -24757,7 +24995,21 @@ async function executeCloudCommand(command) {
|
|
|
24757
24995
|
commandId: command.commandId,
|
|
24758
24996
|
command: command.command
|
|
24759
24997
|
}, "No plugins connected — queuing command");
|
|
24760
|
-
commandQueue.enqueue("_default", ipcRequest, command.commandId)
|
|
24998
|
+
if (!commandQueue.enqueue("_default", ipcRequest, command.commandId)) {
|
|
24999
|
+
logger$1.warn({
|
|
25000
|
+
commandId: command.commandId,
|
|
25001
|
+
command: command.command
|
|
25002
|
+
}, "Plugin command queue is full");
|
|
25003
|
+
return {
|
|
25004
|
+
type: "COMMAND_ACK",
|
|
25005
|
+
commandId: command.commandId,
|
|
25006
|
+
status: "error",
|
|
25007
|
+
result: {
|
|
25008
|
+
code: "QUEUE_FULL",
|
|
25009
|
+
message: "Plugin command queue is at capacity"
|
|
25010
|
+
}
|
|
25011
|
+
};
|
|
25012
|
+
}
|
|
24761
25013
|
return {
|
|
24762
25014
|
type: "COMMAND_ACK",
|
|
24763
25015
|
commandId: command.commandId,
|
|
@@ -24843,7 +25095,7 @@ function handleStatus() {
|
|
|
24843
25095
|
status: "running",
|
|
24844
25096
|
pid: process.pid,
|
|
24845
25097
|
uptime: (Date.now() - startedAt) / 1e3,
|
|
24846
|
-
version:
|
|
25098
|
+
version: resolvedCliVersion,
|
|
24847
25099
|
runtimeVersion: resolvedRuntimeVersion
|
|
24848
25100
|
},
|
|
24849
25101
|
cloud: {
|
|
@@ -24870,18 +25122,18 @@ function handleIntegrationList() {
|
|
|
24870
25122
|
}
|
|
24871
25123
|
function handleIntegrationReport(params, pluginId) {
|
|
24872
25124
|
const { name, status, detail } = params;
|
|
24873
|
-
if (
|
|
25125
|
+
if (typeof name !== "string" || name.length === 0 || name.length > 256 || typeof status !== "string" || status.length === 0 || status.length > 64) return {
|
|
24874
25126
|
ok: false,
|
|
24875
25127
|
error: {
|
|
24876
25128
|
code: "INVALID_PARAMS",
|
|
24877
|
-
message: "name and status
|
|
25129
|
+
message: "name and status must be non-empty bounded strings"
|
|
24878
25130
|
}
|
|
24879
25131
|
};
|
|
24880
25132
|
logger$1.info({
|
|
24881
25133
|
pluginId,
|
|
24882
25134
|
integration: name,
|
|
24883
25135
|
status,
|
|
24884
|
-
detail
|
|
25136
|
+
hasDetail: detail !== void 0
|
|
24885
25137
|
}, "Integration status report");
|
|
24886
25138
|
return {
|
|
24887
25139
|
ok: true,
|
|
@@ -24919,6 +25171,12 @@ async function queryDaemonHealth(socketPath, timeoutMs = 5e3) {
|
|
|
24919
25171
|
}, timeoutMs);
|
|
24920
25172
|
socket.on("data", (data) => {
|
|
24921
25173
|
buffer += data.toString();
|
|
25174
|
+
if (Buffer.byteLength(buffer, "utf8") > 1048576) {
|
|
25175
|
+
clearTimeout(timer);
|
|
25176
|
+
socket.destroy();
|
|
25177
|
+
reject(/* @__PURE__ */ new Error("Health response exceeded IPC size limit"));
|
|
25178
|
+
return;
|
|
25179
|
+
}
|
|
24922
25180
|
const newlineIdx = buffer.indexOf("\n");
|
|
24923
25181
|
if (newlineIdx === -1) return;
|
|
24924
25182
|
const line = buffer.slice(0, newlineIdx).trim();
|
|
@@ -24927,7 +25185,8 @@ async function queryDaemonHealth(socketPath, timeoutMs = 5e3) {
|
|
|
24927
25185
|
socket.end();
|
|
24928
25186
|
try {
|
|
24929
25187
|
const response = JSON.parse(line);
|
|
24930
|
-
if (response
|
|
25188
|
+
if (!isIPCResponse(response)) reject(/* @__PURE__ */ new Error("Invalid health response"));
|
|
25189
|
+
else if (response.ok && response.payload) resolve(response.payload);
|
|
24931
25190
|
else reject(new Error(response.error?.message ?? "Health check failed"));
|
|
24932
25191
|
} catch {
|
|
24933
25192
|
reject(/* @__PURE__ */ new Error("Invalid health response"));
|