@alfe.ai/gateway 0.0.9 → 0.0.11

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,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as installService, c as uninstallService, f as SOCKET_PATH, g as LOG_FILE, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport } from "../health.js";
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";
3
+ import { t as LOG_FILE } from "../logger.js";
3
4
  import { spawn } from "node:child_process";
4
5
  //#region bin/gateway.ts
5
6
  /**
package/dist/health.js CHANGED
@@ -1,9 +1,10 @@
1
+ import { n as logger$1 } from "./logger.js";
1
2
  import { createRequire } from "node:module";
2
3
  import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
3
4
  import { join } from "node:path";
4
5
  import { homedir } from "node:os";
5
6
  import pino from "pino";
6
- import { chmodSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
7
+ import { chmodSync, existsSync, unlinkSync } from "node:fs";
7
8
  import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
8
9
  import crypto from "crypto";
9
10
  import { parse } from "smol-toml";
@@ -53,47 +54,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
53
54
  }) : target, mod));
54
55
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
55
56
  //#endregion
56
- //#region src/logger.ts
57
- /**
58
- * Daemon logger — pino with rolling file output.
59
- *
60
- * Writes to ~/.alfe/logs/gateway.log with 10MB rotation, keep 5 files.
61
- * In managed mode (ECS Fargate): always stdout for CloudWatch capture.
62
- * In development: stdout.
63
- */
64
- const LOG_DIR = join(homedir(), ".alfe", "logs");
65
- const LOG_FILE = join(LOG_DIR, "gateway.log");
66
- const isDev = process.env.NODE_ENV !== "production";
67
- const isManaged = process.env.ALFE_MANAGED === "true";
68
- if (!isManaged) try {
69
- mkdirSync(LOG_DIR, { recursive: true });
70
- } catch {}
71
- /**
72
- * Create the daemon logger.
73
- * Managed mode (ECS): JSON to stdout (CloudWatch captures via awslogs driver).
74
- * Development: JSON to stdout.
75
- * Production (local): JSON to rolling file.
76
- */
77
- function createLogger$1() {
78
- if (isDev || isManaged) return pino({
79
- level: process.env.LOG_LEVEL ?? (isDev ? "debug" : "info"),
80
- transport: {
81
- target: "pino/file",
82
- options: { destination: 1 }
83
- }
84
- });
85
- const transport = pino.transport({
86
- target: "pino-roll",
87
- options: {
88
- file: LOG_FILE,
89
- size: "10m",
90
- limit: { count: 5 }
91
- }
92
- });
93
- return pino({ level: process.env.LOG_LEVEL ?? "info" }, transport);
94
- }
95
- const logger$1 = createLogger$1();
96
- //#endregion
97
57
  //#region ../../packages-internal/ids/dist/prefixes.js
