@alfe.ai/gateway 0.0.4 → 0.0.5
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 +2 -0
- package/dist/health.js +250 -50
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +2 -2
- package/package.json +1 -1
package/dist/bin/gateway.js
CHANGED
|
@@ -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 }
|
|
@@ -3161,6 +3164,26 @@ enumValues({
|
|
|
3161
3164
|
Live: "live"
|
|
3162
3165
|
});
|
|
3163
3166
|
//#endregion
|
|
3167
|
+
//#region ../../packages-internal/types/dist/models.js
|
|
3168
|
+
const AnthropicModel = {
|
|
3169
|
+
Opus: "claude-opus-4-6",
|
|
3170
|
+
Sonnet: "claude-sonnet-4-6",
|
|
3171
|
+
Haiku: "claude-haiku-4-5"
|
|
3172
|
+
};
|
|
3173
|
+
const ANTHROPIC_MODELS = enumValues(AnthropicModel);
|
|
3174
|
+
const OpenAIModel = {
|
|
3175
|
+
GPT4o: "gpt-4o",
|
|
3176
|
+
GPT4oMini: "gpt-4o-mini",
|
|
3177
|
+
O3: "o3"
|
|
3178
|
+
};
|
|
3179
|
+
const OPENAI_MODELS = enumValues(OpenAIModel);
|
|
3180
|
+
[...ANTHROPIC_MODELS, ...OPENAI_MODELS];
|
|
3181
|
+
_enum(ANTHROPIC_MODELS);
|
|
3182
|
+
_enum(OPENAI_MODELS);
|
|
3183
|
+
_enum([...ANTHROPIC_MODELS, ...OPENAI_MODELS]);
|
|
3184
|
+
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3;
|
|
3185
|
+
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3;
|
|
3186
|
+
//#endregion
|
|
3164
3187
|
//#region src/config.ts
|
|
3165
3188
|
/**
|
|
3166
3189
|
* Daemon configuration — reads ~/.alfe/config.toml and resolves agent identity.
|
|
@@ -3179,6 +3202,10 @@ const PID_PATH = join(ALFE_DIR, "gateway.pid");
|
|
|
3179
3202
|
* Returns agentId (derived from tokenId) and orgId (tenantId).
|
|
3180
3203
|
*/
|
|
3181
3204
|
async function resolveAgentIdentity(apiKey, apiEndpoint) {
|
|
3205
|
+
logger$1.debug({
|
|
3206
|
+
apiEndpoint,
|
|
3207
|
+
keyPrefix: apiKey.slice(0, 8) + "..."
|
|
3208
|
+
}, "Resolving agent identity...");
|
|
3182
3209
|
const auth = new AuthService(new AlfeApiClient({
|
|
3183
3210
|
apiBaseUrl: apiEndpoint,
|
|
3184
3211
|
getToken: () => Promise.resolve(apiKey)
|
|
@@ -3186,22 +3213,47 @@ async function resolveAgentIdentity(apiKey, apiEndpoint) {
|
|
|
3186
3213
|
const maxAttempts = 3;
|
|
3187
3214
|
const delayMs = 3e3;
|
|
3188
3215
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3216
|
+
logger$1.debug({
|
|
3217
|
+
attempt,
|
|
3218
|
+
maxAttempts
|
|
3219
|
+
}, "Validating token...");
|
|
3189
3220
|
const result = await auth.validate(apiKey);
|
|
3190
3221
|
if (!result.ok) {
|
|
3222
|
+
logger$1.debug({
|
|
3223
|
+
status: result.status,
|
|
3224
|
+
error: result.error,
|
|
3225
|
+
attempt
|
|
3226
|
+
}, "Token validation failed");
|
|
3191
3227
|
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
3228
|
if (attempt < maxAttempts) {
|
|
3229
|
+
logger$1.debug({
|
|
3230
|
+
delayMs,
|
|
3231
|
+
attempt
|
|
3232
|
+
}, "Retrying token validation after transient error...");
|
|
3193
3233
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
3194
3234
|
continue;
|
|
3195
3235
|
}
|
|
3196
3236
|
throw new Error(`Token validation failed after ${String(maxAttempts)} attempts: ${result.error}. Is your API key valid? Run \`alfe login\` to reconfigure.`);
|
|
3197
3237
|
}
|
|
3238
|
+
logger$1.debug({
|
|
3239
|
+
valid: result.data.valid,
|
|
3240
|
+
tenantId: result.data.tenantId,
|
|
3241
|
+
tokenId: result.data.tokenId
|
|
3242
|
+
}, "Token validation response");
|
|
3198
3243
|
if (!result.data.valid) throw new Error("API key invalid: validation failed. Run `alfe login` to reconfigure.");
|
|
3199
3244
|
const orgId = result.data.tenantId;
|
|
3200
3245
|
if (!orgId) throw new Error("Token validation returned no tenantId — cannot determine org.");
|
|
3246
|
+
const agentId = result.data.tokenId ?? deriveAgentId(apiKey);
|
|
3247
|
+
const runtime = result.data.runtime ?? "openclaw";
|
|
3248
|
+
logger$1.debug({
|
|
3249
|
+
agentId,
|
|
3250
|
+
orgId,
|
|
3251
|
+
runtime
|
|
3252
|
+
}, "Agent identity resolved");
|
|
3201
3253
|
return {
|
|
3202
|
-
agentId
|
|
3254
|
+
agentId,
|
|
3203
3255
|
orgId,
|
|
3204
|
-
runtime
|
|
3256
|
+
runtime
|
|
3205
3257
|
};
|
|
3206
3258
|
}
|
|
3207
3259
|
throw new Error("Token validation failed: exhausted retries.");
|
|
@@ -3260,15 +3312,27 @@ function isManagedMode() {
|
|
|
3260
3312
|
* All config comes from environment variables — no local files needed.
|
|
3261
3313
|
*/
|
|
3262
3314
|
async function loadManagedConfig() {
|
|
3315
|
+
logger$1.debug("Loading managed config from environment...");
|
|
3263
3316
|
const apiKey = process.env.ALFE_API_KEY;
|
|
3264
3317
|
const apiEndpoint = process.env.ALFE_API_ENDPOINT;
|
|
3265
|
-
if (!apiKey || !apiEndpoint) throw new Error(
|
|
3318
|
+
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"})`);
|
|
3319
|
+
logger$1.debug({
|
|
3320
|
+
apiEndpoint,
|
|
3321
|
+
gatewayWsUrlEnv: process.env.ALFE_GATEWAY_WS_URL ?? "(not set)"
|
|
3322
|
+
}, "Environment loaded, resolving identity...");
|
|
3266
3323
|
const identity = await resolveAgentIdentity(apiKey, apiEndpoint);
|
|
3324
|
+
const gatewayWsUrl = process.env.ALFE_GATEWAY_WS_URL ?? deriveGatewayWsUrl(apiEndpoint);
|
|
3325
|
+
logger$1.debug({
|
|
3326
|
+
agentId: identity.agentId,
|
|
3327
|
+
orgId: identity.orgId,
|
|
3328
|
+
runtime: identity.runtime,
|
|
3329
|
+
gatewayWsUrl
|
|
3330
|
+
}, "Managed config resolved");
|
|
3267
3331
|
return {
|
|
3268
3332
|
apiKey,
|
|
3269
3333
|
apiEndpoint,
|
|
3270
|
-
gatewayWsUrl
|
|
3271
|
-
socketPath: "",
|
|
3334
|
+
gatewayWsUrl,
|
|
3335
|
+
socketPath: "/tmp/alfe-gateway.sock",
|
|
3272
3336
|
pidPath: "",
|
|
3273
3337
|
agentId: identity.agentId,
|
|
3274
3338
|
orgId: identity.orgId,
|
|
@@ -3317,26 +3381,44 @@ async function loadDaemonConfig() {
|
|
|
3317
3381
|
*/
|
|
3318
3382
|
async function fetchAgentConfig(apiKey, apiEndpoint) {
|
|
3319
3383
|
try {
|
|
3384
|
+
logger$1.debug({ apiEndpoint }, "Fetching agent workspace config...");
|
|
3320
3385
|
const wsResponse = await fetch(`${apiEndpoint}/agents/me/workspace`, {
|
|
3321
3386
|
method: "GET",
|
|
3322
3387
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
3323
3388
|
});
|
|
3324
|
-
if (!wsResponse.ok)
|
|
3389
|
+
if (!wsResponse.ok) {
|
|
3390
|
+
logger$1.debug({ status: wsResponse.status }, "Workspace config fetch failed");
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
3325
3393
|
const templateKey = (await wsResponse.json()).data?.templateKey;
|
|
3326
|
-
if (!templateKey)
|
|
3394
|
+
if (!templateKey) {
|
|
3395
|
+
logger$1.debug("No templateKey in workspace response");
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3398
|
+
logger$1.debug({ templateKey }, "Fetching template files...");
|
|
3327
3399
|
const filesResponse = await fetch(`${apiEndpoint}/templates/${encodeURIComponent(templateKey)}/files`, {
|
|
3328
3400
|
method: "GET",
|
|
3329
3401
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
3330
3402
|
});
|
|
3331
|
-
if (!filesResponse.ok)
|
|
3403
|
+
if (!filesResponse.ok) {
|
|
3404
|
+
logger$1.debug({ status: filesResponse.status }, "Template files fetch failed");
|
|
3405
|
+
return {
|
|
3406
|
+
templateKey,
|
|
3407
|
+
files: {}
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
const filesResult = await filesResponse.json();
|
|
3411
|
+
const fileCount = Object.keys(filesResult.data?.files ?? {}).length;
|
|
3412
|
+
logger$1.debug({
|
|
3332
3413
|
templateKey,
|
|
3333
|
-
|
|
3334
|
-
};
|
|
3414
|
+
fileCount
|
|
3415
|
+
}, "Template files fetched");
|
|
3335
3416
|
return {
|
|
3336
3417
|
templateKey,
|
|
3337
|
-
files:
|
|
3418
|
+
files: filesResult.data?.files ?? {}
|
|
3338
3419
|
};
|
|
3339
|
-
} catch {
|
|
3420
|
+
} catch (err) {
|
|
3421
|
+
logger$1.debug({ err: err instanceof Error ? err.message : String(err) }, "fetchAgentConfig failed");
|
|
3340
3422
|
return null;
|
|
3341
3423
|
}
|
|
3342
3424
|
}
|
|
@@ -3696,6 +3778,10 @@ var CloudClient = class {
|
|
|
3696
3778
|
* Start the cloud connection with auto-reconnect.
|
|
3697
3779
|
*/
|
|
3698
3780
|
start() {
|
|
3781
|
+
logger$1.debug({
|
|
3782
|
+
wsUrl: this.config.wsUrl,
|
|
3783
|
+
agentId: this.config.agentId
|
|
3784
|
+
}, "Cloud client starting...");
|
|
3699
3785
|
this.closed = false;
|
|
3700
3786
|
this.doConnect();
|
|
3701
3787
|
}
|
|
@@ -3703,15 +3789,18 @@ var CloudClient = class {
|
|
|
3703
3789
|
* Stop the cloud connection and all timers.
|
|
3704
3790
|
*/
|
|
3705
3791
|
stop() {
|
|
3792
|
+
logger$1.debug("Cloud client stopping...");
|
|
3706
3793
|
this.closed = true;
|
|
3707
3794
|
this.stopPingTimer();
|
|
3708
3795
|
if (this.ws) {
|
|
3796
|
+
logger$1.debug({ readyState: this.ws.readyState }, "Cloud: closing WebSocket");
|
|
3709
3797
|
try {
|
|
3710
3798
|
this.ws.close(1e3, "Daemon shutting down");
|
|
3711
3799
|
} catch {}
|
|
3712
3800
|
this.ws = null;
|
|
3713
3801
|
}
|
|
3714
3802
|
this.registered = false;
|
|
3803
|
+
logger$1.debug("Cloud client stopped");
|
|
3715
3804
|
}
|
|
3716
3805
|
/**
|
|
3717
3806
|
* Get connection latency (time since last pong).
|
|
@@ -3722,22 +3811,36 @@ var CloudClient = class {
|
|
|
3722
3811
|
return Date.now() - this.lastPong;
|
|
3723
3812
|
}
|
|
3724
3813
|
doConnect() {
|
|
3725
|
-
if (this.closed)
|
|
3726
|
-
|
|
3814
|
+
if (this.closed) {
|
|
3815
|
+
logger$1.debug("Cloud: doConnect skipped — client is closed");
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
logger$1.info({
|
|
3819
|
+
url: this.config.wsUrl,
|
|
3820
|
+
backoffMs: this.backoffMs
|
|
3821
|
+
}, "Connecting to cloud gateway...");
|
|
3822
|
+
logger$1.debug({
|
|
3823
|
+
agentId: this.config.agentId,
|
|
3824
|
+
keyPrefix: this.config.apiKey.slice(0, 12) + "..."
|
|
3825
|
+
}, "Cloud: connection details");
|
|
3727
3826
|
this.ws = new WebSocket(this.config.wsUrl, {
|
|
3728
3827
|
headers: { authorization: `Bearer ${this.config.apiKey}` },
|
|
3729
|
-
maxPayload: 10 * 1024 * 1024
|
|
3828
|
+
maxPayload: 10 * 1024 * 1024,
|
|
3829
|
+
handshakeTimeout: 1e4
|
|
3730
3830
|
});
|
|
3731
3831
|
this.ws.on("open", () => {
|
|
3732
3832
|
logger$1.info("Cloud WebSocket connected");
|
|
3833
|
+
logger$1.debug({ readyState: this.ws?.readyState }, "Cloud: WebSocket open, sending registration...");
|
|
3733
3834
|
this.backoffMs = 1e3;
|
|
3734
3835
|
this.sendRegister();
|
|
3735
3836
|
});
|
|
3736
3837
|
this.ws.on("message", (data) => {
|
|
3737
3838
|
const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
|
|
3839
|
+
logger$1.debug({ size: text.length }, "Cloud: received message");
|
|
3738
3840
|
this.handleMessage(text);
|
|
3739
3841
|
});
|
|
3740
3842
|
this.ws.on("ping", () => {
|
|
3843
|
+
logger$1.debug("Cloud: received ping, sending pong");
|
|
3741
3844
|
this.ws?.pong();
|
|
3742
3845
|
});
|
|
3743
3846
|
this.ws.on("close", (code, reason) => {
|
|
@@ -3751,11 +3854,18 @@ var CloudClient = class {
|
|
|
3751
3854
|
this.scheduleReconnect();
|
|
3752
3855
|
});
|
|
3753
3856
|
this.ws.on("error", (err) => {
|
|
3754
|
-
logger$1.error({
|
|
3857
|
+
logger$1.error({
|
|
3858
|
+
err: err.message,
|
|
3859
|
+
url: this.config.wsUrl
|
|
3860
|
+
}, "Cloud WebSocket error");
|
|
3755
3861
|
});
|
|
3756
3862
|
}
|
|
3757
3863
|
sendRegister() {
|
|
3758
3864
|
const msg = createServiceRegister(this.config.agentId);
|
|
3865
|
+
logger$1.debug({
|
|
3866
|
+
serviceId: msg.serviceId,
|
|
3867
|
+
agentIds: msg.agentIds
|
|
3868
|
+
}, "Cloud: sending SERVICE_REGISTER");
|
|
3759
3869
|
this.send(msg);
|
|
3760
3870
|
logger$1.info({ serviceId: msg.serviceId }, "Sent SERVICE_REGISTER");
|
|
3761
3871
|
}
|
|
@@ -3868,6 +3978,7 @@ var CloudClient = class {
|
|
|
3868
3978
|
}
|
|
3869
3979
|
send(msg) {
|
|
3870
3980
|
if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(msg));
|
|
3981
|
+
else logger$1.debug({ readyState: this.ws?.readyState }, "Cloud: send skipped — WebSocket not open");
|
|
3871
3982
|
}
|
|
3872
3983
|
startPingTimer() {
|
|
3873
3984
|
this.stopPingTimer();
|
|
@@ -3888,10 +3999,16 @@ var CloudClient = class {
|
|
|
3888
3999
|
}
|
|
3889
4000
|
}
|
|
3890
4001
|
scheduleReconnect() {
|
|
3891
|
-
if (this.closed)
|
|
4002
|
+
if (this.closed) {
|
|
4003
|
+
logger$1.debug("Cloud: reconnect skipped — client is closed");
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
3892
4006
|
const delay = this.backoffMs;
|
|
3893
4007
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
3894
|
-
logger$1.info({
|
|
4008
|
+
logger$1.info({
|
|
4009
|
+
delayMs: delay,
|
|
4010
|
+
nextBackoffMs: this.backoffMs
|
|
4011
|
+
}, "Cloud: scheduling reconnect...");
|
|
3895
4012
|
setTimeout(() => {
|
|
3896
4013
|
this.doConnect();
|
|
3897
4014
|
}, delay);
|
|
@@ -19500,7 +19617,11 @@ var RuntimeProcess = class {
|
|
|
19500
19617
|
switch (this.options.runtime) {
|
|
19501
19618
|
case "openclaw": return {
|
|
19502
19619
|
command: "openclaw",
|
|
19503
|
-
args: [
|
|
19620
|
+
args: [
|
|
19621
|
+
"gateway",
|
|
19622
|
+
"run",
|
|
19623
|
+
"--allow-unconfigured"
|
|
19624
|
+
]
|
|
19504
19625
|
};
|
|
19505
19626
|
default: throw new Error(`Unsupported runtime: ${this.options.runtime}`);
|
|
19506
19627
|
}
|
|
@@ -19635,6 +19756,7 @@ var LocalAgentRelay = class {
|
|
|
19635
19756
|
* Start connecting to the local runtime WS.
|
|
19636
19757
|
*/
|
|
19637
19758
|
start() {
|
|
19759
|
+
log.debug({ wsUrl: this.options.wsUrl }, "Local relay starting...");
|
|
19638
19760
|
this.stopped = false;
|
|
19639
19761
|
this.doConnect();
|
|
19640
19762
|
}
|
|
@@ -19642,12 +19764,14 @@ var LocalAgentRelay = class {
|
|
|
19642
19764
|
* Stop the relay and close the local WS connection.
|
|
19643
19765
|
*/
|
|
19644
19766
|
stop() {
|
|
19767
|
+
log.debug("Local relay stopping...");
|
|
19645
19768
|
this.stopped = true;
|
|
19646
19769
|
if (this.retryTimer) {
|
|
19647
19770
|
clearTimeout(this.retryTimer);
|
|
19648
19771
|
this.retryTimer = null;
|
|
19649
19772
|
}
|
|
19650
19773
|
if (this.ws) {
|
|
19774
|
+
log.debug({ readyState: this.ws.readyState }, "Local relay: closing WebSocket");
|
|
19651
19775
|
try {
|
|
19652
19776
|
this.ws.close(1e3);
|
|
19653
19777
|
} catch (err) {
|
|
@@ -19656,25 +19780,33 @@ var LocalAgentRelay = class {
|
|
|
19656
19780
|
this.ws = null;
|
|
19657
19781
|
}
|
|
19658
19782
|
this.connected = false;
|
|
19783
|
+
log.debug("Local relay stopped");
|
|
19659
19784
|
}
|
|
19660
19785
|
/**
|
|
19661
19786
|
* Forward a message payload from the cloud to the local runtime WS.
|
|
19662
19787
|
*/
|
|
19663
19788
|
forward(payload) {
|
|
19664
19789
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
19665
|
-
log.warn("Cannot forward to local runtime — not connected");
|
|
19790
|
+
log.warn({ readyState: this.ws?.readyState }, "Cannot forward to local runtime — not connected");
|
|
19666
19791
|
return;
|
|
19667
19792
|
}
|
|
19668
19793
|
const data = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
19794
|
+
log.debug({ size: data.length }, "Local relay: forwarding message to runtime");
|
|
19669
19795
|
this.ws.send(data);
|
|
19670
19796
|
}
|
|
19671
19797
|
get isConnected() {
|
|
19672
19798
|
return this.connected;
|
|
19673
19799
|
}
|
|
19674
19800
|
doConnect() {
|
|
19675
|
-
if (this.stopped)
|
|
19676
|
-
|
|
19677
|
-
|
|
19801
|
+
if (this.stopped) {
|
|
19802
|
+
log.debug("Local relay: doConnect skipped — relay is stopped");
|
|
19803
|
+
return;
|
|
19804
|
+
}
|
|
19805
|
+
log.info({
|
|
19806
|
+
url: this.options.wsUrl,
|
|
19807
|
+
retryCount: this.retryCount
|
|
19808
|
+
}, "Connecting to local runtime WS...");
|
|
19809
|
+
this.ws = new WebSocket(this.options.wsUrl, { handshakeTimeout: 1e4 });
|
|
19678
19810
|
this.ws.on("open", () => {
|
|
19679
19811
|
log.info("Local runtime WS connected — performing handshake");
|
|
19680
19812
|
this.retryCount = 0;
|
|
@@ -19682,6 +19814,7 @@ var LocalAgentRelay = class {
|
|
|
19682
19814
|
});
|
|
19683
19815
|
this.ws.on("message", (data) => {
|
|
19684
19816
|
const text = Buffer.isBuffer(data) ? data.toString("utf-8") : Buffer.from(data).toString("utf-8");
|
|
19817
|
+
log.debug({ size: text.length }, "Local relay: received message");
|
|
19685
19818
|
this.handleLocalMessage(text);
|
|
19686
19819
|
});
|
|
19687
19820
|
this.ws.on("close", (code, reason) => {
|
|
@@ -19693,10 +19826,14 @@ var LocalAgentRelay = class {
|
|
|
19693
19826
|
this.scheduleReconnect();
|
|
19694
19827
|
});
|
|
19695
19828
|
this.ws.on("error", (err) => {
|
|
19696
|
-
log.
|
|
19829
|
+
log.warn({
|
|
19830
|
+
err: err.message,
|
|
19831
|
+
url: this.options.wsUrl
|
|
19832
|
+
}, "Local runtime WS error");
|
|
19697
19833
|
});
|
|
19698
19834
|
}
|
|
19699
19835
|
performHandshake() {
|
|
19836
|
+
log.debug("Local relay: sending connect handshake");
|
|
19700
19837
|
const connectReq = {
|
|
19701
19838
|
type: "req",
|
|
19702
19839
|
id: randomUUID(),
|
|
@@ -19705,7 +19842,7 @@ var LocalAgentRelay = class {
|
|
|
19705
19842
|
minProtocol: 3,
|
|
19706
19843
|
maxProtocol: 3,
|
|
19707
19844
|
client: {
|
|
19708
|
-
id: "
|
|
19845
|
+
id: "gateway-client",
|
|
19709
19846
|
displayName: "Alfe Daemon Relay",
|
|
19710
19847
|
version: "0.1.0",
|
|
19711
19848
|
platform: process.platform,
|
|
@@ -19783,18 +19920,33 @@ let runtimeProcess = null;
|
|
|
19783
19920
|
let localRelay = null;
|
|
19784
19921
|
let cloudConnected = false;
|
|
19785
19922
|
let shuttingDown = false;
|
|
19923
|
+
/**
|
|
19924
|
+
* Flush pino's async transport and exit.
|
|
19925
|
+
* process.exit() can drop buffered log lines — this ensures they're written first.
|
|
19926
|
+
*/
|
|
19927
|
+
async function flushAndExit(code) {
|
|
19928
|
+
await new Promise((resolve) => {
|
|
19929
|
+
logger$1.flush();
|
|
19930
|
+
setTimeout(resolve, 500);
|
|
19931
|
+
});
|
|
19932
|
+
process.exit(code);
|
|
19933
|
+
}
|
|
19786
19934
|
async function startDaemon() {
|
|
19787
19935
|
startedAt = Date.now();
|
|
19788
19936
|
const managed = isManagedMode();
|
|
19789
|
-
logger$1.info({
|
|
19937
|
+
logger$1.info({
|
|
19938
|
+
managed,
|
|
19939
|
+
pid: process.pid
|
|
19940
|
+
}, "Starting Alfe Gateway Daemon...");
|
|
19790
19941
|
if (!managed) {
|
|
19791
19942
|
await mkdir(join(homedir(), ".alfe"), { recursive: true });
|
|
19792
19943
|
const existingPid = await checkExistingDaemon();
|
|
19793
19944
|
if (existingPid) {
|
|
19794
19945
|
logger$1.error({ pid: existingPid }, "Daemon already running. Use `alfe gateway stop` first.");
|
|
19795
|
-
|
|
19946
|
+
await flushAndExit(1);
|
|
19796
19947
|
}
|
|
19797
19948
|
}
|
|
19949
|
+
logger$1.debug("Loading daemon config...");
|
|
19798
19950
|
try {
|
|
19799
19951
|
config = await loadDaemonConfig();
|
|
19800
19952
|
logger$1.info({
|
|
@@ -19804,11 +19956,17 @@ async function startDaemon() {
|
|
|
19804
19956
|
}, "Config loaded, identity resolved");
|
|
19805
19957
|
} catch (err) {
|
|
19806
19958
|
const message = err instanceof Error ? err.message : String(err);
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19959
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
19960
|
+
logger$1.error({
|
|
19961
|
+
err: message,
|
|
19962
|
+
stack
|
|
19963
|
+
}, "Failed to load config");
|
|
19964
|
+
await flushAndExit(1);
|
|
19965
|
+
}
|
|
19966
|
+
logger$1.debug("Initializing command queue...");
|
|
19810
19967
|
commandQueue = new CommandQueue();
|
|
19811
19968
|
commandQueue.startGC();
|
|
19969
|
+
logger$1.debug("Starting AI proxy...");
|
|
19812
19970
|
try {
|
|
19813
19971
|
const { createProxyServer } = await import("@alfe.ai/ai-proxy-local");
|
|
19814
19972
|
const { getAiServiceUrlFromToken } = await import("@alfe.ai/config");
|
|
@@ -19832,17 +19990,31 @@ async function startDaemon() {
|
|
|
19832
19990
|
});
|
|
19833
19991
|
} catch (err) {
|
|
19834
19992
|
const message = err instanceof Error ? err.message : String(err);
|
|
19835
|
-
|
|
19993
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
19994
|
+
logger$1.error({
|
|
19995
|
+
err: message,
|
|
19996
|
+
stack
|
|
19997
|
+
}, "Failed to start AI proxy — LLM requests will fail");
|
|
19836
19998
|
}
|
|
19999
|
+
logger$1.debug({ socketPath: config.socketPath }, "Starting IPC server...");
|
|
19837
20000
|
ipcServer = new IPCServer(config.socketPath);
|
|
19838
20001
|
ipcServer.setRequestHandler(handlePluginRequest);
|
|
19839
20002
|
try {
|
|
19840
20003
|
await ipcServer.start();
|
|
20004
|
+
logger$1.debug("IPC server started");
|
|
19841
20005
|
} catch (err) {
|
|
19842
20006
|
const message = err instanceof Error ? err.message : String(err);
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
20007
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
20008
|
+
logger$1.error({
|
|
20009
|
+
err: message,
|
|
20010
|
+
stack
|
|
20011
|
+
}, "Failed to start IPC server");
|
|
20012
|
+
await flushAndExit(1);
|
|
20013
|
+
}
|
|
20014
|
+
logger$1.debug({
|
|
20015
|
+
wsUrl: config.gatewayWsUrl,
|
|
20016
|
+
agentId: config.agentId
|
|
20017
|
+
}, "Connecting cloud client...");
|
|
19846
20018
|
cloudClient = new CloudClient({
|
|
19847
20019
|
wsUrl: config.gatewayWsUrl,
|
|
19848
20020
|
apiKey: config.apiKey,
|
|
@@ -19869,9 +20041,16 @@ async function startDaemon() {
|
|
|
19869
20041
|
const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
|
|
19870
20042
|
cloudClient.setIntegrationManager(integrationAdapter);
|
|
19871
20043
|
cloudClient.start();
|
|
20044
|
+
logger$1.debug("Cloud client started");
|
|
19872
20045
|
if (managed && config.runtime) {
|
|
20046
|
+
logger$1.debug({ runtime: config.runtime }, "Starting agent runtime (managed mode)...");
|
|
19873
20047
|
const runtimeCfg = config.runtimes[config.runtime];
|
|
19874
20048
|
if (runtimeCfg) {
|
|
20049
|
+
const runtimeToken = randomUUID();
|
|
20050
|
+
logger$1.debug({
|
|
20051
|
+
runtime: config.runtime,
|
|
20052
|
+
workspace: runtimeCfg.workspace
|
|
20053
|
+
}, "Creating runtime process...");
|
|
19875
20054
|
runtimeProcess = new RuntimeProcess({
|
|
19876
20055
|
runtime: config.runtime,
|
|
19877
20056
|
workspace: runtimeCfg.workspace,
|
|
@@ -19881,13 +20060,17 @@ async function startDaemon() {
|
|
|
19881
20060
|
ALFE_API_URL: config.apiEndpoint,
|
|
19882
20061
|
ALFE_AGENT_ID: config.agentId,
|
|
19883
20062
|
ANTHROPIC_BASE_URL: "http://127.0.0.1:18193",
|
|
19884
|
-
OPENAI_BASE_URL: "http://127.0.0.1:18193"
|
|
20063
|
+
OPENAI_BASE_URL: "http://127.0.0.1:18193",
|
|
20064
|
+
OPENCLAW_GATEWAY_TOKEN: runtimeToken
|
|
19885
20065
|
}
|
|
19886
20066
|
});
|
|
19887
20067
|
runtimeProcess.start();
|
|
20068
|
+
logger$1.debug("Runtime process started");
|
|
20069
|
+
const RUNTIME_WS_PORT = 18789;
|
|
20070
|
+
logger$1.debug({ runtimeWsPort: RUNTIME_WS_PORT }, "Creating local agent relay...");
|
|
19888
20071
|
const relay = new LocalAgentRelay({
|
|
19889
|
-
wsUrl: `ws://127.0.0.1:${String(
|
|
19890
|
-
apiKey:
|
|
20072
|
+
wsUrl: `ws://127.0.0.1:${String(RUNTIME_WS_PORT)}`,
|
|
20073
|
+
apiKey: runtimeToken,
|
|
19891
20074
|
onAgentMessage: (payload) => {
|
|
19892
20075
|
cloudClient.sendAgentEvent(payload);
|
|
19893
20076
|
}
|
|
@@ -19896,6 +20079,7 @@ async function startDaemon() {
|
|
|
19896
20079
|
cloudClient.setServiceRelayHandler((msg) => {
|
|
19897
20080
|
relay.forward(msg.payload);
|
|
19898
20081
|
});
|
|
20082
|
+
logger$1.debug("Scheduling local relay start in 2s (waiting for runtime WS server)...");
|
|
19899
20083
|
setTimeout(() => {
|
|
19900
20084
|
relay.start();
|
|
19901
20085
|
}, 2e3);
|
|
@@ -19906,22 +20090,38 @@ async function startDaemon() {
|
|
|
19906
20090
|
if (shuttingDown) return;
|
|
19907
20091
|
shuttingDown = true;
|
|
19908
20092
|
logger$1.info({ signal }, "Shutting down...");
|
|
19909
|
-
if (localRelay)
|
|
19910
|
-
|
|
19911
|
-
|
|
20093
|
+
if (localRelay) {
|
|
20094
|
+
logger$1.debug("Stopping local relay...");
|
|
20095
|
+
localRelay.stop();
|
|
20096
|
+
logger$1.debug("Local relay stopped");
|
|
20097
|
+
}
|
|
20098
|
+
if (runtimeProcess) {
|
|
20099
|
+
logger$1.debug("Stopping runtime process...");
|
|
20100
|
+
await runtimeProcess.stop();
|
|
20101
|
+
logger$1.debug("Runtime process stopped");
|
|
20102
|
+
}
|
|
20103
|
+
if (ipcServer) {
|
|
20104
|
+
logger$1.debug("Stopping IPC server...");
|
|
20105
|
+
await ipcServer.stop();
|
|
20106
|
+
logger$1.debug("IPC server stopped");
|
|
20107
|
+
}
|
|
19912
20108
|
if (aiProxyServer) {
|
|
20109
|
+
logger$1.debug("Stopping AI proxy...");
|
|
19913
20110
|
const proxy = aiProxyServer;
|
|
19914
20111
|
await new Promise((resolve) => {
|
|
19915
20112
|
proxy.close(() => {
|
|
19916
20113
|
resolve();
|
|
19917
20114
|
});
|
|
19918
20115
|
});
|
|
20116
|
+
logger$1.debug("AI proxy stopped");
|
|
19919
20117
|
}
|
|
20118
|
+
logger$1.debug("Stopping cloud client...");
|
|
19920
20119
|
cloudClient.stop();
|
|
20120
|
+
logger$1.debug("Cloud client stopped");
|
|
19921
20121
|
commandQueue.stopGC();
|
|
19922
20122
|
if (!managed) await removePidFile();
|
|
19923
20123
|
logger$1.info("Daemon stopped");
|
|
19924
|
-
|
|
20124
|
+
await flushAndExit(0);
|
|
19925
20125
|
};
|
|
19926
20126
|
process.on("SIGTERM", () => {
|
|
19927
20127
|
shutdown("SIGTERM");
|
|
@@ -20173,4 +20373,4 @@ function formatDuration(ms) {
|
|
|
20173
20373
|
return `${String(Math.round(seconds / 3600))}h`;
|
|
20174
20374
|
}
|
|
20175
20375
|
//#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 };
|
|
20376
|
+
export { installService as a, PROTOCOL_VERSION as c, SOCKET_PATH as d, fetchAgentConfig as f, logger$1 as g, 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 };
|
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 {
|
|
@@ -132,6 +134,9 @@ declare function queryDaemonHealth(socketPath: string, timeoutMs?: number): Prom
|
|
|
132
134
|
*/
|
|
133
135
|
declare function formatHealthReport(health: DaemonHealth): string;
|
|
134
136
|
//#endregion
|
|
137
|
+
//#region src/logger.d.ts
|
|
138
|
+
declare const logger: pino.Logger<never, boolean>;
|
|
139
|
+
//#endregion
|
|
135
140
|
//#region src/process-manager.d.ts
|
|
136
141
|
/**
|
|
137
142
|
* Process management — launchd/systemd service installation.
|
|
@@ -161,4 +166,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
|
|
|
161
166
|
*/
|
|
162
167
|
declare function stopExistingDaemon(): Promise<boolean>;
|
|
163
168
|
//#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 };
|
|
169
|
+
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, 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 { a as installService, c as PROTOCOL_VERSION, d as SOCKET_PATH, f as fetchAgentConfig, g as logger, 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, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, stopExistingDaemon, uninstallService };
|