@alfe.ai/gateway 0.0.13 → 0.0.15

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
@@ -4,7 +4,7 @@ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import pino from "pino";
7
- import { chmodSync, existsSync, unlinkSync } from "node:fs";
7
+ import { chmodSync, existsSync, readFileSync, unlinkSync } from "node:fs";
8
8
  import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
9
9
  import crypto from "crypto";
10
10
  import { parse } from "smol-toml";
@@ -3152,7 +3152,16 @@ enumValues({
3152
3152
  object({ gracePeriodDays: number().int().positive().optional() });
3153
3153
  enumValues({
3154
3154
  Active: "active",
3155
- Inactive: "inactive",
3155
+ Pending: "pending",
3156
+ Provisioning: "provisioning",
3157
+ Starting: "starting",
3158
+ Running: "running",
3159
+ Stopping: "stopping",
3160
+ Stopped: "stopped",
3161
+ Rebooting: "rebooting",
3162
+ Failing: "failing",
3163
+ Failed: "failed",
3164
+ BillingSuspended: "billing_suspended",
3156
3165
  Deleted: "deleted"
3157
3166
  });
3158
3167
  enumValues({
@@ -3163,16 +3172,6 @@ enumValues({
3163
3172
  OpenClaw: "openclaw",
3164
3173
  NanoClaw: "nanoclaw"
3165
3174
  });
3166
- enumValues({
3167
- None: "none",
3168
- Pending: "pending",
3169
- Provisioning: "provisioning",
3170
- Running: "running",
3171
- Stopped: "stopped",
3172
- Failing: "failing",
3173
- Failed: "failed",
3174
- BillingSuspended: "billing_suspended"
3175
- });
3176
3175
  enumValues({
3177
3176
  Active: "active",
3178
3177
  Removed: "removed"
@@ -3437,7 +3436,12 @@ async function loadRuntimeConfigs() {
3437
3436
  * of ~/.alfe/config.toml, and IPC/PID features are disabled.
3438
3437
  */
3439
3438
  function isManagedMode() {
3440
- return process.env.ALFE_MANAGED === "true";
3439
+ if (process.env.ALFE_MANAGED === "true") return true;
3440
+ try {
3441
+ return parse(readFileSync(join(homedir(), ".alfe", "config.toml"), "utf-8")).managed === true;
3442
+ } catch {
3443
+ return false;
3444
+ }
3441
3445
  }
3442
3446
  /**
3443
3447
  * Load configuration for managed mode (ECS Fargate).
@@ -20432,44 +20436,78 @@ async function handleCloudCommand(command) {
20432
20436
  };
20433
20437
  }
20434
20438
  }
20435
- if (command.command === "daemon.update") {
20436
- const version = command.payload?.version ?? "latest";
20437
- if (!isManagedMode()) return {
20439
+ if (command.command === "support.bash") {
20440
+ const payload = command.payload;
20441
+ if (!payload.cmd) return {
20438
20442
  type: "COMMAND_ACK",
20439
20443
  commandId: command.commandId,
20440
20444
  status: "error",
20441
20445
  result: {
20442
- code: "NOT_MANAGED",
20443
- message: "daemon.update is only supported in managed mode"
20446
+ code: "MISSING_CMD",
20447
+ message: "No command provided"
20444
20448
  }
20445
20449
  };
20450
+ const workspacePath = Object.values(config.runtimes)[0]?.workspace ?? "~/.openclaw";
20446
20451
  try {
20447
- const { spawnUpgradeScript } = await import("./upgrade.js");
20448
- await spawnUpgradeScript(version);
20449
- logger$1.info({ version }, "Upgrade script spawned daemon will restart shortly");
20452
+ const { exec } = await import("child_process");
20453
+ const { promisify } = await import("util");
20454
+ const { stdout, stderr } = await promisify(exec)(payload.cmd, {
20455
+ cwd: workspacePath,
20456
+ timeout: 25e3,
20457
+ maxBuffer: 512 * 1024
20458
+ });
20450
20459
  return {
20451
20460
  type: "COMMAND_ACK",
20452
20461
  commandId: command.commandId,
20453
20462
  status: "ok",
20454
20463
  result: {
20455
- upgrading: true,
20456
- version
20464
+ stdout: stdout.trim(),
20465
+ stderr: stderr.trim()
20457
20466
  }
20458
20467
  };
20459
20468
  } catch (err) {
20460
- const message = err instanceof Error ? err.message : String(err);
20461
- logger$1.error({ err: message }, "Failed to spawn upgrade script");
20469
+ const execErr = err;
20462
20470
  return {
20463
20471
  type: "COMMAND_ACK",
20464
20472
  commandId: command.commandId,
20465
20473
  status: "error",
20466
20474
  result: {
20467
- code: "UPGRADE_FAILED",
20468
- message
20475
+ code: "EXEC_FAILED",
20476
+ stdout: execErr.stdout?.trim() ?? "",
20477
+ stderr: execErr.stderr?.trim() ?? "",
20478
+ message: execErr.message ?? String(err),
20479
+ exitCode: execErr.code
20469
20480
  }
20470
20481
  };
20471
20482
  }
20472
20483
  }
20484
+ if (command.command === "daemon.update") {
20485
+ const version = command.payload?.version ?? "latest";
20486
+ if (!isManagedMode()) return {
20487
+ type: "COMMAND_ACK",
20488
+ commandId: command.commandId,
20489
+ status: "error",
20490
+ result: {
20491
+ code: "NOT_MANAGED",
20492
+ message: "daemon.update is only supported in managed mode"
20493
+ }
20494
+ };
20495
+ setTimeout(() => {
20496
+ import("./upgrade.js").then(({ upgradeAndExit }) => upgradeAndExit(version)).catch((err) => {
20497
+ logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Upgrade failed");
20498
+ process.exit(1);
20499
+ });
20500
+ }, 500);
20501
+ return {
20502
+ type: "COMMAND_ACK",
20503
+ commandId: command.commandId,
20504
+ status: "ok",
20505
+ result: {
20506
+ upgrading: true,
20507
+ version
20508
+ }
20509
+ };
20510
+ }
20473
20511
  if (command.command === "integration.status") {
20474
20512
  const payload = command.payload;
20475
20513
  try {
package/dist/upgrade.js CHANGED
@@ -1,60 +1,44 @@
1
1
  import { n as logger } from "./logger.js";
2
- import { chmod, writeFile } from "node:fs/promises";
3
- import { spawn } from "node:child_process";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
4
  //#region src/upgrade.ts
5
5
  /**
6
- * CLI upgrade — spawns a detached shell script to upgrade @alfe.ai/cli.
6
+ * CLI upgrade — runs npm install inline then exits.
7
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
8
+ * Systemd has Restart=always, so after the daemon exits the service
9
+ * restarts automatically with the new CLI version. No detached scripts,
10
+ * no race conditions.
13
11
  *
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.
12
+ * Flow:
13
+ * 1. Daemon ACKs the COMMAND (caller handles this before calling upgrade)
14
+ * 2. npm install -g @alfe.ai/cli@{version} runs as a child process
15
+ * 3. Daemon exits with code 0
16
+ * 4. Systemd restarts the service → new version boots
16
17
  */
18
+ const execFileAsync = promisify(execFile);
17
19
  /**
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
20
+ * Upgrade the CLI to a target version and exit.
21
+ * Systemd will restart the daemon with the new version.
25
22
  */
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
- exec > /tmp/alfe-upgrade.log 2>&1
31
-
32
- # Wait for daemon to finish sending COMMAND_ACK
33
- sleep 2
34
-
35
- # Stop the gateway service
36
- systemctl stop alfe-gateway
37
-
38
- # Upgrade CLI to target version
39
- npm install -g @alfe.ai/cli@${version}
40
-
41
- # Restart — systemd ExecStart resolves the new binary automatically
42
- systemctl start alfe-gateway
43
-
44
- # Clean up
45
- rm -f "$0"
46
- `, "utf-8");
47
- await chmod(scriptPath, 493);
48
- const child = spawn("bash", [scriptPath], {
49
- detached: true,
50
- stdio: "ignore"
51
- });
52
- child.unref();
53
- logger.info({
54
- scriptPath,
55
- version,
56
- pid: child.pid
57
- }, "Spawned detached upgrade script");
23
+ async function upgradeAndExit(version) {
24
+ logger.info({ version }, "Upgrading CLI...");
25
+ try {
26
+ const { stdout, stderr } = await execFileAsync("npm", [
27
+ "install",
28
+ "-g",
29
+ `@alfe.ai/cli@${version}`
30
+ ], { timeout: 12e4 });
31
+ if (stdout) logger.debug({ stdout: stdout.trim() }, "npm install stdout");
32
+ if (stderr) logger.debug({ stderr: stderr.trim() }, "npm install stderr");
33
+ logger.info({ version }, "CLI upgraded — exiting for systemd restart");
34
+ } catch (err) {
35
+ const message = err instanceof Error ? err.message : String(err);
36
+ logger.error({
37
+ err: message,
38
+ version
39
+ }, "CLI upgrade failed");
40
+ }
41
+ process.exit(0);
58
42
  }
59
43
  //#endregion
60
- export { spawnUpgradeScript };
44
+ export { upgradeAndExit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,10 +22,10 @@
22
22
  "pino-roll": "^1.2.0",
23
23
  "smol-toml": ">=1.6.1",
24
24
  "ws": "^8.18.0",
25
- "@alfe.ai/ai-proxy-local": "^0.0.3",
26
- "@alfe.ai/config": "^0.0.3",
27
- "@alfe.ai/doctor": "^0.0.3",
28
- "@alfe.ai/integrations": "^0.0.6"
25
+ "@alfe.ai/ai-proxy-local": "^0.0.4",
26
+ "@alfe.ai/config": "^0.0.4",
27
+ "@alfe.ai/doctor": "^0.0.4",
28
+ "@alfe.ai/integrations": "^0.0.8"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.5.13",