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