@alfe.ai/gateway 0.10.9 → 0.11.0

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
@@ -7,7 +7,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSy
7
7
  import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, LockManager, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
8
8
  import { execFile, execSync } from "node:child_process";
9
9
  import { promisify } from "node:util";
10
- import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
10
+ import { getEndpointFromToken, readConfig, resolveAgentWorkspace } from "@alfe.ai/config";
11
11
  import { AgentApiClient } from "@alfe.ai/agent-api-client";
12
12
  import { parse } from "smol-toml";
13
13
  import WebSocket from "ws";
@@ -3744,11 +3744,23 @@ function superRefine(fn) {
3744
3744
  * new host, then cut a CLI release so the pin travels with the CLI version. The
3745
3745
  * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
3746
3746
  *
3747
- * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
3747
+ * NOTE: managed claude-code agents run on Hetzner VMs provisioned by cloud-init
3748
+ * + `alfe setup --managed` (no Docker image), so — unlike
3748
3749
  * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
3749
3750
  * keep in sync.
3750
3751
  */
3751
- const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.6";
3752
+ const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.15";
3753
+ /**
3754
+ * The `@anthropic-ai/claude-code` (the `claude` CLI) version Alfe installs for
3755
+ * the claude-code runtime: always on managed VMs (`alfe setup --managed`), and
3756
+ * on self-hosted machines only when no `claude` is present (a user-installed
3757
+ * `claude` is never replaced). Pinned to npm's `stable` dist-tag as of
3758
+ * 2026-10-14 — `latest` moves daily and the host drives `claude -p` flags
3759
+ * (`--resume`, `--mcp-config`, `--strict-mcp-config`, `--permission-mode`)
3760
+ * whose behaviour must be validated before a bump. The pin travels with the
3761
+ * CLI release.
3762
+ */
3763
+ const PINNED_CLAUDE_CODE_VERSION = "2.1.287";
3752
3764
  //#endregion
3753
3765
  //#region ../../packages-internal/ids/dist/prefixes.js
3754
3766
  const ID_PREFIXES = {
@@ -4388,13 +4400,18 @@ const AnthropicModel = {
4388
4400
  Opus48: "claude-opus-4-8",
4389
4401
  Opus47: "claude-opus-4-7",
4390
4402
  Opus46: "claude-opus-4-6",
4403
+ Sonnet55: "claude-sonnet-5-5",
4391
4404
  Sonnet5: "claude-sonnet-5",
4392
4405
  Sonnet46: "claude-sonnet-4-6",
4406
+ Haiku55: "claude-haiku-5-5",
4393
4407
  Haiku45: "claude-haiku-4-5"
4394
4408
  };
4395
4409
  const ANTHROPIC_MODELS = enumValues(AnthropicModel);
4396
4410
  const OpenAIModel = {
4397
4411
  GPT6Astra: "gpt-6-astra",
4412
+ GPT61Sol: "gpt-6.1-sol",
4413
+ GPT6Sol: "gpt-6-sol",
4414
+ GPT6Luna: "gpt-6-luna",
4398
4415
  GPT4o: "gpt-4o",
4399
4416
  GPT4oMini: "gpt-4o-mini",
4400
4417
  O3: "o3",
@@ -4424,8 +4441,8 @@ const DeepSeekModel = {
4424
4441
  const DEEPSEEK_MODELS = enumValues(DeepSeekModel);
4425
4442
  const GoogleModel = {
4426
4443
  Gemini38Flash: "gemini-3.8-flash",
4427
- Gemini37Flash: "gemini-3.7-flash",
4428
4444
  Gemini36Flash: "gemini-3.6-flash",
4445
+ Gemini37Flash: "gemini-3.7-flash",
4429
4446
  Gemini35Flash: "gemini-3.5-flash",
4430
4447
  Gemini35FlashLite: "gemini-3.5-flash-lite",
4431
4448
  Gemini31Pro: "gemini-3.1-pro-preview",
@@ -4439,12 +4456,11 @@ const MiniMaxModel = {
4439
4456
  M3: "MiniMax-M3",
4440
4457
  M27: "MiniMax-M2.7",
4441
4458
  M27HighSpeed: "MiniMax-M2.7-highspeed",
4442
- M25: "MiniMax-M2.5",
4443
- M21: "MiniMax-M2.1",
4444
- M2: "MiniMax-M2"
4459
+ M25: "MiniMax-M2.5"
4445
4460
  };
4446
4461
  const MINIMAX_MODELS = enumValues(MiniMaxModel);
4447
4462
  const MistralModel = {
4463
+ Large4: "mistral-large-4",
4448
4464
  Large: "mistral-large-latest",
4449
4465
  Medium: "mistral-medium-latest",
4450
4466
  Small: "mistral-small-latest",
@@ -4514,7 +4530,7 @@ _enum(ZHIPU_MODELS);
4514
4530
  _enum(MOONSHOT_MODELS);
4515
4531
  _enum(QWEN_MODELS);
4516
4532
  string().trim().min(1);
4517
- AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, 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.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4533
+ AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet55, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku55, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT61Sol, OpenAIModel.GPT6Sol, OpenAIModel.GPT6Luna, 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.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MistralModel.Large4, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4518
4534
  /**
4519
4535
  * Alfe-hosted open-weight models are promoted dynamically only in regions
4520
4536
  * whose private data plane exists. Keep them out of ALL_MODELS so the standard
@@ -4574,7 +4590,7 @@ const AWS_PRIVATE_MODEL_PRICING = {
4574
4590
  [BedrockModel.TitanEmbedTextV2]: { input: .026004 }
4575
4591
  };
4576
4592
  Object.fromEntries(AWS_PRIVATE_BEDROCK_CHAT_MODELS.map((modelId) => [`alfe-bedrock-${modelId}`, AWS_PRIVATE_MODEL_PRICING[modelId]]));
4577
- AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, 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.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4593
+ AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet55, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku55, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT61Sol, OpenAIModel.GPT6Sol, OpenAIModel.GPT6Luna, 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.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MistralModel.Large4, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4578
4594
  enumValues({
4579
4595
  Standard: "standard",
4580
4596
  AwsPrivate: "aws_private"
@@ -5530,16 +5546,13 @@ function _resolveManagedGatewayWsUrl(apiEndpoint) {
5530
5546
  * workspace = "~/.openclaw"
5531
5547
  */
5532
5548
  /**
5533
- * Derive the agent workspace path from the runtime's home dir.
5534
- *
5535
- * For openclaw, the agent CWD is `${home}/workspace` (matches OpenClaw's
5536
- * `agents.defaults.workspace` default — see OpenClaw README "Agent workspace
5537
- * + skills"). For other runtimes, default to home itself.
5549
+ * Derive the agent workspace path (the runtime's CWD) from its home dir.
5550
+ * Delegates to the shared `@alfe.ai/config` rule so the daemon, the CLI and
5551
+ * the claude-code host cannot drift: openclaw and claude-code run in
5552
+ * `${home}/workspace`, other runtimes run in the home itself.
5538
5553
  */
5539
5554
  function deriveAgentWorkspace(runtime, home) {
5540
- if (runtime === "openclaw") return join(home, "workspace");
5541
- if (runtime === "claude-code") return join(home, "workspace");
5542
- return home;
5555
+ return resolveAgentWorkspace(runtime, home);
5543
5556
  }
5544
5557
  const UNSAFE_RECORD_KEYS = new Set([
5545
5558
  "__proto__",
@@ -5602,6 +5615,7 @@ async function loadManagedConfig() {
5602
5615
  runtime: identity.runtime,
5603
5616
  gatewayWsUrl
5604
5617
  }, "Managed config resolved");
5618
+ const defaultModel = identity.runtime === "claude-code" ? await resolveClaudeCodeDefaultModel(apiKey, apiEndpoint) : void 0;
5605
5619
  return {
5606
5620
  apiKey,
5607
5621
  apiEndpoint,
@@ -5612,7 +5626,8 @@ async function loadManagedConfig() {
5612
5626
  orgId: identity.orgId,
5613
5627
  runtime: identity.runtime,
5614
5628
  runtimes: buildManagedRuntimes(identity.runtime),
5615
- autoStartRuntime: true
5629
+ autoStartRuntime: true,
5630
+ ...defaultModel ? { defaultModel } : {}
5616
5631
  };
5617
5632
  }
5618
5633
  /**
@@ -5635,6 +5650,13 @@ function buildManagedRuntimes(runtime) {
5635
5650
  agentWorkspace: deriveAgentWorkspace("hermes", home)
5636
5651
  } };
5637
5652
  }
5653
+ if (runtime === "claude-code") {
5654
+ const home = join(homedir(), ".claude-code");
5655
+ return { "claude-code": {
5656
+ workspace: home,
5657
+ agentWorkspace: deriveAgentWorkspace("claude-code", home)
5658
+ } };
5659
+ }
5638
5660
  return {};
5639
5661
  }
5640
5662
  /**
@@ -5657,17 +5679,7 @@ async function loadDaemonConfig() {
5657
5679
  const gatewayWsUrl = alfeConfig.gateway_url ?? deriveGatewayWsUrl(apiEndpoint);
5658
5680
  const runtimes = await loadRuntimeConfigs();
5659
5681
  const runtime = alfeConfig.runtime ?? identity.runtime;
5660
- let defaultModel;
5661
- if (runtime === "claude-code") {
5662
- let timer;
5663
- const ws = await Promise.race([fetchAgentConfig(alfeConfig.api_key, apiEndpoint), new Promise((resolve) => {
5664
- timer = setTimeout(() => {
5665
- resolve(null);
5666
- }, 5e3);
5667
- })]);
5668
- if (timer) clearTimeout(timer);
5669
- defaultModel = ws?.defaultModel;
5670
- }
5682
+ const defaultModel = runtime === "claude-code" ? await resolveClaudeCodeDefaultModel(alfeConfig.api_key, apiEndpoint) : void 0;
5671
5683
  return {
5672
5684
  apiKey: alfeConfig.api_key,
5673
5685
  apiEndpoint,
@@ -5683,6 +5695,38 @@ async function loadDaemonConfig() {
5683
5695
  };
5684
5696
  }
5685
5697
  /**
5698
+ * The claude-code agent's bare `defaultModel`, for `ALFE_DEFAULT_MODEL`.
5699
+ * Bounded so a slow or hung workspace endpoint can't block daemon startup; any
5700
+ * failure or timeout yields `undefined` and the host falls back to the `claude`
5701
+ * CLI default. Shared by the managed and self-hosted config loaders.
5702
+ */
5703
+ async function resolveClaudeCodeDefaultModel(apiKey, apiEndpoint, options = {}) {
5704
+ const fetcher = options.fetcher ?? fetchWorkspaceDefaultModel;
5705
+ let timer;
5706
+ try {
5707
+ return (await Promise.race([fetcher(apiKey, apiEndpoint), new Promise((resolve) => {
5708
+ timer = setTimeout(() => {
5709
+ resolve(null);
5710
+ }, options.timeoutMs ?? 5e3);
5711
+ })]))?.defaultModel ?? void 0;
5712
+ } catch {
5713
+ return;
5714
+ } finally {
5715
+ if (timer) clearTimeout(timer);
5716
+ }
5717
+ }
5718
+ /**
5719
+ * Workspace metadata only. The default model lives on the workspace record, so
5720
+ * it must not wait on (or time out behind) the persona/template file fetch
5721
+ * that `fetchAgentConfig` also performs.
5722
+ */
5723
+ async function fetchWorkspaceDefaultModel(apiKey, apiEndpoint) {
5724
+ return { defaultModel: (await new AgentApiClient({
5725
+ apiKey,
5726
+ apiUrl: apiEndpoint
5727
+ }).getWorkspace()).defaultModel ?? void 0 };
5728
+ }
5729
+ /**
5686
5730
  * Fetch agent workspace config from the API.
5687
5731
  *
5688
5732
  * 1. GET /agents/me/workspace → { templateKey }
@@ -7105,8 +7149,10 @@ var ConfigReconciler = class {
7105
7149
  getApplier;
7106
7150
  verifyRetries;
7107
7151
  verifyRetryDelayMs;
7152
+ onKeysApplied;
7108
7153
  constructor(options) {
7109
7154
  this.getApplier = options.getApplier;
7155
+ this.onKeysApplied = options.onKeysApplied;
7110
7156
  this.verifyRetries = options.verifyRetries ?? DEFAULT_VERIFY_RETRIES;
7111
7157
  this.verifyRetryDelayMs = options.verifyRetryDelayMs ?? DEFAULT_VERIFY_RETRY_DELAY_MS;
7112
7158
  }
@@ -7129,6 +7175,7 @@ var ConfigReconciler = class {
7129
7175
  };
7130
7176
  const failures = [];
7131
7177
  const inconclusiveKeys = [];
7178
+ const changedKeys = [];
7132
7179
  for (const [key, want] of entries) try {
7133
7180
  if (getConfigRaw) {
7134
7181
  let current;
@@ -7149,8 +7196,11 @@ var ConfigReconciler = class {
7149
7196
  if (getConfigRaw) {
7150
7197
  const outcome = await this.verify(getConfigRaw, key, want);
7151
7198
  if (outcome === "mismatched") failures.push(`${key}: verify failed (read-back did not match)`);
7152
- else if (outcome === "inconclusive") inconclusiveKeys.push(key);
7153
- }
7199
+ else {
7200
+ if (outcome === "inconclusive") inconclusiveKeys.push(key);
7201
+ changedKeys.push(key);
7202
+ }
7203
+ } else changedKeys.push(key);
7154
7204
  } catch (err) {
7155
7205
  const msg = err instanceof Error ? err.message : String(err);
7156
7206
  if (getConfigRaw) {
@@ -7160,15 +7210,18 @@ var ConfigReconciler = class {
7160
7210
  key,
7161
7211
  err: msg
7162
7212
  }, "config set errored but value landed — treating as applied");
7213
+ changedKeys.push(key);
7163
7214
  continue;
7164
7215
  }
7165
7216
  if (outcome === "inconclusive") {
7166
7217
  inconclusiveKeys.push(key);
7218
+ changedKeys.push(key);
7167
7219
  continue;
7168
7220
  }
7169
7221
  }
7170
7222
  failures.push(`${key}: ${msg}`);
7171
7223
  }
7224
+ this.notifyKeysApplied(changedKeys);
7172
7225
  if (failures.length > 0) {
7173
7226
  const joined = failures.join("; ");
7174
7227
  const reason = joined.length > 480 ? `${joined.slice(0, 480)}… (truncated)` : joined;
@@ -7198,6 +7251,17 @@ var ConfigReconciler = class {
7198
7251
  status: "applied"
7199
7252
  };
7200
7253
  }
7254
+ notifyKeysApplied(keys) {
7255
+ if (keys.length === 0 || !this.onKeysApplied) return;
7256
+ try {
7257
+ this.onKeysApplied(keys);
7258
+ } catch (err) {
7259
+ log$4.warn({
7260
+ keys,
7261
+ err: err instanceof Error ? err.message : String(err)
7262
+ }, "onKeysApplied hook failed — config report unaffected");
7263
+ }
7264
+ }
7201
7265
  async verify(getConfigRaw, key, want) {
7202
7266
  let observedValue = false;
7203
7267
  for (let attempt = 0; attempt <= this.verifyRetries; attempt++) {
@@ -7216,6 +7280,38 @@ var ConfigReconciler = class {
7216
7280
  return observedValue ? "mismatched" : "inconclusive";
7217
7281
  }
7218
7282
  };
7283
+ /** Extra environment for the runtime child, layered over the daemon's own env. */
7284
+ function buildRuntimeEnv(input) {
7285
+ if (input.runtime !== "claude-code") return {};
7286
+ return {
7287
+ ...input.defaultModel ? { ALFE_DEFAULT_MODEL: input.defaultModel } : {},
7288
+ ...input.managed ? {
7289
+ ALFE_MANAGED: "1",
7290
+ DISABLE_AUTOUPDATER: "1"
7291
+ } : {}
7292
+ };
7293
+ }
7294
+ /**
7295
+ * True when a config reconcile that wrote `keys` must restart the runtime child.
7296
+ * Only the claude-code host needs it: it reads its credential once per start.
7297
+ * OpenClaw/Hermes hot-reload their state-shaped keys.
7298
+ */
7299
+ function shouldRestartRuntimeForConfigKeys(runtime, keys) {
7300
+ return runtime === "claude-code" && keys.includes("alfe.claudeCredential.revision");
7301
+ }
7302
+ /**
7303
+ * The `ConfigReconciler.onKeysApplied` hook the daemon installs: restart the
7304
+ * runtime (through the shared, turn-draining restart path) when a reconcile
7305
+ * wrote a new claude-code credential revision. The reconciler value-diffs, so
7306
+ * a reconnect re-sending the same revision does not restart.
7307
+ */
7308
+ function createCredentialRestartHook(runtime, requestRestart, onRestart) {
7309
+ return (keys) => {
7310
+ if (!shouldRestartRuntimeForConfigKeys(runtime, keys)) return;
7311
+ onRestart?.();
7312
+ requestRestart();
7313
+ };
7314
+ }
7219
7315
  //#endregion
7220
7316
  //#region src/command-queue.ts
7221
7317
  const DEFAULT_TTL_MS$1 = 300 * 1e3;
@@ -10264,12 +10360,17 @@ async function startDaemon() {
10264
10360
  const runtimeGate = new RuntimeGate(() => runtimeProcess);
10265
10361
  runtimeGateRef = runtimeGate;
10266
10362
  cloudClient.setIntegrationManager(integrationAdapter, runtimeGate);
10267
- cloudClient.setConfigReconciler(new ConfigReconciler({ getApplier: () => runtimeAppliers.get(config.runtime) }));
10268
10363
  const requestRuntimeRestart = () => {
10269
10364
  logger.info("Runtime state changed — requesting runtime restart");
10270
10365
  runtimeGate.requestRestart();
10271
10366
  };
10272
10367
  cloudClient.setRuntimeRestartNeededHandler(requestRuntimeRestart);
10368
+ cloudClient.setConfigReconciler(new ConfigReconciler({
10369
+ getApplier: () => runtimeAppliers.get(config.runtime),
10370
+ onKeysApplied: createCredentialRestartHook(config.runtime, requestRuntimeRestart, () => {
10371
+ logger.info({ runtime: config.runtime }, "Claude credential changed — restarting runtime to refetch");
10372
+ })
10373
+ }));
10273
10374
  if (config.runtime === "hermes") {
10274
10375
  const hermesCfg = config.runtimes[config.runtime];
10275
10376
  if (hermesCfg) {
@@ -10338,7 +10439,11 @@ async function startDaemon() {
10338
10439
  runtimeProcess = new RuntimeProcess({
10339
10440
  runtime: config.runtime,
10340
10441
  workspace: runtimeCfg.workspace,
10341
- env: config.runtime === "claude-code" && config.defaultModel ? { ALFE_DEFAULT_MODEL: config.defaultModel } : {}
10442
+ env: buildRuntimeEnv({
10443
+ runtime: config.runtime,
10444
+ ...config.defaultModel ? { defaultModel: config.defaultModel } : {},
10445
+ managed
10446
+ })
10342
10447
  });
10343
10448
  runtimeProcess.setTurnActivityProbe(turnActivityProbe);
10344
10449
  if (firstRuntimeStart) {
@@ -10556,7 +10661,7 @@ async function executeCloudCommand(command) {
10556
10661
  pinnedVersion: target.version
10557
10662
  }, "runtime.update requested an OpenClaw version above the pin — upgrading to the pin instead");
10558
10663
  version = target.version;
10559
- } else version = payload?.version ?? (runtime === "claude-code" ? "0.1.6" : void 0);
10664
+ } else version = payload?.version ?? (runtime === "claude-code" ? "0.1.15" : void 0);
10560
10665
  upgradingRuntime = true;
10561
10666
  setTimeout(() => {
10562
10667
  (async () => {
@@ -11031,4 +11136,4 @@ function formatDuration(ms) {
11031
11136
  return `${String(Math.round(seconds / 3600))}h`;
11032
11137
  }
11033
11138
  //#endregion
11034
- export { loadDaemonConfig as _, installService as a, startService as c, uninstallService as d, PROTOCOL_VERSION as f, fetchAgentConfig as g, SOCKET_PATH as h, checkExistingDaemon as i, stopExistingDaemon as l, PID_PATH as m, queryDaemonHealth as n, isServiceInstalled as o, ALFE_DIR as p, startDaemon as r, restartService as s, formatHealthReport as t, stopService as u, resolveAgentIdentity as v, PINNED_CLAUDE_CODE_HOST_VERSION as y };
11139
+ export { loadDaemonConfig as _, installService as a, PINNED_CLAUDE_CODE_VERSION as b, startService as c, uninstallService as d, PROTOCOL_VERSION as f, fetchAgentConfig as g, SOCKET_PATH as h, checkExistingDaemon as i, stopExistingDaemon as l, PID_PATH as m, queryDaemonHealth as n, isServiceInstalled as o, ALFE_DIR as p, startDaemon as r, restartService as s, formatHealthReport as t, stopService as u, resolveAgentIdentity as v, PINNED_CLAUDE_CODE_HOST_VERSION as y };
@@ -145,11 +145,23 @@ declare function remediateManagedNode(runCommand: RemediationCommandRunner, base
145
145
  * new host, then cut a CLI release so the pin travels with the CLI version. The
146
146
  * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
147
147
  *
148
- * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
148
+ * NOTE: managed claude-code agents run on Hetzner VMs provisioned by cloud-init
149
+ * + `alfe setup --managed` (no Docker image), so — unlike
149
150
  * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
150
151
  * keep in sync.
151
152
  */
152
- declare const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.6";
153
+ declare const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.15";
154
+ /**
155
+ * The `@anthropic-ai/claude-code` (the `claude` CLI) version Alfe installs for
156
+ * the claude-code runtime: always on managed VMs (`alfe setup --managed`), and
157
+ * on self-hosted machines only when no `claude` is present (a user-installed
158
+ * `claude` is never replaced). Pinned to npm's `stable` dist-tag as of
159
+ * 2026-10-14 — `latest` moves daily and the host drives `claude -p` flags
160
+ * (`--resume`, `--mcp-config`, `--strict-mcp-config`, `--permission-mode`)
161
+ * whose behaviour must be validated before a bump. The pin travels with the
162
+ * CLI release.
163
+ */
164
+ declare const PINNED_CLAUDE_CODE_VERSION = "2.1.287";
153
165
  //#endregion
154
166
  //#region src/sentry.d.ts
155
167
  /**
@@ -391,6 +403,13 @@ declare function resolveAgentIdentity(apiKey: string, apiEndpoint: string): Prom
391
403
  * In managed mode, loads from environment variables instead.
392
404
  */
393
405
  declare function loadDaemonConfig(): Promise<DaemonConfig>;
406
+ /**
407
+ * The claude-code agent's bare `defaultModel`, for `ALFE_DEFAULT_MODEL`.
408
+ * Bounded so a slow or hung workspace endpoint can't block daemon startup; any
409
+ * failure or timeout yields `undefined` and the host falls back to the `claude`
410
+ * CLI default. Shared by the managed and self-hosted config loaders.
411
+ */
412
+
394
413
  /**
395
414
  * Fetch agent workspace config from the API.
396
415
  *
@@ -495,4 +514,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
495
514
  */
496
515
  declare function stopExistingDaemon(): Promise<boolean>;
497
516
  //#endregion
498
- export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RemediateManagedNodeOptions, type RemediationCommandRunner, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
517
+ export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_CLAUDE_CODE_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RemediateManagedNodeOptions, type RemediationCommandRunner, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
package/dist/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { b as PINNED_OPENCLAW_VERSION, v as OPENCLAW_MANAGED_NODE_MAJOR, w as supportsOpenClawNode, y as OPENCLAW_NODE_REQUIREMENT } from "../runtime-upgrade-recovery.js";
2
- import { _ as loadDaemonConfig, a as installService, c as startService, d as uninstallService, f as PROTOCOL_VERSION, g as fetchAgentConfig, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, m as PID_PATH, n as queryDaemonHealth, o as isServiceInstalled, p as ALFE_DIR, r as startDaemon, s as restartService, t as formatHealthReport, u as stopService, v as resolveAgentIdentity, y as PINNED_CLAUDE_CODE_HOST_VERSION } from "../health.js";
2
+ import { _ as loadDaemonConfig, a as installService, b as PINNED_CLAUDE_CODE_VERSION, c as startService, d as uninstallService, f as PROTOCOL_VERSION, g as fetchAgentConfig, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, m as PID_PATH, n as queryDaemonHealth, o as isServiceInstalled, p as ALFE_DIR, r as startDaemon, s as restartService, t as formatHealthReport, u as stopService, v as resolveAgentIdentity, y as PINNED_CLAUDE_CODE_HOST_VERSION } from "../health.js";
3
3
  import { a as captureFatal, d as flushSentry, f as initAgentSentry, h as logger, i as captureCliFailure, l as captureRuntimeCrash, n as AGENT_MCP_SENTRY_DSN, o as captureIntegrationFailure, p as setAgentContext, r as AGENT_RUNTIME_SENTRY_DSN, s as captureMcpFailure, t as AGENT_DAEMON_SENTRY_DSN, u as captureRuntimeErrorOutput } from "../sentry.js";
4
4
  import { n as remediateManagedNode } from "../node-remediation.js";
5
- export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
5
+ export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_CLAUDE_CODE_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.10.9",
3
+ "version": "0.11.0",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,11 +23,11 @@
23
23
  "pino-roll": "^1.2.0",
24
24
  "smol-toml": ">=1.6.1",
25
25
  "ws": "^8.18.0",
26
- "@alfe.ai/agent-api-client": "^0.20.0",
26
+ "@alfe.ai/agent-api-client": "^0.21.0",
27
27
  "@alfe.ai/ai-proxy-local": "^0.0.17",
28
- "@alfe.ai/config": "^0.4.1",
28
+ "@alfe.ai/config": "^0.4.2",
29
29
  "@alfe.ai/integration-manifest": "^0.5.0",
30
- "@alfe.ai/integrations": "^0.6.16",
30
+ "@alfe.ai/integrations": "^0.7.0",
31
31
  "@alfe.ai/mcp-bundler": "^0.4.3"
32
32
  },
33
33
  "license": "UNLICENSED",