@alfe.ai/gateway 0.0.49 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/health.js +555 -30
- package/package.json +7 -5
package/dist/health.js
CHANGED
|
@@ -6,13 +6,15 @@ import { promisify } from "node:util";
|
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import pino from "pino";
|
|
9
|
-
import { chmodSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
|
|
11
11
|
import crypto from "crypto";
|
|
12
12
|
import { parse } from "smol-toml";
|
|
13
13
|
import WebSocket from "ws";
|
|
14
14
|
import { createConnection, createServer } from "node:net";
|
|
15
|
-
import { IntegrationManager, IntegrationManagerAdapter, OpenClawApplier } from "@alfe.ai/integrations";
|
|
15
|
+
import { IntegrationManager, IntegrationManagerAdapter, McpApplier, OpenClawApplier } from "@alfe.ai/integrations";
|
|
16
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
17
|
+
import { Manager, McpBundler, defaultConnect } from "@alfe.ai/mcp-bundler";
|
|
16
18
|
import stream, { Readable } from "stream";
|
|
17
19
|
import util, { format } from "util";
|
|
18
20
|
import http from "http";
|
|
@@ -90,6 +92,9 @@ const ID_PREFIXES = {
|
|
|
90
92
|
identityVerification: "ivf",
|
|
91
93
|
role: "role",
|
|
92
94
|
directGrant: "dgr",
|
|
95
|
+
oauthConnection: "con",
|
|
96
|
+
oauthConnectionRequest: "crq",
|
|
97
|
+
deviceCode: "dvc",
|
|
93
98
|
attachment: "att",
|
|
94
99
|
run: "run",
|
|
95
100
|
request: "req",
|
|
@@ -255,31 +260,41 @@ var AlfeApiClient = class {
|
|
|
255
260
|
this.getToken = options.getToken;
|
|
256
261
|
this.onAuthFailure = options.onAuthFailure;
|
|
257
262
|
}
|
|
258
|
-
/**
|
|
259
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Shared fetch logic — handles auth, 401, and network errors.
|
|
265
|
+
*
|
|
266
|
+
* `skipAuth` callers (public endpoints like OAuth device-code) opt out of
|
|
267
|
+
* the auth-header injection AND the synthetic 401 short-circuit. Without
|
|
268
|
+
* this opt-out, a CLI calling /auth/device-code (no token yet by design)
|
|
269
|
+
* would never reach the network — the `getToken: () => null` path would
|
|
270
|
+
* synthesize a 401 and fire onAuthFailure, breaking the entire flow.
|
|
271
|
+
*/
|
|
272
|
+
async _fetch(path, options, skipAuth = false) {
|
|
260
273
|
try {
|
|
261
|
-
const token = await this.getToken();
|
|
262
|
-
if (!token) {
|
|
263
|
-
this.onAuthFailure?.();
|
|
264
|
-
return {
|
|
265
|
-
ok: false,
|
|
266
|
-
result: {
|
|
267
|
-
ok: false,
|
|
268
|
-
error: "No auth token available",
|
|
269
|
-
status: 401
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
274
|
const url = `${this.apiBaseUrl}${path}`;
|
|
274
275
|
const headers = new Headers(options?.headers);
|
|
275
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
276
276
|
headers.set("Content-Type", "application/json");
|
|
277
277
|
headers.set("x-correlation-id", correlationId());
|
|
278
|
+
if (!skipAuth) {
|
|
279
|
+
const token = await this.getToken();
|
|
280
|
+
if (!token) {
|
|
281
|
+
this.onAuthFailure?.();
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
result: {
|
|
285
|
+
ok: false,
|
|
286
|
+
error: "No auth token available",
|
|
287
|
+
status: 401
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
292
|
+
}
|
|
278
293
|
const res = await fetch(url, {
|
|
279
294
|
...options,
|
|
280
295
|
headers
|
|
281
296
|
});
|
|
282
|
-
if (res.status === 401) {
|
|
297
|
+
if (res.status === 401 && !skipAuth) {
|
|
283
298
|
this.onAuthFailure?.();
|
|
284
299
|
return {
|
|
285
300
|
ok: false,
|
|
@@ -327,6 +342,20 @@ var AlfeApiClient = class {
|
|
|
327
342
|
};
|
|
328
343
|
}
|
|
329
344
|
/**
|
|
345
|
+
* Make a request to a PUBLIC endpoint that does not require authentication.
|
|
346
|
+
* Skips both the Authorization header injection AND the onAuthFailure
|
|
347
|
+
* callback. Use for endpoints like /auth/device-code that the CLI hits
|
|
348
|
+
* before it has a token.
|
|
349
|
+
*/
|
|
350
|
+
async publicRequest(path, options) {
|
|
351
|
+
const result = await this._fetch(path, options, true);
|
|
352
|
+
if (!result.ok) return result.result;
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
data: result.body.data
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
330
359
|
* Make an authenticated request that returns the body directly (no envelope unwrap).
|
|
331
360
|
* Use for APIs that don't use the @auriclabs/api-core response format (e.g. gateway).
|
|
332
361
|
*/
|
|
@@ -368,6 +397,55 @@ var AuthService = class {
|
|
|
368
397
|
deleteToken(tokenId) {
|
|
369
398
|
return this.client.request(`${this.prefix}/tokens/${tokenId}`, { method: "DELETE" });
|
|
370
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Start a device-code flow. Called by the CLI when the user runs
|
|
402
|
+
* `alfe login` and picks the browser path. The returned `device_code`
|
|
403
|
+
* is the CLI's bearer secret for polling /auth/device-token; the
|
|
404
|
+
* `user_code` is what the user types/sees on the dashboard.
|
|
405
|
+
*
|
|
406
|
+
* Uses `publicRequest` because the CLI has no token yet — the standard
|
|
407
|
+
* `request` path would short-circuit with a synthetic 401.
|
|
408
|
+
*/
|
|
409
|
+
startDeviceCode(input) {
|
|
410
|
+
return this.client.publicRequest(`${this.prefix}/device-code`, {
|
|
411
|
+
method: "POST",
|
|
412
|
+
body: JSON.stringify(input)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Poll for the minted API key. Public endpoint. Returns:
|
|
417
|
+
* 200 → { api_key, tenantId, tokenExpiresAt } (success — write to config)
|
|
418
|
+
* 428 → still pending (caller should sleep `interval` and retry)
|
|
419
|
+
* 429 → polling too fast; caller should back off
|
|
420
|
+
* 410 → device-code expired; user must run `alfe login` again
|
|
421
|
+
* 400 → already redeemed (security: do not retry)
|
|
422
|
+
*
|
|
423
|
+
* Surface the HTTP-status code via the standard ApiResult error shape
|
|
424
|
+
* so callers can branch.
|
|
425
|
+
*/
|
|
426
|
+
pollDeviceToken(deviceCode) {
|
|
427
|
+
return this.client.publicRequest(`${this.prefix}/device-token`, {
|
|
428
|
+
method: "POST",
|
|
429
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/** Dashboard-side. Fetch the device fingerprint for an approval card. */
|
|
433
|
+
lookupDeviceCode(userCode) {
|
|
434
|
+
return this.client.request(`${this.prefix}/device-code/lookup`, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
body: JSON.stringify({ user_code: userCode })
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Dashboard-side. Approve the pending device-code; CLI's next poll gets the key. */
|
|
440
|
+
approveDeviceCode(input) {
|
|
441
|
+
return this.client.request(`${this.prefix}/device-code/approve`, {
|
|
442
|
+
method: "POST",
|
|
443
|
+
body: JSON.stringify({
|
|
444
|
+
user_code: input.userCode,
|
|
445
|
+
expiresIn: input.expiresIn
|
|
446
|
+
})
|
|
447
|
+
});
|
|
448
|
+
}
|
|
371
449
|
getOnboardingStatus() {
|
|
372
450
|
return this.client.request(`${this.prefix}/onboarding/status`);
|
|
373
451
|
}
|
|
@@ -421,6 +499,48 @@ var AuthService = class {
|
|
|
421
499
|
body: JSON.stringify({ seats })
|
|
422
500
|
});
|
|
423
501
|
}
|
|
502
|
+
listOrgMembers() {
|
|
503
|
+
return this.client.request(`${this.prefix}/org/members`);
|
|
504
|
+
}
|
|
505
|
+
patchOrgMember(userId, body) {
|
|
506
|
+
return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, {
|
|
507
|
+
method: "PATCH",
|
|
508
|
+
body: JSON.stringify(body)
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
removeOrgMember(userId) {
|
|
512
|
+
return this.client.request(`${this.prefix}/org/members/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
513
|
+
}
|
|
514
|
+
listOrgInvitations() {
|
|
515
|
+
return this.client.request(`${this.prefix}/org/invitations`);
|
|
516
|
+
}
|
|
517
|
+
createOrgInvitation(input) {
|
|
518
|
+
return this.client.request(`${this.prefix}/org/invitations`, {
|
|
519
|
+
method: "POST",
|
|
520
|
+
body: JSON.stringify(input)
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
revokeOrgInvitation(invitationId) {
|
|
524
|
+
return this.client.request(`${this.prefix}/org/invitations/${encodeURIComponent(invitationId)}`, { method: "DELETE" });
|
|
525
|
+
}
|
|
526
|
+
listOrgDomains() {
|
|
527
|
+
return this.client.request(`${this.prefix}/org/domains`);
|
|
528
|
+
}
|
|
529
|
+
createOrgDomain(input) {
|
|
530
|
+
return this.client.request(`${this.prefix}/org/domains`, {
|
|
531
|
+
method: "POST",
|
|
532
|
+
body: JSON.stringify(input)
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
deleteOrgDomain(domainId) {
|
|
536
|
+
return this.client.request(`${this.prefix}/org/domains/${encodeURIComponent(domainId)}`, { method: "DELETE" });
|
|
537
|
+
}
|
|
538
|
+
updateOrgSettings(input) {
|
|
539
|
+
return this.client.request(`${this.prefix}/org/settings`, {
|
|
540
|
+
method: "PATCH",
|
|
541
|
+
body: JSON.stringify(input)
|
|
542
|
+
});
|
|
543
|
+
}
|
|
424
544
|
};
|
|
425
545
|
//#endregion
|
|
426
546
|
//#region ../../packages-internal/api-client/dist/services/integrations.js
|
|
@@ -536,12 +656,6 @@ var IntegrationsService = class {
|
|
|
536
656
|
const query = email ? `?email=${encodeURIComponent(email)}` : "";
|
|
537
657
|
return this.client.request(`/google/agents/${encodeURIComponent(agentId)}/account${query}`, { method: "DELETE" });
|
|
538
658
|
}
|
|
539
|
-
setDefaultGoogleAccount(agentId, email) {
|
|
540
|
-
return this.client.request(`/google/agents/${encodeURIComponent(agentId)}/account/default`, {
|
|
541
|
-
method: "PUT",
|
|
542
|
-
body: JSON.stringify({ email })
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
659
|
getAtlassianSites(agentId) {
|
|
546
660
|
return this.client.request(`/atlassian/agents/${encodeURIComponent(agentId)}/sites`);
|
|
547
661
|
}
|
|
@@ -4236,7 +4350,9 @@ enumValues({
|
|
|
4236
4350
|
Zhipu: "zhipu",
|
|
4237
4351
|
OpenRouter: "openrouter",
|
|
4238
4352
|
ElevenLabs: "elevenlabs",
|
|
4239
|
-
Twilio: "twilio"
|
|
4353
|
+
Twilio: "twilio",
|
|
4354
|
+
ClaudeMax: "claude-max",
|
|
4355
|
+
OpenAICodexMax: "openai-codex-max"
|
|
4240
4356
|
});
|
|
4241
4357
|
enumValues({
|
|
4242
4358
|
Month: "month",
|
|
@@ -4452,6 +4568,7 @@ const GoogleModel = {
|
|
|
4452
4568
|
const GOOGLE_MODELS = enumValues(GoogleModel);
|
|
4453
4569
|
const MiniMaxModel = {
|
|
4454
4570
|
M27: "MiniMax-M2.7",
|
|
4571
|
+
M27HighSpeed: "MiniMax-M2.7-highspeed",
|
|
4455
4572
|
M25: "MiniMax-M2.5"
|
|
4456
4573
|
};
|
|
4457
4574
|
const MINIMAX_MODELS = enumValues(MiniMaxModel);
|
|
@@ -4491,8 +4608,8 @@ _enum(MISTRAL_MODELS);
|
|
|
4491
4608
|
_enum(XAI_MODELS);
|
|
4492
4609
|
_enum(ZHIPU_MODELS);
|
|
4493
4610
|
string().min(1);
|
|
4494
|
-
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M27, MiniMaxModel.M25, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
|
|
4495
|
-
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M27, MiniMaxModel.M25, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
|
|
4611
|
+
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
|
|
4612
|
+
AnthropicModel.Opus, AnthropicModel.Sonnet, AnthropicModel.Haiku, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MistralModel.Large, MistralModel.Small, MistralModel.Codestral, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM51, ZhipuModel.GLM51Air;
|
|
4496
4613
|
enumValues({
|
|
4497
4614
|
PendingChallenge: "pending_challenge",
|
|
4498
4615
|
Creating: "creating",
|
|
@@ -4799,7 +4916,7 @@ async function loadDaemonConfig() {
|
|
|
4799
4916
|
orgId: identity.orgId,
|
|
4800
4917
|
runtime: identity.runtime,
|
|
4801
4918
|
runtimes,
|
|
4802
|
-
autoStartRuntime: alfeConfig.auto_start_runtime ??
|
|
4919
|
+
autoStartRuntime: alfeConfig.auto_start_runtime ?? true
|
|
4803
4920
|
};
|
|
4804
4921
|
}
|
|
4805
4922
|
/**
|
|
@@ -21659,7 +21776,7 @@ var CommandRegistry = class {
|
|
|
21659
21776
|
* the gateway when needed and uses `callerScopes: ["operator.admin"]`.
|
|
21660
21777
|
* 3. Skip iteration if `pending.json` mtime hasn't changed (cheap fast path)
|
|
21661
21778
|
*/
|
|
21662
|
-
const execFileAsync$
|
|
21779
|
+
const execFileAsync$2 = promisify(execFile);
|
|
21663
21780
|
const APPROVE_TIMEOUT_MS = 1e4;
|
|
21664
21781
|
function resolveStateDir(override) {
|
|
21665
21782
|
if (override) return override;
|
|
@@ -21757,7 +21874,7 @@ function startPairingApprovalPoller(opts) {
|
|
|
21757
21874
|
const stateDir = resolveStateDir(opts.stateDir);
|
|
21758
21875
|
const intervalMs = opts.intervalMs ?? 3e4;
|
|
21759
21876
|
const exec = opts.exec ?? (async (file, args, { timeout }) => {
|
|
21760
|
-
const { stdout, stderr } = await execFileAsync$
|
|
21877
|
+
const { stdout, stderr } = await execFileAsync$2(file, args, { timeout });
|
|
21761
21878
|
return {
|
|
21762
21879
|
stdout,
|
|
21763
21880
|
stderr
|
|
@@ -21799,6 +21916,128 @@ function startPairingApprovalPoller(opts) {
|
|
|
21799
21916
|
};
|
|
21800
21917
|
}
|
|
21801
21918
|
//#endregion
|
|
21919
|
+
//#region src/openclaw-mcp-cleanup.ts
|
|
21920
|
+
/**
|
|
21921
|
+
* One-shot startup migration that drops stale `mcp.servers.*` entries
|
|
21922
|
+
* from openclaw.json on agents set up before the single-source-of-truth
|
|
21923
|
+
* refactor. Pre-cutover agents had two writers:
|
|
21924
|
+
*
|
|
21925
|
+
* - The old `post_activate.mjs` hook wrote `mcp.servers.<integrationId>`
|
|
21926
|
+
* directly (legacy style — e.g. QA Tester's `mcp.servers.atlassian`).
|
|
21927
|
+
* - The bundler manager mirror-wrote `mcp.servers.<integrationId>-<serverId>`
|
|
21928
|
+
* for entries it owned (newer style).
|
|
21929
|
+
*
|
|
21930
|
+
* Post-cutover the alfe store owns everything and the openclaw.json
|
|
21931
|
+
* mirror is gone. If the legacy entries linger they cause:
|
|
21932
|
+
* 1. claude-cli / codex-cli to native-spawn from openclaw.json AND the
|
|
21933
|
+
* daemon's bundler to spawn from the store → two mcp-atlassian
|
|
21934
|
+
* children, the second one likely missing credentials.
|
|
21935
|
+
* 2. The openclaw-mcp-bundler plugin's `hasNativeOpenclawMcpServers`
|
|
21936
|
+
* check returns true (mcp.servers non-empty) → plugin no-ops on
|
|
21937
|
+
* every backend → MiniMax-backed agents lose tools entirely.
|
|
21938
|
+
*
|
|
21939
|
+
* Strategy: on first daemon start after the upgrade, drop every key in
|
|
21940
|
+
* openclaw.json#mcp.servers that the alfe store ALSO holds (direct id
|
|
21941
|
+
* match) OR that maps to an alfe-store entry by integration owner
|
|
21942
|
+
* (catches the legacy `mcp.servers.<integrationId>` shape where the
|
|
21943
|
+
* new store uses `<integrationId>-<serverId>`). User-added private MCPs
|
|
21944
|
+
* (e.g. `mcp.servers.my-private-thing` with no matching alfe entry) are
|
|
21945
|
+
* preserved.
|
|
21946
|
+
*
|
|
21947
|
+
* Sentinel-gated at `~/.alfe/.openclaw-mirror-migrated` — runs exactly
|
|
21948
|
+
* once per agent.
|
|
21949
|
+
*/
|
|
21950
|
+
const execFileAsync$1 = promisify(execFile);
|
|
21951
|
+
const DEFAULT_SENTINEL_PATH = join(homedir(), ".alfe", ".openclaw-mirror-migrated");
|
|
21952
|
+
const defaultOpenclaw = {
|
|
21953
|
+
async listServers() {
|
|
21954
|
+
try {
|
|
21955
|
+
const { stdout } = await execFileAsync$1("openclaw", [
|
|
21956
|
+
"config",
|
|
21957
|
+
"get",
|
|
21958
|
+
"mcp.servers"
|
|
21959
|
+
], { timeout: 1e4 });
|
|
21960
|
+
const trimmed = stdout.trim();
|
|
21961
|
+
if (!trimmed) return [];
|
|
21962
|
+
const parsed = JSON.parse(trimmed);
|
|
21963
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
21964
|
+
return Object.keys(parsed);
|
|
21965
|
+
} catch (err) {
|
|
21966
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21967
|
+
if (/not\s+set|not\s+found|undefined|no such key/i.test(message)) return [];
|
|
21968
|
+
throw err;
|
|
21969
|
+
}
|
|
21970
|
+
},
|
|
21971
|
+
async unsetServer(key) {
|
|
21972
|
+
await execFileAsync$1("openclaw", [
|
|
21973
|
+
"config",
|
|
21974
|
+
"unset",
|
|
21975
|
+
`mcp.servers.${key}`
|
|
21976
|
+
], { timeout: 1e4 });
|
|
21977
|
+
}
|
|
21978
|
+
};
|
|
21979
|
+
/**
|
|
21980
|
+
* Run the cleanup. Idempotent — second call after the sentinel lands
|
|
21981
|
+
* returns immediately. Errors are caught and logged; the daemon must
|
|
21982
|
+
* not abort startup over a best-effort migration.
|
|
21983
|
+
*/
|
|
21984
|
+
async function runOpenclawMcpCleanup(opts) {
|
|
21985
|
+
const sentinelPath = opts.sentinelPath ?? DEFAULT_SENTINEL_PATH;
|
|
21986
|
+
const openclaw = opts.openclaw ?? defaultOpenclaw;
|
|
21987
|
+
if (existsSync(sentinelPath)) {
|
|
21988
|
+
opts.logger.debug({ sentinel: sentinelPath }, "openclaw.json mcp.servers cleanup already ran — skipping");
|
|
21989
|
+
return;
|
|
21990
|
+
}
|
|
21991
|
+
let cleanupErr;
|
|
21992
|
+
try {
|
|
21993
|
+
const existing = await openclaw.listServers();
|
|
21994
|
+
if (existing.length === 0) {
|
|
21995
|
+
opts.logger.debug({}, "openclaw.json#mcp.servers is empty — nothing to clean up");
|
|
21996
|
+
writeSentinel(sentinelPath);
|
|
21997
|
+
return;
|
|
21998
|
+
}
|
|
21999
|
+
const stored = opts.manager.listServers();
|
|
22000
|
+
const storeIds = new Set(stored.map((s) => s.id));
|
|
22001
|
+
const integrationOwners = /* @__PURE__ */ new Set();
|
|
22002
|
+
for (const s of stored) if (s.entry.owner.startsWith("integration:")) integrationOwners.add(s.entry.owner.slice(12));
|
|
22003
|
+
const toUnset = [];
|
|
22004
|
+
for (const key of existing) {
|
|
22005
|
+
if (storeIds.has(key)) {
|
|
22006
|
+
toUnset.push(key);
|
|
22007
|
+
continue;
|
|
22008
|
+
}
|
|
22009
|
+
if (integrationOwners.has(key)) toUnset.push(key);
|
|
22010
|
+
}
|
|
22011
|
+
if (toUnset.length === 0) {
|
|
22012
|
+
opts.logger.info({ existingKeys: existing }, "openclaw.json#mcp.servers has entries but none match alfe-managed integrations — preserving as user customizations");
|
|
22013
|
+
writeSentinel(sentinelPath);
|
|
22014
|
+
return;
|
|
22015
|
+
}
|
|
22016
|
+
for (const key of toUnset) try {
|
|
22017
|
+
await openclaw.unsetServer(key);
|
|
22018
|
+
opts.logger.info({ key }, "Cleaned up stale openclaw.json#mcp.servers entry");
|
|
22019
|
+
} catch (err) {
|
|
22020
|
+
opts.logger.warn({
|
|
22021
|
+
key,
|
|
22022
|
+
err: err instanceof Error ? err.message : String(err)
|
|
22023
|
+
}, "Failed to unset stale mcp.servers entry — continuing");
|
|
22024
|
+
}
|
|
22025
|
+
} catch (err) {
|
|
22026
|
+
cleanupErr = err;
|
|
22027
|
+
opts.logger.warn({ err: err instanceof Error ? err.message : String(err) }, "openclaw.json#mcp.servers cleanup hit an error — leaving sentinel un-touched so next start retries");
|
|
22028
|
+
}
|
|
22029
|
+
if (cleanupErr === void 0) writeSentinel(sentinelPath);
|
|
22030
|
+
}
|
|
22031
|
+
function writeSentinel(path) {
|
|
22032
|
+
try {
|
|
22033
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
22034
|
+
writeFileSync(path, `${(/* @__PURE__ */ new Date()).toISOString()}\n`, {
|
|
22035
|
+
encoding: "utf8",
|
|
22036
|
+
mode: 384
|
|
22037
|
+
});
|
|
22038
|
+
} catch {}
|
|
22039
|
+
}
|
|
22040
|
+
//#endregion
|
|
21802
22041
|
//#region src/daemon.ts
|
|
21803
22042
|
/**
|
|
21804
22043
|
* Alfe Gateway Daemon — main entry point.
|
|
@@ -21821,6 +22060,8 @@ let ipcServer = null;
|
|
|
21821
22060
|
let commandQueue;
|
|
21822
22061
|
let startedAt;
|
|
21823
22062
|
let integrationManager;
|
|
22063
|
+
let mcpBundler = null;
|
|
22064
|
+
let mcpManagerRef = null;
|
|
21824
22065
|
let aiProxyServer = null;
|
|
21825
22066
|
let runtimeProcess = null;
|
|
21826
22067
|
let aiProxyUrl = null;
|
|
@@ -21841,6 +22082,30 @@ let stopPairingApprovalPoller = null;
|
|
|
21841
22082
|
* (works when systemd runs the gateway binary directly, since
|
|
21842
22083
|
* the path is .../cli/node_modules/@alfe.ai/gateway/dist/...)
|
|
21843
22084
|
*/
|
|
22085
|
+
/**
|
|
22086
|
+
* Adapter from `AgentApiClient`'s per-provider methods to the
|
|
22087
|
+
* `CredentialsResolver` shape the MCP applier expects. Returns
|
|
22088
|
+
* `undefined` for unknown providers and on 404/network error so the
|
|
22089
|
+
* applier can skip registration silently (its documented contract).
|
|
22090
|
+
*/
|
|
22091
|
+
async function fetchProviderCredentials(agentApi, provider) {
|
|
22092
|
+
const key = provider.toLowerCase();
|
|
22093
|
+
try {
|
|
22094
|
+
switch (key) {
|
|
22095
|
+
case "atlassian": return await agentApi.getAtlassianCredentials();
|
|
22096
|
+
case "github": return await agentApi.getGithubCredentials();
|
|
22097
|
+
case "xero": return await agentApi.getXeroCredentials();
|
|
22098
|
+
case "notion": return await agentApi.getNotionCredentials();
|
|
22099
|
+
case "myob": return await agentApi.getMYOBCredentials();
|
|
22100
|
+
case "google": return await agentApi.getGoogleCredentials();
|
|
22101
|
+
default:
|
|
22102
|
+
logger$1.warn({ provider }, "Unknown OAuth provider for requires_credentials — MCP server will be skipped");
|
|
22103
|
+
return;
|
|
22104
|
+
}
|
|
22105
|
+
} catch {
|
|
22106
|
+
return;
|
|
22107
|
+
}
|
|
22108
|
+
}
|
|
21844
22109
|
async function getCliVersion() {
|
|
21845
22110
|
if (process.env.ALFE_CLI_VERSION) return process.env.ALFE_CLI_VERSION;
|
|
21846
22111
|
try {
|
|
@@ -22056,8 +22321,44 @@ async function startDaemon() {
|
|
|
22056
22321
|
apiBaseUrl: config.apiEndpoint,
|
|
22057
22322
|
getToken: () => Promise.resolve(config.apiKey)
|
|
22058
22323
|
}));
|
|
22324
|
+
const agentApi = new AgentApiClient({
|
|
22325
|
+
apiKey: config.apiKey,
|
|
22326
|
+
apiUrl: config.apiEndpoint
|
|
22327
|
+
});
|
|
22328
|
+
const mcpManager = new Manager({ logger: logger$1 });
|
|
22329
|
+
mcpManagerRef = mcpManager;
|
|
22330
|
+
mcpBundler = new McpBundler({
|
|
22331
|
+
logger: logger$1,
|
|
22332
|
+
idleTtlMs: 0
|
|
22333
|
+
}, { connect: defaultConnect });
|
|
22334
|
+
await mcpManager.loadIntoBundler(mcpBundler);
|
|
22335
|
+
const warmBundler = (reason) => {
|
|
22336
|
+
if (!mcpBundler) return;
|
|
22337
|
+
mcpBundler.warmup().catch((err) => {
|
|
22338
|
+
logger$1.warn({
|
|
22339
|
+
err: err instanceof Error ? err.message : String(err),
|
|
22340
|
+
reason
|
|
22341
|
+
}, "MCP bundler warmup failed");
|
|
22342
|
+
});
|
|
22343
|
+
};
|
|
22344
|
+
warmBundler("startup");
|
|
22345
|
+
mcpManager.onChange(() => {
|
|
22346
|
+
warmBundler("store change");
|
|
22347
|
+
});
|
|
22348
|
+
logger$1.info("MCP bundler attached to manager — warming children");
|
|
22349
|
+
await runOpenclawMcpCleanup({
|
|
22350
|
+
manager: mcpManager,
|
|
22351
|
+
logger: logger$1
|
|
22352
|
+
}).catch((err) => {
|
|
22353
|
+
logger$1.warn({ err: err instanceof Error ? err.message : String(err) }, "openclaw.json mcp.servers cleanup threw — startup continues");
|
|
22354
|
+
});
|
|
22059
22355
|
integrationManager = new IntegrationManager({
|
|
22060
22356
|
runtimeAppliers,
|
|
22357
|
+
mcpApplier: new McpApplier({
|
|
22358
|
+
manager: mcpManager,
|
|
22359
|
+
credentials: { getCredentials: (provider) => fetchProviderCredentials(agentApi, provider) },
|
|
22360
|
+
platform: { apiUrl: config.apiEndpoint }
|
|
22361
|
+
}),
|
|
22061
22362
|
registryFetcher: async () => {
|
|
22062
22363
|
const result = await integrationsService.getRegistry();
|
|
22063
22364
|
if (!result.ok) throw new Error(result.error);
|
|
@@ -22091,6 +22392,7 @@ async function startDaemon() {
|
|
|
22091
22392
|
});
|
|
22092
22393
|
cloudClient.start();
|
|
22093
22394
|
logger$1.debug("Cloud client started");
|
|
22395
|
+
if (!config.autoStartRuntime && config.runtime) logger$1.warn({ runtime: config.runtime }, "Runtime configured but auto_start_runtime=false — runtime will not be spawned; in-runtime plugins (chat/console/sync) will be offline until you start it manually");
|
|
22094
22396
|
if (config.autoStartRuntime && config.runtime) {
|
|
22095
22397
|
logger$1.debug({ runtime: config.runtime }, "Starting agent runtime...");
|
|
22096
22398
|
const runtimeCfg = config.runtimes[config.runtime];
|
|
@@ -22127,6 +22429,16 @@ async function startDaemon() {
|
|
|
22127
22429
|
await runtimeProcess.stop();
|
|
22128
22430
|
logger$1.debug("Runtime process stopped");
|
|
22129
22431
|
}
|
|
22432
|
+
if (mcpBundler) {
|
|
22433
|
+
logger$1.debug("Stopping MCP bundler...");
|
|
22434
|
+
await mcpBundler.dispose();
|
|
22435
|
+
mcpBundler = null;
|
|
22436
|
+
logger$1.debug("MCP bundler stopped");
|
|
22437
|
+
}
|
|
22438
|
+
logger$1.debug("Stopping MCP bundler manager...");
|
|
22439
|
+
await mcpManager.dispose();
|
|
22440
|
+
mcpManagerRef = null;
|
|
22441
|
+
logger$1.debug("MCP bundler manager stopped");
|
|
22130
22442
|
if (ipcServer) {
|
|
22131
22443
|
logger$1.debug("Stopping IPC server...");
|
|
22132
22444
|
await ipcServer.stop();
|
|
@@ -22444,6 +22756,11 @@ function handlePluginRequest(method, params, pluginId) {
|
|
|
22444
22756
|
case "status": return Promise.resolve(handleStatus());
|
|
22445
22757
|
case "integration.list": return Promise.resolve(handleIntegrationList());
|
|
22446
22758
|
case "integration.report": return Promise.resolve(handleIntegrationReport(params, pluginId));
|
|
22759
|
+
case "mcp.list_tools": return Promise.resolve(handleMcpListTools(mcpBundler));
|
|
22760
|
+
case "mcp.call_tool": return handleMcpCallTool(mcpBundler, params);
|
|
22761
|
+
case "mcp.list_servers": return Promise.resolve(handleMcpListServers());
|
|
22762
|
+
case "mcp.add_server": return handleMcpAddServer(params);
|
|
22763
|
+
case "mcp.remove_server": return handleMcpRemoveServer(params);
|
|
22447
22764
|
default: return Promise.resolve({
|
|
22448
22765
|
ok: false,
|
|
22449
22766
|
error: {
|
|
@@ -22486,6 +22803,214 @@ function handleIntegrationList() {
|
|
|
22486
22803
|
payload: { integrations: integrationManager.list() }
|
|
22487
22804
|
};
|
|
22488
22805
|
}
|
|
22806
|
+
/**
|
|
22807
|
+
* Return the daemon-hosted MCP bundler's current namespaced tool catalog.
|
|
22808
|
+
* Called by the openclaw-mcp-bundler plugin on every tool-factory invocation;
|
|
22809
|
+
* cheap (in-memory snapshot, no I/O).
|
|
22810
|
+
*/
|
|
22811
|
+
function handleMcpListTools(bundler) {
|
|
22812
|
+
if (!bundler) return {
|
|
22813
|
+
ok: false,
|
|
22814
|
+
error: {
|
|
22815
|
+
code: "MCP_BUNDLER_UNAVAILABLE",
|
|
22816
|
+
message: "MCP bundler not initialized"
|
|
22817
|
+
}
|
|
22818
|
+
};
|
|
22819
|
+
return {
|
|
22820
|
+
ok: true,
|
|
22821
|
+
payload: { tools: bundler.listTools() }
|
|
22822
|
+
};
|
|
22823
|
+
}
|
|
22824
|
+
/**
|
|
22825
|
+
* Route a tool call to the appropriate MCP child via the daemon-hosted
|
|
22826
|
+
* bundler. `name` is the prefixed (`mcp__<server>__<tool>`) name; args is
|
|
22827
|
+
* the raw JSON object the LLM produced.
|
|
22828
|
+
*/
|
|
22829
|
+
/**
|
|
22830
|
+
* List every server entry in the alfe bundler store. Lets agents inspect
|
|
22831
|
+
* what they've already registered before adding a new one.
|
|
22832
|
+
*/
|
|
22833
|
+
function handleMcpListServers(manager = mcpManagerRef) {
|
|
22834
|
+
if (!manager) return {
|
|
22835
|
+
ok: false,
|
|
22836
|
+
error: {
|
|
22837
|
+
code: "MCP_MANAGER_UNAVAILABLE",
|
|
22838
|
+
message: "MCP manager not initialized"
|
|
22839
|
+
}
|
|
22840
|
+
};
|
|
22841
|
+
return {
|
|
22842
|
+
ok: true,
|
|
22843
|
+
payload: { servers: manager.listServers() }
|
|
22844
|
+
};
|
|
22845
|
+
}
|
|
22846
|
+
/**
|
|
22847
|
+
* Register a new MCP server in the alfe bundler store on behalf of the
|
|
22848
|
+
* agent. Owned as `'manual'` so the agent can later remove it without
|
|
22849
|
+
* an expectedOwner conflict — matches what `alfe mcp add` does from the
|
|
22850
|
+
* CLI. The daemon's bundler will pick the change up via the manager's
|
|
22851
|
+
* store watcher and spawn the child on the next tool-factory call.
|
|
22852
|
+
*/
|
|
22853
|
+
async function handleMcpAddServer(params, manager = mcpManagerRef) {
|
|
22854
|
+
if (!manager) return {
|
|
22855
|
+
ok: false,
|
|
22856
|
+
error: {
|
|
22857
|
+
code: "MCP_MANAGER_UNAVAILABLE",
|
|
22858
|
+
message: "MCP manager not initialized"
|
|
22859
|
+
}
|
|
22860
|
+
};
|
|
22861
|
+
const p = params;
|
|
22862
|
+
if (typeof p.id !== "string" || p.id.length === 0) return {
|
|
22863
|
+
ok: false,
|
|
22864
|
+
error: {
|
|
22865
|
+
code: "INVALID_PARAMS",
|
|
22866
|
+
message: "id is required (string)"
|
|
22867
|
+
}
|
|
22868
|
+
};
|
|
22869
|
+
const config = buildServerConfig(p);
|
|
22870
|
+
if (!config) return {
|
|
22871
|
+
ok: false,
|
|
22872
|
+
error: {
|
|
22873
|
+
code: "INVALID_PARAMS",
|
|
22874
|
+
message: "expected either { command, args?, env?, cwd? } for stdio or { url, transport, headers? } for remote"
|
|
22875
|
+
}
|
|
22876
|
+
};
|
|
22877
|
+
try {
|
|
22878
|
+
await manager.addServer(config, {
|
|
22879
|
+
id: p.id,
|
|
22880
|
+
owner: "manual"
|
|
22881
|
+
});
|
|
22882
|
+
return {
|
|
22883
|
+
ok: true,
|
|
22884
|
+
payload: { id: p.id }
|
|
22885
|
+
};
|
|
22886
|
+
} catch (err) {
|
|
22887
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22888
|
+
logger$1.warn({
|
|
22889
|
+
id: p.id,
|
|
22890
|
+
err: message
|
|
22891
|
+
}, "mcp.add_server failed");
|
|
22892
|
+
return {
|
|
22893
|
+
ok: false,
|
|
22894
|
+
error: {
|
|
22895
|
+
code: "MCP_ADD_FAILED",
|
|
22896
|
+
message
|
|
22897
|
+
}
|
|
22898
|
+
};
|
|
22899
|
+
}
|
|
22900
|
+
}
|
|
22901
|
+
function buildServerConfig(p) {
|
|
22902
|
+
if (typeof p.command === "string" && p.command.length > 0) {
|
|
22903
|
+
const cfg = { command: p.command };
|
|
22904
|
+
if (Array.isArray(p.args) && p.args.every((a) => typeof a === "string")) cfg.args = p.args;
|
|
22905
|
+
if (p.env && typeof p.env === "object" && !Array.isArray(p.env)) {
|
|
22906
|
+
const env = {};
|
|
22907
|
+
for (const [k, v] of Object.entries(p.env)) if (typeof v === "string") env[k] = v;
|
|
22908
|
+
cfg.env = env;
|
|
22909
|
+
}
|
|
22910
|
+
if (typeof p.cwd === "string") cfg.cwd = p.cwd;
|
|
22911
|
+
return cfg;
|
|
22912
|
+
}
|
|
22913
|
+
if (typeof p.url === "string" && p.url.length > 0) {
|
|
22914
|
+
const transport = p.transport === "streamable-http" ? "streamable-http" : "sse";
|
|
22915
|
+
const cfg = {
|
|
22916
|
+
url: p.url,
|
|
22917
|
+
transport
|
|
22918
|
+
};
|
|
22919
|
+
if (p.headers && typeof p.headers === "object" && !Array.isArray(p.headers)) {
|
|
22920
|
+
const headers = {};
|
|
22921
|
+
for (const [k, v] of Object.entries(p.headers)) if (typeof v === "string") headers[k] = v;
|
|
22922
|
+
cfg.headers = headers;
|
|
22923
|
+
}
|
|
22924
|
+
return cfg;
|
|
22925
|
+
}
|
|
22926
|
+
return null;
|
|
22927
|
+
}
|
|
22928
|
+
/**
|
|
22929
|
+
* Drop a server entry the agent previously registered. Restricted to
|
|
22930
|
+
* `manual`-owned entries so the agent can't accidentally clobber
|
|
22931
|
+
* integration-installed or CLI-installed servers (the daemon owns
|
|
22932
|
+
* those; the agent can ask the user to uninstall an integration via
|
|
22933
|
+
* the dashboard).
|
|
22934
|
+
*/
|
|
22935
|
+
async function handleMcpRemoveServer(params, manager = mcpManagerRef) {
|
|
22936
|
+
if (!manager) return {
|
|
22937
|
+
ok: false,
|
|
22938
|
+
error: {
|
|
22939
|
+
code: "MCP_MANAGER_UNAVAILABLE",
|
|
22940
|
+
message: "MCP manager not initialized"
|
|
22941
|
+
}
|
|
22942
|
+
};
|
|
22943
|
+
const { id } = params;
|
|
22944
|
+
if (typeof id !== "string" || id.length === 0) return {
|
|
22945
|
+
ok: false,
|
|
22946
|
+
error: {
|
|
22947
|
+
code: "INVALID_PARAMS",
|
|
22948
|
+
message: "id is required (string)"
|
|
22949
|
+
}
|
|
22950
|
+
};
|
|
22951
|
+
try {
|
|
22952
|
+
return {
|
|
22953
|
+
ok: true,
|
|
22954
|
+
payload: { removed: await manager.removeServer(id, { expectedOwner: "manual" }) }
|
|
22955
|
+
};
|
|
22956
|
+
} catch (err) {
|
|
22957
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22958
|
+
if (message.includes("owned by")) return {
|
|
22959
|
+
ok: false,
|
|
22960
|
+
error: {
|
|
22961
|
+
code: "MCP_OWNER_MISMATCH",
|
|
22962
|
+
message
|
|
22963
|
+
}
|
|
22964
|
+
};
|
|
22965
|
+
logger$1.warn({
|
|
22966
|
+
id,
|
|
22967
|
+
err: message
|
|
22968
|
+
}, "mcp.remove_server failed");
|
|
22969
|
+
return {
|
|
22970
|
+
ok: false,
|
|
22971
|
+
error: {
|
|
22972
|
+
code: "MCP_REMOVE_FAILED",
|
|
22973
|
+
message
|
|
22974
|
+
}
|
|
22975
|
+
};
|
|
22976
|
+
}
|
|
22977
|
+
}
|
|
22978
|
+
async function handleMcpCallTool(bundler, params) {
|
|
22979
|
+
if (!bundler) return {
|
|
22980
|
+
ok: false,
|
|
22981
|
+
error: {
|
|
22982
|
+
code: "MCP_BUNDLER_UNAVAILABLE",
|
|
22983
|
+
message: "MCP bundler not initialized"
|
|
22984
|
+
}
|
|
22985
|
+
};
|
|
22986
|
+
const { name, args } = params;
|
|
22987
|
+
if (typeof name !== "string" || name.length === 0) return {
|
|
22988
|
+
ok: false,
|
|
22989
|
+
error: {
|
|
22990
|
+
code: "INVALID_PARAMS",
|
|
22991
|
+
message: "name is required (string)"
|
|
22992
|
+
}
|
|
22993
|
+
};
|
|
22994
|
+
try {
|
|
22995
|
+
return {
|
|
22996
|
+
ok: true,
|
|
22997
|
+
payload: await bundler.callTool(name, args)
|
|
22998
|
+
};
|
|
22999
|
+
} catch (err) {
|
|
23000
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23001
|
+
logger$1.warn({
|
|
23002
|
+
tool: name,
|
|
23003
|
+
err: message
|
|
23004
|
+
}, "mcp.call_tool failed");
|
|
23005
|
+
return {
|
|
23006
|
+
ok: false,
|
|
23007
|
+
error: {
|
|
23008
|
+
code: "MCP_CALL_FAILED",
|
|
23009
|
+
message
|
|
23010
|
+
}
|
|
23011
|
+
};
|
|
23012
|
+
}
|
|
23013
|
+
}
|
|
22489
23014
|
function handleIntegrationReport(params, pluginId) {
|
|
22490
23015
|
const { name, status, detail } = params;
|
|
22491
23016
|
if (!name || !status) return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
"pino-roll": "^1.2.0",
|
|
23
23
|
"smol-toml": ">=1.6.1",
|
|
24
24
|
"ws": "^8.18.0",
|
|
25
|
-
"@alfe.ai/
|
|
26
|
-
"@alfe.ai/
|
|
27
|
-
"@alfe.ai/
|
|
28
|
-
"@alfe.ai/
|
|
25
|
+
"@alfe.ai/agent-api-client": "^0.1.4",
|
|
26
|
+
"@alfe.ai/ai-proxy-local": "^0.0.10",
|
|
27
|
+
"@alfe.ai/config": "^0.0.9",
|
|
28
|
+
"@alfe.ai/integration-manifest": "^0.1.0",
|
|
29
|
+
"@alfe.ai/integrations": "^0.1.1",
|
|
30
|
+
"@alfe.ai/mcp-bundler": "^0.1.1"
|
|
29
31
|
},
|
|
30
32
|
"license": "UNLICENSED",
|
|
31
33
|
"scripts": {
|