@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,698 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import type {
4
- ConfigProviderMeta,
5
- InstructionContext,
6
- WorkerTokenData,
7
- } from "@lobu/core";
8
- import { createLogger, encrypt, verifyWorkerToken } from "@lobu/core";
9
- import type { Context } from "hono";
10
- import { Hono } from "hono";
11
- import { stream } from "hono/streaming";
12
- import type { ApiKeyProviderModule } from "../auth/api-key-provider-module";
13
- import type { McpConfigService } from "../auth/mcp/config-service";
14
- import type { McpProxy } from "../auth/mcp/proxy";
15
- import type { McpTool } from "../auth/mcp/tool-cache";
16
- import type { ProviderCatalogService } from "../auth/provider-catalog";
17
- import { resolveEffectiveModelRef } from "../auth/settings/model-selection";
18
- import type { IMessageQueue } from "../infrastructure/queue";
19
- import type { InstructionService } from "../services/instruction-service";
20
- import type { SettingsResolver } from "../services/settings-resolver";
21
- import type { SystemSkillsService } from "../services/system-skills-service";
22
- import type { ISessionManager } from "../session";
23
- import { type SSEWriter, WorkerConnectionManager } from "./connection-manager";
24
- import { WorkerJobRouter } from "./job-router";
25
-
26
- const logger = createLogger("worker-gateway");
27
-
28
- /**
29
- * Worker Gateway - SSE and HTTP endpoints for worker communication
30
- * Workers connect via SSE to receive jobs, send responses via HTTP POST
31
- * Uses encrypted tokens for authentication and routing
32
- */
33
- export class WorkerGateway {
34
- private app: Hono;
35
- private connectionManager: WorkerConnectionManager;
36
- private jobRouter: WorkerJobRouter;
37
- private queue: IMessageQueue;
38
- private mcpConfigService: McpConfigService;
39
- private instructionService: InstructionService;
40
- private publicGatewayUrl: string;
41
- private mcpProxy?: McpProxy;
42
- private providerCatalogService?: ProviderCatalogService;
43
- private settingsResolver?: SettingsResolver;
44
- private systemSkillsService?: SystemSkillsService;
45
-
46
- constructor(
47
- queue: IMessageQueue,
48
- publicGatewayUrl: string,
49
- sessionManager: ISessionManager,
50
- mcpConfigService: McpConfigService,
51
- instructionService: InstructionService,
52
- mcpProxy?: McpProxy,
53
- providerCatalogService?: ProviderCatalogService,
54
- settingsResolver?: SettingsResolver,
55
- systemSkillsService?: SystemSkillsService
56
- ) {
57
- this.queue = queue;
58
- this.publicGatewayUrl = publicGatewayUrl;
59
- this.connectionManager = new WorkerConnectionManager();
60
- this.jobRouter = new WorkerJobRouter(
61
- queue,
62
- this.connectionManager,
63
- sessionManager
64
- );
65
- this.mcpConfigService = mcpConfigService;
66
- this.instructionService = instructionService;
67
- this.mcpProxy = mcpProxy;
68
- this.providerCatalogService = providerCatalogService;
69
- this.settingsResolver = settingsResolver;
70
- this.systemSkillsService = systemSkillsService;
71
-
72
- // Setup Hono app
73
- this.app = new Hono();
74
- this.setupRoutes();
75
- }
76
-
77
- /**
78
- * Get the Hono app
79
- */
80
- getApp(): Hono {
81
- return this.app;
82
- }
83
-
84
- /**
85
- * Get the connection manager (for sending SSE notifications from external routes)
86
- */
87
- getConnectionManager(): WorkerConnectionManager {
88
- return this.connectionManager;
89
- }
90
-
91
- /**
92
- * Setup routes on Hono app
93
- */
94
- private setupRoutes() {
95
- // SSE endpoint for workers to receive jobs
96
- // Routes are mounted at /worker, so paths here should be relative
97
- this.app.get("/stream", (c) => this.handleStreamConnection(c));
98
-
99
- // HTTP POST endpoint for workers to send responses
100
- this.app.post("/response", (c) => this.handleWorkerResponse(c));
101
-
102
- // Unified session context endpoint (includes MCP + instructions)
103
- this.app.get("/session-context", (c) =>
104
- this.handleSessionContextRequest(c)
105
- );
106
-
107
- logger.debug("Worker gateway routes registered");
108
- }
109
-
110
- /**
111
- * Handle SSE connection from worker
112
- */
113
- private async handleStreamConnection(c: Context): Promise<Response> {
114
- const auth = this.authenticateWorker(c);
115
- if (!auth) {
116
- return c.json({ error: "Invalid token" }, 401);
117
- }
118
-
119
- const { deploymentName, userId, conversationId, agentId } =
120
- auth.tokenData as any;
121
- if (!conversationId) {
122
- return c.json({ error: "Invalid token (missing conversationId)" }, 401);
123
- }
124
-
125
- // Extract httpPort from query params (worker HTTP server registration)
126
- const httpPortParam = c.req.query("httpPort");
127
- const httpPort = httpPortParam ? parseInt(httpPortParam, 10) : undefined;
128
-
129
- // Create an SSE stream
130
- return stream(c, async (streamWriter) => {
131
- let isClosed = false;
132
-
133
- // Create an SSE writer adapter
134
- const sseWriter: SSEWriter = {
135
- write: (data: string): boolean => {
136
- try {
137
- void streamWriter.write(data);
138
- return true;
139
- } catch {
140
- return false;
141
- }
142
- },
143
- end: () => {
144
- try {
145
- streamWriter.close();
146
- } catch {
147
- // Already closed
148
- }
149
- },
150
- onClose: (callback: () => void) => {
151
- streamWriter.onAbort(() => {
152
- isClosed = true;
153
- callback();
154
- });
155
- },
156
- };
157
-
158
- // Set SSE headers
159
- c.header("Content-Type", "text/event-stream");
160
- c.header("Cache-Control", "no-cache");
161
- c.header("Connection", "keep-alive");
162
- c.header("X-Accel-Buffering", "no");
163
-
164
- // Clean up stale state before registering new connection.
165
- // When a container dies without cleanly closing its TCP socket,
166
- // the old SSE connection may still appear valid. Pause the BullMQ
167
- // worker first to prevent it from sending jobs to the dead connection,
168
- // then remove the stale connection so any in-flight handleJob will
169
- // fail and trigger a retry against the new connection.
170
- await this.jobRouter.pauseWorker(deploymentName);
171
- if (this.connectionManager.isConnected(deploymentName)) {
172
- logger.info(
173
- `Cleaning up stale connection for ${deploymentName} before new SSE`
174
- );
175
- // Intentionally no expectedWriter — always evict the old connection
176
- this.connectionManager.removeConnection(deploymentName);
177
- }
178
-
179
- // Register new (live) connection
180
- this.connectionManager.addConnection(
181
- deploymentName,
182
- userId,
183
- conversationId,
184
- agentId || "",
185
- sseWriter,
186
- httpPort
187
- );
188
-
189
- // Register BullMQ worker (idempotent) and resume job processing
190
- await this.jobRouter.registerWorker(deploymentName);
191
- await this.jobRouter.resumeWorker(deploymentName);
192
-
193
- // Handle client disconnect — only act if this is still the active writer
194
- sseWriter.onClose(() => {
195
- const current = this.connectionManager.getConnection(deploymentName);
196
- if (current && current.writer !== sseWriter) {
197
- logger.debug(
198
- `Ignoring stale disconnect for ${deploymentName} (replaced by newer SSE)`
199
- );
200
- return;
201
- }
202
- this.jobRouter.pauseWorker(deploymentName).catch((err) => {
203
- logger.error(`Failed to pause worker ${deploymentName}:`, err);
204
- });
205
- this.connectionManager.removeConnection(deploymentName);
206
- });
207
-
208
- // Keep the connection open until the stream is actually aborted.
209
- while (!isClosed) {
210
- await streamWriter.sleep(1000);
211
- }
212
- });
213
- }
214
-
215
- /**
216
- * Handle HTTP response from worker
217
- */
218
- private async handleWorkerResponse(c: Context): Promise<Response> {
219
- const auth = this.authenticateWorker(c);
220
- if (!auth) {
221
- return c.json({ error: "Invalid token" }, 401);
222
- }
223
-
224
- const { deploymentName } = auth.tokenData;
225
-
226
- // Update connection activity
227
- this.connectionManager.touchConnection(deploymentName);
228
-
229
- try {
230
- const body = await c.req.json();
231
- const { jobId, ...responseData } = body;
232
- const enrichedResponse =
233
- auth.tokenData.connectionId &&
234
- (!responseData.platformMetadata ||
235
- typeof responseData.platformMetadata === "object")
236
- ? {
237
- ...responseData,
238
- platformMetadata: {
239
- ...(responseData.platformMetadata || {}),
240
- connectionId: auth.tokenData.connectionId,
241
- },
242
- }
243
- : responseData;
244
-
245
- // Acknowledge job completion if jobId provided
246
- if (jobId) {
247
- this.jobRouter.acknowledgeJob(jobId);
248
- }
249
-
250
- // Delivery receipts (worker ACKs) have no message payload — just acknowledge and return
251
- if (enrichedResponse.received) {
252
- if (enrichedResponse.heartbeat) {
253
- // touchConnection already ran above for all /worker/response calls,
254
- // keeping this worker alive in stale-cleanup.
255
- logger.debug(
256
- `[WORKER-GATEWAY] Received heartbeat ACK from ${deploymentName}`
257
- );
258
- }
259
- return c.json({ success: true });
260
- }
261
-
262
- // Log for debugging
263
- logger.info(
264
- `[WORKER-GATEWAY] Received response with fields: ${Object.keys(enrichedResponse).join(", ")}`
265
- );
266
- if (enrichedResponse.delta) {
267
- logger.info(
268
- `[WORKER-GATEWAY] Stream delta: deltaLength=${enrichedResponse.delta.length}`
269
- );
270
- }
271
-
272
- // Send response to thread_response queue
273
- await this.queue.send("thread_response", enrichedResponse);
274
-
275
- return c.json({ success: true });
276
- } catch (error) {
277
- logger.error(`Error handling worker response: ${error}`);
278
- return c.json({ error: "Failed to process response" }, 500);
279
- }
280
- }
281
-
282
- /**
283
- * Unified session context endpoint
284
- */
285
- private async handleSessionContextRequest(c: Context): Promise<Response> {
286
- if (!this.mcpConfigService || !this.instructionService) {
287
- return c.json({ error: "session_context_unavailable" }, 503);
288
- }
289
-
290
- const auth = this.authenticateWorker(c);
291
- if (!auth) {
292
- return c.json({ error: "Invalid token" }, 401);
293
- }
294
-
295
- try {
296
- const {
297
- userId,
298
- platform,
299
- sessionKey,
300
- conversationId,
301
- agentId,
302
- deploymentName,
303
- } = auth.tokenData;
304
- const baseUrl = this.getRequestBaseUrl(c);
305
- if (!conversationId) {
306
- return c.json({ error: "Invalid token (missing conversationId)" }, 401);
307
- }
308
-
309
- // Build instruction context
310
- const instructionContext: InstructionContext = {
311
- userId,
312
- agentId: agentId || "",
313
- sessionKey: sessionKey || "",
314
- workingDirectory: "/workspace",
315
- availableProjects: [],
316
- };
317
-
318
- // Build settings URL as a short-lived claim link so platform users
319
- // can open it without a pre-existing browser session.
320
- const CLAIM_TTL_MS = 10 * 60 * 1000; // 10 minutes
321
- const claimToken = encrypt(
322
- JSON.stringify({
323
- userId,
324
- platform: platform || "unknown",
325
- agentId: agentId || undefined,
326
- exp: Date.now() + CLAIM_TTL_MS,
327
- })
328
- );
329
- const settingsUrl = new URL("/connect/claim", baseUrl);
330
- settingsUrl.searchParams.set("claim", claimToken);
331
- if (agentId) {
332
- settingsUrl.searchParams.set("agent", agentId);
333
- }
334
-
335
- // Fetch MCP config and session context in parallel
336
- const [mcpConfig, contextData] = await Promise.all([
337
- this.mcpConfigService.getWorkerConfig({
338
- baseUrl,
339
- workerToken: auth.token,
340
- deploymentName,
341
- }),
342
- this.instructionService.getSessionContext(
343
- platform || "unknown",
344
- instructionContext,
345
- { settingsUrl: settingsUrl.toString() }
346
- ),
347
- ]);
348
-
349
- // Fetch tool lists and instructions for ALL MCPs (unauthenticated ones
350
- // will attempt discovery without credentials)
351
- const mcpTools: Record<string, McpTool[]> = {};
352
- const mcpInstructions: Record<string, string> = {};
353
- if (this.mcpProxy && contextData.mcpStatus.length > 0) {
354
- const toolResults = await Promise.allSettled(
355
- contextData.mcpStatus.map(async (mcp) => {
356
- const result = await this.mcpProxy?.fetchToolsForMcp(
357
- mcp.id,
358
- agentId || userId,
359
- auth.tokenData
360
- );
361
- return { mcpId: mcp.id, ...(result || { tools: [] }) };
362
- })
363
- );
364
-
365
- for (const result of toolResults) {
366
- if (result.status === "fulfilled") {
367
- if (result.value.tools && result.value.tools.length > 0) {
368
- mcpTools[result.value.mcpId] = result.value.tools;
369
- }
370
- if (result.value.instructions) {
371
- mcpInstructions[result.value.mcpId] = result.value.instructions;
372
- }
373
- } else {
374
- logger.error("MCP tool fetch rejected", {
375
- reason:
376
- result.reason instanceof Error
377
- ? result.reason.message
378
- : String(result.reason),
379
- });
380
- }
381
- }
382
- }
383
-
384
- // Resolve dynamic provider configuration (with template agent fallback)
385
- const agentSettings =
386
- this.settingsResolver && agentId
387
- ? await this.settingsResolver.getEffectiveSettings(agentId)
388
- : null;
389
- const providerConfig = await this.resolveProviderConfig(
390
- agentSettings?.templateAgentId || agentId || "",
391
- resolveEffectiveModelRef(agentSettings),
392
- baseUrl
393
- );
394
-
395
- // Fetch enabled skills with content for worker filesystem sync
396
- let skillsConfig: Array<{ name: string; content: string }> = [];
397
- const mcpContext: Record<string, string> = {};
398
- if (this.settingsResolver && agentId) {
399
- try {
400
- const settings =
401
- await this.settingsResolver.getEffectiveSettings(agentId);
402
- const skills = settings?.skillsConfig?.skills || [];
403
- skillsConfig = skills
404
- .filter((s) => s.enabled && s.content)
405
- .map((s) => ({ name: s.name, content: s.content! }));
406
- // Build MCP context map: MCP server ID → skill instructions
407
- for (const skill of skills) {
408
- if (
409
- skill.enabled &&
410
- skill.instructions?.trim() &&
411
- skill.mcpServers?.length
412
- ) {
413
- for (const mcp of skill.mcpServers) {
414
- mcpContext[mcp.id] = skill.instructions.trim();
415
- }
416
- }
417
- }
418
- } catch (error) {
419
- logger.error("Failed to fetch skills config for worker sync", {
420
- error,
421
- });
422
- }
423
- }
424
-
425
- let systemSkillsInstructions = "";
426
- if (this.systemSkillsService) {
427
- try {
428
- const runtimeSystemSkills =
429
- await this.systemSkillsService.getRuntimeSystemSkills();
430
-
431
- if (runtimeSystemSkills.length > 0) {
432
- // Only write SKILL.md files for system skills without instructions
433
- // (skills with instructions are fully inlined — no cat needed)
434
- const existingSkillNames = new Set(skillsConfig.map((s) => s.name));
435
- let hasSkillFiles = false;
436
- for (const skill of runtimeSystemSkills) {
437
- if (skill.instructions?.trim()) continue;
438
- const workspaceSkillName = `system-${skill.id}`;
439
- if (!existingSkillNames.has(workspaceSkillName)) {
440
- skillsConfig.push({
441
- name: workspaceSkillName,
442
- content: skill.content,
443
- });
444
- hasSkillFiles = true;
445
- }
446
- }
447
-
448
- const summaryLines = runtimeSystemSkills.map((skill, index) => {
449
- const description = skill.description
450
- ? ` - ${skill.description}`
451
- : "";
452
- const line = `${index + 1}. ${skill.name} (\`${skill.repo}\`)${description}`;
453
- if (skill.instructions?.trim()) {
454
- return `${line}\n → ${skill.instructions.trim()}`;
455
- }
456
- return line;
457
- });
458
-
459
- const catHint = hasSkillFiles
460
- ? "\n\nRead full instructions using `cat .skills/system-*/SKILL.md` when needed."
461
- : "";
462
-
463
- systemSkillsInstructions =
464
- [
465
- "## Built-in System Skills",
466
- "",
467
- "These system skills are always available in this workspace:",
468
- "",
469
- ...summaryLines,
470
- ].join("\n") + catHint;
471
- }
472
- } catch (error) {
473
- logger.error("Failed to fetch runtime system skills", { error });
474
- }
475
- }
476
-
477
- const mergedSkillsInstructions = [
478
- contextData.skillsInstructions,
479
- systemSkillsInstructions,
480
- ]
481
- .filter(Boolean)
482
- .join("\n\n");
483
-
484
- logger.info(
485
- `Session context for ${userId}: ${Object.keys(mcpConfig.mcpServers || {}).length} MCPs, ${contextData.agentInstructions.length} chars agent instructions, ${contextData.platformInstructions.length} chars platform instructions, ${contextData.networkInstructions.length} chars network instructions, ${mergedSkillsInstructions.length} chars skills instructions, ${contextData.mcpStatus.length} MCP status entries, ${Object.keys(mcpTools).length} MCP tool lists, ${Object.keys(mcpInstructions).length} MCP instructions, ${skillsConfig.length} skills, provider: ${providerConfig.defaultProvider || "none"}`
486
- );
487
-
488
- return c.json({
489
- mcpConfig,
490
- agentInstructions: contextData.agentInstructions,
491
- platformInstructions: contextData.platformInstructions,
492
- networkInstructions: contextData.networkInstructions,
493
- skillsInstructions: mergedSkillsInstructions,
494
- mcpStatus: contextData.mcpStatus,
495
- mcpTools,
496
- mcpInstructions,
497
- mcpContext,
498
- providerConfig,
499
- skillsConfig,
500
- });
501
- } catch (error) {
502
- logger.error("Failed to generate session context", { error });
503
- return c.json({ error: "session_context_error" }, 500);
504
- }
505
- }
506
-
507
- private authenticateWorker(
508
- c: Context
509
- ): { tokenData: WorkerTokenData; token: string } | null {
510
- const authHeader = c.req.header("authorization");
511
-
512
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
513
- return null;
514
- }
515
-
516
- const token = authHeader.substring(7);
517
- const tokenData = verifyWorkerToken(token);
518
-
519
- if (!tokenData) {
520
- logger.warn("Invalid token");
521
- return null;
522
- }
523
-
524
- return { tokenData, token };
525
- }
526
-
527
- private getRequestBaseUrl(c: Context): string {
528
- const forwardedProto = c.req.header("x-forwarded-proto");
529
- const protocolCandidate = Array.isArray(forwardedProto)
530
- ? forwardedProto[0]
531
- : forwardedProto?.split(",")[0];
532
- const protocol = (protocolCandidate || "http").trim();
533
- const host = c.req.header("host");
534
- if (host) {
535
- return `${protocol}://${host}`;
536
- }
537
- return this.publicGatewayUrl;
538
- }
539
-
540
- /**
541
- * Get active worker connections
542
- */
543
- getActiveConnections(): string[] {
544
- return this.connectionManager.getActiveConnections();
545
- }
546
-
547
- /**
548
- * Resolve dynamic provider configuration for a given agent.
549
- * Mirrors the provider resolution logic in base-deployment-manager's
550
- * generateEnvironmentVariables() but returns config values instead of env vars.
551
- */
552
- private async resolveProviderConfig(
553
- agentId: string,
554
- agentModel?: string,
555
- requestBaseUrl?: string
556
- ): Promise<{
557
- credentialEnvVarName?: string;
558
- defaultProvider?: string;
559
- defaultModel?: string;
560
- cliBackends?: Array<{
561
- providerId: string;
562
- name: string;
563
- command: string;
564
- args?: string[];
565
- env?: Record<string, string>;
566
- modelArg?: string;
567
- sessionArg?: string;
568
- }>;
569
- providerBaseUrlMappings?: Record<string, string>;
570
- configProviders?: Record<string, ConfigProviderMeta>;
571
- }> {
572
- if (!this.providerCatalogService || !agentId) {
573
- return {};
574
- }
575
-
576
- const effectiveProviders =
577
- await this.providerCatalogService.getInstalledModules(agentId);
578
- if (effectiveProviders.length === 0) {
579
- return {};
580
- }
581
-
582
- // Determine primary provider
583
- let primaryProvider = agentModel
584
- ? await this.providerCatalogService.findProviderForModel(
585
- agentModel,
586
- effectiveProviders
587
- )
588
- : undefined;
589
-
590
- if (!primaryProvider) {
591
- for (const candidate of effectiveProviders) {
592
- if (
593
- candidate.hasSystemKey() ||
594
- (await candidate.hasCredentials(agentId))
595
- ) {
596
- primaryProvider = candidate;
597
- break;
598
- }
599
- }
600
- }
601
-
602
- // Build proxy base URL mappings for all installed providers
603
- // Use the request base URL (the worker's DISPATCHER_URL) for internal routing
604
- const proxyBaseUrl = `${requestBaseUrl || this.publicGatewayUrl}/api/proxy`;
605
- const providerBaseUrlMappings: Record<string, string> = {};
606
- for (const provider of effectiveProviders) {
607
- Object.assign(
608
- providerBaseUrlMappings,
609
- provider.getProxyBaseUrlMappings(proxyBaseUrl, agentId)
610
- );
611
- }
612
-
613
- // Build CLI backend configs
614
- const cliBackends: Array<{
615
- providerId: string;
616
- name: string;
617
- command: string;
618
- args?: string[];
619
- env?: Record<string, string>;
620
- modelArg?: string;
621
- sessionArg?: string;
622
- }> = [];
623
- for (const provider of effectiveProviders) {
624
- const config = provider.getCliBackendConfig?.();
625
- if (config) {
626
- cliBackends.push({ providerId: provider.providerId, ...config });
627
- }
628
- }
629
-
630
- // Collect metadata from config-driven providers for worker model resolution
631
- const configProviders: Record<string, ConfigProviderMeta> = {};
632
- for (const provider of effectiveProviders) {
633
- const meta = (provider as ApiKeyProviderModule).getProviderMetadata?.();
634
- if (meta) {
635
- configProviders[provider.providerId] = meta;
636
- }
637
- }
638
-
639
- // Build credential placeholders for proxy mode — in-process workers need
640
- // these so the runtime doesn't reject requests before they reach the proxy.
641
- const credentialPlaceholders: Record<string, string> = {};
642
- for (const provider of effectiveProviders) {
643
- if (provider.hasSystemKey() || (await provider.hasCredentials(agentId))) {
644
- const credVar = provider.getCredentialEnvVarName();
645
- const placeholder = provider.buildCredentialPlaceholder
646
- ? await provider.buildCredentialPlaceholder(agentId)
647
- : "lobu-proxy";
648
- credentialPlaceholders[credVar] = placeholder;
649
- }
650
- }
651
-
652
- const result: {
653
- credentialEnvVarName?: string;
654
- defaultProvider?: string;
655
- defaultModel?: string;
656
- cliBackends?: typeof cliBackends;
657
- providerBaseUrlMappings?: Record<string, string>;
658
- configProviders?: typeof configProviders;
659
- credentialPlaceholders?: Record<string, string>;
660
- } = {};
661
-
662
- if (primaryProvider) {
663
- result.credentialEnvVarName = primaryProvider.getCredentialEnvVarName();
664
- const upstream = primaryProvider.getUpstreamConfig?.();
665
- result.defaultProvider = upstream?.slug || primaryProvider.providerId;
666
- }
667
-
668
- if (agentModel) {
669
- result.defaultModel = agentModel;
670
- }
671
-
672
- if (Object.keys(providerBaseUrlMappings).length > 0) {
673
- result.providerBaseUrlMappings = providerBaseUrlMappings;
674
- }
675
-
676
- if (cliBackends.length > 0) {
677
- result.cliBackends = cliBackends;
678
- }
679
-
680
- if (Object.keys(configProviders).length > 0) {
681
- result.configProviders = configProviders;
682
- }
683
-
684
- if (Object.keys(credentialPlaceholders).length > 0) {
685
- result.credentialPlaceholders = credentialPlaceholders;
686
- }
687
-
688
- return result;
689
- }
690
-
691
- /**
692
- * Shutdown gateway
693
- */
694
- shutdown(): void {
695
- this.connectionManager.shutdown();
696
- this.jobRouter.shutdown();
697
- }
698
- }