@alfe.ai/gateway 0.0.8 → 0.0.10

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",
@@ -407,6 +367,95 @@ var AuthService = class {
407
367
  }
408
368
  };
409
369
  //#endregion
370
+ //#region ../../packages-internal/api-client/dist/services/integrations.js
371
+ var IntegrationsService = class {
372
+ client;
373
+ voiceClient;
374
+ constructor(client, voiceClient) {
375
+ this.client = client;
376
+ this.voiceClient = voiceClient;
377
+ }
378
+ listIntegrations(agentId) {
379
+ return this.client.request(`/integrations/agents/${agentId}`);
380
+ }
381
+ installIntegration(agentId, data) {
382
+ return this.client.request(`/integrations/agents/${agentId}`, {
383
+ method: "POST",
384
+ body: JSON.stringify(data)
385
+ });
386
+ }
387
+ getIntegrationConfig(agentId, integrationId) {
388
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}/config`);
389
+ }
390
+ updateIntegration(agentId, integrationId, data) {
391
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, {
392
+ method: "PATCH",
393
+ body: JSON.stringify(data)
394
+ });
395
+ }
396
+ removeIntegration(agentId, integrationId) {
397
+ return this.client.request(`/integrations/agents/${agentId}/${integrationId}`, { method: "DELETE" });
398
+ }
399
+ getRegistry() {
400
+ return this.client.request("/integrations/registry");
401
+ }
402
+ triggerSync(agentId) {
403
+ return this.client.request("/integrations/sync/trigger", {
404
+ method: "POST",
405
+ body: JSON.stringify({ agentId })
406
+ });
407
+ }
408
+ listVoices() {
409
+ if (this.voiceClient) return this.voiceClient.request("/api/voices");
410
+ return this.client.request("/integrations/voices");
411
+ }
412
+ getDiscordGuildChannels(guildId) {
413
+ return this.client.request(`/discord/guilds/${encodeURIComponent(guildId)}/channels`);
414
+ }
415
+ listMobileNumbers() {
416
+ return this.client.request("/mobile/numbers");
417
+ }
418
+ assignMobileNumber(agentId, phoneNumber) {
419
+ return this.client.request("/mobile/numbers/assign", {
420
+ method: "POST",
421
+ body: JSON.stringify({
422
+ agentId,
423
+ phoneNumber
424
+ })
425
+ });
426
+ }
427
+ recallJoinMeeting(agentId, meetingUrl, botName) {
428
+ if (this.voiceClient) return this.voiceClient.request("/api/join", {
429
+ method: "POST",
430
+ body: JSON.stringify({
431
+ agentId,
432
+ meetingUrl,
433
+ botName
434
+ })
435
+ });
436
+ return this.client.request("/integrations/recall/join", {
437
+ method: "POST",
438
+ body: JSON.stringify({
439
+ agentId,
440
+ meetingUrl,
441
+ botName
442
+ })
443
+ });
444
+ }
445
+ listSlackChannels(agentId) {
446
+ return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/channels`);
447
+ }
448
+ sendSlackMessage(agentId, channel, text) {
449
+ return this.client.request(`/slack/agents/${encodeURIComponent(agentId)}/send`, {
450
+ method: "POST",
451
+ body: JSON.stringify({
452
+ channel,
453
+ text
454
+ })
455
+ });
456
+ }
457
+ };
458
+ //#endregion
410
459
  //#region ../../packages-internal/types/dist/lib/enum-values.js
