@alfe.ai/gateway 0.0.4 → 0.0.6

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, d as SOCKET_PATH, h as LOG_FILE, i as checkExistingDaemon, n as queryDaemonHealth, o as stopExistingDaemon, r as startDaemon, s as uninstallService, t as formatHealthReport } from "../health.js";
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";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region bin/gateway.ts
5
5
  /**
@@ -118,7 +118,9 @@ Examples:
118
118
  }
119
119
  main().catch((err) => {
120
120
  const message = err instanceof Error ? err.message : String(err);
121
+ const stack = err instanceof Error ? err.stack : void 0;
121
122
  console.error("Fatal error:", message);
123
+ if (stack) console.error(stack);
122
124
  process.exit(1);
123
125
  });
124
126
  //#endregion
package/dist/health.js CHANGED
@@ -11,6 +11,7 @@ import WebSocket from "ws";
11
11
  import { createConnection, createServer } from "node:net";
12
12
  import { execSync, spawn } from "node:child_process";
13
13
  import { IntegrationManager, IntegrationManagerAdapter, OpenClawApplier } from "@alfe.ai/integrations";
14
+ import { randomUUID } from "node:crypto";
14
15
  import stream, { Readable } from "stream";
15
16
  import util, { format } from "util";
16
17
  import http from "http";
@@ -19,7 +20,6 @@ import url from "url";
19
20
  import http2 from "http2";
20
21
  import zlib from "zlib";
21
22
  import { EventEmitter } from "events";
22
- import { randomUUID } from "node:crypto";
23
23
  //#region \0rolldown/runtime.js
24
24
  var __create = Object.create;
25
25
  var __defProp = Object.defineProperty;
@@ -58,22 +58,25 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
58
58
  * Daemon logger — pino with rolling file output.
59
59
  *
60
60
  * Writes to ~/.alfe/logs/gateway.log with 10MB rotation, keep 5 files.
61
- * Also outputs to stdout in development mode.
61
+ * In managed mode (ECS Fargate): always stdout for CloudWatch capture.
62
+ * In development: stdout.
62
63
  */
63
64
  const LOG_DIR = join(homedir(), ".alfe", "logs");
64
65
  const LOG_FILE = join(LOG_DIR, "gateway.log");
