@alfe.ai/gateway 0.8.2 → 0.9.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as installService, c as uninstallService, f as SOCKET_PATH, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport } from "../health.js";
2
+ import { a as installService, d as uninstallService, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, n as queryDaemonHealth, r as startDaemon, t as formatHealthReport } from "../health.js";
3
3
  import { t as LOG_FILE } from "../logger.js";
4
4
  import { spawn } from "node:child_process";
5
5
  //#region bin/gateway.ts
package/dist/health.js CHANGED
@@ -76,6 +76,26 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
76
76
  */
77
77
  const PINNED_OPENCLAW_VERSION = "2026.6.11";
78
78
  //#endregion
79
+ //#region src/claude-code-host-version.ts
80
+ /**
81
+ * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
82
+ * the single source of truth for its install paths:
83
+ * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
84
+ * - the daemon `runtime.update` default (this package)
85
+ *
86
+ * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
87
+ * `latest`) is deliberate: the host is the claude-code runtime child on every
88
+ * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
89
+ * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
90
+ * new host, then cut a CLI release so the pin travels with the CLI version. The
91
+ * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
92
+ *
93
+ * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
94
+ * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
95
+ * keep in sync.
96
+ */
97
+ const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.2";
98
+ //#endregion
79
99
  //#region ../../packages-internal/ids/dist/prefixes.js
