@lobu/gateway 3.0.9 → 3.0.13
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/api/platform.d.ts.map +1 -1
- package/dist/api/platform.js +7 -26
- package/dist/api/platform.js.map +1 -1
- package/dist/auth/mcp/proxy.d.ts +14 -0
- package/dist/auth/mcp/proxy.d.ts.map +1 -1
- package/dist/auth/mcp/proxy.js +149 -13
- package/dist/auth/mcp/proxy.js.map +1 -1
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +29 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/connections/chat-instance-manager.d.ts.map +1 -1
- package/dist/connections/chat-instance-manager.js +2 -1
- package/dist/connections/chat-instance-manager.js.map +1 -1
- package/dist/connections/interaction-bridge.d.ts +9 -2
- package/dist/connections/interaction-bridge.d.ts.map +1 -1
- package/dist/connections/interaction-bridge.js +121 -261
- package/dist/connections/interaction-bridge.js.map +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/interactions.d.ts +9 -43
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +10 -52
- package/dist/interactions.js.map +1 -1
- package/dist/routes/public/agent.d.ts +4 -0
- package/dist/routes/public/agent.d.ts.map +1 -1
- package/dist/routes/public/agent.js +21 -0
- package/dist/routes/public/agent.js.map +1 -1
- package/dist/services/core-services.d.ts.map +1 -1
- package/dist/services/core-services.js +4 -0
- package/dist/services/core-services.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/agent-config-routes.test.ts +0 -254
- package/src/__tests__/agent-history-routes.test.ts +0 -72
- package/src/__tests__/agent-routes.test.ts +0 -68
- package/src/__tests__/agent-schedules-routes.test.ts +0 -59
- package/src/__tests__/agent-settings-store.test.ts +0 -323
- package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
- package/src/__tests__/bedrock-openai-service.test.ts +0 -157
- package/src/__tests__/bedrock-provider-module.test.ts +0 -56
- package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
- package/src/__tests__/chat-response-bridge.test.ts +0 -131
- package/src/__tests__/config-memory-plugins.test.ts +0 -92
- package/src/__tests__/config-request-store.test.ts +0 -127
- package/src/__tests__/connection-routes.test.ts +0 -144
- package/src/__tests__/core-services-store-selection.test.ts +0 -92
- package/src/__tests__/docker-deployment.test.ts +0 -1211
- package/src/__tests__/embedded-deployment.test.ts +0 -342
- package/src/__tests__/grant-store.test.ts +0 -148
- package/src/__tests__/http-proxy.test.ts +0 -281
- package/src/__tests__/instruction-service.test.ts +0 -37
- package/src/__tests__/link-buttons.test.ts +0 -112
- package/src/__tests__/lobu.test.ts +0 -32
- package/src/__tests__/mcp-config-service.test.ts +0 -347
- package/src/__tests__/mcp-proxy.test.ts +0 -694
- package/src/__tests__/message-handler-bridge.test.ts +0 -17
- package/src/__tests__/model-selection.test.ts +0 -172
- package/src/__tests__/oauth-templates.test.ts +0 -39
- package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
- package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
- package/src/__tests__/provider-inheritance.test.ts +0 -212
- package/src/__tests__/routes/cli-auth.test.ts +0 -337
- package/src/__tests__/routes/interactions.test.ts +0 -121
- package/src/__tests__/secret-proxy.test.ts +0 -85
- package/src/__tests__/session-manager.test.ts +0 -572
- package/src/__tests__/setup.ts +0 -133
- package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
- package/src/__tests__/slack-routes.test.ts +0 -161
- package/src/__tests__/system-config-resolver.test.ts +0 -75
- package/src/__tests__/system-message-limiter.test.ts +0 -89
- package/src/__tests__/system-skills-service.test.ts +0 -362
- package/src/__tests__/transcription-service.test.ts +0 -222
- package/src/__tests__/utils/rate-limiter.test.ts +0 -102
- package/src/__tests__/worker-connection-manager.test.ts +0 -497
- package/src/__tests__/worker-job-router.test.ts +0 -722
- package/src/api/index.ts +0 -1
- package/src/api/platform.ts +0 -292
- package/src/api/response-renderer.ts +0 -157
- package/src/auth/agent-metadata-store.ts +0 -168
- package/src/auth/api-auth-middleware.ts +0 -69
- package/src/auth/api-key-provider-module.ts +0 -213
- package/src/auth/base-provider-module.ts +0 -201
- package/src/auth/bedrock/provider-module.ts +0 -110
- package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
- package/src/auth/chatgpt/device-code-client.ts +0 -218
- package/src/auth/chatgpt/index.ts +0 -1
- package/src/auth/claude/oauth-module.ts +0 -280
- package/src/auth/cli/token-service.ts +0 -249
- package/src/auth/external/client.ts +0 -560
- package/src/auth/external/device-code-client.ts +0 -235
- package/src/auth/mcp/config-service.ts +0 -420
- package/src/auth/mcp/proxy.ts +0 -1086
- package/src/auth/mcp/string-substitution.ts +0 -17
- package/src/auth/mcp/tool-cache.ts +0 -90
- package/src/auth/oauth/base-client.ts +0 -267
- package/src/auth/oauth/client.ts +0 -153
- package/src/auth/oauth/credentials.ts +0 -7
- package/src/auth/oauth/providers.ts +0 -69
- package/src/auth/oauth/state-store.ts +0 -150
- package/src/auth/oauth-templates.ts +0 -179
- package/src/auth/provider-catalog.ts +0 -220
- package/src/auth/provider-model-options.ts +0 -41
- package/src/auth/settings/agent-settings-store.ts +0 -565
- package/src/auth/settings/auth-profiles-manager.ts +0 -216
- package/src/auth/settings/index.ts +0 -12
- package/src/auth/settings/model-preference-store.ts +0 -52
- package/src/auth/settings/model-selection.ts +0 -135
- package/src/auth/settings/resolved-settings-view.ts +0 -298
- package/src/auth/settings/template-utils.ts +0 -44
- package/src/auth/settings/token-service.ts +0 -88
- package/src/auth/system-env-store.ts +0 -98
- package/src/auth/user-agents-store.ts +0 -68
- package/src/channels/binding-service.ts +0 -214
- package/src/channels/index.ts +0 -4
- package/src/cli/gateway.ts +0 -1312
- package/src/cli/index.ts +0 -74
- package/src/commands/built-in-commands.ts +0 -80
- package/src/commands/command-dispatcher.ts +0 -94
- package/src/commands/command-reply-adapters.ts +0 -27
- package/src/config/file-loader.ts +0 -618
- package/src/config/index.ts +0 -588
- package/src/config/network-allowlist.ts +0 -71
- package/src/connections/chat-instance-manager.ts +0 -1284
- package/src/connections/chat-response-bridge.ts +0 -618
- package/src/connections/index.ts +0 -7
- package/src/connections/interaction-bridge.ts +0 -831
- package/src/connections/message-handler-bridge.ts +0 -440
- package/src/connections/platform-auth-methods.ts +0 -15
- package/src/connections/types.ts +0 -84
- package/src/gateway/connection-manager.ts +0 -291
- package/src/gateway/index.ts +0 -698
- package/src/gateway/job-router.ts +0 -201
- package/src/gateway-main.ts +0 -200
- package/src/index.ts +0 -41
- package/src/infrastructure/queue/index.ts +0 -12
- package/src/infrastructure/queue/queue-producer.ts +0 -148
- package/src/infrastructure/queue/redis-queue.ts +0 -361
- package/src/infrastructure/queue/types.ts +0 -133
- package/src/infrastructure/redis/system-message-limiter.ts +0 -94
- package/src/interactions/config-request-store.ts +0 -198
- package/src/interactions.ts +0 -363
- package/src/lobu.ts +0 -311
- package/src/metrics/prometheus.ts +0 -159
- package/src/modules/module-system.ts +0 -179
- package/src/orchestration/base-deployment-manager.ts +0 -900
- package/src/orchestration/deployment-utils.ts +0 -98
- package/src/orchestration/impl/docker-deployment.ts +0 -620
- package/src/orchestration/impl/embedded-deployment.ts +0 -268
- package/src/orchestration/impl/index.ts +0 -8
- package/src/orchestration/impl/k8s/deployment.ts +0 -1061
- package/src/orchestration/impl/k8s/helpers.ts +0 -610
- package/src/orchestration/impl/k8s/index.ts +0 -1
- package/src/orchestration/index.ts +0 -333
- package/src/orchestration/message-consumer.ts +0 -584
- package/src/orchestration/scheduled-wakeup.ts +0 -704
- package/src/permissions/approval-policy.ts +0 -36
- package/src/permissions/grant-store.ts +0 -219
- package/src/platform/file-handler.ts +0 -66
- package/src/platform/link-buttons.ts +0 -57
- package/src/platform/renderer-utils.ts +0 -44
- package/src/platform/response-renderer.ts +0 -84
- package/src/platform/unified-thread-consumer.ts +0 -194
- package/src/platform.ts +0 -318
- package/src/proxy/http-proxy.ts +0 -752
- package/src/proxy/proxy-manager.ts +0 -81
- package/src/proxy/secret-proxy.ts +0 -402
- package/src/proxy/token-refresh-job.ts +0 -143
- package/src/routes/internal/audio.ts +0 -141
- package/src/routes/internal/device-auth.ts +0 -652
- package/src/routes/internal/files.ts +0 -226
- package/src/routes/internal/history.ts +0 -69
- package/src/routes/internal/images.ts +0 -127
- package/src/routes/internal/interactions.ts +0 -84
- package/src/routes/internal/middleware.ts +0 -23
- package/src/routes/internal/schedule.ts +0 -226
- package/src/routes/internal/types.ts +0 -22
- package/src/routes/openapi-auto.ts +0 -239
- package/src/routes/public/agent-access.ts +0 -23
- package/src/routes/public/agent-config.ts +0 -675
- package/src/routes/public/agent-history.ts +0 -422
- package/src/routes/public/agent-schedules.ts +0 -296
- package/src/routes/public/agent.ts +0 -1086
- package/src/routes/public/agents.ts +0 -373
- package/src/routes/public/channels.ts +0 -191
- package/src/routes/public/cli-auth.ts +0 -896
- package/src/routes/public/connections.ts +0 -574
- package/src/routes/public/landing.ts +0 -16
- package/src/routes/public/oauth.ts +0 -147
- package/src/routes/public/settings-auth.ts +0 -104
- package/src/routes/public/slack.ts +0 -173
- package/src/routes/shared/agent-ownership.ts +0 -101
- package/src/routes/shared/token-verifier.ts +0 -34
- package/src/services/bedrock-model-catalog.ts +0 -217
- package/src/services/bedrock-openai-service.ts +0 -658
- package/src/services/core-services.ts +0 -1072
- package/src/services/image-generation-service.ts +0 -257
- package/src/services/instruction-service.ts +0 -318
- package/src/services/mcp-registry.ts +0 -94
- package/src/services/platform-helpers.ts +0 -287
- package/src/services/session-manager.ts +0 -262
- package/src/services/settings-resolver.ts +0 -74
- package/src/services/system-config-resolver.ts +0 -89
- package/src/services/system-skills-service.ts +0 -229
- package/src/services/transcription-service.ts +0 -684
- package/src/session.ts +0 -110
- package/src/spaces/index.ts +0 -1
- package/src/spaces/space-resolver.ts +0 -17
- package/src/stores/in-memory-agent-store.ts +0 -403
- package/src/stores/redis-agent-store.ts +0 -279
- package/src/utils/public-url.ts +0 -44
- package/src/utils/rate-limiter.ts +0 -94
- package/tsconfig.json +0 -33
- package/tsconfig.tsbuildinfo +0 -1
package/src/cli/gateway.ts
DELETED
|
@@ -1,1312 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import type { Server } from "node:http";
|
|
4
|
-
import { createServer } from "node:http";
|
|
5
|
-
import { getRequestListener } from "@hono/node-server";
|
|
6
|
-
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
7
|
-
import { createLogger } from "@lobu/core";
|
|
8
|
-
import { apiReference } from "@scalar/hono-api-reference";
|
|
9
|
-
import { cors } from "hono/cors";
|
|
10
|
-
import { secureHeaders } from "hono/secure-headers";
|
|
11
|
-
import type { AgentMetadata } from "../auth/agent-metadata-store";
|
|
12
|
-
import type { GatewayConfig } from "../config";
|
|
13
|
-
import { getModelProviderModules } from "../modules/module-system";
|
|
14
|
-
import { registerAutoOpenApiRoutes } from "../routes/openapi-auto";
|
|
15
|
-
|
|
16
|
-
const logger = createLogger("gateway-startup");
|
|
17
|
-
|
|
18
|
-
let httpServer: Server | null = null;
|
|
19
|
-
|
|
20
|
-
export interface CreateGatewayAppOptions {
|
|
21
|
-
secretProxy: any;
|
|
22
|
-
workerGateway: any;
|
|
23
|
-
mcpProxy: any;
|
|
24
|
-
interactionService?: any;
|
|
25
|
-
platformRegistry?: any;
|
|
26
|
-
coreServices?: any;
|
|
27
|
-
chatInstanceManager?: import("../connections").ChatInstanceManager | null;
|
|
28
|
-
/** Custom auth provider for embedded mode. When set, gateway delegates auth to this function instead of using cookie-based sessions. */
|
|
29
|
-
authProvider?: import("../routes/public/settings-auth").AuthProvider;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create the Hono app with all gateway routes.
|
|
34
|
-
* Returns the app without starting an HTTP server — the caller can mount it
|
|
35
|
-
* on their own server (embedded mode) or pass it to `startGatewayServer()`.
|
|
36
|
-
*/
|
|
37
|
-
export function createGatewayApp(
|
|
38
|
-
options: CreateGatewayAppOptions
|
|
39
|
-
): OpenAPIHono {
|
|
40
|
-
const {
|
|
41
|
-
secretProxy,
|
|
42
|
-
workerGateway,
|
|
43
|
-
mcpProxy,
|
|
44
|
-
interactionService,
|
|
45
|
-
platformRegistry,
|
|
46
|
-
coreServices,
|
|
47
|
-
chatInstanceManager,
|
|
48
|
-
authProvider,
|
|
49
|
-
} = options;
|
|
50
|
-
|
|
51
|
-
// Wire injectable auth provider (for embedded mode)
|
|
52
|
-
if (authProvider) {
|
|
53
|
-
const { setAuthProvider } = require("../routes/public/settings-auth");
|
|
54
|
-
setAuthProvider(authProvider);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const app = new OpenAPIHono();
|
|
58
|
-
|
|
59
|
-
// Global middleware
|
|
60
|
-
app.use(
|
|
61
|
-
"*",
|
|
62
|
-
secureHeaders({
|
|
63
|
-
xFrameOptions: false,
|
|
64
|
-
xContentTypeOptions: "nosniff",
|
|
65
|
-
referrerPolicy: "strict-origin-when-cross-origin",
|
|
66
|
-
strictTransportSecurity: "max-age=63072000; includeSubDomains",
|
|
67
|
-
contentSecurityPolicy: {
|
|
68
|
-
defaultSrc: ["'self'"],
|
|
69
|
-
frameAncestors: ["'self'", "*"],
|
|
70
|
-
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
|
|
71
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
72
|
-
imgSrc: ["'self'", "data:", "https:"],
|
|
73
|
-
connectSrc: ["'self'", "ws:", "wss:"],
|
|
74
|
-
fontSrc: ["'self'", "https://fonts.gstatic.com"],
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
);
|
|
78
|
-
app.use(
|
|
79
|
-
"*",
|
|
80
|
-
cors({
|
|
81
|
-
origin: process.env.ALLOWED_ORIGINS
|
|
82
|
-
? process.env.ALLOWED_ORIGINS.split(",")
|
|
83
|
-
: [],
|
|
84
|
-
credentials: true,
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// Health endpoints
|
|
89
|
-
app.get("/health", (c) => {
|
|
90
|
-
const mode =
|
|
91
|
-
process.env.LOBU_MODE ||
|
|
92
|
-
(process.env.DEPLOYMENT_MODE === "docker" ? "local" : "cloud");
|
|
93
|
-
|
|
94
|
-
return c.json({
|
|
95
|
-
status: "ok",
|
|
96
|
-
mode,
|
|
97
|
-
version: process.env.npm_package_version || "2.3.0",
|
|
98
|
-
timestamp: new Date().toISOString(),
|
|
99
|
-
publicGatewayUrl:
|
|
100
|
-
coreServices?.getPublicGatewayUrl?.() || process.env.PUBLIC_GATEWAY_URL,
|
|
101
|
-
capabilities: {
|
|
102
|
-
agents: ["claude"],
|
|
103
|
-
streaming: true,
|
|
104
|
-
toolApproval: true,
|
|
105
|
-
},
|
|
106
|
-
wsUrl: `ws://localhost:8080/ws`,
|
|
107
|
-
secretProxy: !!secretProxy,
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
app.get("/ready", (c) => c.json({ ready: true }));
|
|
112
|
-
|
|
113
|
-
// Compute adminPassword once — used by Agent API, CLI auth, metrics, and messaging
|
|
114
|
-
const crypto = require("node:crypto");
|
|
115
|
-
const adminPassword: string =
|
|
116
|
-
process.env.ADMIN_PASSWORD || crypto.randomBytes(16).toString("base64url");
|
|
117
|
-
|
|
118
|
-
// Prometheus metrics endpoint.
|
|
119
|
-
// Keep auth optional so existing ServiceMonitor configs continue to scrape.
|
|
120
|
-
app.get("/metrics", async (c) => {
|
|
121
|
-
const metricsAuthToken = process.env.METRICS_AUTH_TOKEN;
|
|
122
|
-
if (metricsAuthToken) {
|
|
123
|
-
const authHeader = c.req.header("Authorization");
|
|
124
|
-
if (authHeader !== `Bearer ${metricsAuthToken}`) {
|
|
125
|
-
return c.text("Unauthorized", 401);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const { getMetricsText } = await import("../metrics/prometheus");
|
|
129
|
-
c.header("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
130
|
-
return c.text(getMetricsText());
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Secret injection proxy (Hono)
|
|
134
|
-
if (secretProxy) {
|
|
135
|
-
app.route("/api/proxy", secretProxy.getApp());
|
|
136
|
-
logger.debug("Secret proxy enabled at :8080/api/proxy");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (coreServices) {
|
|
140
|
-
const bedrockOpenAIService = coreServices.getBedrockOpenAIService?.();
|
|
141
|
-
if (bedrockOpenAIService) {
|
|
142
|
-
app.route("/api/bedrock", bedrockOpenAIService.getApp());
|
|
143
|
-
logger.debug("Bedrock routes enabled at :8080/api/bedrock/*");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Worker Gateway routes (Hono)
|
|
148
|
-
if (workerGateway) {
|
|
149
|
-
app.route("/worker", workerGateway.getApp());
|
|
150
|
-
logger.debug("Worker gateway routes enabled at :8080/worker/*");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Register module endpoints
|
|
154
|
-
const { moduleRegistry: coreModuleRegistry } = require("@lobu/core");
|
|
155
|
-
if (coreModuleRegistry.registerHonoEndpoints) {
|
|
156
|
-
coreModuleRegistry.registerHonoEndpoints(app);
|
|
157
|
-
} else {
|
|
158
|
-
// Create express-like adapter for module registry
|
|
159
|
-
const expressApp = createExpressAdapter(app);
|
|
160
|
-
coreModuleRegistry.registerEndpoints(expressApp);
|
|
161
|
-
}
|
|
162
|
-
logger.debug("Module endpoints registered");
|
|
163
|
-
|
|
164
|
-
// MCP proxy routes (Hono)
|
|
165
|
-
if (mcpProxy) {
|
|
166
|
-
// Handle root path requests with X-Mcp-Id header
|
|
167
|
-
app.all("/", async (c, next) => {
|
|
168
|
-
if (mcpProxy.isMcpRequest(c)) {
|
|
169
|
-
// Forward to MCP proxy - need to handle directly since it's at root
|
|
170
|
-
return mcpProxy.getApp().fetch(c.req.raw);
|
|
171
|
-
}
|
|
172
|
-
return next();
|
|
173
|
-
});
|
|
174
|
-
// Mount MCP proxy at /mcp/*
|
|
175
|
-
app.route("/mcp", mcpProxy.getApp());
|
|
176
|
-
logger.debug("MCP proxy routes enabled at :8080/mcp/*");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// File routes (already Hono) - uses platform registry for per-platform file handling
|
|
180
|
-
if (platformRegistry) {
|
|
181
|
-
const { createFileRoutes } = require("../routes/internal/files");
|
|
182
|
-
const fileRouter = createFileRoutes(platformRegistry);
|
|
183
|
-
app.route("/internal/files", fileRouter);
|
|
184
|
-
logger.debug("File routes enabled at :8080/internal/files/*");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// History routes (already Hono)
|
|
188
|
-
{
|
|
189
|
-
const { createHistoryRoutes } = require("../routes/internal/history");
|
|
190
|
-
const historyRouter = createHistoryRoutes();
|
|
191
|
-
app.route("/internal", historyRouter);
|
|
192
|
-
logger.debug("History routes enabled at :8080/internal/history");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Schedule routes (worker scheduling endpoints)
|
|
196
|
-
if (coreServices) {
|
|
197
|
-
const scheduledWakeupService = coreServices.getScheduledWakeupService();
|
|
198
|
-
if (scheduledWakeupService) {
|
|
199
|
-
const { createScheduleRoutes } = require("../routes/internal/schedule");
|
|
200
|
-
const scheduleRouter = createScheduleRoutes(scheduledWakeupService);
|
|
201
|
-
app.route("", scheduleRouter);
|
|
202
|
-
logger.debug("Schedule routes enabled at :8080/internal/schedule");
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Device auth routes (gateway-mediated OAuth for workers)
|
|
207
|
-
if (coreServices) {
|
|
208
|
-
const {
|
|
209
|
-
createDeviceAuthRoutes,
|
|
210
|
-
} = require("../routes/internal/device-auth");
|
|
211
|
-
const redisClient = coreServices.getQueue().getRedisClient();
|
|
212
|
-
const mcpConfigService = coreServices.getMcpConfigService();
|
|
213
|
-
if (mcpConfigService) {
|
|
214
|
-
const deviceAuthRouter = createDeviceAuthRoutes({
|
|
215
|
-
redis: redisClient,
|
|
216
|
-
mcpConfigService,
|
|
217
|
-
});
|
|
218
|
-
app.route("", deviceAuthRouter);
|
|
219
|
-
logger.debug(
|
|
220
|
-
"Device auth routes enabled at :8080/internal/device-auth/*"
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Audio routes (TTS synthesis for workers)
|
|
226
|
-
if (coreServices) {
|
|
227
|
-
const transcriptionService = coreServices.getTranscriptionService();
|
|
228
|
-
if (transcriptionService) {
|
|
229
|
-
const { createAudioRoutes } = require("../routes/internal/audio");
|
|
230
|
-
const audioRouter = createAudioRoutes(transcriptionService);
|
|
231
|
-
app.route("", audioRouter);
|
|
232
|
-
logger.debug("Audio routes enabled at :8080/internal/audio/*");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Image routes (image generation for workers)
|
|
237
|
-
if (coreServices) {
|
|
238
|
-
const imageGenerationService = coreServices.getImageGenerationService();
|
|
239
|
-
if (imageGenerationService) {
|
|
240
|
-
const { createImageRoutes } = require("../routes/internal/images");
|
|
241
|
-
const imageRouter = createImageRoutes(imageGenerationService);
|
|
242
|
-
app.route("", imageRouter);
|
|
243
|
-
logger.debug("Image routes enabled at :8080/internal/images/*");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Interaction routes (already Hono)
|
|
248
|
-
if (interactionService) {
|
|
249
|
-
const {
|
|
250
|
-
createInteractionRoutes,
|
|
251
|
-
} = require("../routes/internal/interactions");
|
|
252
|
-
const internalRouter = createInteractionRoutes(interactionService);
|
|
253
|
-
app.route("", internalRouter);
|
|
254
|
-
logger.debug("Internal interaction routes enabled");
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Create CLI token service early so it can be shared by messaging + agent API
|
|
258
|
-
let cliTokenService: any;
|
|
259
|
-
if (coreServices) {
|
|
260
|
-
const { CliTokenService } = require("../auth/cli/token-service");
|
|
261
|
-
const redisClient = coreServices.getQueue().getRedisClient();
|
|
262
|
-
cliTokenService = new CliTokenService(redisClient);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Agent API routes (direct API access + platform-routed messaging)
|
|
266
|
-
if (coreServices) {
|
|
267
|
-
const queueProducer = coreServices.getQueueProducer();
|
|
268
|
-
const sessionMgr = coreServices.getSessionManager();
|
|
269
|
-
const interactionSvc = coreServices.getInteractionService();
|
|
270
|
-
const publicUrl = coreServices.getPublicGatewayUrl();
|
|
271
|
-
|
|
272
|
-
if (queueProducer && sessionMgr && interactionSvc) {
|
|
273
|
-
const { createAgentApi } = require("../routes/public/agent");
|
|
274
|
-
const agentApi = createAgentApi({
|
|
275
|
-
queueProducer,
|
|
276
|
-
sessionManager: sessionMgr,
|
|
277
|
-
publicGatewayUrl: publicUrl,
|
|
278
|
-
adminPassword,
|
|
279
|
-
cliTokenService,
|
|
280
|
-
externalAuthClient: coreServices.getExternalAuthClient(),
|
|
281
|
-
agentSettingsStore: coreServices.getAgentSettingsStore(),
|
|
282
|
-
agentConfigStore: coreServices.getConfigStore(),
|
|
283
|
-
platformRegistry,
|
|
284
|
-
});
|
|
285
|
-
app.route("", agentApi);
|
|
286
|
-
logger.debug(
|
|
287
|
-
"Agent API enabled at :8080/api/v1/agents/* with docs at :8080/api/docs"
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (coreServices) {
|
|
293
|
-
// Mount OAuth modules under unified auth router
|
|
294
|
-
const authRouter = new OpenAPIHono();
|
|
295
|
-
const registeredProviders: string[] = [];
|
|
296
|
-
|
|
297
|
-
{
|
|
298
|
-
const {
|
|
299
|
-
createCliAuthRoutes,
|
|
300
|
-
createConnectAuthRoutes,
|
|
301
|
-
} = require("../routes/public/cli-auth");
|
|
302
|
-
const cliAuthRouter = createCliAuthRoutes({
|
|
303
|
-
queue: coreServices.getQueue(),
|
|
304
|
-
externalAuthClient: coreServices.getExternalAuthClient(),
|
|
305
|
-
allowAdminPasswordLogin: process.env.NODE_ENV !== "production",
|
|
306
|
-
adminPassword,
|
|
307
|
-
});
|
|
308
|
-
const connectAuthRouter = createConnectAuthRoutes({
|
|
309
|
-
queue: coreServices.getQueue(),
|
|
310
|
-
externalAuthClient: coreServices.getExternalAuthClient(),
|
|
311
|
-
allowAdminPasswordLogin: process.env.NODE_ENV !== "production",
|
|
312
|
-
adminPassword,
|
|
313
|
-
});
|
|
314
|
-
authRouter.route("", cliAuthRouter);
|
|
315
|
-
app.route("", connectAuthRouter);
|
|
316
|
-
registeredProviders.push("cli-auth");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const providerModules = getModelProviderModules();
|
|
320
|
-
|
|
321
|
-
const authProfilesManager = coreServices.getAuthProfilesManager();
|
|
322
|
-
if (authProfilesManager) {
|
|
323
|
-
const {
|
|
324
|
-
verifySettingsSessionOrToken,
|
|
325
|
-
} = require("../routes/public/settings-auth");
|
|
326
|
-
const {
|
|
327
|
-
createAuthProfileLabel,
|
|
328
|
-
} = require("../auth/settings/auth-profiles-manager");
|
|
329
|
-
const agentMetadataStore = coreServices.getAgentMetadataStore();
|
|
330
|
-
const userAgentsStore = coreServices.getUserAgentsStore();
|
|
331
|
-
|
|
332
|
-
const verifyProviderAuth = async (
|
|
333
|
-
c: any,
|
|
334
|
-
agentId: string
|
|
335
|
-
): Promise<boolean> => {
|
|
336
|
-
const payload = verifySettingsSessionOrToken(c);
|
|
337
|
-
if (!payload) return false;
|
|
338
|
-
if (payload.isAdmin) return true;
|
|
339
|
-
|
|
340
|
-
if (payload.agentId) return payload.agentId === agentId;
|
|
341
|
-
|
|
342
|
-
if (userAgentsStore) {
|
|
343
|
-
const owns = await userAgentsStore.ownsAgent(
|
|
344
|
-
payload.platform,
|
|
345
|
-
payload.userId,
|
|
346
|
-
agentId
|
|
347
|
-
);
|
|
348
|
-
if (owns) return true;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (agentMetadataStore) {
|
|
352
|
-
const metadata = await agentMetadataStore.getMetadata(agentId);
|
|
353
|
-
const isOwner =
|
|
354
|
-
metadata?.owner?.platform === payload.platform &&
|
|
355
|
-
metadata?.owner?.userId === payload.userId;
|
|
356
|
-
if (isOwner) {
|
|
357
|
-
userAgentsStore
|
|
358
|
-
?.addAgent(payload.platform, payload.userId, agentId)
|
|
359
|
-
.catch(() => {
|
|
360
|
-
/* best-effort reconciliation */
|
|
361
|
-
});
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return false;
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
authRouter.post("/:provider/save-key", async (c: any) => {
|
|
370
|
-
try {
|
|
371
|
-
const providerId = c.req.param("provider");
|
|
372
|
-
const mod = getModelProviderModules().find(
|
|
373
|
-
(m) => m.providerId === providerId
|
|
374
|
-
);
|
|
375
|
-
if (!mod) return c.json({ error: "Unknown provider" }, 404);
|
|
376
|
-
|
|
377
|
-
const body = await c.req.json();
|
|
378
|
-
const { agentId, apiKey } = body;
|
|
379
|
-
if (!agentId || !apiKey) {
|
|
380
|
-
return c.json({ error: "Missing agentId or apiKey" }, 400);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (!(await verifyProviderAuth(c, agentId))) {
|
|
384
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
await authProfilesManager.upsertProfile({
|
|
388
|
-
agentId,
|
|
389
|
-
provider: providerId,
|
|
390
|
-
credential: apiKey,
|
|
391
|
-
authType: "api-key",
|
|
392
|
-
label: createAuthProfileLabel(mod.providerDisplayName, apiKey),
|
|
393
|
-
makePrimary: true,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
return c.json({ success: true });
|
|
397
|
-
} catch (error) {
|
|
398
|
-
logger.error("Failed to save API key", { error });
|
|
399
|
-
return c.json({ error: "Failed to save API key" }, 500);
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
authRouter.post("/:provider/start", async (c: any) => {
|
|
404
|
-
try {
|
|
405
|
-
const providerId = c.req.param("provider");
|
|
406
|
-
const mod = getModelProviderModules().find(
|
|
407
|
-
(m) => m.providerId === providerId
|
|
408
|
-
);
|
|
409
|
-
if (!mod) return c.json({ error: "Unknown provider" }, 404);
|
|
410
|
-
|
|
411
|
-
const supportsDeviceCode =
|
|
412
|
-
mod.authType === "device-code" ||
|
|
413
|
-
mod.supportedAuthTypes?.includes("device-code");
|
|
414
|
-
if (!supportsDeviceCode) {
|
|
415
|
-
return c.json(
|
|
416
|
-
{ error: "Provider does not support device code" },
|
|
417
|
-
400
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (typeof mod.startDeviceCode !== "function") {
|
|
422
|
-
return c.json({ error: "Device code start not implemented" }, 501);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const body = (await c.req.json().catch(() => ({}))) as {
|
|
426
|
-
agentId?: string;
|
|
427
|
-
};
|
|
428
|
-
const agentId = body.agentId?.trim();
|
|
429
|
-
if (!agentId) return c.json({ error: "Missing agentId" }, 400);
|
|
430
|
-
|
|
431
|
-
if (!(await verifyProviderAuth(c, agentId))) {
|
|
432
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const result = await mod.startDeviceCode(agentId);
|
|
436
|
-
return c.json(result);
|
|
437
|
-
} catch (error) {
|
|
438
|
-
logger.error("Failed to start device code flow", { error });
|
|
439
|
-
return c.json({ error: "Failed to start device code flow" }, 500);
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
authRouter.post("/:provider/poll", async (c: any) => {
|
|
444
|
-
try {
|
|
445
|
-
const providerId = c.req.param("provider");
|
|
446
|
-
const mod = getModelProviderModules().find(
|
|
447
|
-
(m) => m.providerId === providerId
|
|
448
|
-
);
|
|
449
|
-
if (!mod) return c.json({ error: "Unknown provider" }, 404);
|
|
450
|
-
|
|
451
|
-
const supportsDeviceCode =
|
|
452
|
-
mod.authType === "device-code" ||
|
|
453
|
-
mod.supportedAuthTypes?.includes("device-code");
|
|
454
|
-
if (!supportsDeviceCode) {
|
|
455
|
-
return c.json(
|
|
456
|
-
{ error: "Provider does not support device code" },
|
|
457
|
-
400
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (typeof mod.pollDeviceCode !== "function") {
|
|
462
|
-
return c.json({ error: "Device code poll not implemented" }, 501);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const body = (await c.req.json().catch(() => ({}))) as {
|
|
466
|
-
agentId?: string;
|
|
467
|
-
deviceAuthId?: string;
|
|
468
|
-
userCode?: string;
|
|
469
|
-
};
|
|
470
|
-
const agentId = body.agentId?.trim();
|
|
471
|
-
const deviceAuthId = body.deviceAuthId?.trim();
|
|
472
|
-
const userCode = body.userCode?.trim();
|
|
473
|
-
if (!agentId || !deviceAuthId || !userCode) {
|
|
474
|
-
return c.json(
|
|
475
|
-
{ error: "Missing agentId, deviceAuthId, or userCode" },
|
|
476
|
-
400
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (!(await verifyProviderAuth(c, agentId))) {
|
|
481
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const result = await mod.pollDeviceCode(agentId, {
|
|
485
|
-
deviceAuthId,
|
|
486
|
-
userCode,
|
|
487
|
-
});
|
|
488
|
-
return c.json(result);
|
|
489
|
-
} catch (error) {
|
|
490
|
-
logger.error("Failed to poll device code flow", { error });
|
|
491
|
-
return c.json({ error: "Failed to poll device code flow" }, 500);
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
authRouter.post("/:provider/logout", async (c: any) => {
|
|
496
|
-
try {
|
|
497
|
-
const providerId = c.req.param("provider");
|
|
498
|
-
const mod = getModelProviderModules().find(
|
|
499
|
-
(m) => m.providerId === providerId
|
|
500
|
-
);
|
|
501
|
-
if (!mod) return c.json({ error: "Unknown provider" }, 404);
|
|
502
|
-
|
|
503
|
-
const body = await c.req.json().catch(() => ({}));
|
|
504
|
-
const agentId = body.agentId || c.req.query("agentId");
|
|
505
|
-
if (!agentId) {
|
|
506
|
-
return c.json({ error: "Missing agentId" }, 400);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (!(await verifyProviderAuth(c, agentId))) {
|
|
510
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
await authProfilesManager.deleteProviderProfiles(
|
|
514
|
-
agentId,
|
|
515
|
-
providerId,
|
|
516
|
-
body.profileId
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
return c.json({ success: true });
|
|
520
|
-
} catch (error) {
|
|
521
|
-
logger.error("Failed to logout", { error });
|
|
522
|
-
return c.json({ error: "Failed to logout" }, 500);
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Get shared dependencies (needed before mounting auth router)
|
|
528
|
-
const agentSettingsStore = coreServices.getAgentSettingsStore();
|
|
529
|
-
const claudeOAuthStateStore = coreServices.getOAuthStateStore();
|
|
530
|
-
const scheduledWakeupService = coreServices.getScheduledWakeupService();
|
|
531
|
-
|
|
532
|
-
// Build provider stores and overrides dynamically from registered modules
|
|
533
|
-
const providerStores: Record<
|
|
534
|
-
string,
|
|
535
|
-
{ hasCredentials(agentId: string): Promise<boolean> }
|
|
536
|
-
> = {};
|
|
537
|
-
const providerConnectedOverrides: Record<string, boolean> = {};
|
|
538
|
-
for (const mod of providerModules) {
|
|
539
|
-
providerStores[mod.providerId] = mod;
|
|
540
|
-
providerConnectedOverrides[mod.providerId] = mod.hasSystemKey();
|
|
541
|
-
if (mod.getApp) {
|
|
542
|
-
authRouter.route(`/${mod.providerId}`, mod.getApp());
|
|
543
|
-
registeredProviders.push(mod.providerId);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const systemSkillsService = coreServices.getSystemSkillsService();
|
|
548
|
-
|
|
549
|
-
if (systemSkillsService) {
|
|
550
|
-
const { SystemEnvStore } = require("../auth/system-env-store");
|
|
551
|
-
const { setEnvResolver } = require("../auth/mcp/string-substitution");
|
|
552
|
-
const systemEnvStore = new SystemEnvStore(
|
|
553
|
-
coreServices.getQueue().getRedisClient()
|
|
554
|
-
);
|
|
555
|
-
systemEnvStore.refreshCache().catch((e: any) => {
|
|
556
|
-
logger.error("Failed to refresh system env cache", { error: e });
|
|
557
|
-
});
|
|
558
|
-
setEnvResolver((key: string) => systemEnvStore.resolve(key));
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (!process.env.ADMIN_PASSWORD) {
|
|
562
|
-
logger.info(
|
|
563
|
-
"An admin password has been auto-generated. For security reasons, it is not logged. Set the ADMIN_PASSWORD env var to use a fixed password."
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Landing page (docs + integrations)
|
|
568
|
-
{
|
|
569
|
-
const { createLandingRoutes } = require("../routes/public/landing");
|
|
570
|
-
const landingRouter = createLandingRoutes();
|
|
571
|
-
app.route("", landingRouter);
|
|
572
|
-
logger.debug("Landing page enabled at :8080/");
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Agent history routes (proxy to worker HTTP server)
|
|
576
|
-
{
|
|
577
|
-
const connectionManager = coreServices
|
|
578
|
-
.getWorkerGateway()
|
|
579
|
-
?.getConnectionManager();
|
|
580
|
-
if (connectionManager) {
|
|
581
|
-
const {
|
|
582
|
-
createAgentHistoryRoutes,
|
|
583
|
-
} = require("../routes/public/agent-history");
|
|
584
|
-
const agentHistoryRouter = createAgentHistoryRoutes({
|
|
585
|
-
connectionManager,
|
|
586
|
-
chatInstanceManager: chatInstanceManager ?? undefined,
|
|
587
|
-
agentConfigStore: coreServices.getConfigStore(),
|
|
588
|
-
userAgentsStore: coreServices.getUserAgentsStore(),
|
|
589
|
-
});
|
|
590
|
-
app.route("/api/v1/agents/:agentId/history", agentHistoryRouter);
|
|
591
|
-
logger.debug(
|
|
592
|
-
"Agent history routes enabled at :8080/api/v1/agents/{agentId}/history/*"
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Agent config routes (/api/v1/agents/{id}/config)
|
|
598
|
-
if (agentSettingsStore) {
|
|
599
|
-
const {
|
|
600
|
-
createAgentConfigRoutes,
|
|
601
|
-
} = require("../routes/public/agent-config");
|
|
602
|
-
|
|
603
|
-
const agentConfigRouter = createAgentConfigRoutes({
|
|
604
|
-
agentSettingsStore,
|
|
605
|
-
agentConfigStore: coreServices.getConfigStore()!,
|
|
606
|
-
userAgentsStore: coreServices.getUserAgentsStore(),
|
|
607
|
-
queue: coreServices.getQueue(),
|
|
608
|
-
providerStores:
|
|
609
|
-
Object.keys(providerStores).length > 0 ? providerStores : undefined,
|
|
610
|
-
providerConnectedOverrides,
|
|
611
|
-
providerCatalogService: coreServices.getProviderCatalogService(),
|
|
612
|
-
authProfilesManager: coreServices.getAuthProfilesManager(),
|
|
613
|
-
connectionManager: coreServices
|
|
614
|
-
.getWorkerGateway()
|
|
615
|
-
?.getConnectionManager(),
|
|
616
|
-
grantStore: coreServices.getGrantStore(),
|
|
617
|
-
scheduledWakeupService: coreServices.getScheduledWakeupService(),
|
|
618
|
-
});
|
|
619
|
-
app.route("/api/v1/agents/:agentId/config", agentConfigRouter);
|
|
620
|
-
logger.debug(
|
|
621
|
-
"Agent config routes enabled at :8080/api/v1/agents/{id}/config"
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Agent schedules routes (/api/v1/agents/{id}/schedules)
|
|
626
|
-
{
|
|
627
|
-
const {
|
|
628
|
-
createAgentSchedulesRoutes,
|
|
629
|
-
} = require("../routes/public/agent-schedules");
|
|
630
|
-
const agentSchedulesRouter = createAgentSchedulesRoutes({
|
|
631
|
-
scheduledWakeupService,
|
|
632
|
-
externalAuthClient: coreServices.getExternalAuthClient(),
|
|
633
|
-
userAgentsStore: coreServices.getUserAgentsStore(),
|
|
634
|
-
agentMetadataStore: coreServices.getConfigStore(),
|
|
635
|
-
});
|
|
636
|
-
app.route("/api/v1/agents/:agentId/schedules", agentSchedulesRouter);
|
|
637
|
-
logger.debug(
|
|
638
|
-
"Agent schedules routes enabled at :8080/api/v1/agents/{id}/schedules"
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// OAuth routes (mounted under unified auth router)
|
|
643
|
-
if (agentSettingsStore) {
|
|
644
|
-
const { createOAuthRoutes } = require("../routes/public/oauth");
|
|
645
|
-
const { OAuthClient } = require("../auth/oauth/client");
|
|
646
|
-
const { CLAUDE_PROVIDER } = require("../auth/oauth/providers");
|
|
647
|
-
const claudeOAuthClient = new OAuthClient(CLAUDE_PROVIDER);
|
|
648
|
-
const oauthRouter = createOAuthRoutes({
|
|
649
|
-
providerStores:
|
|
650
|
-
Object.keys(providerStores).length > 0 ? providerStores : undefined,
|
|
651
|
-
oauthClients: { claude: claudeOAuthClient },
|
|
652
|
-
oauthStateStore: claudeOAuthStateStore,
|
|
653
|
-
});
|
|
654
|
-
authRouter.route("", oauthRouter);
|
|
655
|
-
registeredProviders.push("oauth");
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Mount unified auth router (includes provider modules + OAuth)
|
|
659
|
-
if (registeredProviders.length > 0) {
|
|
660
|
-
app.route("/api/v1/auth", authRouter);
|
|
661
|
-
logger.debug(
|
|
662
|
-
`Auth routes enabled at :8080/api/v1/auth/* for: ${registeredProviders.join(", ")}`
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Channel binding routes (mount under agent API)
|
|
667
|
-
const channelBindingService = coreServices.getChannelBindingService();
|
|
668
|
-
if (channelBindingService) {
|
|
669
|
-
const {
|
|
670
|
-
createChannelBindingRoutes,
|
|
671
|
-
} = require("../routes/public/channels");
|
|
672
|
-
const channelBindingRouter = createChannelBindingRoutes({
|
|
673
|
-
channelBindingService,
|
|
674
|
-
userAgentsStore: coreServices.getUserAgentsStore(),
|
|
675
|
-
agentMetadataStore: coreServices.getAgentMetadataStore(),
|
|
676
|
-
});
|
|
677
|
-
// Mount as a sub-router under /api/v1/agents/:agentId/channels
|
|
678
|
-
app.route("/api/v1/agents/:agentId/channels", channelBindingRouter);
|
|
679
|
-
logger.debug(
|
|
680
|
-
"Channel binding routes enabled at :8080/api/v1/agents/{agentId}/channels/*"
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Agent management routes (separate from Agent API's /api/v1/agents)
|
|
685
|
-
{
|
|
686
|
-
const userAgentsStore = coreServices.getUserAgentsStore();
|
|
687
|
-
const agentMetadataStore = coreServices.getAgentMetadataStore();
|
|
688
|
-
const { createAgentRoutes } = require("../routes/public/agents");
|
|
689
|
-
const agentManagementRouter = createAgentRoutes({
|
|
690
|
-
userAgentsStore,
|
|
691
|
-
agentMetadataStore,
|
|
692
|
-
agentSettingsStore,
|
|
693
|
-
channelBindingService,
|
|
694
|
-
});
|
|
695
|
-
app.route("/api/v1/agents", agentManagementRouter);
|
|
696
|
-
logger.debug("Agent management routes enabled at :8080/api/v1/agents/*");
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Chat SDK connection routes (webhook + CRUD)
|
|
701
|
-
if (chatInstanceManager) {
|
|
702
|
-
const {
|
|
703
|
-
createSlackRoutes,
|
|
704
|
-
createConnectionWebhookRoutes,
|
|
705
|
-
createConnectionCrudRoutes,
|
|
706
|
-
} = {
|
|
707
|
-
...require("../routes/public/slack"),
|
|
708
|
-
...require("../routes/public/connections"),
|
|
709
|
-
};
|
|
710
|
-
app.route("", createSlackRoutes(chatInstanceManager));
|
|
711
|
-
app.route("", createConnectionWebhookRoutes(chatInstanceManager));
|
|
712
|
-
app.route(
|
|
713
|
-
"",
|
|
714
|
-
createConnectionCrudRoutes(chatInstanceManager, {
|
|
715
|
-
userAgentsStore: coreServices.getUserAgentsStore(),
|
|
716
|
-
agentMetadataStore: coreServices.getConfigStore()!,
|
|
717
|
-
})
|
|
718
|
-
);
|
|
719
|
-
logger.debug(
|
|
720
|
-
"Slack and connection routes enabled at :8080/slack/*, :8080/api/v1/connections/*, and :8080/api/v1/webhooks/*"
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// ─── Reload endpoint (file-first dev mode) ──────────────────────────────────
|
|
725
|
-
// Re-reads lobu.toml + markdown and re-populates InMemoryAgentStore.
|
|
726
|
-
// Only works in dev mode (files exist), authenticated with ADMIN_PASSWORD.
|
|
727
|
-
app.post("/api/v1/reload", async (c) => {
|
|
728
|
-
if (process.env.NODE_ENV === "production") {
|
|
729
|
-
return c.json({ error: "Not found" }, 404);
|
|
730
|
-
}
|
|
731
|
-
const authHeader = c.req.header("Authorization");
|
|
732
|
-
if (authHeader !== `Bearer ${adminPassword}`) {
|
|
733
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
if (!coreServices?.isFileFirstMode()) {
|
|
737
|
-
return c.json(
|
|
738
|
-
{ error: "Reload only available in file-first dev mode" },
|
|
739
|
-
400
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
try {
|
|
744
|
-
const result = await coreServices.reloadFromFiles();
|
|
745
|
-
return c.json(result);
|
|
746
|
-
} catch (err) {
|
|
747
|
-
logger.error("Reload failed", {
|
|
748
|
-
error: err instanceof Error ? err.message : String(err),
|
|
749
|
-
});
|
|
750
|
-
return c.json({ error: "Reload failed" }, 500);
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
// ─── Internal CLI status endpoint ──────────────────────────────────────────
|
|
755
|
-
// Returns agents, connections, and sandboxes for `lobu status`.
|
|
756
|
-
// Only available in non-production, authenticated with ADMIN_PASSWORD.
|
|
757
|
-
app.get("/internal/status", async (c) => {
|
|
758
|
-
if (process.env.NODE_ENV === "production") {
|
|
759
|
-
return c.json({ error: "Not found" }, 404);
|
|
760
|
-
}
|
|
761
|
-
const authHeader = c.req.header("Authorization");
|
|
762
|
-
if (authHeader !== `Bearer ${adminPassword}`) {
|
|
763
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const agentConfigStore = coreServices?.getConfigStore();
|
|
767
|
-
|
|
768
|
-
const allAgents: AgentMetadata[] = agentConfigStore
|
|
769
|
-
? await agentConfigStore.listAgents()
|
|
770
|
-
: [];
|
|
771
|
-
const templateAgents = allAgents.filter(
|
|
772
|
-
(a: AgentMetadata) => !a.parentConnectionId
|
|
773
|
-
);
|
|
774
|
-
const sandboxAgents = allAgents.filter(
|
|
775
|
-
(a: AgentMetadata) => !!a.parentConnectionId
|
|
776
|
-
);
|
|
777
|
-
|
|
778
|
-
const connections = chatInstanceManager
|
|
779
|
-
? await chatInstanceManager.listConnections()
|
|
780
|
-
: [];
|
|
781
|
-
|
|
782
|
-
const agentDetails = [];
|
|
783
|
-
for (const a of templateAgents) {
|
|
784
|
-
const settings = agentConfigStore
|
|
785
|
-
? await agentConfigStore.getSettings(a.agentId)
|
|
786
|
-
: null;
|
|
787
|
-
const providers = (settings?.installedProviders || []).map(
|
|
788
|
-
(p: { providerId: string }) => p.providerId
|
|
789
|
-
);
|
|
790
|
-
agentDetails.push({
|
|
791
|
-
agentId: a.agentId,
|
|
792
|
-
name: a.name,
|
|
793
|
-
providers,
|
|
794
|
-
model:
|
|
795
|
-
settings?.modelSelection?.mode === "pinned"
|
|
796
|
-
? (settings.modelSelection as { pinnedModel?: string })
|
|
797
|
-
.pinnedModel || "pinned"
|
|
798
|
-
: settings?.modelSelection?.mode || "auto",
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
return c.json({
|
|
803
|
-
agents: agentDetails,
|
|
804
|
-
connections: connections.map(
|
|
805
|
-
(conn: {
|
|
806
|
-
id: string;
|
|
807
|
-
platform: string;
|
|
808
|
-
templateAgentId?: string;
|
|
809
|
-
metadata?: Record<string, string>;
|
|
810
|
-
}) => ({
|
|
811
|
-
id: conn.id,
|
|
812
|
-
platform: conn.platform,
|
|
813
|
-
status: chatInstanceManager?.getInstance(conn.id)
|
|
814
|
-
? "connected"
|
|
815
|
-
: "disconnected",
|
|
816
|
-
templateAgentId: conn.templateAgentId || null,
|
|
817
|
-
botUsername: conn.metadata?.botUsername || null,
|
|
818
|
-
})
|
|
819
|
-
),
|
|
820
|
-
sandboxes: sandboxAgents.map((s: AgentMetadata) => ({
|
|
821
|
-
agentId: s.agentId,
|
|
822
|
-
name: s.name,
|
|
823
|
-
parentConnectionId: s.parentConnectionId || null,
|
|
824
|
-
lastUsedAt: s.lastUsedAt ?? null,
|
|
825
|
-
})),
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
// Auto-register any non-openapi routes so everything shows up in the schema
|
|
830
|
-
registerAutoOpenApiRoutes(app);
|
|
831
|
-
|
|
832
|
-
// OpenAPI Documentation
|
|
833
|
-
app.doc("/api/docs/openapi.json", {
|
|
834
|
-
openapi: "3.0.0",
|
|
835
|
-
info: {
|
|
836
|
-
title: "Lobu API",
|
|
837
|
-
version: "1.0.0",
|
|
838
|
-
description: `
|
|
839
|
-
## Overview
|
|
840
|
-
|
|
841
|
-
The Lobu API allows you to create and interact with AI agents programmatically.
|
|
842
|
-
|
|
843
|
-
## Authentication
|
|
844
|
-
|
|
845
|
-
1. Authenticate the agent-creation request with an admin password or CLI access token
|
|
846
|
-
2. Create an agent with \`POST /api/v1/agents\` to get a worker token
|
|
847
|
-
3. Use the returned worker token as a Bearer token for subsequent agent requests
|
|
848
|
-
|
|
849
|
-
## Quick Start
|
|
850
|
-
|
|
851
|
-
\`\`\`bash
|
|
852
|
-
# 1. Create an agent (authenticate with admin password or CLI token)
|
|
853
|
-
curl -X POST http://localhost:8080/api/v1/agents \\
|
|
854
|
-
-H "Authorization: Bearer $ADMIN_PASSWORD" \\
|
|
855
|
-
-H "Content-Type: application/json" \\
|
|
856
|
-
-d '{"provider": "claude"}'
|
|
857
|
-
|
|
858
|
-
# 2. Send a message (use worker token from step 1)
|
|
859
|
-
curl -X POST http://localhost:8080/api/v1/agents/{agentId}/messages \\
|
|
860
|
-
-H "Authorization: Bearer {token}" \\
|
|
861
|
-
-H "Content-Type: application/json" \\
|
|
862
|
-
-d '{"content": "Hello!"}'
|
|
863
|
-
\`\`\`
|
|
864
|
-
|
|
865
|
-
## MCP Servers
|
|
866
|
-
|
|
867
|
-
Agents can be configured with custom MCP (Model Context Protocol) servers:
|
|
868
|
-
|
|
869
|
-
\`\`\`json
|
|
870
|
-
{
|
|
871
|
-
"mcpServers": {
|
|
872
|
-
"my-http-mcp": { "url": "https://my-mcp.com/sse" },
|
|
873
|
-
"my-stdio-mcp": { "command": "npx", "args": ["-y", "@org/mcp"] }
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
\`\`\`
|
|
877
|
-
`,
|
|
878
|
-
},
|
|
879
|
-
tags: [
|
|
880
|
-
{
|
|
881
|
-
name: "Agents",
|
|
882
|
-
description: "Create, list, update, and delete agents.",
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
name: "Messages",
|
|
886
|
-
description:
|
|
887
|
-
"Send messages to agents and subscribe to real-time events (SSE).",
|
|
888
|
-
},
|
|
889
|
-
{
|
|
890
|
-
name: "Configuration",
|
|
891
|
-
description:
|
|
892
|
-
"Agent configuration — LLM providers, Nix packages, domain grants.",
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
name: "Channels",
|
|
896
|
-
description:
|
|
897
|
-
"Bind agents to messaging platform channels (Slack, Telegram, WhatsApp).",
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: "Connections",
|
|
901
|
-
description:
|
|
902
|
-
"Manage Chat SDK-backed platform connections and their lifecycle.",
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
name: "Schedules",
|
|
906
|
-
description: "Scheduled wakeups and recurring reminders.",
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
name: "History",
|
|
910
|
-
description: "Session messages, stats, and connection status.",
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
name: "Auth",
|
|
914
|
-
description:
|
|
915
|
-
"Provider authentication — API keys, OAuth, device code flows.",
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
name: "Integrations",
|
|
919
|
-
description: "Browse and install skills and MCP servers.",
|
|
920
|
-
},
|
|
921
|
-
],
|
|
922
|
-
servers: [
|
|
923
|
-
{ url: "http://localhost:8080", description: "Local development" },
|
|
924
|
-
],
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
app.get(
|
|
928
|
-
"/api/docs",
|
|
929
|
-
apiReference({
|
|
930
|
-
url: "/api/docs/openapi.json",
|
|
931
|
-
theme: "kepler",
|
|
932
|
-
layout: "modern",
|
|
933
|
-
defaultHttpClient: { targetKey: "js", clientKey: "fetch" },
|
|
934
|
-
})
|
|
935
|
-
);
|
|
936
|
-
logger.debug("API docs enabled at /api/docs");
|
|
937
|
-
|
|
938
|
-
return app;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Start an HTTP server for the gateway Hono app.
|
|
943
|
-
* Used in standalone mode. In embedded mode, the host creates its own server.
|
|
944
|
-
*/
|
|
945
|
-
export function startGatewayServer(app: OpenAPIHono, port = 8080): Server {
|
|
946
|
-
const honoListener = getRequestListener(app.fetch);
|
|
947
|
-
const server = createServer(honoListener);
|
|
948
|
-
server.listen(port);
|
|
949
|
-
logger.debug(`Server listening on port ${port}`);
|
|
950
|
-
return server;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* Handle Express-style handler with Hono context
|
|
955
|
-
*/
|
|
956
|
-
async function handleExpressHandler(c: any, handler: any): Promise<Response> {
|
|
957
|
-
const { req, res, responsePromise } = createExpressCompatObjects(c);
|
|
958
|
-
await handler(req, res);
|
|
959
|
-
return responsePromise;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
* Create Express-compatible request/response objects from Hono context
|
|
964
|
-
*/
|
|
965
|
-
function createExpressCompatObjects(c: any, overridePath?: string) {
|
|
966
|
-
let resolveResponse: (response: Response) => void;
|
|
967
|
-
const responsePromise = new Promise<Response>((resolve) => {
|
|
968
|
-
resolveResponse = resolve;
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
const url = new URL(c.req.url);
|
|
972
|
-
const headers: Record<string, string> = {};
|
|
973
|
-
c.req.raw.headers.forEach((value: string, key: string) => {
|
|
974
|
-
headers[key] = value;
|
|
975
|
-
});
|
|
976
|
-
|
|
977
|
-
// Express-compatible request object
|
|
978
|
-
const req: any = {
|
|
979
|
-
method: c.req.method,
|
|
980
|
-
url: c.req.url,
|
|
981
|
-
path: overridePath || url.pathname,
|
|
982
|
-
headers,
|
|
983
|
-
query: Object.fromEntries(url.searchParams),
|
|
984
|
-
params: c.req.param() || {},
|
|
985
|
-
body: null,
|
|
986
|
-
get: (name: string) => headers[name.toLowerCase()],
|
|
987
|
-
on: () => {
|
|
988
|
-
// Express event listener stub - not used in Hono compat layer
|
|
989
|
-
},
|
|
990
|
-
};
|
|
991
|
-
|
|
992
|
-
// Response state
|
|
993
|
-
let statusCode = 200;
|
|
994
|
-
const responseHeaders = new Headers();
|
|
995
|
-
let isStreaming = false;
|
|
996
|
-
let streamController: ReadableStreamDefaultController<Uint8Array> | null =
|
|
997
|
-
null;
|
|
998
|
-
|
|
999
|
-
// Express-compatible response object
|
|
1000
|
-
const res: any = {
|
|
1001
|
-
statusCode: 200,
|
|
1002
|
-
destroyed: false,
|
|
1003
|
-
writableEnded: false,
|
|
1004
|
-
|
|
1005
|
-
status(code: number) {
|
|
1006
|
-
statusCode = code;
|
|
1007
|
-
this.statusCode = code;
|
|
1008
|
-
return this;
|
|
1009
|
-
},
|
|
1010
|
-
|
|
1011
|
-
setHeader(name: string, value: string) {
|
|
1012
|
-
responseHeaders.set(name, value);
|
|
1013
|
-
return this;
|
|
1014
|
-
},
|
|
1015
|
-
|
|
1016
|
-
set(name: string, value: string) {
|
|
1017
|
-
responseHeaders.set(name, value);
|
|
1018
|
-
return this;
|
|
1019
|
-
},
|
|
1020
|
-
|
|
1021
|
-
json(data: any) {
|
|
1022
|
-
responseHeaders.set("Content-Type", "application/json");
|
|
1023
|
-
resolveResponse?.(
|
|
1024
|
-
new Response(JSON.stringify(data), {
|
|
1025
|
-
status: statusCode,
|
|
1026
|
-
headers: responseHeaders,
|
|
1027
|
-
})
|
|
1028
|
-
);
|
|
1029
|
-
},
|
|
1030
|
-
|
|
1031
|
-
send(data: any) {
|
|
1032
|
-
resolveResponse?.(
|
|
1033
|
-
new Response(data, {
|
|
1034
|
-
status: statusCode,
|
|
1035
|
-
headers: responseHeaders,
|
|
1036
|
-
})
|
|
1037
|
-
);
|
|
1038
|
-
},
|
|
1039
|
-
|
|
1040
|
-
text(data: string) {
|
|
1041
|
-
resolveResponse?.(
|
|
1042
|
-
new Response(data, {
|
|
1043
|
-
status: statusCode,
|
|
1044
|
-
headers: responseHeaders,
|
|
1045
|
-
})
|
|
1046
|
-
);
|
|
1047
|
-
},
|
|
1048
|
-
|
|
1049
|
-
end(data?: any) {
|
|
1050
|
-
this.writableEnded = true;
|
|
1051
|
-
if (isStreaming && streamController) {
|
|
1052
|
-
if (data) {
|
|
1053
|
-
streamController.enqueue(
|
|
1054
|
-
typeof data === "string" ? new TextEncoder().encode(data) : data
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
streamController.close();
|
|
1058
|
-
} else {
|
|
1059
|
-
resolveResponse?.(
|
|
1060
|
-
new Response(data || null, {
|
|
1061
|
-
status: statusCode,
|
|
1062
|
-
headers: responseHeaders,
|
|
1063
|
-
})
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
},
|
|
1067
|
-
|
|
1068
|
-
write(chunk: any) {
|
|
1069
|
-
if (!isStreaming) {
|
|
1070
|
-
isStreaming = true;
|
|
1071
|
-
const stream = new ReadableStream({
|
|
1072
|
-
start(controller) {
|
|
1073
|
-
streamController = controller;
|
|
1074
|
-
if (chunk) {
|
|
1075
|
-
controller.enqueue(
|
|
1076
|
-
typeof chunk === "string"
|
|
1077
|
-
? new TextEncoder().encode(chunk)
|
|
1078
|
-
: chunk
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
},
|
|
1082
|
-
});
|
|
1083
|
-
resolveResponse?.(
|
|
1084
|
-
new Response(stream, {
|
|
1085
|
-
status: statusCode,
|
|
1086
|
-
headers: responseHeaders,
|
|
1087
|
-
})
|
|
1088
|
-
);
|
|
1089
|
-
} else if (streamController) {
|
|
1090
|
-
streamController.enqueue(
|
|
1091
|
-
typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
return true;
|
|
1095
|
-
},
|
|
1096
|
-
|
|
1097
|
-
flushHeaders() {
|
|
1098
|
-
// No-op for compatibility
|
|
1099
|
-
},
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
|
-
// Parse body for POST/PUT/PATCH
|
|
1103
|
-
if (["POST", "PUT", "PATCH"].includes(c.req.method)) {
|
|
1104
|
-
const contentType = c.req.header("content-type") || "";
|
|
1105
|
-
c.req.raw
|
|
1106
|
-
.clone()
|
|
1107
|
-
.arrayBuffer()
|
|
1108
|
-
.then((buffer: ArrayBuffer) => {
|
|
1109
|
-
if (contentType.includes("application/json")) {
|
|
1110
|
-
try {
|
|
1111
|
-
req.body = JSON.parse(new TextDecoder().decode(buffer));
|
|
1112
|
-
} catch {
|
|
1113
|
-
req.body = buffer;
|
|
1114
|
-
}
|
|
1115
|
-
} else {
|
|
1116
|
-
req.body = buffer;
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
return { req, res, responsePromise };
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
/**
|
|
1125
|
-
* Create Express-like adapter for compatibility with module registry
|
|
1126
|
-
*/
|
|
1127
|
-
function createExpressAdapter(honoApp: any) {
|
|
1128
|
-
return {
|
|
1129
|
-
get: (path: string, ...handlers: any[]) => {
|
|
1130
|
-
const handler = handlers[handlers.length - 1];
|
|
1131
|
-
honoApp.get(path, (c: any) => handleExpressHandler(c, handler));
|
|
1132
|
-
},
|
|
1133
|
-
post: (path: string, ...handlers: any[]) => {
|
|
1134
|
-
const handler = handlers[handlers.length - 1];
|
|
1135
|
-
honoApp.post(path, (c: any) => handleExpressHandler(c, handler));
|
|
1136
|
-
},
|
|
1137
|
-
put: (path: string, ...handlers: any[]) => {
|
|
1138
|
-
const handler = handlers[handlers.length - 1];
|
|
1139
|
-
honoApp.put(path, (c: any) => handleExpressHandler(c, handler));
|
|
1140
|
-
},
|
|
1141
|
-
delete: (path: string, ...handlers: any[]) => {
|
|
1142
|
-
const handler = handlers[handlers.length - 1];
|
|
1143
|
-
honoApp.delete(path, (c: any) => handleExpressHandler(c, handler));
|
|
1144
|
-
},
|
|
1145
|
-
use: (pathOrHandler: any, handler?: any) => {
|
|
1146
|
-
if (typeof pathOrHandler === "function") {
|
|
1147
|
-
// Global middleware - skip for now
|
|
1148
|
-
} else if (handler) {
|
|
1149
|
-
honoApp.all(`${pathOrHandler}/*`, (c: any) =>
|
|
1150
|
-
handleExpressHandler(c, handler)
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
},
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* Start the gateway with the provided configuration
|
|
1159
|
-
*/
|
|
1160
|
-
export async function startGateway(config: GatewayConfig): Promise<void> {
|
|
1161
|
-
logger.info("Starting Lobu Gateway");
|
|
1162
|
-
|
|
1163
|
-
// Start filtering proxy for worker network isolation (if enabled)
|
|
1164
|
-
const { startFilteringProxy } = await import("../proxy/proxy-manager");
|
|
1165
|
-
await startFilteringProxy();
|
|
1166
|
-
|
|
1167
|
-
// Import dependencies
|
|
1168
|
-
const { Orchestrator } = await import("../orchestration");
|
|
1169
|
-
const { Gateway } = await import("../gateway-main");
|
|
1170
|
-
|
|
1171
|
-
// Create and start orchestrator
|
|
1172
|
-
logger.debug("Creating orchestrator", { mode: process.env.DEPLOYMENT_MODE });
|
|
1173
|
-
const orchestrator = new Orchestrator(config.orchestration);
|
|
1174
|
-
await orchestrator.start();
|
|
1175
|
-
logger.debug("Orchestrator started");
|
|
1176
|
-
|
|
1177
|
-
// Create Gateway
|
|
1178
|
-
const gateway = new Gateway(config);
|
|
1179
|
-
|
|
1180
|
-
// Register API platform (always enabled)
|
|
1181
|
-
const { ApiPlatform } = await import("../api");
|
|
1182
|
-
const apiPlatform = new ApiPlatform();
|
|
1183
|
-
gateway.registerPlatform(apiPlatform);
|
|
1184
|
-
logger.debug("API platform registered");
|
|
1185
|
-
|
|
1186
|
-
// Start gateway
|
|
1187
|
-
await gateway.start();
|
|
1188
|
-
logger.debug("Gateway started");
|
|
1189
|
-
|
|
1190
|
-
// Get core services
|
|
1191
|
-
const coreServices = gateway.getCoreServices();
|
|
1192
|
-
|
|
1193
|
-
// Wire grant store to HTTP proxy for domain grant checks
|
|
1194
|
-
const grantStore = coreServices.getGrantStore();
|
|
1195
|
-
if (grantStore) {
|
|
1196
|
-
const { setProxyGrantStore } = await import("../proxy/http-proxy");
|
|
1197
|
-
setProxyGrantStore(grantStore);
|
|
1198
|
-
logger.debug("Grant store connected to HTTP proxy");
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// Inject core services into orchestrator (provider modules carry their own credential stores)
|
|
1202
|
-
await orchestrator.injectCoreServices(
|
|
1203
|
-
coreServices.getQueue().getRedisClient(),
|
|
1204
|
-
coreServices.getProviderCatalogService(),
|
|
1205
|
-
coreServices.getGrantStore() ?? undefined
|
|
1206
|
-
);
|
|
1207
|
-
logger.debug("Orchestrator configured with core services");
|
|
1208
|
-
|
|
1209
|
-
// Initialize Chat SDK connection manager (API-driven platform connections)
|
|
1210
|
-
const { ChatInstanceManager, ChatResponseBridge } = await import(
|
|
1211
|
-
"../connections"
|
|
1212
|
-
);
|
|
1213
|
-
const chatInstanceManager = new ChatInstanceManager();
|
|
1214
|
-
try {
|
|
1215
|
-
await chatInstanceManager.initialize(coreServices);
|
|
1216
|
-
|
|
1217
|
-
// Register chat platform adapters (delegates to ChatInstanceManager)
|
|
1218
|
-
for (const adapter of chatInstanceManager.createPlatformAdapters()) {
|
|
1219
|
-
gateway.registerPlatform(adapter);
|
|
1220
|
-
}
|
|
1221
|
-
logger.debug("ChatInstanceManager initialized");
|
|
1222
|
-
|
|
1223
|
-
// Seed connections from file-loaded agents (file-first architecture)
|
|
1224
|
-
const fileLoadedAgents = coreServices.getFileLoadedAgents();
|
|
1225
|
-
if (fileLoadedAgents.length > 0) {
|
|
1226
|
-
for (const agent of fileLoadedAgents) {
|
|
1227
|
-
if (!agent.connections?.length) continue;
|
|
1228
|
-
for (const conn of agent.connections) {
|
|
1229
|
-
const existing = await chatInstanceManager.listConnections({
|
|
1230
|
-
platform: conn.type,
|
|
1231
|
-
templateAgentId: agent.agentId,
|
|
1232
|
-
});
|
|
1233
|
-
if (existing.length > 0) continue;
|
|
1234
|
-
try {
|
|
1235
|
-
await chatInstanceManager.addConnection(
|
|
1236
|
-
conn.type,
|
|
1237
|
-
agent.agentId,
|
|
1238
|
-
{ platform: conn.type as any, ...conn.config },
|
|
1239
|
-
{ allowGroups: true }
|
|
1240
|
-
);
|
|
1241
|
-
logger.debug(
|
|
1242
|
-
`Created ${conn.type} connection for agent "${agent.agentId}"`
|
|
1243
|
-
);
|
|
1244
|
-
} catch (err) {
|
|
1245
|
-
logger.error(
|
|
1246
|
-
`Failed to create ${conn.type} connection for agent "${agent.agentId}"`,
|
|
1247
|
-
{ error: err instanceof Error ? err.message : String(err) }
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// Wire ChatResponseBridge into unified thread consumer
|
|
1255
|
-
const unifiedConsumer = gateway.getUnifiedConsumer();
|
|
1256
|
-
if (unifiedConsumer) {
|
|
1257
|
-
const chatResponseBridge = new ChatResponseBridge(chatInstanceManager);
|
|
1258
|
-
unifiedConsumer.setChatResponseBridge(chatResponseBridge);
|
|
1259
|
-
logger.debug("ChatResponseBridge wired to unified thread consumer");
|
|
1260
|
-
}
|
|
1261
|
-
} catch (error) {
|
|
1262
|
-
logger.warn(
|
|
1263
|
-
{ error: String(error) },
|
|
1264
|
-
"ChatInstanceManager initialization failed — connections feature disabled"
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// Setup server on port 8080 (single port for all HTTP traffic)
|
|
1269
|
-
if (!httpServer) {
|
|
1270
|
-
const app = createGatewayApp({
|
|
1271
|
-
secretProxy: coreServices.getSecretProxy(),
|
|
1272
|
-
workerGateway: coreServices.getWorkerGateway(),
|
|
1273
|
-
mcpProxy: coreServices.getMcpProxy(),
|
|
1274
|
-
interactionService: coreServices.getInteractionService(),
|
|
1275
|
-
platformRegistry: gateway.getPlatformRegistry(),
|
|
1276
|
-
coreServices,
|
|
1277
|
-
chatInstanceManager,
|
|
1278
|
-
});
|
|
1279
|
-
httpServer = startGatewayServer(app);
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
logger.info("Lobu Gateway is running!");
|
|
1283
|
-
|
|
1284
|
-
// Setup graceful shutdown
|
|
1285
|
-
const cleanup = async () => {
|
|
1286
|
-
logger.info("Shutting down gateway...");
|
|
1287
|
-
|
|
1288
|
-
// Hard deadline: force exit after 30s if graceful shutdown stalls
|
|
1289
|
-
const hardDeadline = setTimeout(() => {
|
|
1290
|
-
logger.error("Graceful shutdown timed out after 30s, forcing exit");
|
|
1291
|
-
process.exit(1);
|
|
1292
|
-
}, 30_000);
|
|
1293
|
-
hardDeadline.unref();
|
|
1294
|
-
|
|
1295
|
-
await chatInstanceManager.shutdown();
|
|
1296
|
-
await orchestrator.stop();
|
|
1297
|
-
await gateway.stop();
|
|
1298
|
-
if (httpServer) {
|
|
1299
|
-
httpServer.close();
|
|
1300
|
-
}
|
|
1301
|
-
logger.info("Gateway shutdown complete");
|
|
1302
|
-
process.exit(0);
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
|
-
process.on("SIGINT", cleanup);
|
|
1306
|
-
process.on("SIGTERM", cleanup);
|
|
1307
|
-
|
|
1308
|
-
process.on("SIGUSR1", () => {
|
|
1309
|
-
const status = gateway.getStatus();
|
|
1310
|
-
logger.info("Health check:", JSON.stringify(status, null, 2));
|
|
1311
|
-
});
|
|
1312
|
-
}
|