@lobu/gateway 3.0.8 → 3.0.12

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.
Files changed (219) hide show
  1. package/dist/api/platform.d.ts.map +1 -1
  2. package/dist/api/platform.js +8 -26
  3. package/dist/api/platform.js.map +1 -1
  4. package/dist/auth/mcp/proxy.d.ts +14 -0
  5. package/dist/auth/mcp/proxy.d.ts.map +1 -1
  6. package/dist/auth/mcp/proxy.js +149 -13
  7. package/dist/auth/mcp/proxy.js.map +1 -1
  8. package/dist/cli/gateway.d.ts.map +1 -1
  9. package/dist/cli/gateway.js +29 -0
  10. package/dist/cli/gateway.js.map +1 -1
  11. package/dist/cli/index.js +2 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/connections/chat-instance-manager.d.ts.map +1 -1
  14. package/dist/connections/chat-instance-manager.js +2 -1
  15. package/dist/connections/chat-instance-manager.js.map +1 -1
  16. package/dist/connections/interaction-bridge.d.ts +9 -2
  17. package/dist/connections/interaction-bridge.d.ts.map +1 -1
  18. package/dist/connections/interaction-bridge.js +132 -230
  19. package/dist/connections/interaction-bridge.js.map +1 -1
  20. package/dist/connections/message-handler-bridge.d.ts.map +1 -1
  21. package/dist/connections/message-handler-bridge.js +44 -26
  22. package/dist/connections/message-handler-bridge.js.map +1 -1
  23. package/dist/interactions.d.ts +9 -43
  24. package/dist/interactions.d.ts.map +1 -1
  25. package/dist/interactions.js +10 -52
  26. package/dist/interactions.js.map +1 -1
  27. package/dist/orchestration/base-deployment-manager.js +7 -7
  28. package/dist/orchestration/base-deployment-manager.js.map +1 -1
  29. package/dist/platform/unified-thread-consumer.d.ts.map +1 -1
  30. package/dist/platform/unified-thread-consumer.js +38 -34
  31. package/dist/platform/unified-thread-consumer.js.map +1 -1
  32. package/dist/routes/public/agent.d.ts +4 -0
  33. package/dist/routes/public/agent.d.ts.map +1 -1
  34. package/dist/routes/public/agent.js +21 -0
  35. package/dist/routes/public/agent.js.map +1 -1
  36. package/dist/services/core-services.d.ts.map +1 -1
  37. package/dist/services/core-services.js +4 -0
  38. package/dist/services/core-services.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/__tests__/agent-config-routes.test.ts +0 -254
  41. package/src/__tests__/agent-history-routes.test.ts +0 -72
  42. package/src/__tests__/agent-routes.test.ts +0 -68
  43. package/src/__tests__/agent-schedules-routes.test.ts +0 -59
  44. package/src/__tests__/agent-settings-store.test.ts +0 -323
  45. package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
  46. package/src/__tests__/bedrock-openai-service.test.ts +0 -157
  47. package/src/__tests__/bedrock-provider-module.test.ts +0 -56
  48. package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
  49. package/src/__tests__/chat-response-bridge.test.ts +0 -131
  50. package/src/__tests__/config-memory-plugins.test.ts +0 -92
  51. package/src/__tests__/config-request-store.test.ts +0 -127
  52. package/src/__tests__/connection-routes.test.ts +0 -144
  53. package/src/__tests__/core-services-store-selection.test.ts +0 -92
  54. package/src/__tests__/docker-deployment.test.ts +0 -1211
  55. package/src/__tests__/embedded-deployment.test.ts +0 -342
  56. package/src/__tests__/grant-store.test.ts +0 -148
  57. package/src/__tests__/http-proxy.test.ts +0 -281
  58. package/src/__tests__/instruction-service.test.ts +0 -37
  59. package/src/__tests__/link-buttons.test.ts +0 -112
  60. package/src/__tests__/lobu.test.ts +0 -32
  61. package/src/__tests__/mcp-config-service.test.ts +0 -347
  62. package/src/__tests__/mcp-proxy.test.ts +0 -694
  63. package/src/__tests__/message-handler-bridge.test.ts +0 -17
  64. package/src/__tests__/model-selection.test.ts +0 -172
  65. package/src/__tests__/oauth-templates.test.ts +0 -39
  66. package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
  67. package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
  68. package/src/__tests__/provider-inheritance.test.ts +0 -212
  69. package/src/__tests__/routes/cli-auth.test.ts +0 -337
  70. package/src/__tests__/routes/interactions.test.ts +0 -121
  71. package/src/__tests__/secret-proxy.test.ts +0 -85
  72. package/src/__tests__/session-manager.test.ts +0 -572
  73. package/src/__tests__/setup.ts +0 -133
  74. package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
  75. package/src/__tests__/slack-routes.test.ts +0 -161
  76. package/src/__tests__/system-config-resolver.test.ts +0 -75
  77. package/src/__tests__/system-message-limiter.test.ts +0 -89
  78. package/src/__tests__/system-skills-service.test.ts +0 -362
  79. package/src/__tests__/transcription-service.test.ts +0 -222
  80. package/src/__tests__/utils/rate-limiter.test.ts +0 -102
  81. package/src/__tests__/worker-connection-manager.test.ts +0 -497
  82. package/src/__tests__/worker-job-router.test.ts +0 -722
  83. package/src/api/index.ts +0 -1
  84. package/src/api/platform.ts +0 -292
  85. package/src/api/response-renderer.ts +0 -157
  86. package/src/auth/agent-metadata-store.ts +0 -168
  87. package/src/auth/api-auth-middleware.ts +0 -69
  88. package/src/auth/api-key-provider-module.ts +0 -213
  89. package/src/auth/base-provider-module.ts +0 -201
  90. package/src/auth/bedrock/provider-module.ts +0 -110
  91. package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
  92. package/src/auth/chatgpt/device-code-client.ts +0 -218
  93. package/src/auth/chatgpt/index.ts +0 -1
  94. package/src/auth/claude/oauth-module.ts +0 -280
  95. package/src/auth/cli/token-service.ts +0 -249
  96. package/src/auth/external/client.ts +0 -560
  97. package/src/auth/external/device-code-client.ts +0 -235
  98. package/src/auth/mcp/config-service.ts +0 -420
  99. package/src/auth/mcp/proxy.ts +0 -1086
  100. package/src/auth/mcp/string-substitution.ts +0 -17
  101. package/src/auth/mcp/tool-cache.ts +0 -90
  102. package/src/auth/oauth/base-client.ts +0 -267
  103. package/src/auth/oauth/client.ts +0 -153
  104. package/src/auth/oauth/credentials.ts +0 -7
  105. package/src/auth/oauth/providers.ts +0 -69
  106. package/src/auth/oauth/state-store.ts +0 -150
  107. package/src/auth/oauth-templates.ts +0 -179
  108. package/src/auth/provider-catalog.ts +0 -220
  109. package/src/auth/provider-model-options.ts +0 -41
  110. package/src/auth/settings/agent-settings-store.ts +0 -565
  111. package/src/auth/settings/auth-profiles-manager.ts +0 -216
  112. package/src/auth/settings/index.ts +0 -12
  113. package/src/auth/settings/model-preference-store.ts +0 -52
  114. package/src/auth/settings/model-selection.ts +0 -135
  115. package/src/auth/settings/resolved-settings-view.ts +0 -298
  116. package/src/auth/settings/template-utils.ts +0 -44
  117. package/src/auth/settings/token-service.ts +0 -88
  118. package/src/auth/system-env-store.ts +0 -98
  119. package/src/auth/user-agents-store.ts +0 -68
  120. package/src/channels/binding-service.ts +0 -214
  121. package/src/channels/index.ts +0 -4
  122. package/src/cli/gateway.ts +0 -1312
  123. package/src/cli/index.ts +0 -74
  124. package/src/commands/built-in-commands.ts +0 -80
  125. package/src/commands/command-dispatcher.ts +0 -94
  126. package/src/commands/command-reply-adapters.ts +0 -27
  127. package/src/config/file-loader.ts +0 -618
  128. package/src/config/index.ts +0 -588
  129. package/src/config/network-allowlist.ts +0 -71
  130. package/src/connections/chat-instance-manager.ts +0 -1284
  131. package/src/connections/chat-response-bridge.ts +0 -618
  132. package/src/connections/index.ts +0 -7
  133. package/src/connections/interaction-bridge.ts +0 -831
  134. package/src/connections/message-handler-bridge.ts +0 -415
  135. package/src/connections/platform-auth-methods.ts +0 -15
  136. package/src/connections/types.ts +0 -84
  137. package/src/gateway/connection-manager.ts +0 -291
  138. package/src/gateway/index.ts +0 -698
  139. package/src/gateway/job-router.ts +0 -201
  140. package/src/gateway-main.ts +0 -200
  141. package/src/index.ts +0 -41
  142. package/src/infrastructure/queue/index.ts +0 -12
  143. package/src/infrastructure/queue/queue-producer.ts +0 -148
  144. package/src/infrastructure/queue/redis-queue.ts +0 -361
  145. package/src/infrastructure/queue/types.ts +0 -133
  146. package/src/infrastructure/redis/system-message-limiter.ts +0 -94
  147. package/src/interactions/config-request-store.ts +0 -198
  148. package/src/interactions.ts +0 -363
  149. package/src/lobu.ts +0 -311
  150. package/src/metrics/prometheus.ts +0 -159
  151. package/src/modules/module-system.ts +0 -179
  152. package/src/orchestration/base-deployment-manager.ts +0 -900
  153. package/src/orchestration/deployment-utils.ts +0 -98
  154. package/src/orchestration/impl/docker-deployment.ts +0 -620
  155. package/src/orchestration/impl/embedded-deployment.ts +0 -268
  156. package/src/orchestration/impl/index.ts +0 -8
  157. package/src/orchestration/impl/k8s/deployment.ts +0 -1061
  158. package/src/orchestration/impl/k8s/helpers.ts +0 -610
  159. package/src/orchestration/impl/k8s/index.ts +0 -1
  160. package/src/orchestration/index.ts +0 -333
  161. package/src/orchestration/message-consumer.ts +0 -584
  162. package/src/orchestration/scheduled-wakeup.ts +0 -704
  163. package/src/permissions/approval-policy.ts +0 -36
  164. package/src/permissions/grant-store.ts +0 -219
  165. package/src/platform/file-handler.ts +0 -66
  166. package/src/platform/link-buttons.ts +0 -57
  167. package/src/platform/renderer-utils.ts +0 -44
  168. package/src/platform/response-renderer.ts +0 -84
  169. package/src/platform/unified-thread-consumer.ts +0 -187
  170. package/src/platform.ts +0 -318
  171. package/src/proxy/http-proxy.ts +0 -752
  172. package/src/proxy/proxy-manager.ts +0 -81
  173. package/src/proxy/secret-proxy.ts +0 -402
  174. package/src/proxy/token-refresh-job.ts +0 -143
  175. package/src/routes/internal/audio.ts +0 -141
  176. package/src/routes/internal/device-auth.ts +0 -652
  177. package/src/routes/internal/files.ts +0 -226
  178. package/src/routes/internal/history.ts +0 -69
  179. package/src/routes/internal/images.ts +0 -127
  180. package/src/routes/internal/interactions.ts +0 -84
  181. package/src/routes/internal/middleware.ts +0 -23
  182. package/src/routes/internal/schedule.ts +0 -226
  183. package/src/routes/internal/types.ts +0 -22
  184. package/src/routes/openapi-auto.ts +0 -239
  185. package/src/routes/public/agent-access.ts +0 -23
  186. package/src/routes/public/agent-config.ts +0 -675
  187. package/src/routes/public/agent-history.ts +0 -422
  188. package/src/routes/public/agent-schedules.ts +0 -296
  189. package/src/routes/public/agent.ts +0 -1086
  190. package/src/routes/public/agents.ts +0 -373
  191. package/src/routes/public/channels.ts +0 -191
  192. package/src/routes/public/cli-auth.ts +0 -896
  193. package/src/routes/public/connections.ts +0 -574
  194. package/src/routes/public/landing.ts +0 -16
  195. package/src/routes/public/oauth.ts +0 -147
  196. package/src/routes/public/settings-auth.ts +0 -104
  197. package/src/routes/public/slack.ts +0 -173
  198. package/src/routes/shared/agent-ownership.ts +0 -101
  199. package/src/routes/shared/token-verifier.ts +0 -34
  200. package/src/services/bedrock-model-catalog.ts +0 -217
  201. package/src/services/bedrock-openai-service.ts +0 -658
  202. package/src/services/core-services.ts +0 -1072
  203. package/src/services/image-generation-service.ts +0 -257
  204. package/src/services/instruction-service.ts +0 -318
  205. package/src/services/mcp-registry.ts +0 -94
  206. package/src/services/platform-helpers.ts +0 -287
  207. package/src/services/session-manager.ts +0 -262
  208. package/src/services/settings-resolver.ts +0 -74
  209. package/src/services/system-config-resolver.ts +0 -89
  210. package/src/services/system-skills-service.ts +0 -229
  211. package/src/services/transcription-service.ts +0 -684
  212. package/src/session.ts +0 -110
  213. package/src/spaces/index.ts +0 -1
  214. package/src/spaces/space-resolver.ts +0 -17
  215. package/src/stores/in-memory-agent-store.ts +0 -403
  216. package/src/stores/redis-agent-store.ts +0 -279
  217. package/src/utils/public-url.ts +0 -44
  218. package/src/utils/rate-limiter.ts +0 -94
  219. package/tsconfig.json +0 -33
@@ -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
- }