411
460
  /**
412
461
  * Converts a const enum object into a non-empty readonly tuple.
@@ -3550,13 +3599,15 @@ function createServiceRegister(agentId, capabilities = [
3550
3599
  "integrations",
3551
3600
  "lifecycle",
3552
3601
  "health"
3553
- ]) {
3554
- return {
3602
+ ], cliVersion) {
3603
+ const msg = {
3555
3604
  type: "SERVICE_REGISTER",
3556
3605
  serviceId: `gateway-daemon-${agentId}`,
3557
3606
  agentIds: [agentId],
3558
3607
  capabilities
3559
3608
  };
3609
+ if (cliVersion) msg.cliVersion = cliVersion;
3610
+ return msg;
3560
3611
  }
3561
3612
  /**
3562
3613
  * Create an IPC success response.
@@ -3943,10 +3994,11 @@ var CloudClient = class {
3943
3994
  });
3944
3995
  }
3945
3996
  sendRegister() {
3946
- const msg = createServiceRegister(this.config.agentId);
3997
+ const msg = createServiceRegister(this.config.agentId, void 0, this.config.cliVersion);
3947
3998
  logger$1.debug({
3948
3999
  serviceId: msg.serviceId,
3949
- agentIds: msg.agentIds
4000
+ agentIds: msg.agentIds,
4001
+ cliVersion: msg.cliVersion
3950
4002
  }, "Cloud: sending SERVICE_REGISTER");
3951
4003
  this.send(msg);
3952
4004
  logger$1.info({ serviceId: msg.serviceId }, "Sent SERVICE_REGISTER");
@@ -20048,6 +20100,22 @@ let aiProxyUrl = null;
20048
20100
  let aiProxyRunning = false;
20049
20101
  let cloudConnected = false;
20050
20102
  let shuttingDown = false;
20103
+ let resolvedCliVersion;
20104
+ /**
20105
+ * Resolve the installed @alfe.ai/cli version.
20106
+ * Prefers ALFE_CLI_VERSION env var (set by the CLI entry point),
20107
+ * falls back to resolving @alfe.ai/cli/package.json directly.
20108
+ */
20109
+ async function getCliVersion() {
20110
+ if (process.env.ALFE_CLI_VERSION) return process.env.ALFE_CLI_VERSION;
20111
+ try {
20112
+ const raw = await readFile(createRequire(import.meta.url).resolve("@alfe.ai/cli/package.json"), "utf-8");
20113
+ return JSON.parse(raw).version;
20114
+ } catch {
20115
+ logger$1.debug("Could not resolve @alfe.ai/cli version — daemon may not be running from CLI");
20116
+ return;
20117
+ }
20118
+ }
20051
20119
  /**
20052
20120
  * Flush pino's async transport and exit.
20053
20121
  * process.exit() can drop buffered log lines — this ensures they're written first.
@@ -20140,6 +20208,8 @@ async function startDaemon() {
20140
20208
  }, "Failed to start IPC server");
20141
20209
  await flushAndExit(1);
20142
20210
  }
20211
+ resolvedCliVersion = await getCliVersion();
20212
+ logger$1.info({ cliVersion: resolvedCliVersion }, "Resolved CLI version");
20143
20213
  logger$1.debug({
20144
20214
  wsUrl: config.gatewayWsUrl,
20145
20215
  agentId: config.agentId
@@ -20147,7 +20217,8 @@ async function startDaemon() {
20147
20217
  cloudClient = new CloudClient({
20148
20218
  wsUrl: config.gatewayWsUrl,
20149
20219
  apiKey: config.apiKey,
20150
- agentId: config.agentId
20220
+ agentId: config.agentId,
20221
+ cliVersion: resolvedCliVersion
20151
20222
  });
20152
20223
  cloudClient.setCommandHandler(handleCloudCommand);
20153
20224
  cloudClient.setConnectionChangeHandler((connected) => {
@@ -20163,9 +20234,17 @@ async function startDaemon() {
20163
20234
  workspace: runtimeCfg.workspace
20164
20235
  }, "Registered OpenClaw runtime applier");
20165
20236
  } else logger$1.warn({ runtime: name }, "Unknown runtime type — skipping");
20237
+ const integrationsService = new IntegrationsService(new AlfeApiClient({
20238
+ apiBaseUrl: config.apiEndpoint,
20239
+ getToken: () => Promise.resolve(config.apiKey)
20240
+ }));
20166
20241
  integrationManager = new IntegrationManager({
20167
- logger: logger$1,
20168
- runtimeAppliers
20242
+ runtimeAppliers,
20243
+ registryFetcher: async () => {
20244
+ const result = await integrationsService.getRegistry();
20245
+ if (!result.ok) throw new Error(result.error);
20246
+ return result.data.integrations;
20247
+ }
20169
20248
  });
20170
20249
  const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
20171
20250
  cloudClient.setIntegrationManager(integrationAdapter);
@@ -20314,6 +20393,44 @@ async function handleCloudCommand(command) {
20314
20393
  };
20315
20394
  }
20316
20395
  }
20396
+ if (command.command === "daemon.update") {
20397
+ const version = command.payload?.version ?? "latest";
20398
+ if (!isManagedMode()) return {
20399
+ type: "COMMAND_ACK",
20400
+ commandId: command.commandId,
20401
+ status: "error",
20402
+ result: {
20403
+ code: "NOT_MANAGED",
20404
+ message: "daemon.update is only supported in managed mode"
20405
+ }
20406
+ };
20407
+ try {
20408
+ const { spawnUpgradeScript } = await import("./upgrade.js");
20409
+ await spawnUpgradeScript(version);
20410
+ logger$1.info({ version }, "Upgrade script spawned — daemon will restart shortly");
20411
+ return {
20412
+ type: "COMMAND_ACK",
20413
+ commandId: command.commandId,
20414
+ status: "ok",
20415
+ result: {
20416
+ upgrading: true,
20417
+ version
20418
+ }
20419
+ };
20420
+ } catch (err) {
20421
+ const message = err instanceof Error ? err.message : String(err);
20422
+ logger$1.error({ err: message }, "Failed to spawn upgrade script");
20423
+ return {
20424
+ type: "COMMAND_ACK",
20425
+ commandId: command.commandId,
20426
+ status: "error",
20427
+ result: {
20428
+ code: "UPGRADE_FAILED",
20429
+ message
20430
+ }
20431
+ };
20432
+ }
20433
+ }
20317
20434
  if (command.command === "integration.status") {
20318
20435
  const payload = command.payload;
20319
20436
  try {
@@ -20568,4 +20685,4 @@ function formatDuration(ms) {
20568
20685
  return `${String(Math.round(seconds / 3600))}h`;
20569
20686
  }
20570
20687
  //#endregion
20571
- 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 };
20688
+ 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.8",
3
+ "version": "0.0.10",
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.2"
28
+ "@alfe.ai/integrations": "^0.0.3"
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",