@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.
- package/dist/bin/gateway.js +3 -1
- package/dist/health.js +484 -94
- package/dist/src/index.d.ts +15 -1
- package/dist/src/index.js +2 -2
- package/package.json +2 -2
package/dist/bin/gateway.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as installService,
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
72
|
-
*
|
|
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
|
|
3337
|
+
agentId,
|
|
3203
3338
|
orgId,
|
|
3204
|
-
runtime
|
|
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(
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
3334
|
-
};
|
|
3499
|
+
fileCount
|
|
3500
|
+
}, "Template files fetched");
|
|
3335
3501
|
return {
|
|
3336
3502
|
templateKey,
|
|
3337
|
-
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)
|
|
3726
|
-
|
|
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({
|
|
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)
|
|
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({
|
|
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
|
|
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=${
|
|
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
|
|
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
|
|
4445
|
-
|
|
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({
|
|
4662
|
+
logger$1.info({
|
|
4663
|
+
path: unitPath,
|
|
4664
|
+
root
|
|
4665
|
+
}, "Wrote systemd unit");
|
|
4448
4666
|
try {
|
|
4449
|
-
execSync(
|
|
4450
|
-
execSync(
|
|
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
|
|
4673
|
+
const root = isRootUser();
|
|
4674
|
+
const unitPath = root ? getSystemdSystemServicePath() : getSystemdServicePath();
|
|
4675
|
+
const ctl = root ? "systemctl" : "systemctl --user";
|
|
4456
4676
|
try {
|
|
4457
|
-
execSync(
|
|
4458
|
-
execSync(
|
|
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(
|
|
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: [
|
|
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)
|
|
19676
|
-
|
|
19677
|
-
|
|
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.
|
|
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: "
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
19808
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
19844
|
-
|
|
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
|
-
|
|
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(
|
|
19890
|
-
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)
|
|
19910
|
-
|
|
19911
|
-
|
|
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
|
-
|
|
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")
|
|
19941
|
-
const { runDiagnostic } = await import("@alfe.ai/doctor");
|
|
20265
|
+
if (command.command === "support.diagnostic") {
|
|
19942
20266
|
const payload = command.payload;
|
|
19943
|
-
|
|
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: "
|
|
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,
|
|
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 };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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",
|