98
58
  const ID_PREFIXES = {
99
59
  agent: "agt",
@@ -3639,13 +3599,15 @@ function createServiceRegister(agentId, capabilities = [
3639
3599
  "integrations",
3640
3600
  "lifecycle",
3641
3601
  "health"
3642
- ]) {
3643
- return {
3602
+ ], cliVersion) {
3603
+ const msg = {
3644
3604
  type: "SERVICE_REGISTER",
3645
3605
  serviceId: `gateway-daemon-${agentId}`,
3646
3606
  agentIds: [agentId],
3647
3607
  capabilities
3648
3608
  };
3609
+ if (cliVersion) msg.cliVersion = cliVersion;
3610
+ return msg;
3649
3611
  }
3650
3612
  /**
3651
3613
  * Create an IPC success response.
@@ -4032,10 +3994,11 @@ var CloudClient = class {
4032
3994
  });
4033
3995
  }
4034
3996
  sendRegister() {
4035
- const msg = createServiceRegister(this.config.agentId);
3997
+ const msg = createServiceRegister(this.config.agentId, void 0, this.config.cliVersion);
4036
3998
  logger$1.debug({
4037
3999
  serviceId: msg.serviceId,
4038
- agentIds: msg.agentIds
4000
+ agentIds: msg.agentIds,
4001
+ cliVersion: msg.cliVersion
4039
4002
  }, "Cloud: sending SERVICE_REGISTER");
4040
4003
  this.send(msg);
4041
4004
  logger$1.info({ serviceId: msg.serviceId }, "Sent SERVICE_REGISTER");
@@ -20137,6 +20100,36 @@ let aiProxyUrl = null;
20137
20100
  let aiProxyRunning = false;
20138
20101
  let cloudConnected = false;
20139
20102
  let shuttingDown = false;
20103
+ let resolvedCliVersion;
20104
+ /**
20105
+ * Resolve the installed @alfe.ai/cli version.
20106
+ *
20107
+ * Strategy (in order):
20108
+ * 1. ALFE_CLI_VERSION env var (set by CLI entry point when run directly)
20109
+ * 2. Walk up from this file to find @alfe.ai/cli/package.json
20110
+ * (works when systemd runs the gateway binary directly, since
20111
+ * the path is .../cli/node_modules/@alfe.ai/gateway/dist/...)
20112
+ */
20113
+ async function getCliVersion() {
20114
+ if (process.env.ALFE_CLI_VERSION) return process.env.ALFE_CLI_VERSION;
20115
+ try {
20116
+ const { fileURLToPath } = await import("node:url");
20117
+ const { dirname } = await import("node:path");
20118
+ let dir = dirname(fileURLToPath(import.meta.url));
20119
+ for (let i = 0; i < 10; i++) {
20120
+ const candidate = join(dir, "package.json");
20121
+ try {
20122
+ const raw = await readFile(candidate, "utf-8");
20123
+ const pkg = JSON.parse(raw);
20124
+ if (pkg.name === "@alfe.ai/cli") return pkg.version;
20125
+ } catch {}
20126
+ const parent = dirname(dir);
20127
+ if (parent === dir) break;
20128
+ dir = parent;
20129
+ }
20130
+ } catch {}
20131
+ logger$1.debug("Could not resolve @alfe.ai/cli version");
20132
+ }
20140
20133
  /**
20141
20134
  * Flush pino's async transport and exit.
20142
20135
  * process.exit() can drop buffered log lines — this ensures they're written first.
@@ -20229,6 +20222,8 @@ async function startDaemon() {
20229
20222
  }, "Failed to start IPC server");
20230
20223
  await flushAndExit(1);
20231
20224
  }
20225
+ resolvedCliVersion = await getCliVersion();
20226
+ logger$1.info({ cliVersion: resolvedCliVersion }, "Resolved CLI version");
20232
20227
  logger$1.debug({
20233
20228
  wsUrl: config.gatewayWsUrl,
20234
20229
  agentId: config.agentId
@@ -20236,7 +20231,8 @@ async function startDaemon() {
20236
20231
  cloudClient = new CloudClient({
20237
20232
  wsUrl: config.gatewayWsUrl,
20238
20233
  apiKey: config.apiKey,
20239
- agentId: config.agentId
20234
+ agentId: config.agentId,
20235
+ cliVersion: resolvedCliVersion
20240
20236
  });
20241
20237
  cloudClient.setCommandHandler(handleCloudCommand);
20242
20238
  cloudClient.setConnectionChangeHandler((connected) => {
@@ -20411,6 +20407,44 @@ async function handleCloudCommand(command) {
20411
20407
  };
20412
20408
  }
20413
20409
  }
20410
+ if (command.command === "daemon.update") {
20411
+ const version = command.payload?.version ?? "latest";
20412
+ if (!isManagedMode()) return {
20413
+ type: "COMMAND_ACK",
20414
+ commandId: command.commandId,
20415
+ status: "error",
20416
+ result: {
20417
+ code: "NOT_MANAGED",
20418
+ message: "daemon.update is only supported in managed mode"
20419
+ }
20420
+ };
20421
+ try {
20422
+ const { spawnUpgradeScript } = await import("./upgrade.js");
20423
+ await spawnUpgradeScript(version);
20424
+ logger$1.info({ version }, "Upgrade script spawned — daemon will restart shortly");
20425
+ return {
20426
+ type: "COMMAND_ACK",
20427
+ commandId: command.commandId,
20428
+ status: "ok",
20429
+ result: {
20430
+ upgrading: true,
20431
+ version
20432
+ }
20433
+ };
20434
+ } catch (err) {
20435
+ const message = err instanceof Error ? err.message : String(err);
20436
+ logger$1.error({ err: message }, "Failed to spawn upgrade script");
20437
+ return {
20438
+ type: "COMMAND_ACK",
20439
+ commandId: command.commandId,
20440
+ status: "error",
20441
+ result: {
20442
+ code: "UPGRADE_FAILED",
20443
+ message
20444
+ }
20445
+ };
20446
+ }
20447
+ }
20414
20448
  if (command.command === "integration.status") {
20415
20449
  const payload = command.payload;
20416
20450
  try {
@@ -20665,4 +20699,4 @@ function formatDuration(ms) {
20665
20699
  return `${String(Math.round(seconds / 3600))}h`;
20666
20700
  }
20667
20701
  //#endregion
20668
- export { logger$1 as _, installService as a, uninstallService as c, PID_PATH as d, SOCKET_PATH as f, LOG_FILE 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 };
20702
+ export { installService as a, uninstallService as c, PID_PATH as d, SOCKET_PATH as f, 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 };
package/dist/logger.js ADDED
@@ -0,0 +1,46 @@
1
+ import { join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import pino from "pino";
4
+ import { mkdirSync } from "node:fs";
5
+ //#region src/logger.ts
6
+ /**
7
+ * Daemon logger — pino with rolling file output.
8
+ *
9
+ * Writes to ~/.alfe/logs/gateway.log with 10MB rotation, keep 5 files.
10
+ * In managed mode (ECS Fargate): always stdout for CloudWatch capture.
11
+ * In development: stdout.
12
+ */
13
+ const LOG_DIR = join(homedir(), ".alfe", "logs");
14
+ const LOG_FILE = join(LOG_DIR, "gateway.log");
15
+ const isDev = process.env.NODE_ENV !== "production";
16
+ const isManaged = process.env.ALFE_MANAGED === "true";
17
+ if (!isManaged) try {
18
+ mkdirSync(LOG_DIR, { recursive: true });
19
+ } catch {}
20
+ /**
21
+ * Create the daemon logger.
22
+ * Managed mode (ECS): JSON to stdout (CloudWatch captures via awslogs driver).
23
+ * Development: JSON to stdout.
24
+ * Production (local): JSON to rolling file.
25
+ */
26
+ function createLogger() {
27
+ if (isDev || isManaged) return pino({
28
+ level: process.env.LOG_LEVEL ?? (isDev ? "debug" : "info"),
29
+ transport: {
30
+ target: "pino/file",
31
+ options: { destination: 1 }
32
+ }
33
+ });
34
+ const transport = pino.transport({
35
+ target: "pino-roll",
36
+ options: {
37
+ file: LOG_FILE,
38
+ size: "10m",
39
+ limit: { count: 5 }
40
+ }
41
+ });
42
+ return pino({ level: process.env.LOG_LEVEL ?? "info" }, transport);
43
+ }
44
+ const logger = createLogger();
45
+ //#endregion
46
+ export { logger as n, LOG_FILE as t };
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
- import { _ as logger, a as installService, c as uninstallService, d as PID_PATH, f as SOCKET_PATH, 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 { a as installService, c as uninstallService, d as PID_PATH, f as SOCKET_PATH, 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";
2
+ import { n as logger } from "../logger.js";
2
3
  export { ALFE_DIR, PID_PATH, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, startService, stopExistingDaemon, uninstallService };
@@ -0,0 +1,62 @@
1
+ import { n as logger } from "./logger.js";
2
+ import { chmod, writeFile } from "node:fs/promises";
3
+ import { spawn } from "node:child_process";
4
+ //#region src/upgrade.ts
5
+ /**
6
+ * CLI upgrade — spawns a detached shell script to upgrade @alfe.ai/cli.
7
+ *
8
+ * The daemon process IS the service being upgraded, so it cannot manage the
9
+ * upgrade itself. Instead we:
10
+ * 1. Write a bash script to /tmp
11
+ * 2. Spawn it detached (survives parent death)
12
+ * 3. The script stops the service, upgrades, re-runs setup, and restarts
13
+ *
14
+ * The daemon ACKs the COMMAND before the script runs, so the cloud gets
15
+ * confirmation. After restart the daemon reconnects with the new version.
16
+ */
17
+ /**
18
+ * Spawn a detached upgrade script that will:
19
+ * 1. Wait briefly for the daemon to finish sending its ACK
20
+ * 2. Stop the systemd service
21
+ * 3. Install the target CLI version globally
22
+ * 4. Re-run `alfe setup --managed` to regenerate the systemd unit
23
+ * 5. Start the service again
24
+ * 6. Clean up the script file
25
+ */
26
+ async function spawnUpgradeScript(version) {
27
+ const scriptPath = `/tmp/alfe-upgrade-${String(Date.now())}.sh`;
28
+ await writeFile(scriptPath, `#!/bin/bash
29
+ set -euo pipefail
30
+
31
+ # Wait for daemon to finish sending COMMAND_ACK
32
+ sleep 2
33
+
34
+ # Stop the gateway service
35
+ systemctl stop alfe-gateway
36
+
37
+ # Upgrade CLI to target version
38
+ npm install -g @alfe.ai/cli@${version}
39
+
40
+ # Re-run setup to regenerate systemd unit with new binary path
41
+ alfe setup --managed
42
+
43
+ # Start the service — daemon will reconnect with new version
44
+ systemctl start alfe-gateway
45
+
46
+ # Clean up
47
+ rm -f "$0"
48
+ `, "utf-8");
49
+ await chmod(scriptPath, 493);
50
+ const child = spawn("bash", [scriptPath], {
51
+ detached: true,
52
+ stdio: "ignore"
53
+ });
54
+ child.unref();
55
+ logger.info({
56
+ scriptPath,
57
+ version,
58
+ pid: child.pid
59
+ }, "Spawned detached upgrade script");
60
+ }
61
+ //#endregion
62
+ export { spawnUpgradeScript };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,12 +25,12 @@
25
25
  "@alfe.ai/ai-proxy-local": "^0.0.2",
26
26
  "@alfe.ai/config": "^0.0.2",
27
27
  "@alfe.ai/doctor": "^0.0.2",
28
- "@alfe.ai/integrations": "^0.0.3"
28
+ "@alfe.ai/integrations": "^0.0.4"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.5.13",
32
32
  "tsx": "^4.19.0",
33
- "@alfe/api-client": "0.1.0",
33
+ "@alfe/api-client": "0.1.1",
34
34
  "@alfe/ids": "0.1.0"
35
35
  },
36
36
  "license": "UNLICENSED",