@alfe.ai/gateway 0.7.4 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/health.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { n as logger$1 } from "./logger.js";
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";
2
3
  import { createRequire } from "node:module";
3
4
  import { mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
4
5
  import { execFile, execSync, spawn } from "node:child_process";
@@ -7,12 +8,12 @@ import { dirname, join } from "node:path";
7
8
  import { homedir } from "node:os";
8
9
  import pino from "pino";
9
10
  import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
10
- import { getEndpointFromToken, readConfig, resolveConfig } from "@alfe.ai/config";
11
+ import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
11
12
  import crypto from "crypto";
12
13
  import { parse } from "smol-toml";
13
14
  import WebSocket from "ws";
14
15
  import { createConnection, createServer } from "node:net";
15
- import { HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
16
+ import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
16
17
  import { AgentApiClient } from "@alfe.ai/agent-api-client";
17
18
  import { Manager, McpBundler, defaultConnect } from "@alfe.ai/mcp-bundler";
18
19
  import stream, { Readable } from "stream";
@@ -563,6 +564,17 @@ var AuthService = class {
563
564
  body: JSON.stringify(input)
564
565
  });
565
566
  }
567
+ /**
568
+ * Preview the proration cost for changing a personal (individual-tier)
569
+ * subscriber's platform plan — read-only companion to `changePlatformPlan`.
570
+ * Nothing is charged.
571
+ */
572
+ previewPlatformPlanChange(input) {
573
+ return this.client.request(`${this.prefix}/subscription/change/preview`, {
574
+ method: "POST",
575
+ body: JSON.stringify(input)
576
+ });
577
+ }
566
578
  updateSeats(seats) {
567
579
  return this.client.request(`${this.prefix}/subscription/seats`, {
568
580
  method: "POST",
@@ -699,6 +711,15 @@ var IntegrationsService = class {
699
711
  reinstallIntegration(agentId, integrationId) {
700
712
  return this.client.request(`/integrations/agents/${agentId}/${integrationId}/reinstall`, { method: "POST" });
701
713
  }
714
+ /**
715
+ * Upgrade an integration to the registry's latest version via the fast,
716
+ * diff-based daemon path (bumps `version` without `reinstallRequestedAt`).
717
+ * The agent stays online — no destructive teardown. Distinct from
718
+ * {@link reinstallIntegration}, which is the destructive repair path.
719
+ */
720
+ upgradeIntegration(agentId, integrationId) {
721
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}/upgrade`, { method: "POST" });
722
+ }
702
723
  getRegistry() {
703
724
  return this.client.request("/integrations/registry");
704
725
  }
@@ -4499,6 +4520,7 @@ enumValues({
4499
4520
  IndividualPro: "individual_pro",
4500
4521
  IndividualMax: "individual_max",
4501
4522
  OrgFree: "org_free",
4523
+ OrgStartup: "org_startup",
4502
4524
  OrgProfessional: "org_professional",
4503
4525
  OrgEnterprise: "org_enterprise"
4504
4526
  });
@@ -4535,7 +4557,8 @@ enumValues({
4535
4557
  enumValues({
4536
4558
  OpenClaw: "openclaw",
4537
4559
  NanoClaw: "nanoclaw",
4538
- Hermes: "hermes"
4560
+ Hermes: "hermes",
4561
+ ClaudeCode: "claude-code"
4539
4562
  });
4540
4563
  const TTS_MODELS = enumValues({
4541
4564
  ElevenLabsTurbo: "elevenlabs-turbo",
@@ -4676,9 +4699,10 @@ enumValues({
4676
4699
  const AnthropicModel = {
4677
4700
  Opus48: "claude-opus-4-8",
4678
4701
  Opus47: "claude-opus-4-7",
4679
- Opus: "claude-opus-4-6",
4680
- Sonnet: "claude-sonnet-4-6",
4681
- Haiku: "claude-haiku-4-5"
4702
+ Opus46: "claude-opus-4-6",
4703
+ Sonnet5: "claude-sonnet-5",
4704
+ Sonnet46: "claude-sonnet-4-6",
4705
+ Haiku45: "claude-haiku-4-5"
4682
4706
  };
4683
4707
  const ANTHROPIC_MODELS = enumValues(AnthropicModel);
4684
4708
  const OpenAIModel = {
@@ -4693,9 +4717,14 @@ const OpenAIModel = {
4693
4717
  GPT54Nano: "gpt-5.4-nano",
4694
4718
  GPT54Pro: "gpt-5.4-pro",
4695
4719
  GPT55: "gpt-5.5",
4720
+ GPT55Pro: "gpt-5.5-pro",
4721
+ GPT56Sol: "gpt-5.6-sol",
4722
+ GPT56Terra: "gpt-5.6-terra",
4723
+ GPT56Luna: "gpt-5.6-luna",
4696
4724
  O3Mini: "o3-mini",
4697
4725
  O4Mini: "o4-mini",
4698
- TextEmbedding3Small: "text-embedding-3-small"
4726
+ TextEmbedding3Small: "text-embedding-3-small",
4727
+ TextEmbedding3Large: "text-embedding-3-large"
4699
4728
  };
4700
4729
  const OPENAI_MODELS = enumValues(OpenAIModel);
4701
4730
  const DeepSeekModel = {
@@ -4719,16 +4748,25 @@ const MiniMaxModel = {
4719
4748
  M3: "MiniMax-M3",
4720
4749
  M27: "MiniMax-M2.7",
4721
4750
  M27HighSpeed: "MiniMax-M2.7-highspeed",
4722
- M25: "MiniMax-M2.5"
4751
+ M25: "MiniMax-M2.5",
4752
+ M21: "MiniMax-M2.1",
4753
+ M2: "MiniMax-M2"
4723
4754
  };
4724
4755
  const MINIMAX_MODELS = enumValues(MiniMaxModel);
4725
4756
  const MistralModel = {
4726
4757
  Large: "mistral-large-latest",
4758
+ Medium: "mistral-medium-latest",
4727
4759
  Small: "mistral-small-latest",
4728
- Codestral: "codestral-latest"
4760
+ Codestral: "codestral-latest",
4761
+ Ministral8b: "ministral-8b-latest",
4762
+ Ministral3b: "ministral-3b-latest",
4763
+ MagistralMedium: "magistral-medium-latest",
4764
+ MagistralSmall: "magistral-small-latest",
4765
+ DevstralMedium: "devstral-medium-latest"
4729
4766
  };
4730
4767
  const MISTRAL_MODELS = enumValues(MistralModel);
4731
4768
  const XAIModel = {
4769
+ Grok45: "grok-4.5",
4732
4770
  Grok43: "grok-4.3",
4733
4771
  Grok4: "grok-4",
4734
4772
  Grok41Fast: "grok-4.1-fast"
@@ -4737,10 +4775,12 @@ const XAI_MODELS = enumValues(XAIModel);
4737
4775
  const ZhipuModel = {
4738
4776
  GLM52: "glm-5.2",
4739
4777
  GLM51: "glm-5.1",
4740
- GLM51Air: "glm-5.1-air"
4778
+ GLM46: "glm-4.6",
4779
+ GLM45: "glm-4.5",
4780
+ GLM45Air: "glm-4.5-air"
4741
4781
  };
4742
4782
  const ZHIPU_MODELS = enumValues(ZhipuModel);
4743
- AnthropicModel.Opus48;
4783
+ AnthropicModel.Sonnet5;
4744
4784
  [
4745
4785
  ...ANTHROPIC_MODELS,
4746
4786
  ...OPENAI_MODELS,
@@ -4760,8 +4800,8 @@ _enum(MISTRAL_MODELS);
4760
4800
  _enum(XAI_MODELS);
4761
4801
  _enum(ZHIPU_MODELS);
4762
4802
  string().min(1);
4763
- AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, 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, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
4764
- AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, 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, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
4803
+ 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;
4804
+ 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;
4765
4805
  enumValues({
4766
4806
  PendingChallenge: "pending_challenge",
4767
4807
  Creating: "creating",
@@ -4837,6 +4877,7 @@ const NotificationType = {
4837
4877
  AutoRechargeFailed: "auto-recharge.failed",
4838
4878
  SubscriptionCreated: "subscription.created",
4839
4879
  SubscriptionCancelled: "subscription.cancelled",
4880
+ StartupGrantEnded: "startup.grant_ended",
4840
4881
  SubscriptionPastDue: "subscription.past_due",
4841
4882
  BalanceThresholdWarning: "balance.threshold.warning",
4842
4883
  PlatformTierPriceIncrease: "platform.tier_price_increase",
@@ -4861,7 +4902,7 @@ const RecipientStrategy = {
4861
4902
  TenantAdmins: "tenant_admins",
4862
4903
  SpecificUser: "specific_user"
4863
4904
  };
4864
- NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PaymentFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.TopUpCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AutoRechargeCompleted, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AutoRechargeFailed, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.SubscriptionCreated, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.SubscriptionCancelled, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.SubscriptionPastDue, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationChannel.Sms, NotificationType.BalanceThresholdWarning, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.PlatformTierPriceIncrease, NotificationCategory.Billing, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationType.AgentCreated, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentProvisionFailed, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentBillingSuspended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.AgentDisconnectedExtended, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.BrowserTakeoverRequested, NotificationCategory.Agents, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.InviteCreated, NotificationCategory.System, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationType.OrgClaimed, NotificationCategory.System, RecipientStrategy.TenantAdmins, NotificationChannel.Email, NotificationChannel.Push, NotificationType.TeamMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.ProjectMemberAdded, NotificationCategory.Team, RecipientStrategy.SpecificUser, NotificationChannel.Email, NotificationChannel.Push, NotificationType.IntegrationInstalled, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push, NotificationType.IntegrationRemoved, NotificationCategory.Integrations, RecipientStrategy.TenantAdmins, NotificationChannel.Push;
4905
+ 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.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;
4865
4906
  //#endregion
4866
4907
  //#region src/config.ts
4867
4908
  /**
@@ -4977,6 +5018,7 @@ function deriveGatewayWsUrl(apiEndpoint) {
4977
5018
  */
4978
5019
  function deriveAgentWorkspace(runtime, home) {
4979
5020
  if (runtime === "openclaw") return join(home, "workspace");
5021
+ if (runtime === "claude-code") return join(home, "workspace");
4980
5022
  return home;
4981
5023
  }
4982
5024
  async function loadRuntimeConfigs() {
@@ -5397,390 +5439,6 @@ var NoopRuntimeGate = class {
5397
5439
  requestRestart() {}
5398
5440
  };
5399
5441
  //#endregion
5400
- //#region src/sentry.ts
5401
- /**
5402
- * Agent-side error reporting (Sentry) for the Alfe CLI + gateway daemon.
5403
- *
5404
- * This module is the single Sentry bootstrap shared by both published
5405
- * agent-side packages — `@alfe.ai/gateway` (the daemon) and `@alfe.ai/cli`
5406
- * (which re-imports these helpers from `@alfe.ai/gateway`). It exists so
5407
- * integration-installation failures on customer/managed VMs surface centrally
5408
- * instead of silently rotting until someone SSHes into the box.
5409
- *
5410
- * Design constraints (see packages/gateway/DEVELOPING.md → "Error reporting"):
5411
- * - Errors only. No tracing/profiling (`tracesSampleRate: 0`, `sampleRate: 1`).
5412
- * - `@sentry/node` is lazy-imported so `alfe --version` doesn't pay for it.
5413
- * - Sentry can NEVER break the CLI/daemon — every entry point is try/catch'd.
5414
- * - The DSNs are baked as constants: published tarballs run on agent VMs and
5415
- * cannot read the repo's `config/config.ts`. That file's `SENTRY_DSNS`
5416
- * registry (keys `agentDaemon` + `cli`) mirrors them as the human reference.
5417
- * - Secrets are scrubbed in `beforeSend`; request/env payloads are dropped.
5418
- * - Opt-out via `ALFE_ERROR_REPORTING=0|false` or `errorReporting = false`
5419
- * in `~/.alfe/config.toml`. Default ON.
5420
- */
5421
- /**
5422
- * Baked per-surface Sentry DSNs (org `alfe-ai`). DSNs are public by design
5423
- * (they only permit event ingestion). Mirrors of `config/config.ts` →
5424
- * `SENTRY_DSNS.agentDaemon` / `SENTRY_DSNS.cli`. If you rotate a DSN, update
5425
- * both places.
5426
- */
5427
- const AGENT_DAEMON_SENTRY_DSN = "https://a47f010d8d39fb4350f913492189f283@o4511008239452160.ingest.us.sentry.io/4511679448547328";
5428
- const CLI_SENTRY_DSN = "https://82dde4631336561f2fcc89d7531623de@o4511008239452160.ingest.us.sentry.io/4511679411191808";
5429
- /**
5430
- * Dedicated project for errors emitted BY the agent runtime child process
5431
- * (OpenClaw/Hermes) — crashes, spawn failures, and error-looking output. Kept
5432
- * separate from `agent-daemon` so runtime noise never drowns daemon issues.
5433
- * Mirrors `SENTRY_DSNS.agentRuntime` in `config/config.ts`.
5434
- */
5435
- const AGENT_RUNTIME_SENTRY_DSN = "https://feb7fb2e3b8e723aec518e74715bfa6d@o4511008239452160.ingest.us.sentry.io/4511683667558400";
5436
- /**
5437
- * MCP tool/server failures on agent VMs — reported by the daemon-hosted MCP
5438
- * bundler (tool errors, child crashes, stderr output). Dedicated `local-mcp`
5439
- * project for the locally-installed MCP surface: the cloud MCP Fly service
5440
- * keeps the `mcp` project (`SENTRY_DSNS.mcp`), so planned daemon restarts
5441
- * tearing down MCP children never pollute the remote service's signal.
5442
- * The `source: agent-daemon` + `agentId` tags are kept for continuity.
5443
- */
5444
- const AGENT_MCP_SENTRY_DSN = "https://b4c4b4e19135692206bacabcd12a7fd3@o4511008239452160.ingest.us.sentry.io/4511697094377472";
5445
- /** Surface → Sentry project: `cli` and `agent-daemon` respectively. */
5446
- const SURFACE_DSNS = {
5447
- cli: CLI_SENTRY_DSN,
5448
- daemon: AGENT_DAEMON_SENTRY_DSN
5449
- };
5450
- /** Held after a successful init so the capture helpers can run synchronously. */
5451
- let sentry = null;
5452
- /**
5453
- * Additional bound clients (daemon surface only). The default client keeps the
5454
- * daemon's own DSN; runtime child-process events and MCP failures are routed
5455
- * to their own projects via explicitly-bound Scopes — the documented
5456
- * multi-client pattern. `null` until `initAgentSentry({ surface: "daemon" })`
5457
- * succeeds.
5458
- */
5459
- let runtimeClient = null;
5460
- let runtimeScope = null;
5461
- let mcpClient = null;
5462
- let mcpScope = null;
5463
- /**
5464
- * Map the configured API URL to a coarse environment tag. Best-effort — returns
5465
- * `"unknown"` when no config is present yet (e.g. `alfe login` pre-setup).
5466
- */
5467
- function deriveEnvironment() {
5468
- try {
5469
- const { apiUrl } = resolveConfig();
5470
- if (apiUrl.includes("api.dev.alfe.ai") || apiUrl.includes("dev.alfe.ai")) return "dev";
5471
- if (apiUrl.includes("api.test.alfe.ai")) return "test";
5472
- if (apiUrl.includes("api.demo.alfe.ai")) return "demo";
5473
- if (apiUrl.includes("api.alfe.ai")) return "prod";
5474
- } catch {}
5475
- return "unknown";
5476
- }
5477
- /** Active runtime (`openclaw`/`hermes`) from config, or undefined if unknown. */
5478
- function deriveRuntime() {
5479
- try {
5480
- return resolveConfig().runtime;
5481
- } catch {
5482
- return;
5483
- }
5484
- }
5485
- /** True when the operator has opted out of error reporting. */
5486
- function errorReportingDisabled() {
5487
- const env = process.env.ALFE_ERROR_REPORTING?.trim().toLowerCase();
5488
- if (env === "0" || env === "false") return true;
5489
- try {
5490
- if (readConfig().errorReporting === false) return true;
5491
- } catch {}
5492
- return false;
5493
- }
5494
- const KV_SECRET_RE = /(?<![A-Za-z0-9])([A-Za-z0-9_-]*(?:authorization|api[_-]?key|apikey|token|secret|password|passwd|bearer|credential)s?)(\s*[:=]\s*|\s+)("?)([^\s"']+)\3/gi;
5495
- const URL_QUERY_SECRET_RE = /([?&](?:[A-Za-z0-9_-]*(?:token|secret|key|signature|credential|password)|sig|code|x-amz-[a-z-]+)=)[^&\s"']+/gi;
5496
- const MIN_BARE_SECRET_LEN = 16;
5497
- const ALFE_TOKEN_RE = /alfe_(?:dev|test|demo|live)_[A-Za-z0-9._-]+/g;
5498
- const BEARER_RE = /\bbearer\s+[A-Za-z0-9._\-+/=]+/gi;
5499
- function scrubString(value) {
5500
- return value.replace(BEARER_RE, "Bearer [REDACTED]").replace(URL_QUERY_SECRET_RE, "$1[REDACTED]").replace(KV_SECRET_RE, (match, label, sep, _q, secret) => {
5501
- if (!/[:=]/.test(sep) && secret.length < MIN_BARE_SECRET_LEN) return match;
5502
- return `${label}${sep}[REDACTED]`;
5503
- }).replace(ALFE_TOKEN_RE, "alfe_[REDACTED]");
5504
- }
5505
- /** Scrub secrets from a breadcrumb's message + string data values in place. */
5506
- function scrubBreadcrumb(b) {
5507
- if (typeof b.message === "string") b.message = scrubString(b.message);
5508
- const data = b.data;
5509
- if (data) for (const k of Object.keys(data)) {
5510
- const dv = data[k];
5511
- if (typeof dv === "string") data[k] = scrubString(dv);
5512
- }
5513
- return b;
5514
- }
5515
- /**
5516
- * Scrub secrets and drop request/env payloads before an event is sent.
5517
- * Generic so it preserves the caller's exact event subtype (`beforeSend`
5518
- * receives — and must return — an `ErrorEvent`, not a widened `Event`).
5519
- */
5520
- function scrubEvent(event) {
5521
- delete event.request;
5522
- if (event.contexts) delete event.contexts.runtime;
5523
- delete event.extra;
5524
- delete event.server_name;
5525
- if (typeof event.message === "string") event.message = scrubString(event.message);
5526
- const values = event.exception?.values;
5527
- if (values) {
5528
- for (const v of values) if (typeof v.value === "string") v.value = scrubString(v.value);
5529
- }
5530
- if (event.breadcrumbs) for (const b of event.breadcrumbs) scrubBreadcrumb(b);
5531
- return event;
5532
- }
5533
- /**
5534
- * Initialise Sentry for the agent-side CLI/daemon. Idempotent, best-effort, and
5535
- * a no-op when opted out or when the baked DSN is empty. Never throws.
5536
- */
5537
- async function initAgentSentry(options) {
5538
- try {
5539
- if (sentry) return;
5540
- if (errorReportingDisabled()) return;
5541
- const dsn = SURFACE_DSNS[options.surface].trim();
5542
- if (!dsn) return;
5543
- const mod = await import("@sentry/node");
5544
- if (options.surface === "daemon") {
5545
- const buildBoundScope = (dsn, extraTags) => {
5546
- try {
5547
- if (!dsn.trim()) return null;
5548
- const client = new mod.NodeClient({
5549
- dsn: dsn.trim(),
5550
- environment: deriveEnvironment(),
5551
- ...options.release ? { release: options.release } : {},
5552
- sampleRate: 1,
5553
- tracesSampleRate: 0,
5554
- sendDefaultPii: false,
5555
- transport: mod.makeNodeTransport,
5556
- stackParser: mod.defaultStackParser,
5557
- integrations: [],
5558
- beforeSend: (event) => scrubEvent(event)
5559
- });
5560
- client.init();
5561
- const scope = new mod.Scope();
5562
- scope.setClient(client);
5563
- const runtime = deriveRuntime();
5564
- if (runtime) scope.setTag("runtime", runtime);
5565
- for (const [k, v] of Object.entries(extraTags)) scope.setTag(k, v);
5566
- return {
5567
- client,
5568
- scope
5569
- };
5570
- } catch {
5571
- return null;
5572
- }
5573
- };
5574
- if (!runtimeScope) {
5575
- const bound = buildBoundScope(AGENT_RUNTIME_SENTRY_DSN, {});
5576
- runtimeClient = bound?.client ?? null;
5577
- runtimeScope = bound?.scope ?? null;
5578
- }
5579
- if (!mcpScope) {
5580
- const bound = buildBoundScope(AGENT_MCP_SENTRY_DSN, { source: "agent-daemon" });
5581
- mcpClient = bound?.client ?? null;
5582
- mcpScope = bound?.scope ?? null;
5583
- }
5584
- }
5585
- if (mod.getClient()) {
5586
- sentry = mod;
5587
- return;
5588
- }
5589
- mod.init({
5590
- dsn,
5591
- environment: deriveEnvironment(),
5592
- ...options.release ? { release: options.release } : {},
5593
- sampleRate: 1,
5594
- tracesSampleRate: 0,
5595
- sendDefaultPii: false,
5596
- ...options.surface === "daemon" ? { integrations: (defaults) => defaults.filter((i) => i.name !== "OnUncaughtException" && i.name !== "OnUnhandledRejection") } : {},
5597
- beforeSend: (event) => scrubEvent(event),
5598
- beforeBreadcrumb: (breadcrumb) => scrubBreadcrumb(breadcrumb)
5599
- });
5600
- sentry = mod;
5601
- mod.setTag("surface", options.surface);
5602
- const runtime = deriveRuntime();
5603
- if (runtime) mod.setTag("runtime", runtime);
5604
- } catch {
5605
- sentry = null;
5606
- }
5607
- }
5608
- /**
5609
- * Attach the resolved agent identity to all subsequent events. Called by the
5610
- * daemon once `loadDaemonConfig()` has resolved `agentId`/`runtime`.
5611
- */
5612
- function setAgentContext(context) {
5613
- if (!sentry) return;
5614
- try {
5615
- for (const scope of [
5616
- sentry,
5617
- runtimeScope,
5618
- mcpScope
5619
- ]) {
5620
- if (!scope) continue;
5621
- if (context.agentId) scope.setTag("agentId", context.agentId);
5622
- if (context.runtime) scope.setTag("runtime", context.runtime);
5623
- }
5624
- } catch {}
5625
- }
5626
- /**
5627
- * Capture an integration lifecycle failure (install/activate/reinstall/remove)
5628
- * with `{ integration, phase }` tags. Accepts an `Error` (captured with stack)
5629
- * or a plain message string (captured as an error-level message). No-op when
5630
- * Sentry is not initialised.
5631
- */
5632
- function captureIntegrationFailure(integration, phase, cause) {
5633
- if (!sentry) return;
5634
- try {
5635
- if (cause instanceof Error) sentry.captureException(cause, { tags: {
5636
- integration,
5637
- phase
5638
- } });
5639
- else sentry.captureMessage(String(cause), {
5640
- level: "error",
5641
- tags: {
5642
- integration,
5643
- phase
5644
- }
5645
- });
5646
- } catch {}
5647
- }
5648
- /**
5649
- * Capture a generic CLI failure with a `{ context }` tag. The CLI counterpart
5650
- * to `captureIntegrationFailure` — used by `exitWithError` at the handled-error
5651
- * `process.exit()` sites in `@alfe.ai/cli`'s commands, which otherwise report
5652
- * nothing (the error was caught, never thrown). Accepts an `Error` (captured
5653
- * with stack), a defined non-Error value (captured as an error-level message),
5654
- * or nothing (the `context` string itself becomes the message). No-op when
5655
- * Sentry is not initialised. Never throws.
5656
- */
5657
- function captureCliFailure(context, cause) {
5658
- if (!sentry) return;
5659
- try {
5660
- if (cause instanceof Error) sentry.captureException(cause, { tags: { context } });
5661
- else if (cause === void 0) sentry.captureMessage(context, { level: "error" });
5662
- else sentry.captureMessage(String(cause), {
5663
- level: "error",
5664
- tags: { context }
5665
- });
5666
- } catch {}
5667
- }
5668
- /**
5669
- * Capture a runtime child-process crash (non-graceful exit) or spawn failure
5670
- * into the dedicated `agent-runtime` project. Recent output travels as
5671
- * breadcrumbs — NEVER `extra`, which `scrubEvent` (the runtime client's
5672
- * `beforeSend`) deletes. Throttling is the caller's job (`CaptureThrottle` in
5673
- * `runtime-output-monitor.ts`). No-op when the runtime client is not
5674
- * initialised. Never throws.
5675
- */
5676
- function captureRuntimeCrash(opts) {
5677
- if (!runtimeScope) return;
5678
- try {
5679
- const scope = runtimeScope.clone();
5680
- for (const { stream, line } of opts.recentOutput) scope.addBreadcrumb({
5681
- category: `runtime.${stream}`,
5682
- level: stream === "stderr" ? "warning" : "info",
5683
- message: line
5684
- });
5685
- const crashKey = opts.spawnError ? `spawn:${opts.spawnError.code ?? "error"}` : String(opts.code ?? opts.signal ?? "unknown");
5686
- scope.setFingerprint([
5687
- "runtime-crash",
5688
- opts.runtime,
5689
- crashKey
5690
- ]);
5691
- scope.setTags({
5692
- runtime: opts.runtime,
5693
- exitCode: String(opts.code),
5694
- signal: String(opts.signal),
5695
- crashesSuppressed: String(opts.crashesSuppressed ?? 0)
5696
- });
5697
- if (opts.spawnError) scope.captureException(opts.spawnError);
5698
- else scope.captureMessage(`Runtime ${opts.runtime} crashed (code=${String(opts.code)}, signal=${String(opts.signal)})`, "error");
5699
- } catch {}
5700
- }
5701
- /**
5702
- * Capture an error-looking block of runtime output (stack trace, Python
5703
- * traceback, or ERROR-level log line) detected while the child is running.
5704
- * The block lines travel as breadcrumbs; the head line is the message.
5705
- * Throttling/dedupe is the caller's job. No-op when Sentry is not initialised.
5706
- * Never throws.
5707
- */
5708
- function captureRuntimeErrorOutput(opts) {
5709
- if (!runtimeScope) return;
5710
- try {
5711
- const scope = runtimeScope.clone();
5712
- for (const line of opts.lines) scope.addBreadcrumb({
5713
- category: `runtime.${opts.stream}`,
5714
- level: "error",
5715
- message: line
5716
- });
5717
- scope.setFingerprint([
5718
- "runtime-output",
5719
- opts.runtime,
5720
- opts.kind,
5721
- opts.fingerprintKey
5722
- ]);
5723
- scope.setTags({
5724
- runtime: opts.runtime,
5725
- stream: opts.stream,
5726
- kind: opts.kind,
5727
- suppressedCount: String(opts.suppressedCount ?? 0)
5728
- });
5729
- scope.captureMessage(opts.lines[0].slice(0, 300), "error");
5730
- } catch {}
5731
- }
5732
- /**
5733
- * Capture an MCP failure (tool error, server crash, or error-looking stderr
5734
- * output) into the `mcp` project via the daemon's bound MCP client. Throttling
5735
- * is the caller's job (`mcp-error-capture.ts`). No-op when the MCP client is
5736
- * not initialised. Never throws.
5737
- */
5738
- function captureMcpFailure(opts) {
5739
- if (!mcpScope) return;
5740
- try {
5741
- const scope = mcpScope.clone();
5742
- for (const line of opts.lines ?? []) scope.addBreadcrumb({
5743
- category: `mcp.${opts.server}`,
5744
- level: "error",
5745
- message: line
5746
- });
5747
- scope.setFingerprint([
5748
- "agent-mcp",
5749
- opts.server,
5750
- opts.kind,
5751
- ...opts.tool ? [opts.tool] : []
5752
- ]);
5753
- scope.setTags({
5754
- server: opts.server,
5755
- kind: opts.kind,
5756
- ...opts.tool ? { tool: opts.tool } : {},
5757
- suppressedCount: String(opts.suppressedCount ?? 0)
5758
- });
5759
- scope.captureMessage(`MCP ${opts.server}${opts.tool ? `.${opts.tool}` : ""} ${opts.kind}: ${opts.message.slice(0, 300)}`, "error");
5760
- } catch {}
5761
- }
5762
- /** Flush queued events (small timeout) so short-lived commands deliver them. */
5763
- async function flushSentry(timeoutMs = 2e3) {
5764
- for (const client of [runtimeClient, mcpClient]) try {
5765
- if (client) await client.flush(timeoutMs);
5766
- } catch {}
5767
- if (!sentry) return;
5768
- try {
5769
- await sentry.flush(timeoutMs);
5770
- } catch {}
5771
- }
5772
- /**
5773
- * Capture a fatal error — for the CLI's top-level catch. Does NOT flush: the
5774
- * caller's `finally { await flushSentry() }` delivers it (flushing here too
5775
- * doubled the worst-case exit delay on a failing command).
5776
- */
5777
- function captureFatal(cause) {
5778
- if (!sentry) return;
5779
- try {
5780
- sentry.captureException(cause);
5781
- } catch {}
5782
- }
5783
- //#endregion
5784
5442
  //#region src/reconciliation.ts
5785
5443
  const log$5 = logger$1.child({ component: "Reconciliation" });
5786
5444
  /**
@@ -7099,6 +6757,14 @@ var CommandQueue = class {
7099
6757
  */
7100
6758
  const LAUNCHD_LABEL = "ai.alfe.gateway";
7101
6759
  const SYSTEMD_SERVICE = "alfe-gateway";
6760
+ /**
6761
+ * On-disk path for the boot-time self-heal guard script (Linux only).
6762
+ * Written next to the systemd unit at setup time and invoked via
6763
+ * `ExecStartPre=` — see `writeGuardScript` and `generateSystemdUnit`.
6764
+ */
6765
+ function getGuardScriptPath() {
6766
+ return isRootUser() ? "/usr/local/lib/alfe/alfe-cli-guard.sh" : join(homedir(), ".alfe", "alfe-cli-guard.sh");
6767
+ }
7102
6768
  function isRootUser() {
7103
6769
  return process.getuid?.() === 0;
7104
6770
  }
@@ -7174,16 +6840,108 @@ function generateLaunchdPlist() {
7174
6840
  </plist>`;
7175
6841
  }
7176
6842
  /**
6843
+ * Boot-time self-heal guard (Linux only).
6844
+ *
6845
+ * Runs as an `ExecStartPre=` BEFORE the daemon launches, OUTSIDE the
6846
+ * (possibly-broken) `alfe` binary. It exists for the case defense #1
6847
+ * (verify-before-exit in `upgrade.ts`) structurally cannot cover: the daemon
6848
+ * is KILLED during `npm install -g @alfe.ai/cli` (reboot / OOM), leaving the
6849
+ * package with deps but no `dist/` and a dangling `/usr/bin/alfe` symlink.
6850
+ * systemd then execs a dangling binary forever → 203/EXEC crash-loop, a
6851
+ * permanent brick with no automatic recovery (real prod incident,
6852
+ * ~13,570 restarts over 2 days).
6853
+ *
6854
+ * The guard:
6855
+ * - checks the CLI is intact: the symlink target (`dist/index.js`) exists AND
6856
+ * `alfe --version` exits 0;
6857
+ * - if broken, reinstalls `@alfe.ai/cli@$ALFE_CLI_VERSION` (the exact version
6858
+ * baked into the unit's `Environment=`; falls back to `@latest` when
6859
+ * unset), so the daemon then execs a working binary;
6860
+ * - is idempotent + fast (no-op when healthy) and NEVER wedges boot: any of
6861
+ * its own failures log + `exit 0` so a human can still SSH in.
6862
+ *
6863
+ * POSIX sh (dash-safe): the box may not have bash. Keep the two intactness
6864
+ * checks in lockstep with `verifyCliInstall` in `verify-cli-install.ts`.
6865
+ */
6866
+ /** @internal exported for unit tests; not re-exported from the barrel. */
6867
+ function generateGuardScript() {
6868
+ const version = process.env.ALFE_CLI_VERSION;
6869
+ return `#!/bin/sh
6870
+ # Alfe CLI boot-time self-heal guard. Auto-generated by 'alfe setup' — do not edit.
6871
+ # Repairs an interrupted 'npm install -g @alfe.ai/cli' before the daemon starts,
6872
+ # so a mid-install reboot can't brick the agent with a 203/EXEC crash-loop.
6873
+ # Must never wedge boot: every failure path logs and exits 0.
6874
+ set -u
6875
+
6876
+ TARGET='${version && version.length > 0 ? `@alfe.ai/cli@${version}` : "@alfe.ai/cli@latest"}'
6877
+ log() { echo "[alfe-cli-guard] $*" >&2; }
6878
+
6879
+ # Resolve the alfe bin and its real target (dist/index.js). readlink -f follows
6880
+ # the whole symlink chain; fall back to the bin path itself if unavailable.
6881
+ BIN="$(command -v alfe 2>/dev/null || true)"
6882
+ if [ -n "$BIN" ]; then
6883
+ ENTRY="$(readlink -f "$BIN" 2>/dev/null || echo "$BIN")"
6884
+ else
6885
+ ENTRY=""
6886
+ fi
6887
+
6888
+ healthy=1
6889
+ if [ -z "$ENTRY" ] || [ ! -f "$ENTRY" ]; then
6890
+ healthy=0
6891
+ log "CLI entry missing (bin=\${BIN:-<none>} entry=\${ENTRY:-<none>})"
6892
+ elif ! alfe --version >/dev/null 2>&1; then
6893
+ healthy=0
6894
+ log "alfe --version failed"
6895
+ fi
6896
+
6897
+ if [ "$healthy" -eq 1 ]; then
6898
+ exit 0
6899
+ fi
6900
+
6901
+ log "CLI install looks broken — reinstalling $TARGET"
6902
+ # Blow away a partial tree first so npm re-materialises dist/ cleanly. Best-effort.
6903
+ GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"
6904
+ if [ -n "$GLOBAL_ROOT" ] && [ -d "$GLOBAL_ROOT/@alfe.ai/cli" ]; then
6905
+ rm -rf "$GLOBAL_ROOT/@alfe.ai/cli" 2>/dev/null || true
6906
+ fi
6907
+
6908
+ # Bound the reinstall so a slow/unreachable registry at boot can't stall the
6909
+ # ExecStartPre up to systemd's DefaultTimeoutStartSec (~90s). Two layers:
6910
+ # - npm --fetch-timeout/--fetch-retries cap per-request/hung-socket time;
6911
+ # - a wall-clock 'timeout' wrapper caps the whole install when present
6912
+ # ('timeout' isn't guaranteed, so fall back to a bare install).
6913
+ # stdout->stderr ('>&2') so npm's chatter lands in the journal, not on fd1.
6914
+ if command -v timeout >/dev/null 2>&1; then
6915
+ timeout 120 npm install -g "$TARGET" --fetch-timeout=60000 --fetch-retries=2 >&2
6916
+ rc=$?
6917
+ else
6918
+ npm install -g "$TARGET" --fetch-timeout=60000 --fetch-retries=2 >&2
6919
+ rc=$?
6920
+ fi
6921
+ if [ "$rc" -eq 0 ]; then
6922
+ log "reinstall succeeded"
6923
+ else
6924
+ log "reinstall FAILED (rc=$rc) — daemon start may still fail; a human can SSH in to repair"
6925
+ fi
6926
+
6927
+ # Always exit 0: even a failed repair must not block the unit from attempting
6928
+ # ExecStart (which then fails visibly) rather than wedging silently in pre-start.
6929
+ exit 0
6930
+ `;
6931
+ }
6932
+ /**
7177
6933
  * Generate a systemd unit for Linux.
7178
6934
  * Root users get a system-level unit; non-root get a user-level unit.
7179
6935
  */
6936
+ /** @internal exported for unit tests; not re-exported from the barrel. */
7180
6937
  function generateSystemdUnit() {
7181
6938
  const alfeBin = getAlfeBinPath();
7182
6939
  const root = isRootUser();
7183
6940
  const envLines = [
7184
6941
  "ALFE_MANAGED",
7185
6942
  "ALFE_API_KEY",
7186
- "LOG_LEVEL"
6943
+ "LOG_LEVEL",
6944
+ "ALFE_CLI_VERSION"
7187
6945
  ].filter((key) => process.env[key]).map((key) => `Environment=${key}=${process.env[key] ?? ""}`).join("\n");
7188
6946
  return `[Unit]
7189
6947
  Description=Alfe Gateway Daemon
@@ -7192,6 +6950,7 @@ Wants=network-online.target
7192
6950
 
7193
6951
  [Service]
7194
6952
  Type=simple
6953
+ ExecStartPre=-/bin/sh ${getGuardScriptPath()}
7195
6954
  ExecStart=${alfeBin} gateway daemon
7196
6955
  Restart=always
7197
6956
  RestartSec=10
@@ -7208,6 +6967,19 @@ ${envLines}
7208
6967
  WantedBy=${root ? "multi-user.target" : "default.target"}`;
7209
6968
  }
7210
6969
  /**
6970
+ * Write the boot-time self-heal guard script to disk (Linux only) and make it
6971
+ * executable. Called from `installSystemd` before the unit is written.
6972
+ */
6973
+ async function writeGuardScript() {
6974
+ const guardPath = getGuardScriptPath();
6975
+ await mkdir(dirname(guardPath), { recursive: true });
6976
+ await writeFile(guardPath, generateGuardScript(), {
6977
+ encoding: "utf-8",
6978
+ mode: 493
6979
+ });
6980
+ logger$1.info({ path: guardPath }, "Wrote CLI self-heal guard script");
6981
+ }
6982
+ /**
7211
6983
  * Install the service unit for the current platform.
7212
6984
  */
7213
6985
  async function installService() {
@@ -7269,6 +7041,7 @@ async function installSystemd() {
7269
7041
  const dir = root ? "/etc/systemd/system" : join(homedir(), ".config", "systemd", "user");
7270
7042
  const ctl = root ? "systemctl" : "systemctl --user";
7271
7043
  if (!root) await mkdir(dir, { recursive: true });
7044
+ await writeGuardScript();
7272
7045
  await writeFile(unitPath, generateSystemdUnit(), "utf-8");
7273
7046
  logger$1.info({
7274
7047
  path: unitPath,
@@ -7291,6 +7064,9 @@ async function uninstallSystemd() {
7291
7064
  try {
7292
7065
  await unlink(unitPath);
7293
7066
  } catch {}
7067
+ try {
7068
+ await unlink(getGuardScriptPath());
7069
+ } catch {}
7294
7070
  try {
7295
7071
  execSync(`${ctl} daemon-reload`, { stdio: "pipe" });
7296
7072
  } catch {}
@@ -22659,6 +22435,10 @@ var RuntimeProcess = class {
22659
22435
  command: "hermes",
22660
22436
  args: ["gateway", "run"]
22661
22437
  };
22438
+ case "claude-code": return {
22439
+ command: "alfe-claude-host",
22440
+ args: ["run"]
22441
+ };
22662
22442
  default: throw new Error(`Unsupported runtime: ${this.options.runtime}`);
22663
22443
  }
22664
22444
  }
@@ -23642,6 +23422,13 @@ let mcpManagerRef = null;
23642
23422
  * and for managed/self-hosted runtimes other than hermes.
23643
23423
  */
23644
23424
  let hermesMcpSync = null;
23425
+ /**
23426
+ * claude-code-only MCP store consumer. Mirrors the runtime-agnostic MCP store
23427
+ * into the host's `--mcp-config` JSON (~/.claude-code/mcp-config.json), injecting
23428
+ * ALFE_API_KEY into every stdio server's env. Null for every other runtime
23429
+ * (never constructed) — same shape/role as {@link hermesMcpSync}.
23430
+ */
23431
+ let claudeCodeMcpSync = null;
23645
23432
  let aiProxyServer = null;
23646
23433
  let runtimeProcess = null;
23647
23434
  let turnActivityProbe = null;
@@ -23755,6 +23542,10 @@ const RUNTIME_VERSION_COMMANDS = {
23755
23542
  hermes: {
23756
23543
  command: "hermes",
23757
23544
  args: ["version"]
23545
+ },
23546
+ "claude-code": {
23547
+ command: "alfe-claude-host",
23548
+ args: ["--version"]
23758
23549
  }
23759
23550
  };
23760
23551
  /**
@@ -23993,6 +23784,13 @@ async function startDaemon() {
23993
23784
  runtime: name,
23994
23785
  home: runtimeCfg.workspace
23995
23786
  }, "Registered Hermes runtime applier");
23787
+ } else if (name === "claude-code") {
23788
+ runtimeAppliers.set(name, new ClaudeCodeApplier({ home: runtimeCfg.workspace }));
23789
+ logger$1.info({
23790
+ runtime: name,
23791
+ home: runtimeCfg.workspace,
23792
+ agentWorkspace: runtimeCfg.agentWorkspace
23793
+ }, "Registered Claude Code runtime applier");
23996
23794
  } else logger$1.warn({ runtime: name }, "Unknown runtime type — skipping");
23997
23795
  runtimeAppliersRef = runtimeAppliers;
23998
23796
  const integrationsService = new IntegrationsService(new AlfeApiClient({
@@ -24011,7 +23809,7 @@ async function startDaemon() {
24011
23809
  idleTtlMs: 0,
24012
23810
  ...mcpErrorHooks
24013
23811
  }, { connect: defaultConnect });
24014
- if (config.runtime === "hermes") logger$1.info("Hermes runtime daemon MCP bundler left idle (store mirrored to config.yaml by HermesMcpSync)");
23812
+ if (config.runtime === "hermes" || config.runtime === "claude-code") logger$1.info({ runtime: config.runtime }, "MCP store mirrored into runtime config by the runtime MCP sync — daemon MCP bundler left idle");
24015
23813
  else {
24016
23814
  await mcpManager.loadIntoBundler(mcpBundler);
24017
23815
  const warmBundler = (reason) => {
@@ -24070,6 +23868,18 @@ async function startDaemon() {
24070
23868
  hermesMcpSync.start();
24071
23869
  }
24072
23870
  }
23871
+ if (config.runtime === "claude-code") {
23872
+ const ccCfg = config.runtimes[config.runtime];
23873
+ if (ccCfg) {
23874
+ claudeCodeMcpSync = new ClaudeCodeMcpSync({
23875
+ manager: mcpManager,
23876
+ home: ccCfg.workspace,
23877
+ apiKey: config.apiKey,
23878
+ requestRestart: requestRuntimeRestart
23879
+ });
23880
+ claudeCodeMcpSync.start();
23881
+ }
23882
+ }
24073
23883
  cloudClient.setOnReconciliationComplete(() => {
24074
23884
  try {
24075
23885
  const activeCommands = integrationManager.getActiveCommands();
@@ -24133,6 +23943,10 @@ async function startDaemon() {
24133
23943
  hermesMcpSync.stop();
24134
23944
  hermesMcpSync = null;
24135
23945
  }
23946
+ if (claudeCodeMcpSync) {
23947
+ claudeCodeMcpSync.stop();
23948
+ claudeCodeMcpSync = null;
23949
+ }
24136
23950
  if (mcpBundler) {
24137
23951
  logger$1.debug("Stopping MCP bundler...");
24138
23952
  await mcpBundler.dispose();
@@ -24966,4 +24780,4 @@ function formatDuration(ms) {
24966
24780
  return `${String(Math.round(seconds / 3600))}h`;
24967
24781
  }
24968
24782
  //#endregion
24969
- export { PID_PATH as C, resolveAgentIdentity as D, loadDaemonConfig as E, PINNED_OPENCLAW_VERSION as O, ALFE_DIR as S, fetchAgentConfig as T, captureRuntimeErrorOutput as _, installService as a, setAgentContext as b, uninstallService as c, AGENT_RUNTIME_SENTRY_DSN as d, captureCliFailure as f, captureRuntimeCrash as g, captureMcpFailure as h, checkExistingDaemon as i, AGENT_DAEMON_SENTRY_DSN as l, captureIntegrationFailure as m, queryDaemonHealth as n, startService as o, captureFatal as p, startDaemon as r, stopExistingDaemon as s, formatHealthReport as t, AGENT_MCP_SENTRY_DSN as u, flushSentry as v, SOCKET_PATH as w, PROTOCOL_VERSION as x, initAgentSentry as y };
24783
+ export { installService as a, uninstallService as c, PID_PATH as d, SOCKET_PATH as f, PINNED_OPENCLAW_VERSION as g, resolveAgentIdentity as h, checkExistingDaemon as i, PROTOCOL_VERSION as l, loadDaemonConfig as m, queryDaemonHealth as n, startService as o, fetchAgentConfig as p, startDaemon as r, stopExistingDaemon as s, formatHealthReport as t, ALFE_DIR as u };