65
- try {
66
+ const isDev = process.env.NODE_ENV !== "production";
67
+ const isManaged = process.env.ALFE_MANAGED === "true";
68
+ if (!isManaged) try {
66
69
  mkdirSync(LOG_DIR, { recursive: true });
67
70
  } catch {}
68
- const isDev = process.env.NODE_ENV !== "production";
69
71
  /**
70
72
  * Create the daemon logger.
71
- * In development: pretty-prints to stdout.
72
- * In production: JSON to rolling file.
73
+ * Managed mode (ECS): JSON to stdout (CloudWatch captures via awslogs driver).
74
+ * Development: JSON to stdout.
75
+ * Production (local): JSON to rolling file.
73
76
  */
74
77
  function createLogger$1() {
75
- if (isDev) return pino({
76
- level: process.env.LOG_LEVEL ?? "debug",
78
+ if (isDev || isManaged) return pino({
79
+ level: process.env.LOG_LEVEL ?? (isDev ? "debug" : "info"),
77
80
  transport: {
78
81
  target: "pino/file",
79
82
  options: { destination: 1 }
@@ -101,6 +104,15 @@ const ID_PREFIXES = {
101
104
  transaction: "txn",
102
105
  subscription: "sub",
103
106
  conversation: "conv",
107
+ referral: "ref",
108
+ promoCode: "prc",
109
+ promoRedemption: "prr",
110
+ pendingPromo: "pdp",
111
+ webhook: "whk",
112
+ webhookDelivery: "wdl",
113
+ onboardingSession: "obs",
114
+ inviteToken: "inv",
115
+ claimToken: "clm",
104
116
  run: "run",
105
117
  request: "req",
106
118
  connection: "conn",
@@ -378,6 +390,21 @@ var AuthService = class {
378
390
  deleteToken(tokenId) {
379
391
  return this.client.request(`${this.prefix}/tokens/${tokenId}`, { method: "DELETE" });
380
392
  }
393
+ getReferralCode() {
394
+ return this.client.request(`${this.prefix}/referral/code`);
395
+ }
396
+ redeemCode(code) {
397
+ return this.client.request(`${this.prefix}/referral/redeem`, {
398
+ method: "POST",
399
+ body: JSON.stringify({ code })
400
+ });
401
+ }
402
+ getReferralStatus() {
403
+ return this.client.request(`${this.prefix}/referral/status`);
404
+ }
405
+ getReferralHistory() {
406
+ return this.client.request(`${this.prefix}/referral/history`);
407
+ }
381
408
  };
382
409
  //#endregion
383
410
  //#region ../../packages-internal/types/dist/lib/enum-values.js
@@ -3066,7 +3093,8 @@ enumValues({
3066
3093
  });
3067
3094
  enumValues({
3068
3095
  Anthropic: "anthropic",
3069
- OpenAI: "openai"
3096
+ OpenAI: "openai",
3097
+ ElevenLabs: "elevenlabs"
3070
3098
  });
3071
3099
  enumValues({
3072
3100
  Month: "month",
@@ -3092,6 +3120,7 @@ enumValues({
3092
3120
  Provisioning: "provisioning",
3093
3121
  Running: "running",
3094
3122
  Stopped: "stopped",
3123
+ Failing: "failing",
3095
3124
  Failed: "failed",
3096
3125
  BillingSuspended: "billing_suspended"
3097
3126
  });
@@ -3118,6 +3147,8 @@ enumValues({
3118
3147
  SubscriptionCredit: "subscription_credit",
3119
3148
  AutoRecharge: "auto_recharge",
3120
3149
  AdminGift: "admin_gift",
3150
+ ReferralCredit: "referral_credit",
3151
+ PromoCredit: "promo_credit",
3121
3152
  Refund: "refund"
3122
3153
  });
3123
3154
  enumValues({
@@ -3131,6 +3162,36 @@ enumValues({
3131
3162
  Week: "week",
3132
3163
  Month: "month"
3133
3164
  });
3165
+ enumValues({
3166
+ Active: "active",
3167
+ Paused: "paused",
3168
+ Revoked: "revoked"
3169
+ });
3170
+ enumValues({
3171
+ Pending: "pending",
3172
+ Completed: "completed",
3173
+ Failed: "failed"
3174
+ });
3175
+ enumValues({
3176
+ BalanceCredit: "balance_credit",
3177
+ SubscriptionDiscount: "subscription_discount",
3178
+ FreeTrial: "free_trial"
3179
+ });
3180
+ enumValues({
3181
+ Active: "active",
3182
+ Paused: "paused",
3183
+ Revoked: "revoked"
3184
+ });
3185
+ enumValues({
3186
+ Pending: "pending",
3187
+ Completed: "completed",
3188
+ Failed: "failed"
3189
+ });
3190
+ enumValues({
3191
+ Pending: "pending",
3192
+ Consumed: "consumed",
3193
+ Expired: "expired"
3194
+ });
3134
3195
  enumValues({
3135
3196
  Synced: "synced",
3136
3197
  Stale: "stale",
@@ -3161,6 +3222,51 @@ enumValues({
3161
3222
  Live: "live"
3162
3223
  });
3163
3224
  //#endregion
3225
+ //#region ../../packages-internal/types/dist/models.js
3226
+ const AnthropicModel = {
3227
+ Opus: "claude-opus-4-6",
3228
+ Sonnet: "claude-sonnet-4-6",
3229
+ Haiku: "claude-haiku-4-5"
3230
+ };
3231
+ const ANTHROPIC_MODELS = enumValues(AnthropicModel);
3232
+ const OpenAIModel = {
3233
+ GPT4o: "gpt-4o",
3234
+ GPT4oMini: "gpt-4o-mini",
3235
+ O3: "o3"
3236
+ };
3237
+ const OPENAI_MODELS = enumValues(OpenAIModel);
3238
+ [...ANTHROPIC_MODELS, ...OPENAI_MODELS];
3239
+ _enum(ANTHROPIC_MODELS);
3240
+ _enum(OPENAI_MODELS);
3241
+ _enum([...ANTHROPIC_MODELS, ...OPENAI_MODELS]);
3242
+ AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3;
3243
+ AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3;
3244
+ enumValues({
3245
+ PendingChallenge: "pending_challenge",
3246
+ Creating: "creating",
3247
+ Complete: "complete",
3248
+ Failed: "failed",
3249
+ Expired: "expired"
3250
+ });
3251
+ enumValues({
3252
+ Active: "active",
3253
+ Used: "used",
3254
+ Expired: "expired",
3255
+ Revoked: "revoked"
3256
+ });
3257
+ enumValues({
3258
+ Pending: "pending",
3259
+ Claimed: "claimed",
3260
+ Expired: "expired"
3261
+ });
3262
+ enumValues({
3263
+ Mcp: "mcp",
3264
+ Api: "api",
3265
+ Cli: "cli",
3266
+ Dashboard: "dashboard",
3267
+ Agent: "agent"
3268
+ });
3269
+ //#endregion
3164
3270
  //#region src/config.ts
3165
3271
  /**
3166
3272
  * Daemon configuration — reads ~/.alfe/config.toml and resolves agent identity.
@@ -3179,6 +3285,10 @@ const PID_PATH = join(ALFE_DIR, "gateway.pid");
3179
3285
  * Returns agentId (derived from tokenId) and orgId (tenantId).
3180
3286
  */
3181
3287
  async function resolveAgentIdentity(apiKey, apiEndpoint) {
3288
+ logger$1.debug({
3289
+ apiEndpoint,
3290
+ keyPrefix: apiKey.slice(0, 8) + "..."
3291
+ }, "Resolving agent identity...");
3182
3292
  const auth = new AuthService(new AlfeApiClient({
3183
3293
  apiBaseUrl: apiEndpoint,
3184
3294
  getToken: () => Promise.resolve(apiKey)
@@ -3186,22 +3296,47 @@ async function resolveAgentIdentity(apiKey, apiEndpoint) {
3186
3296
  const maxAttempts = 3;
3187
3297
  const delayMs = 3e3;
3188
3298
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
3299
+ logger$1.debug({
3300
+ attempt,
3301
+ maxAttempts
3302
+ }, "Validating token...");
3189
3303
  const result = await auth.validate(apiKey);
3190
3304
  if (!result.ok) {
3305
+ logger$1.debug({
3306
+ status: result.status,
3307
+ error: result.error,
3308
+ attempt
3309
+ }, "Token validation failed");
3191
3310
  if (result.status === 401 || result.status === 403) throw new Error(`Token validation failed: ${result.error}. Is your API key valid? Run \`alfe login\` to reconfigure.`);
3192
3311
  if (attempt < maxAttempts) {
3312
+ logger$1.debug({
3313
+ delayMs,
3314
+ attempt
3315
+ }, "Retrying token validation after transient error...");
3193
3316
  await new Promise((r) => setTimeout(r, delayMs));
3194
3317
  continue;
3195
3318
  }
3196
3319
  throw new Error(`Token validation failed after ${String(maxAttempts)} attempts: ${result.error}. Is your API key valid? Run \`alfe login\` to reconfigure.`);
3197
3320
  }
3321
+ logger$1.debug({
3322
+ valid: result.data.valid,
3323
+ tenantId: result.data.tenantId,
3324
+ tokenId: result.data.tokenId
3325
+ }, "Token validation response");
3198
3326
  if (!result.data.valid) throw new Error("API key invalid: validation failed. Run `alfe login` to reconfigure.");
3199
3327
  const orgId = result.data.tenantId;
3200
3328
  if (!orgId) throw new Error("Token validation returned no tenantId — cannot determine org.");
3329
+ const agentId = result.data.tokenId ?? deriveAgentId(apiKey);
3330
+ const runtime = result.data.runtime ?? "openclaw";
3331
+ logger$1.debug({
3332
+ agentId,
3333
+ orgId,
3334
+ runtime
3335
+ }, "Agent identity resolved");
3201
3336
  return {
3202
- agentId: result.data.tokenId ?? deriveAgentId(apiKey),
3337
+ agentId,
3203
3338
  orgId,
3204
- runtime: result.data.runtime ?? "openclaw"
3339
+ runtime
3205
3340
  };
3206
3341
  }
3207
3342
  throw new Error("Token validation failed: exhausted retries.");
@@ -3260,20 +3395,33 @@ function isManagedMode() {
3260
3395
  * All config comes from environment variables — no local files needed.
3261
3396
  */
3262
3397
  async function loadManagedConfig() {
3398
+ logger$1.debug("Loading managed config from environment...");
3263
3399
  const apiKey = process.env.ALFE_API_KEY;
3264
3400
  const apiEndpoint = process.env.ALFE_API_ENDPOINT;
3265
- if (!apiKey || !apiEndpoint) throw new Error("ALFE_API_KEY and ALFE_API_ENDPOINT required in managed mode");
3401
+ if (!apiKey || !apiEndpoint) throw new Error(`ALFE_API_KEY and ALFE_API_ENDPOINT required in managed mode (apiKey=${apiKey ? "set" : "missing"}, apiEndpoint=${apiEndpoint ? "set" : "missing"})`);
3402
+ logger$1.debug({
3403
+ apiEndpoint,
3404
+ gatewayWsUrlEnv: process.env.ALFE_GATEWAY_WS_URL ?? "(not set)"
3405
+ }, "Environment loaded, resolving identity...");
3266
3406
  const identity = await resolveAgentIdentity(apiKey, apiEndpoint);
3407
+ const gatewayWsUrl = process.env.ALFE_GATEWAY_WS_URL ?? deriveGatewayWsUrl(apiEndpoint);
3408
+ logger$1.debug({
3409
+ agentId: identity.agentId,
3410
+ orgId: identity.orgId,
3411
+ runtime: identity.runtime,
3412
+ gatewayWsUrl
3413
+ }, "Managed config resolved");
3267
3414
  return {
3268
3415
  apiKey,
3269
3416
  apiEndpoint,
3270
- gatewayWsUrl: process.env.ALFE_GATEWAY_WS_URL ?? deriveGatewayWsUrl(apiEndpoint),
3271
- socketPath: "",
3417
+ gatewayWsUrl,
3418
+ socketPath: "/tmp/alfe-gateway.sock",
3272
3419
  pidPath: "",
3273
3420
  agentId: identity.agentId,
3274
3421
  orgId: identity.orgId,
3275
3422
  runtime: identity.runtime,
3276
- runtimes: identity.runtime === "openclaw" ? { openclaw: { workspace: join(homedir(), ".openclaw") } } : {}
3423
+ runtimes: identity.runtime === "openclaw" ? { openclaw: { workspace: join(homedir(), ".openclaw") } } : {},
3424
+ autoStartRuntime: true
3277
3425
  };
3278
3426
  }
3279
3427
  /**
@@ -3304,7 +3452,8 @@ async function loadDaemonConfig() {
3304
3452
  agentId: identity.agentId,
3305
3453
  orgId: identity.orgId,
3306
3454
  runtime: identity.runtime,
3307
- runtimes
3455
+ runtimes,
3456
+ autoStartRuntime: alfeConfig.auto_start_runtime ?? false
3308
3457
  };
3309
3458
  }
3310
3459
  /**
@@ -3317,26 +3466,44 @@ async function loadDaemonConfig() {
3317
3466
  */
3318
3467
  async function fetchAgentConfig(apiKey, apiEndpoint) {
3319
3468
  try {
3469
+ logger$1.debug({ apiEndpoint }, "Fetching agent workspace config...");
3320
3470
  const wsResponse = await fetch(`${apiEndpoint}/agents/me/workspace`, {
3321
3471
  method: "GET",
3322
3472
  headers: { "Authorization": `Bearer ${apiKey}` }
3323
3473
  });
3324
- if (!wsResponse.ok) return null;
3474
+ if (!wsResponse.ok) {
3475
+ logger$1.debug({ status: wsResponse.status }, "Workspace config fetch failed");
3476
+ return null;
3477
+ }
3325
3478
  const templateKey = (await wsResponse.json()).data?.templateKey;
3326
- if (!templateKey) return null;
3479
+ if (!templateKey) {
3480
+ logger$1.debug("No templateKey in workspace response");
3481
+ return null;
3482
+ }
3483
+ logger$1.debug({ templateKey }, "Fetching template files...");
3327
3484
  const filesResponse = await fetch(`${apiEndpoint}/templates/${encodeURIComponent(templateKey)}/files`, {
3328
3485
  method: "GET",
3329
3486
  headers: { "Authorization": `Bearer ${apiKey}` }
3330
3487
  });
3331
- if (!filesResponse.ok) return {
3488
+ if (!filesResponse.ok) {
3489
+ logger$1.debug({ status: filesResponse.status }, "Template files fetch failed");
3490
+ return {
3491
+ templateKey,
3492
+ files: {}
3493
+ };
3494
+ }
3495
+ const filesResult = await filesResponse.json();
3496
+ const fileCount = Object.keys(filesResult.data?.files ?? {}).length;
3497
+ logger$1.debug({
3332
3498
  templateKey,
3333
- files: {}
3334
- };
3499
+ fileCount
3500
+ }, "Template files fetched");
3335
3501
  return {
3336
3502
  templateKey,
3337
- files: (await filesResponse.json()).data?.files ?? {}
3503
+ files: filesResult.data?.files ?? {}
3338
3504
  };
3339
- } catch {
3505
+ } catch (err) {
3506
+ logger$1.debug({ err: err instanceof Error ? err.message : String(err) }, "fetchAgentConfig failed");
3340
3507
  return null;
3341
3508
  }
3342
3509
  }
@@ -3696,6 +3863,10 @@ var CloudClient = class {
3696
3863
  * Start the cloud connection with auto-reconnect.
3697
3864
  */
3698
3865
  start() {
3866
+ logger$1.debug({
3867
+ wsUrl: this.config.wsUrl,
3868
+ agentId: this.config.agentId
3869
+ }, "Cloud client starting...");
3699
3870
  this.closed = false;
3700
3871
  this.doConnect();
3701
3872
  }
@@ -3703,15 +3874,18 @@ var CloudClient = class {
3703
3874
  * Stop the cloud connection and all timers.
3704
3875
  */
3705
3876
  stop() {
3877
+ logger$1.debug("Cloud client stopping...");
3706
3878
  this.closed = true;
3707
3879
  this.stopPingTimer();
3708
3880
  if (this.ws) {
3881
+ logger$1.debug({ readyState: this.ws.readyState }, "Cloud: closing WebSocket");
3709
3882
  try {
3710
3883
  this.ws.close(1e3, "Daemon shutting down");
3711
3884
  } catch {}
3712
3885
  this.ws = null;
3713
3886
  }
3714
3887
  this.registered = false;
3888
+ logger$1.debug("Cloud client stopped");
3715
3889
  }
3716
3890
  /**
3717
3891
  * Get connection latency (time since last pong).
@@ -3722,22 +3896,36 @@ var CloudClient = class {
3722
3896
  return Date.now() - this.lastPong;
3723
3897
  }
3724
3898
  doConnect() {
3725
- if (this.closed) return;
3726
- logger$1.info({ url: this.config.wsUrl }, "Connecting to cloud gateway...");
3899
+ if (this.closed) {
3900
+ logger$1.debug("Cloud: doConnect skipped client is closed");
3901
+ return;
3902
+ }
3903
+ logger$1.info({
3904
+ url: this.config.wsUrl,
3905
+ backoffMs: this.backoffMs
3906
+ }, "Connecting to cloud gateway...");
3907
+ logger$1.debug({
3908
+ agentId: this.config.agentId,
3909
+ keyPrefix: this.config.apiKey.slice(0, 12) + "..."
3910
+ }, "Cloud: connection details");
3727
3911
  this.ws = new WebSocket(this.config.wsUrl, {
3728
3912
  headers: { authorization: `Bearer ${this.config.apiKey}` },
3729
- maxPayload: 10 * 1024 * 1024
3913
+ maxPayload: 10 * 1024 * 1024,
3914
+ handshakeTimeout: 1e4
3730
3915
  });
3731
3916
  this.ws.on("open", () => {
3732
3917
  logger$1.info("Cloud WebSocket connected");
3918
+ logger$1.debug({ readyState: this.ws?.readyState }, "Cloud: WebSocket open, sending registration...");
3733
3919
  this.backoffMs = 1e3;
3734
3920
  this.sendRegister();
3735
3921
  });
3736
3922
  this.ws.on("message", (data) => {
3737
3923
  const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
3924
+ logger$1.debug({ size: text.length }, "Cloud: received message");
3738
3925
  this.handleMessage(text);
3739
3926
  });
3740
3927
  this.ws.on("ping", () => {
3928
+ logger$1.debug("Cloud: received ping, sending pong");
3741
3929
  this.ws?.pong();
3742
3930
  });
3743
3931
  this.ws.on("close", (code, reason) => {
@@ -3751,11 +3939,18 @@ var CloudClient = class {
3751
3939
  this.scheduleReconnect();
3752
3940
  });
3753
3941
  this.ws.on("error", (err) => {
3754
- logger$1.error({ err: err.message }, "Cloud WebSocket error");
3942
+ logger$1.error({
3943
+ err: err.message,
3944
+ url: this.config.wsUrl
3945
+ }, "Cloud WebSocket error");
3755
3946
  });
3756
3947
  }
3757
3948
  sendRegister() {
3758
3949
  const msg = createServiceRegister(this.config.agentId);
3950
+ logger$1.debug({
3951
+ serviceId: msg.serviceId,
3952
+ agentIds: msg.agentIds
3953
+ }, "Cloud: sending SERVICE_REGISTER");
3759
3954
  this.send(msg);
3760
3955
  logger$1.info({ serviceId: msg.serviceId }, "Sent SERVICE_REGISTER");
3761
3956
  }
@@ -3868,6 +4063,7 @@ var CloudClient = class {
3868
4063
  }
3869
4064
  send(msg) {
3870
4065
  if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(msg));
4066
+ else logger$1.debug({ readyState: this.ws?.readyState }, "Cloud: send skipped — WebSocket not open");
3871
4067
  }
3872
4068
  startPingTimer() {
3873
4069
  this.stopPingTimer();
@@ -3888,10 +4084,16 @@ var CloudClient = class {
3888
4084
  }
3889
4085
  }
3890
4086
  scheduleReconnect() {
3891
- if (this.closed) return;
4087
+ if (this.closed) {
4088
+ logger$1.debug("Cloud: reconnect skipped — client is closed");
4089
+ return;
4090
+ }
3892
4091
  const delay = this.backoffMs;
3893
4092
  this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
3894
- logger$1.info({ delayMs: delay }, "Cloud: reconnecting...");
4093
+ logger$1.info({
4094
+ delayMs: delay,
4095
+ nextBackoffMs: this.backoffMs
4096
+ }, "Cloud: scheduling reconnect...");
3895
4097
  setTimeout(() => {
3896
4098
  this.doConnect();
3897
4099
  }, delay);
@@ -4324,6 +4526,12 @@ var CommandQueue = class {
4324
4526
  */
4325
4527
  const LAUNCHD_LABEL = "ai.alfe.gateway";
4326
4528
  const SYSTEMD_SERVICE = "alfe-gateway";
4529
+ function isRootUser() {
4530
+ return process.getuid?.() === 0;
4531
+ }
4532
+ function getSystemdSystemServicePath() {
4533
+ return `/etc/systemd/system/${SYSTEMD_SERVICE}.service`;
4534
+ }
4327
4535
  function getLaunchdPlistPath() {
4328
4536
  return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
4329
4537
  }
@@ -4384,9 +4592,13 @@ function generateLaunchdPlist() {
4384
4592
  </plist>`;
4385
4593
  }
4386
4594
  /**
4387
- * Generate a systemd user unit for Linux.
4595
+ * Generate a systemd unit for Linux.
4596
+ * Root users get a system-level unit; non-root get a user-level unit.
4388
4597
  */
4389
4598
  function generateSystemdUnit() {
4599
+ const nodePath = getNodePath();
4600
+ const gatewayBin = getGatewayBinPath();
4601
+ const root = isRootUser();
4390
4602
  return `[Unit]
4391
4603
  Description=Alfe Gateway Daemon
4392
4604
  After=network-online.target
@@ -4394,13 +4606,13 @@ Wants=network-online.target
4394
4606
 
4395
4607
  [Service]
4396
4608
  Type=simple
4397
- ExecStart=${getNodePath()} ${getGatewayBinPath()} daemon
4609
+ ExecStart=${nodePath} ${gatewayBin} daemon
4398
4610
  Restart=always
4399
4611
  RestartSec=10
4400
- Environment=NODE_ENV=production
4612
+ Environment=NODE_ENV=production${root ? "\nEnvironment=HOME=/root\nWorkingDirectory=/root" : ""}
4401
4613
 
4402
4614
  [Install]
4403
- WantedBy=default.target`;
4615
+ WantedBy=${root ? "multi-user.target" : "default.target"}`;
4404
4616
  }
4405
4617
  /**
4406
4618
  * Install the service unit for the current platform.
@@ -4441,31 +4653,57 @@ async function uninstallLaunchd() {
4441
4653
  return `Uninstalled: ${plistPath}`;
4442
4654
  }
4443
4655
  async function installSystemd() {
4444
- const unitPath = getSystemdServicePath();
4445
- await mkdir(join(homedir(), ".config", "systemd", "user"), { recursive: true });
4656
+ const root = isRootUser();
4657
+ const unitPath = root ? getSystemdSystemServicePath() : getSystemdServicePath();
4658
+ const dir = root ? "/etc/systemd/system" : join(homedir(), ".config", "systemd", "user");
4659
+ const ctl = root ? "systemctl" : "systemctl --user";
4660
+ if (!root) await mkdir(dir, { recursive: true });
4446
4661
  await writeFile(unitPath, generateSystemdUnit(), "utf-8");
4447
- logger$1.info({ path: unitPath }, "Wrote systemd unit");
4662
+ logger$1.info({
4663
+ path: unitPath,
4664
+ root
4665
+ }, "Wrote systemd unit");
4448
4666
  try {
4449
- execSync("systemctl --user daemon-reload", { stdio: "pipe" });
4450
- execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4667
+ execSync(`${ctl} daemon-reload`, { stdio: "pipe" });
4668
+ execSync(`${ctl} enable ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4451
4669
  } catch {}
4452
- return `Installed: ${unitPath}\nService enabled for user session.`;
4670
+ return `Installed: ${unitPath}\nService enabled${root ? " (system-level)" : " for user session"}.`;
4453
4671
  }
4454
4672
  async function uninstallSystemd() {
4455
- const unitPath = getSystemdServicePath();
4673
+ const root = isRootUser();
4674
+ const unitPath = root ? getSystemdSystemServicePath() : getSystemdServicePath();
4675
+ const ctl = root ? "systemctl" : "systemctl --user";
4456
4676
  try {
4457
- execSync(`systemctl --user disable ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4458
- execSync(`systemctl --user stop ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4677
+ execSync(`${ctl} disable ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4678
+ execSync(`${ctl} stop ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4459
4679
  } catch {}
4460
4680
  try {
4461
4681
  await unlink(unitPath);
4462
4682
  } catch {}
4463
4683
  try {
4464
- execSync("systemctl --user daemon-reload", { stdio: "pipe" });
4684
+ execSync(`${ctl} daemon-reload`, { stdio: "pipe" });
4465
4685
  } catch {}
4466
4686
  return `Uninstalled: ${unitPath}`;
4467
4687
  }
4468
4688
  /**
4689
+ * Start the installed service via systemctl/launchctl.
4690
+ */
4691
+ function startService() {
4692
+ const platform = process.platform;
4693
+ if (platform === "darwin") {
4694
+ const plistPath = getLaunchdPlistPath();
4695
+ execSync(`launchctl start ${LAUNCHD_LABEL}`, { stdio: "pipe" });
4696
+ logger$1.info({ path: plistPath }, "Started launchd service");
4697
+ return;
4698
+ }
4699
+ if (platform === "linux") {
4700
+ execSync(`${isRootUser() ? "systemctl" : "systemctl --user"} start ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
4701
+ logger$1.info("Started systemd service");
4702
+ return;
4703
+ }
4704
+ throw new Error(`Unsupported platform: ${platform}`);
4705
+ }
4706
+ /**
4469
4707
  * Write the current process PID to the PID file.
4470
4708
  */
4471
4709
  async function writePidFile() {
@@ -19500,7 +19738,11 @@ var RuntimeProcess = class {
19500
19738
  switch (this.options.runtime) {
19501
19739
  case "openclaw": return {
19502
19740
  command: "openclaw",
19503
- args: ["--workspace", this.options.workspace]
19741
+ args: [
19742
+ "gateway",
19743
+ "run",
19744
+ "--allow-unconfigured"
19745
+ ]
19504
19746
  };
19505
19747
  default: throw new Error(`Unsupported runtime: ${this.options.runtime}`);
19506
19748
  }
@@ -19635,6 +19877,7 @@ var LocalAgentRelay = class {
19635
19877
  * Start connecting to the local runtime WS.
19636
19878
  */
19637
19879
  start() {
19880
+ log.debug({ wsUrl: this.options.wsUrl }, "Local relay starting...");
19638
19881
  this.stopped = false;
19639
19882
  this.doConnect();
19640
19883
  }
@@ -19642,12 +19885,14 @@ var LocalAgentRelay = class {
19642
19885
  * Stop the relay and close the local WS connection.
19643
19886
  */
19644
19887
  stop() {
19888
+ log.debug("Local relay stopping...");
19645
19889
  this.stopped = true;
19646
19890
  if (this.retryTimer) {
19647
19891
  clearTimeout(this.retryTimer);
19648
19892
  this.retryTimer = null;
19649
19893
  }
19650
19894
  if (this.ws) {
19895
+ log.debug({ readyState: this.ws.readyState }, "Local relay: closing WebSocket");
19651
19896
  try {
19652
19897
  this.ws.close(1e3);
19653
19898
  } catch (err) {
@@ -19656,25 +19901,33 @@ var LocalAgentRelay = class {
19656
19901
  this.ws = null;
19657
19902
  }
19658
19903
  this.connected = false;
19904
+ log.debug("Local relay stopped");
19659
19905
  }
19660
19906
  /**
19661
19907
  * Forward a message payload from the cloud to the local runtime WS.
19662
19908
  */
19663
19909
  forward(payload) {
19664
19910
  if (this.ws?.readyState !== WebSocket.OPEN) {
19665
- log.warn("Cannot forward to local runtime — not connected");
19911
+ log.warn({ readyState: this.ws?.readyState }, "Cannot forward to local runtime — not connected");
19666
19912
  return;
19667
19913
  }
19668
19914
  const data = typeof payload === "string" ? payload : JSON.stringify(payload);
19915
+ log.debug({ size: data.length }, "Local relay: forwarding message to runtime");
19669
19916
  this.ws.send(data);
19670
19917
  }
19671
19918
  get isConnected() {
19672
19919
  return this.connected;
19673
19920
  }
19674
19921
  doConnect() {
19675
- if (this.stopped) return;
19676
- log.info({ url: this.options.wsUrl }, "Connecting to local runtime WS...");
19677
- this.ws = new WebSocket(this.options.wsUrl);
19922
+ if (this.stopped) {
19923
+ log.debug("Local relay: doConnect skipped relay is stopped");
19924
+ return;
19925
+ }
19926
+ log.info({
19927
+ url: this.options.wsUrl,
19928
+ retryCount: this.retryCount
19929
+ }, "Connecting to local runtime WS...");
19930
+ this.ws = new WebSocket(this.options.wsUrl, { handshakeTimeout: 1e4 });
19678
19931
  this.ws.on("open", () => {
19679
19932
  log.info("Local runtime WS connected — performing handshake");
19680
19933
  this.retryCount = 0;
@@ -19682,6 +19935,7 @@ var LocalAgentRelay = class {
19682
19935
  });
19683
19936
  this.ws.on("message", (data) => {
19684
19937
  const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
19938
+ log.debug({ size: text.length }, "Local relay: received message");
19685
19939
  this.handleLocalMessage(text);
19686
19940
  });
19687
19941
  this.ws.on("close", (code, reason) => {
@@ -19693,10 +19947,14 @@ var LocalAgentRelay = class {
19693
19947
  this.scheduleReconnect();
19694
19948
  });
19695
19949
  this.ws.on("error", (err) => {
19696
- log.debug({ err: err.message }, "Local runtime WS error");
19950
+ log.warn({
19951
+ err: err.message,
19952
+ url: this.options.wsUrl
19953
+ }, "Local runtime WS error");
19697
19954
  });
19698
19955
  }
19699
19956
  performHandshake() {
19957
+ log.debug("Local relay: sending connect handshake");
19700
19958
  const connectReq = {
19701
19959
  type: "req",
19702
19960
  id: randomUUID(),
@@ -19705,7 +19963,7 @@ var LocalAgentRelay = class {
19705
19963
  minProtocol: 3,
19706
19964
  maxProtocol: 3,
19707
19965
  client: {
19708
- id: "alfe-daemon-relay",
19966
+ id: "gateway-client",
19709
19967
  displayName: "Alfe Daemon Relay",
19710
19968
  version: "0.1.0",
19711
19969
  platform: process.platform,
@@ -19781,20 +20039,37 @@ let integrationManager;
19781
20039
  let aiProxyServer = null;
19782
20040
  let runtimeProcess = null;
19783
20041
  let localRelay = null;
20042
+ let aiProxyUrl = null;
20043
+ let aiProxyRunning = false;
19784
20044
  let cloudConnected = false;
19785
20045
  let shuttingDown = false;
20046
+ /**
20047
+ * Flush pino's async transport and exit.
20048
+ * process.exit() can drop buffered log lines — this ensures they're written first.
20049
+ */
20050
+ async function flushAndExit(code) {
20051
+ await new Promise((resolve) => {
20052
+ logger$1.flush();
20053
+ setTimeout(resolve, 500);
20054
+ });
20055
+ process.exit(code);
20056
+ }
19786
20057
  async function startDaemon() {
19787
20058
  startedAt = Date.now();
19788
20059
  const managed = isManagedMode();
19789
- logger$1.info({ managed }, "Starting Alfe Gateway Daemon...");
20060
+ logger$1.info({
20061
+ managed,
20062
+ pid: process.pid
20063
+ }, "Starting Alfe Gateway Daemon...");
19790
20064
  if (!managed) {
19791
20065
  await mkdir(join(homedir(), ".alfe"), { recursive: true });
19792
20066
  const existingPid = await checkExistingDaemon();
19793
20067
  if (existingPid) {
19794
20068
  logger$1.error({ pid: existingPid }, "Daemon already running. Use `alfe gateway stop` first.");
19795
- process.exit(1);
20069
+ await flushAndExit(1);
19796
20070
  }
19797
20071
  }
20072
+ logger$1.debug("Loading daemon config...");
19798
20073
  try {
19799
20074
  config = await loadDaemonConfig();
19800
20075
  logger$1.info({
@@ -19804,26 +20079,33 @@ async function startDaemon() {
19804
20079
  }, "Config loaded, identity resolved");
19805
20080
  } catch (err) {
19806
20081
  const message = err instanceof Error ? err.message : String(err);
19807
- logger$1.error({ err: message }, "Failed to load config");
19808
- process.exit(1);
19809
- }
20082
+ const stack = err instanceof Error ? err.stack : void 0;
20083
+ logger$1.error({
20084
+ err: message,
20085
+ stack
20086
+ }, "Failed to load config");
20087
+ await flushAndExit(1);
20088
+ }
20089
+ logger$1.debug("Initializing command queue...");
19810
20090
  commandQueue = new CommandQueue();
19811
20091
  commandQueue.startGC();
20092
+ logger$1.debug("Starting AI proxy...");
19812
20093
  try {
19813
- const { createProxyServer } = await import("@alfe.ai/ai-proxy-local");
20094
+ const { createProxyServer, DEFAULT_AI_PROXY_PORT } = await import("@alfe.ai/ai-proxy-local");
19814
20095
  const { getAiServiceUrlFromToken } = await import("@alfe.ai/config");
19815
20096
  const proxyUrl = getAiServiceUrlFromToken(config.apiKey);
19816
- const AI_PROXY_PORT = 18193;
19817
20097
  aiProxyServer = createProxyServer({
19818
- port: AI_PROXY_PORT,
20098
+ port: DEFAULT_AI_PROXY_PORT,
19819
20099
  apiKey: config.apiKey,
19820
20100
  proxyUrl
19821
20101
  });
19822
20102
  const server = aiProxyServer;
19823
20103
  await new Promise((resolve, reject) => {
19824
- server.listen(AI_PROXY_PORT, "127.0.0.1", () => {
20104
+ server.listen(DEFAULT_AI_PROXY_PORT, "127.0.0.1", () => {
20105
+ aiProxyRunning = true;
20106
+ aiProxyUrl = `http://127.0.0.1:${String(DEFAULT_AI_PROXY_PORT)}`;
19825
20107
  logger$1.info({
19826
- port: AI_PROXY_PORT,
20108
+ port: DEFAULT_AI_PROXY_PORT,
19827
20109
  upstream: proxyUrl
19828
20110
  }, "AI proxy started");
19829
20111
  resolve();
@@ -19832,17 +20114,31 @@ async function startDaemon() {
19832
20114
  });
19833
20115
  } catch (err) {
19834
20116
  const message = err instanceof Error ? err.message : String(err);
19835
- logger$1.error({ err: message }, "Failed to start AI proxy LLM requests will fail");
20117
+ const stack = err instanceof Error ? err.stack : void 0;
20118
+ logger$1.error({
20119
+ err: message,
20120
+ stack
20121
+ }, "Failed to start AI proxy — LLM requests will fail");
19836
20122
  }
20123
+ logger$1.debug({ socketPath: config.socketPath }, "Starting IPC server...");
19837
20124
  ipcServer = new IPCServer(config.socketPath);
19838
20125
  ipcServer.setRequestHandler(handlePluginRequest);
19839
20126
  try {
19840
20127
  await ipcServer.start();
20128
+ logger$1.debug("IPC server started");
19841
20129
  } catch (err) {
19842
20130
  const message = err instanceof Error ? err.message : String(err);
19843
- logger$1.error({ err: message }, "Failed to start IPC server");
19844
- process.exit(1);
19845
- }
20131
+ const stack = err instanceof Error ? err.stack : void 0;
20132
+ logger$1.error({
20133
+ err: message,
20134
+ stack
20135
+ }, "Failed to start IPC server");
20136
+ await flushAndExit(1);
20137
+ }
20138
+ logger$1.debug({
20139
+ wsUrl: config.gatewayWsUrl,
20140
+ agentId: config.agentId
20141
+ }, "Connecting cloud client...");
19846
20142
  cloudClient = new CloudClient({
19847
20143
  wsUrl: config.gatewayWsUrl,
19848
20144
  apiKey: config.apiKey,
@@ -19869,9 +20165,16 @@ async function startDaemon() {
19869
20165
  const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
19870
20166
  cloudClient.setIntegrationManager(integrationAdapter);
19871
20167
  cloudClient.start();
19872
- if (managed && config.runtime) {
20168
+ logger$1.debug("Cloud client started");
20169
+ if (config.autoStartRuntime && config.runtime) {
20170
+ logger$1.debug({ runtime: config.runtime }, "Starting agent runtime...");
19873
20171
  const runtimeCfg = config.runtimes[config.runtime];
19874
20172
  if (runtimeCfg) {
20173
+ const runtimeToken = randomUUID();
20174
+ logger$1.debug({
20175
+ runtime: config.runtime,
20176
+ workspace: runtimeCfg.workspace
20177
+ }, "Creating runtime process...");
19875
20178
  runtimeProcess = new RuntimeProcess({
19876
20179
  runtime: config.runtime,
19877
20180
  workspace: runtimeCfg.workspace,
@@ -19880,14 +20183,18 @@ async function startDaemon() {
19880
20183
  ALFE_API_KEY: config.apiKey,
19881
20184
  ALFE_API_URL: config.apiEndpoint,
19882
20185
  ALFE_AGENT_ID: config.agentId,
19883
- ANTHROPIC_BASE_URL: "http://127.0.0.1:18193",
19884
- OPENAI_BASE_URL: "http://127.0.0.1:18193"
20186
+ ANTHROPIC_BASE_URL: aiProxyUrl ?? "http://127.0.0.1:18193",
20187
+ OPENAI_BASE_URL: aiProxyUrl ?? "http://127.0.0.1:18193",
20188
+ OPENCLAW_GATEWAY_TOKEN: runtimeToken
19885
20189
  }
19886
20190
  });
19887
20191
  runtimeProcess.start();
20192
+ logger$1.debug("Runtime process started");
20193
+ const RUNTIME_WS_PORT = 18789;
20194
+ logger$1.debug({ runtimeWsPort: RUNTIME_WS_PORT }, "Creating local agent relay...");
19888
20195
  const relay = new LocalAgentRelay({
19889
- wsUrl: `ws://127.0.0.1:${String(18789)}`,
19890
- apiKey: config.apiKey,
20196
+ wsUrl: `ws://127.0.0.1:${String(RUNTIME_WS_PORT)}`,
20197
+ apiKey: runtimeToken,
19891
20198
  onAgentMessage: (payload) => {
19892
20199
  cloudClient.sendAgentEvent(payload);
19893
20200
  }
@@ -19896,6 +20203,7 @@ async function startDaemon() {
19896
20203
  cloudClient.setServiceRelayHandler((msg) => {
19897
20204
  relay.forward(msg.payload);
19898
20205
  });
20206
+ logger$1.debug("Scheduling local relay start in 2s (waiting for runtime WS server)...");
19899
20207
  setTimeout(() => {
19900
20208
  relay.start();
19901
20209
  }, 2e3);
@@ -19906,22 +20214,39 @@ async function startDaemon() {
19906
20214
  if (shuttingDown) return;
19907
20215
  shuttingDown = true;
19908
20216
  logger$1.info({ signal }, "Shutting down...");
19909
- if (localRelay) localRelay.stop();
19910
- if (runtimeProcess) await runtimeProcess.stop();
19911
- if (ipcServer) await ipcServer.stop();
20217
+ if (localRelay) {
20218
+ logger$1.debug("Stopping local relay...");
20219
+ localRelay.stop();
20220
+ logger$1.debug("Local relay stopped");
20221
+ }
20222
+ if (runtimeProcess) {
20223
+ logger$1.debug("Stopping runtime process...");
20224
+ await runtimeProcess.stop();
20225
+ logger$1.debug("Runtime process stopped");
20226
+ }
20227
+ if (ipcServer) {
20228
+ logger$1.debug("Stopping IPC server...");
20229
+ await ipcServer.stop();
20230
+ logger$1.debug("IPC server stopped");
20231
+ }
19912
20232
  if (aiProxyServer) {
20233
+ logger$1.debug("Stopping AI proxy...");
19913
20234
  const proxy = aiProxyServer;
19914
20235
  await new Promise((resolve) => {
19915
20236
  proxy.close(() => {
19916
20237
  resolve();
19917
20238
  });
19918
20239
  });
20240
+ aiProxyRunning = false;
20241
+ logger$1.debug("AI proxy stopped");
19919
20242
  }
20243
+ logger$1.debug("Stopping cloud client...");
19920
20244
  cloudClient.stop();
20245
+ logger$1.debug("Cloud client stopped");
19921
20246
  commandQueue.stopGC();
19922
20247
  if (!managed) await removePidFile();
19923
20248
  logger$1.info("Daemon stopped");
19924
- process.exit(0);
20249
+ await flushAndExit(0);
19925
20250
  };
19926
20251
  process.on("SIGTERM", () => {
19927
20252
  shutdown("SIGTERM");
@@ -19937,33 +20262,94 @@ async function startDaemon() {
19937
20262
  }, "Alfe Gateway Daemon started ✅");
19938
20263
  }
19939
20264
  async function handleCloudCommand(command) {
19940
- if (command.command === "support.diagnostic") try {
19941
- const { runDiagnostic } = await import("@alfe.ai/doctor");
20265
+ if (command.command === "support.diagnostic") {
19942
20266
  const payload = command.payload;
19943
- const workspacePath = Object.values(config.runtimes)[0]?.workspace ?? "~/.openclaw";
19944
- const report = await runDiagnostic({
19945
- task: payload.task,
19946
- workspacePath,
19947
- timeoutSeconds: 300
19948
- });
19949
- return {
19950
- type: "COMMAND_ACK",
19951
- commandId: command.commandId,
19952
- status: report.success ? "ok" : "error",
19953
- result: report
19954
- };
19955
- } catch (err) {
19956
- const message = err instanceof Error ? err.message : String(err);
19957
- logger$1.error({ err: message }, "Diagnostic failed");
19958
- return {
20267
+ if (!payload.apiKey && !aiProxyRunning) return {
19959
20268
  type: "COMMAND_ACK",
19960
20269
  commandId: command.commandId,
19961
20270
  status: "error",
19962
20271
  result: {
19963
- code: "DIAGNOSTIC_FAILED",
19964
- message
20272
+ code: "PROXY_NOT_RUNNING",
20273
+ message: "AI proxy is not running — diagnostic requires LLM access"
19965
20274
  }
19966
20275
  };
20276
+ let proxyUrl;
20277
+ if (aiProxyRunning) proxyUrl = aiProxyUrl ?? void 0;
20278
+ else {
20279
+ const { getAiServiceUrlFromToken } = await import("@alfe.ai/config");
20280
+ proxyUrl = getAiServiceUrlFromToken(config.apiKey);
20281
+ }
20282
+ try {
20283
+ const { runDiagnostic } = await import("@alfe.ai/doctor");
20284
+ const workspacePath = Object.values(config.runtimes)[0]?.workspace ?? "~/.openclaw";
20285
+ const report = await runDiagnostic({
20286
+ task: payload.task,
20287
+ workspacePath,
20288
+ timeoutSeconds: 300,
20289
+ proxyUrl,
20290
+ apiKey: payload.apiKey
20291
+ });
20292
+ return {
20293
+ type: "COMMAND_ACK",
20294
+ commandId: command.commandId,
20295
+ status: report.success ? "ok" : "error",
20296
+ result: report
20297
+ };
20298
+ } catch (err) {
20299
+ const message = err instanceof Error ? err.message : String(err);
20300
+ logger$1.error({ err: message }, "Diagnostic failed");
20301
+ return {
20302
+ type: "COMMAND_ACK",
20303
+ commandId: command.commandId,
20304
+ status: "error",
20305
+ result: {
20306
+ code: "DIAGNOSTIC_FAILED",
20307
+ message
20308
+ }
20309
+ };
20310
+ }
20311
+ }
20312
+ if (command.command === "integration.status") {
20313
+ const payload = command.payload;
20314
+ try {
20315
+ if (payload?.integrationId) {
20316
+ const info = integrationManager.get(payload.integrationId);
20317
+ if (!info) return {
20318
+ type: "COMMAND_ACK",
20319
+ commandId: command.commandId,
20320
+ status: "error",
20321
+ result: {
20322
+ code: "NOT_FOUND",
20323
+ message: `Integration "${payload.integrationId}" not found`
20324
+ }
20325
+ };
20326
+ return {
20327
+ type: "COMMAND_ACK",
20328
+ commandId: command.commandId,
20329
+ status: "ok",
20330
+ result: { integrations: [info] }
20331
+ };
20332
+ }
20333
+ const integrations = integrationManager.list();
20334
+ return {
20335
+ type: "COMMAND_ACK",
20336
+ commandId: command.commandId,
20337
+ status: "ok",
20338
+ result: { integrations }
20339
+ };
20340
+ } catch (err) {
20341
+ const message = err instanceof Error ? err.message : String(err);
20342
+ logger$1.error({ err: message }, "Integration status failed");
20343
+ return {
20344
+ type: "COMMAND_ACK",
20345
+ commandId: command.commandId,
20346
+ status: "error",
20347
+ result: {
20348
+ code: "STATUS_FAILED",
20349
+ message
20350
+ }
20351
+ };
20352
+ }
19967
20353
  }
19968
20354
  const ipcRequest = cloudCommandToIPCRequest(command);
19969
20355
  if (!ipcRequest) {
@@ -20049,6 +20435,7 @@ function handleStatus() {
20049
20435
  status: cloudConnected ? "connected" : "disconnected",
20050
20436
  latencyMs: cloudClient.getLatencyMs()
20051
20437
  },
20438
+ aiProxy: { status: aiProxyRunning ? "running" : "stopped" },
20052
20439
  plugins: (ipcServer?.getRegisteredPlugins() ?? []).map(([, info]) => ({
20053
20440
  name: info.name,
20054
20441
  version: info.version,
@@ -20136,6 +20523,7 @@ async function queryDaemonHealth(socketPath, timeoutMs = 5e3) {
20136
20523
  if (err.code === "ECONNREFUSED" || err.code === "ENOENT") resolve({
20137
20524
  daemon: { status: "stopped" },
20138
20525
  cloud: { status: "unknown" },
20526
+ aiProxy: { status: "stopped" },
20139
20527
  plugins: [],
20140
20528
  commandQueue: { totalPending: 0 }
20141
20529
  });
@@ -20152,6 +20540,8 @@ function formatHealthReport(health) {
20152
20540
  lines.push(` Gateway daemon ${daemonIcon} ${health.daemon.status}` + (health.daemon.uptime ? ` (uptime: ${formatUptime(health.daemon.uptime)})` : "") + (health.daemon.version ? ` v${health.daemon.version}` : ""));
20153
20541
  const cloudIcon = health.cloud.status === "connected" ? "✓" : "✗";
20154
20542
  lines.push(` Cloud connection ${cloudIcon} ${health.cloud.status}` + (health.cloud.latencyMs !== void 0 ? ` (latency: ${String(health.cloud.latencyMs)}ms)` : ""));
20543
+ const proxyIcon = health.aiProxy.status === "running" ? "✓" : "✗";
20544
+ lines.push(` AI proxy ${proxyIcon} ${health.aiProxy.status}`);
20155
20545
  if (health.plugins.length === 0) lines.push(" Plugins – none connected");
20156
20546
  else for (const plugin of health.plugins) {
20157
20547
  const lastSeenAgo = Date.now() - plugin.lastSeen;
@@ -20173,4 +20563,4 @@ function formatDuration(ms) {
20173
20563
  return `${String(Math.round(seconds / 3600))}h`;
20174
20564
  }
20175
20565
  //#endregion
20176
- export { installService as a, PROTOCOL_VERSION as c, SOCKET_PATH as d, fetchAgentConfig as f, LOG_FILE as h, checkExistingDaemon as i, ALFE_DIR as l, resolveAgentIdentity as m, queryDaemonHealth as n, stopExistingDaemon as o, loadDaemonConfig as p, startDaemon as r, uninstallService as s, formatHealthReport as t, PID_PATH as u };
20566
+ 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 };
@@ -1,3 +1,5 @@
1
+ import pino from "pino";
2
+
1
3
  //#region src/protocol.d.ts
2
4
 
3
5
  interface IPCRequest {
@@ -58,6 +60,8 @@ interface DaemonConfig {
58
60
  runtime: string;
59
61
  /** Runtime configurations keyed by runtime name (e.g. 'openclaw') */
60
62
  runtimes: Record<string, RuntimeConfig>;
63
+ /** Whether to auto-start the agent runtime process (e.g. openclaw) */
64
+ autoStartRuntime: boolean;
61
65
  }
62
66
  interface AgentIdentity {
63
67
  agentId: string;
@@ -111,6 +115,9 @@ interface DaemonHealth {
111
115
  status: 'connected' | 'disconnected' | 'unknown';
112
116
  latencyMs?: number;
113
117
  };
118
+ aiProxy: {
119
+ status: 'running' | 'stopped';
120
+ };
114
121
  plugins: {
115
122
  name: string;
116
123
  version: string;
@@ -132,6 +139,9 @@ declare function queryDaemonHealth(socketPath: string, timeoutMs?: number): Prom
132
139
  */
133
140
  declare function formatHealthReport(health: DaemonHealth): string;
134
141
  //#endregion
142
+ //#region src/logger.d.ts
143
+ declare const logger: pino.Logger<never, boolean>;
144
+ //#endregion
135
145
  //#region src/process-manager.d.ts
136
146
  /**
137
147
  * Process management — launchd/systemd service installation.
@@ -147,6 +157,10 @@ declare function installService(): Promise<string>;
147
157
  * Uninstall the service unit for the current platform.
148
158
  */
149
159
  declare function uninstallService(): Promise<string>;
160
+ /**
161
+ * Start the installed service via systemctl/launchctl.
162
+ */
163
+ declare function startService(): void;
150
164
  /**
151
165
  * Write the current process PID to the PID file.
152
166
  */
@@ -161,4 +175,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
161
175
  */
162
176
  declare function stopExistingDaemon(): Promise<boolean>;
163
177
  //#endregion
164
- export { ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, PID_PATH, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, queryDaemonHealth, resolveAgentIdentity, startDaemon, stopExistingDaemon, uninstallService };
178
+ export { ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, PID_PATH, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, startService, stopExistingDaemon, uninstallService };
package/dist/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as installService, c as PROTOCOL_VERSION, d as SOCKET_PATH, f as fetchAgentConfig, i as checkExistingDaemon, l as ALFE_DIR, m as resolveAgentIdentity, n as queryDaemonHealth, o as stopExistingDaemon, p as loadDaemonConfig, r as startDaemon, s as uninstallService, t as formatHealthReport, u as PID_PATH } from "../health.js";
2
- export { ALFE_DIR, PID_PATH, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, queryDaemonHealth, resolveAgentIdentity, startDaemon, stopExistingDaemon, uninstallService };
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";
2
+ export { ALFE_DIR, PID_PATH, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, startService, stopExistingDaemon, uninstallService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,8 +19,8 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "pino": "^9.6.0",
22
- "smol-toml": "^1.3.0",
23
22
  "pino-roll": "^1.2.0",
23
+ "smol-toml": ">=1.6.1",
24
24
  "ws": "^8.18.0",
25
25
  "@alfe.ai/ai-proxy-local": "^0.0.2",
26
26
  "@alfe.ai/config": "^0.0.2",