80
100
  const ID_PREFIXES = {
81
101
  agent: "agt",
@@ -7073,12 +7093,25 @@ async function uninstallSystemd() {
7073
7093
  return `Uninstalled: ${unitPath}`;
7074
7094
  }
7075
7095
  /**
7076
- * Start the installed service via systemctl/launchctl.
7096
+ * Bootstrap (load) the launchd service from its plist if it isn't already
7097
+ * loaded. Already-loaded is an error we ignore. Needed so `start`/`restart`
7098
+ * work after `stopService()` boots the service OUT.
7099
+ */
7100
+ function ensureLaunchdLoaded(uid) {
7101
+ try {
7102
+ execSync(`launchctl bootstrap gui/${uid} ${getLaunchdPlistPath()}`, { stdio: "pipe" });
7103
+ } catch {}
7104
+ }
7105
+ /**
7106
+ * Start the installed service via systemctl/launchctl. Bootstraps the launchd
7107
+ * service first so this also works after `stopService()` booted it out.
7077
7108
  */
7078
7109
  function startService() {
7079
7110
  const platform = process.platform;
7080
7111
  if (platform === "darwin") {
7081
- execSync(`launchctl kickstart -k gui/${getLaunchdUid()}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7112
+ const uid = getLaunchdUid();
7113
+ ensureLaunchdLoaded(uid);
7114
+ execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7082
7115
  logger$1.info("Started launchd service");
7083
7116
  return;
7084
7117
  }
@@ -7090,6 +7123,59 @@ function startService() {
7090
7123
  throw new Error(`Unsupported platform: ${platform}`);
7091
7124
  }
7092
7125
  /**
7126
+ * Restart the installed service via systemctl/launchctl. This is the
7127
+ * service-manager-native restart (`kickstart -k` / `systemctl restart`) — the
7128
+ * CLI uses it instead of killing the process inline, which would fight launchd
7129
+ * `KeepAlive` / systemd `Restart=always`.
7130
+ */
7131
+ function restartService() {
7132
+ const platform = process.platform;
7133
+ if (platform === "darwin") {
7134
+ const uid = getLaunchdUid();
7135
+ ensureLaunchdLoaded(uid);
7136
+ execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7137
+ logger$1.info("Restarted launchd service");
7138
+ return;
7139
+ }
7140
+ if (platform === "linux") {
7141
+ execSync(`${isRootUser() ? "systemctl" : "systemctl --user"} restart ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
7142
+ logger$1.info("Restarted systemd service");
7143
+ return;
7144
+ }
7145
+ throw new Error(`Unsupported platform: ${platform}`);
7146
+ }
7147
+ /**
7148
+ * Stop the installed service via systemctl/launchctl. On macOS this boots the
7149
+ * service OUT (unloads it) so launchd's `KeepAlive` does NOT respawn it;
7150
+ * `startService()` bootstraps it again. On Linux the unit stays enabled (starts
7151
+ * on next boot); `systemctl stop` just halts the current run.
7152
+ */
7153
+ function stopService() {
7154
+ const platform = process.platform;
7155
+ if (platform === "darwin") {
7156
+ execSync(`launchctl bootout gui/${getLaunchdUid()}/${LAUNCHD_LABEL}`, { stdio: "pipe" });
7157
+ logger$1.info("Stopped launchd service");
7158
+ return;
7159
+ }
7160
+ if (platform === "linux") {
7161
+ execSync(`${isRootUser() ? "systemctl" : "systemctl --user"} stop ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
7162
+ logger$1.info("Stopped systemd service");
7163
+ return;
7164
+ }
7165
+ throw new Error(`Unsupported platform: ${platform}`);
7166
+ }
7167
+ /**
7168
+ * True when the gateway is installed as a system service (launchd plist /
7169
+ * systemd unit present). Lets the CLI restart/stop/start THROUGH the service
7170
+ * manager instead of driving the daemon inline via the PID file.
7171
+ */
7172
+ function isServiceInstalled() {
7173
+ const platform = process.platform;
7174
+ if (platform === "darwin") return existsSync(getLaunchdPlistPath());
7175
+ if (platform === "linux") return existsSync(isRootUser() ? getSystemdSystemServicePath() : getSystemdServicePath());
7176
+ return false;
7177
+ }
7178
+ /**
7093
7179
  * Write the current process PID to the PID file.
7094
7180
  */
7095
7181
  async function writePidFile() {
@@ -23814,7 +23900,10 @@ async function startDaemon() {
23814
23900
  await mcpManager.loadIntoBundler(mcpBundler);
23815
23901
  const warmBundler = (reason) => {
23816
23902
  if (!mcpBundler) return;
23817
- mcpBundler.warmup().catch((err) => {
23903
+ const target = mcpBundler;
23904
+ target.warmup().then(() => {
23905
+ warnFailedMcpServers(target, logger$1, reason);
23906
+ }).catch((err) => {
23818
23907
  logger$1.warn({
23819
23908
  err: err instanceof Error ? err.message : String(err),
23820
23909
  reason
@@ -24102,7 +24191,7 @@ async function executeCloudCommand(command) {
24102
24191
  };
24103
24192
  const payload = command.payload;
24104
24193
  const runtime = config.runtime;
24105
- const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : void 0);
24194
+ const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : runtime === "claude-code" ? "0.1.2" : void 0);
24106
24195
  upgradingRuntime = true;
24107
24196
  setTimeout(() => {
24108
24197
  (async () => {
@@ -24442,6 +24531,28 @@ function handleMcpListTools(bundler) {
24442
24531
  };
24443
24532
  }
24444
24533
  /**
24534
+ * Observability backstop for the INTEGRATION warm path. `applyForIntegration`
24535
+ * registers MCP servers into the store and the daemon warms them silently via
24536
+ * the store-change `onChange` hook — so a server that fails its connect (e.g.
24537
+ * an MCP server whose backing account/credential wasn't resolvable at warm
24538
+ * time, like ctrader-mcp `exit(1)`-ing on empty accounts) leaves NO trace,
24539
+ * making the black hole undiagnosable. After a warm, read `bundler.statuses()`
24540
+ * and warn once per still-failed server so the failure is visible. These
24541
+ * self-heal on the bundler's background retry sweep once the dependency
24542
+ * appears. Returns the servers it warned about (for tests / callers).
24543
+ */
24544
+ function warnFailedMcpServers(bundler, log, reason) {
24545
+ if (!bundler) return [];
24546
+ const failed = bundler.statuses().filter((s) => !s.connected && s.lastError !== void 0);
24547
+ for (const status of failed) log.warn({
24548
+ server: status.name,
24549
+ reason,
24550
+ consecutiveFailures: status.consecutiveFailures,
24551
+ lastError: status.lastError
24552
+ }, `MCP server "${status.name}" failed to connect: ${status.lastError ?? ""}`);
24553
+ return failed;
24554
+ }
24555
+ /**
24445
24556
  * Route a tool call to the appropriate MCP child via the daemon-hosted
24446
24557
  * bundler. `name` is the prefixed (`mcp__<server>__<tool>`) name; args is
24447
24558
  * the raw JSON object the LLM produced.
@@ -24780,4 +24891,4 @@ function formatDuration(ms) {
24780
24891
  return `${String(Math.round(seconds / 3600))}h`;
24781
24892
  }
24782
24893
  //#endregion
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 };
24894
+ export { loadDaemonConfig as _, installService as a, PINNED_OPENCLAW_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 };
@@ -19,13 +19,19 @@ const execFileAsync = promisify(execFile);
19
19
  /**
20
20
  * Resolve the per-runtime upgrade command.
21
21
  *
22
- * - `openclaw`: `npm install -g openclaw@<version>` — version-pinned (unchanged).
23
- * - `hermes`: `hermes update --yes` Hermes self-updates from its own channel.
24
- * A pinned version does not map to a Hermes CLI flag, so the
25
- * `version` arg is intentionally ignored on this branch.
22
+ * - `openclaw`: `npm install -g openclaw@<version>` — version-pinned.
23
+ * - `claude-code`: `npm install -g @alfe.ai/claude-code-host@<version>` the
24
+ * runtime child is the host binary; mirrors openclaw. The
25
+ * caller defaults a missing version to the pin.
26
+ * - `hermes`: `hermes update --yes` — Hermes self-updates from its own
27
+ * channel. A pinned version does not map to a Hermes CLI flag,
28
+ * so the `version` arg is intentionally ignored on this branch.
26
29
  *
27
- * Returns `undefined` for an unknown runtime (or openclaw with no version),
28
- * which the caller treats as a failed upgrade (restart on the old version).
30
+ * Returns `undefined` for an unknown runtime (or openclaw/claude-code with no
31
+ * version), which the caller treats as a failed upgrade (restart on the old
32
+ * version). `upgradeRuntime` restarts the RuntimeProcess child regardless of
33
+ * runtime, so a new branch here is all that's needed to cycle onto the new
34
+ * version.
29
35
  */
30
36
  function resolveUpgradeCommand(runtime, version) {
31
37
  switch (runtime) {
@@ -39,6 +45,16 @@ function resolveUpgradeCommand(runtime, version) {
39
45
  `openclaw@${version}`
40
46
  ]
41
47
  };
48
+ case "claude-code":
49
+ if (!version) return void 0;
50
+ return {
51
+ command: "npm",
52
+ args: [
53
+ "install",
54
+ "-g",
55
+ `@alfe.ai/claude-code-host@${version}`
56
+ ]
57
+ };
42
58
  case "hermes": return {
43
59
  command: "hermes",
44
60
  args: ["update", "--yes"]
@@ -1,5 +1,9 @@
1
1
  import pino from "pino";
2
2
 
3
+ //#region src/logger.d.ts
4
+
5
+ declare const logger: pino.Logger<never, boolean>;
6
+ //#endregion
3
7
  //#region src/protocol.d.ts
4
8
 
5
9
  interface IPCRequest {
@@ -55,6 +59,26 @@ declare function startDaemon(): Promise<void>;
55
59
  */
56
60
  declare const PINNED_OPENCLAW_VERSION = "2026.6.11";
57
61
  //#endregion
62
+ //#region src/claude-code-host-version.d.ts
63
+ /**
64
+ * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
65
+ * the single source of truth for its install paths:
66
+ * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
67
+ * - the daemon `runtime.update` default (this package)
68
+ *
69
+ * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
70
+ * `latest`) is deliberate: the host is the claude-code runtime child on every
71
+ * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
72
+ * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
73
+ * new host, then cut a CLI release so the pin travels with the CLI version. The
74
+ * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
75
+ *
76
+ * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
77
+ * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
78
+ * keep in sync.
79
+ */
80
+ declare const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.2";
81
+ //#endregion
58
82
  //#region src/sentry.d.ts
59
83
  /**
60
84
  * Agent-side error reporting (Sentry) for the Alfe CLI + gateway daemon.
@@ -335,9 +359,6 @@ declare function queryDaemonHealth(socketPath: string, timeoutMs?: number): Prom
335
359
  */
336
360
  declare function formatHealthReport(health: DaemonHealth): string;
337
361
  //#endregion
338
- //#region src/logger.d.ts
339
- declare const logger: pino.Logger<never, boolean>;
340
- //#endregion
341
362
  //#region src/process-manager.d.ts
342
363
  /**
343
364
  * Install the service unit for the current platform.
@@ -348,9 +369,30 @@ declare function installService(): Promise<string>;
348
369
  */
349
370
  declare function uninstallService(): Promise<string>;
350
371
  /**
351
- * Start the installed service via systemctl/launchctl.
372
+ * Start the installed service via systemctl/launchctl. Bootstraps the launchd
373
+ * service first so this also works after `stopService()` booted it out.
352
374
  */
353
375
  declare function startService(): void;
376
+ /**
377
+ * Restart the installed service via systemctl/launchctl. This is the
378
+ * service-manager-native restart (`kickstart -k` / `systemctl restart`) — the
379
+ * CLI uses it instead of killing the process inline, which would fight launchd
380
+ * `KeepAlive` / systemd `Restart=always`.
381
+ */
382
+ declare function restartService(): void;
383
+ /**
384
+ * Stop the installed service via systemctl/launchctl. On macOS this boots the
385
+ * service OUT (unloads it) so launchd's `KeepAlive` does NOT respawn it;
386
+ * `startService()` bootstraps it again. On Linux the unit stays enabled (starts
387
+ * on next boot); `systemctl stop` just halts the current run.
388
+ */
389
+ declare function stopService(): void;
390
+ /**
391
+ * True when the gateway is installed as a system service (launchd plist /
392
+ * systemd unit present). Lets the CLI restart/stop/start THROUGH the service
393
+ * manager instead of driving the daemon inline via the PID file.
394
+ */
395
+ declare function isServiceInstalled(): boolean;
354
396
  /**
355
397
  * Write the current process PID to the PID file.
356
398
  */
@@ -365,4 +407,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
365
407
  */
366
408
  declare function stopExistingDaemon(): Promise<boolean>;
367
409
  //#endregion
368
- 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, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
410
+ 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, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, uninstallService };
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as installService, c as uninstallService, d as PID_PATH, f as SOCKET_PATH, g as PINNED_OPENCLAW_VERSION, h as resolveAgentIdentity, i as checkExistingDaemon, l as PROTOCOL_VERSION, m as loadDaemonConfig, n as queryDaemonHealth, o as startService, p as fetchAgentConfig, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, u as ALFE_DIR } from "../health.js";
1
+ import { _ as loadDaemonConfig, a as installService, b as PINNED_OPENCLAW_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";
2
2
  import { n as logger } from "../logger.js";
3
3
  import { a as captureFatal, c as captureRuntimeCrash, d as initAgentSentry, f as setAgentContext, i as captureCliFailure, l as captureRuntimeErrorOutput, n as AGENT_MCP_SENTRY_DSN, o as captureIntegrationFailure, r as AGENT_RUNTIME_SENTRY_DSN, s as captureMcpFailure, t as AGENT_DAEMON_SENTRY_DSN, u as flushSentry } from "../sentry.js";
4
- export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
4
+ export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, 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, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, uninstallService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,10 +25,10 @@
25
25
  "ws": "^8.18.0",
26
26
  "@alfe.ai/agent-api-client": "^0.11.0",
27
27
  "@alfe.ai/ai-proxy-local": "^0.0.13",
28
- "@alfe.ai/integration-manifest": "^0.3.2",
29
28
  "@alfe.ai/config": "^0.3.0",
30
- "@alfe.ai/integrations": "^0.5.1",
31
- "@alfe.ai/mcp-bundler": "^0.3.1"
29
+ "@alfe.ai/integration-manifest": "^0.3.2",
30
+ "@alfe.ai/integrations": "^0.5.2",
31
+ "@alfe.ai/mcp-bundler": "^0.3.2"
32
32
  },
33
33
  "license": "UNLICENSED",
34
34
  "homepage": "https://alfe.ai",