@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/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.3";
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 body = await res.json();
351
- if (!res.ok) return {
352
- ok: false,
353
- result: {
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
- error: body.message || `API error: ${String(res.status)}`,
356
- status: res.status
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
- return Object.values(obj);
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.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
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", "true");
4850
- if (options?.effective) params.set("effective", "true");
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(`/integrations/agents/${agentId}${qs ? `?${qs}` : ""}`);
4887
+ return this.client.request(`${this.agentIntegrationsPath(agentId)}${qs ? `?${qs}` : ""}`);
4853
4888
  }
4854
4889
  installIntegration(agentId, data) {
4855
- return this.client.request(`/integrations/agents/${agentId}`, {
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(`/integrations/agents/${agentId}/${integrationId}/config`);
4896
+ return this.client.request(`${this.agentIntegrationPath(agentId, integrationId)}/config`);
4862
4897
  }
4863
4898
  updateIntegration(agentId, integrationId, data) {
4864
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, {
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(`/integrations/agents/${agentId}/${integrationId}`, { method: "DELETE" });
4905
+ return this.client.request(this.agentIntegrationPath(agentId, integrationId), { method: "DELETE" });
4871
4906
  }
4872
4907
  reinstallIntegration(agentId, integrationId) {
4873
- return this.client.request(`/integrations/agents/${agentId}/${integrationId}/reinstall`, { method: "POST" });
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(`/integrations/agents/${agentId}/${integrationId}/upgrade`, { method: "POST" });
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
- return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels`);
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
- return this.client.request(`/mobile/numbers/search?${params}`);
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
- const runtimes = parse(await readFile(configPath, "utf-8")).runtimes;
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
- return typeof msg === "object" && msg !== null && msg.type === "COMMAND" && typeof msg.commandId === "string";
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 typeof msg === "object" && msg !== null && msg.type === "SERVICE_ACK";
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
- return typeof msg === "object" && msg !== null && msg.type === "DESIRED_STATE" && Array.isArray(msg.integrations);
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 typeof msg === "object" && msg !== null && msg.type === "req" && typeof msg.id === "string" && typeof msg.method === "string";
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
- localIntegrations = [];
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
- logger$1.debug({ readyState: this.ws.readyState }, "Cloud: closing WebSocket");
6087
+ const ws = this.ws;
6088
+ this.ws = null;
6089
+ logger$1.debug({ readyState: ws.readyState }, "Cloud: closing WebSocket");
5999
6090
  try {
6000
- this.ws.close(1e3, "Daemon shutting down");
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
- agentId: this.config.agentId,
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.on("open", () => {
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: this.ws?.readyState }, "Cloud: WebSocket open, sending registration...");
6145
+ logger$1.debug({ readyState: ws.readyState }, "Cloud: WebSocket open, sending registration...");
6053
6146
  this.backoffMs = 1e3;
6054
6147
  this.sendRegister();
6055
6148
  });
6056
- this.ws.on("message", (data) => {
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
- this.ws.on("ping", () => {
6155
+ ws.on("ping", () => {
6156
+ if (this.ws !== ws) return;
6062
6157
  logger$1.debug("Cloud: received ping, sending pong");
6063
- this.ws?.pong();
6158
+ ws.pong();
6064
6159
  });
6065
- this.ws.on("close", (code, reason) => {
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
- this.ws.on("error", (err) => {
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 version = process.env.ALFE_CLI_VERSION;
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='${version && version.length > 0 ? `@alfe.ai/cli@${version}` : "@alfe.ai/cli@latest"}'
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 envLines = [
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
- ${envLines}
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 pidStr = await readFile(PID_PATH, "utf-8");
6881
- const pid = parseInt(pidStr.trim(), 10);
6882
- if (isNaN(pid)) {
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 line = conn.buffer.slice(0, newlineIdx).trim();
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
- if (!name || !version) {
7194
- this.sendResponse(conn, createIPCError(request.id, "INVALID_REGISTER", "name and version are required"));
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: "0.1.0",
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
- this.child = spawn(command, args, {
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.stdout?.on("data", (data) => {
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
- this.child.stderr?.on("data", (data) => {
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
- this.child.on("exit", (code, signal) => {
23159
- this.child = null;
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
- this.child.on("error", (err) => {
23212
- log$3.error({
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.on("exit", () => {
23300
- clearTimeout(killTimer);
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 = 3e4) {
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
- this.entries.set(commandId, {
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.catch(() => {
23646
- this.entries.delete(commandId);
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 oldest = this.entries.keys().next().value;
23674
- if (oldest === void 0) break;
23675
- this.entries.delete(oldest);
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: await getCliVersion()
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.3" : void 0);
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
- value
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: "0.1.0",
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 (!name || !status) return {
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 are required"
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.ok && response.payload) resolve(response.payload);
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"));