@alfe.ai/gateway 0.9.5 → 0.9.6

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