@lobu/gateway 3.0.5 → 3.0.6

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 (175) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/agent-config-routes.test.ts +254 -0
  3. package/src/__tests__/agent-history-routes.test.ts +72 -0
  4. package/src/__tests__/agent-routes.test.ts +68 -0
  5. package/src/__tests__/agent-schedules-routes.test.ts +59 -0
  6. package/src/__tests__/agent-settings-store.test.ts +323 -0
  7. package/src/__tests__/chat-instance-manager-slack.test.ts +204 -0
  8. package/src/__tests__/chat-response-bridge.test.ts +131 -0
  9. package/src/__tests__/config-memory-plugins.test.ts +92 -0
  10. package/src/__tests__/config-request-store.test.ts +127 -0
  11. package/src/__tests__/connection-routes.test.ts +144 -0
  12. package/src/__tests__/core-services-store-selection.test.ts +92 -0
  13. package/src/__tests__/docker-deployment.test.ts +1211 -0
  14. package/src/__tests__/embedded-deployment.test.ts +342 -0
  15. package/src/__tests__/grant-store.test.ts +148 -0
  16. package/src/__tests__/http-proxy.test.ts +281 -0
  17. package/src/__tests__/instruction-service.test.ts +37 -0
  18. package/src/__tests__/link-buttons.test.ts +112 -0
  19. package/src/__tests__/lobu.test.ts +32 -0
  20. package/src/__tests__/mcp-config-service.test.ts +347 -0
  21. package/src/__tests__/mcp-proxy.test.ts +696 -0
  22. package/src/__tests__/message-handler-bridge.test.ts +17 -0
  23. package/src/__tests__/model-selection.test.ts +172 -0
  24. package/src/__tests__/oauth-templates.test.ts +39 -0
  25. package/src/__tests__/platform-adapter-slack-send.test.ts +114 -0
  26. package/src/__tests__/platform-helpers-model-resolution.test.ts +253 -0
  27. package/src/__tests__/provider-inheritance.test.ts +212 -0
  28. package/src/__tests__/routes/cli-auth.test.ts +337 -0
  29. package/src/__tests__/routes/interactions.test.ts +121 -0
  30. package/src/__tests__/secret-proxy.test.ts +85 -0
  31. package/src/__tests__/session-manager.test.ts +572 -0
  32. package/src/__tests__/setup.ts +133 -0
  33. package/src/__tests__/skill-and-mcp-registry.test.ts +203 -0
  34. package/src/__tests__/slack-routes.test.ts +161 -0
  35. package/src/__tests__/system-config-resolver.test.ts +75 -0
  36. package/src/__tests__/system-message-limiter.test.ts +89 -0
  37. package/src/__tests__/system-skills-service.test.ts +362 -0
  38. package/src/__tests__/transcription-service.test.ts +222 -0
  39. package/src/__tests__/utils/rate-limiter.test.ts +102 -0
  40. package/src/__tests__/worker-connection-manager.test.ts +497 -0
  41. package/src/__tests__/worker-job-router.test.ts +722 -0
  42. package/src/api/index.ts +1 -0
  43. package/src/api/platform.ts +292 -0
  44. package/src/api/response-renderer.ts +157 -0
  45. package/src/auth/agent-metadata-store.ts +168 -0
  46. package/src/auth/api-auth-middleware.ts +69 -0
  47. package/src/auth/api-key-provider-module.ts +213 -0
  48. package/src/auth/base-provider-module.ts +201 -0
  49. package/src/auth/chatgpt/chatgpt-oauth-module.ts +185 -0
  50. package/src/auth/chatgpt/device-code-client.ts +218 -0
  51. package/src/auth/chatgpt/index.ts +1 -0
  52. package/src/auth/claude/oauth-module.ts +280 -0
  53. package/src/auth/cli/token-service.ts +249 -0
  54. package/src/auth/external/client.ts +560 -0
  55. package/src/auth/external/device-code-client.ts +225 -0
  56. package/src/auth/mcp/config-service.ts +392 -0
  57. package/src/auth/mcp/proxy.ts +1088 -0
  58. package/src/auth/mcp/string-substitution.ts +17 -0
  59. package/src/auth/mcp/tool-cache.ts +90 -0
  60. package/src/auth/oauth/base-client.ts +267 -0
  61. package/src/auth/oauth/client.ts +153 -0
  62. package/src/auth/oauth/credentials.ts +7 -0
  63. package/src/auth/oauth/providers.ts +69 -0
  64. package/src/auth/oauth/state-store.ts +150 -0
  65. package/src/auth/oauth-templates.ts +179 -0
  66. package/src/auth/provider-catalog.ts +220 -0
  67. package/src/auth/provider-model-options.ts +41 -0
  68. package/src/auth/settings/agent-settings-store.ts +565 -0
  69. package/src/auth/settings/auth-profiles-manager.ts +216 -0
  70. package/src/auth/settings/index.ts +12 -0
  71. package/src/auth/settings/model-preference-store.ts +52 -0
  72. package/src/auth/settings/model-selection.ts +135 -0
  73. package/src/auth/settings/resolved-settings-view.ts +298 -0
  74. package/src/auth/settings/template-utils.ts +44 -0
  75. package/src/auth/settings/token-service.ts +88 -0
  76. package/src/auth/system-env-store.ts +98 -0
  77. package/src/auth/user-agents-store.ts +68 -0
  78. package/src/channels/binding-service.ts +214 -0
  79. package/src/channels/index.ts +4 -0
  80. package/src/cli/gateway.ts +1304 -0
  81. package/src/cli/index.ts +74 -0
  82. package/src/commands/built-in-commands.ts +80 -0
  83. package/src/commands/command-dispatcher.ts +94 -0
  84. package/src/commands/command-reply-adapters.ts +27 -0
  85. package/src/config/file-loader.ts +618 -0
  86. package/src/config/index.ts +588 -0
  87. package/src/config/network-allowlist.ts +71 -0
  88. package/src/connections/chat-instance-manager.ts +1284 -0
  89. package/src/connections/chat-response-bridge.ts +618 -0
  90. package/src/connections/index.ts +7 -0
  91. package/src/connections/interaction-bridge.ts +831 -0
  92. package/src/connections/message-handler-bridge.ts +415 -0
  93. package/src/connections/platform-auth-methods.ts +15 -0
  94. package/src/connections/types.ts +84 -0
  95. package/src/gateway/connection-manager.ts +291 -0
  96. package/src/gateway/index.ts +700 -0
  97. package/src/gateway/job-router.ts +201 -0
  98. package/src/gateway-main.ts +200 -0
  99. package/src/index.ts +41 -0
  100. package/src/infrastructure/queue/index.ts +12 -0
  101. package/src/infrastructure/queue/queue-producer.ts +148 -0
  102. package/src/infrastructure/queue/redis-queue.ts +361 -0
  103. package/src/infrastructure/queue/types.ts +133 -0
  104. package/src/infrastructure/redis/system-message-limiter.ts +94 -0
  105. package/src/interactions/config-request-store.ts +198 -0
  106. package/src/interactions.ts +363 -0
  107. package/src/lobu.ts +311 -0
  108. package/src/metrics/prometheus.ts +159 -0
  109. package/src/modules/module-system.ts +179 -0
  110. package/src/orchestration/base-deployment-manager.ts +900 -0
  111. package/src/orchestration/deployment-utils.ts +98 -0
  112. package/src/orchestration/impl/docker-deployment.ts +620 -0
  113. package/src/orchestration/impl/embedded-deployment.ts +268 -0
  114. package/src/orchestration/impl/index.ts +8 -0
  115. package/src/orchestration/impl/k8s/deployment.ts +1061 -0
  116. package/src/orchestration/impl/k8s/helpers.ts +610 -0
  117. package/src/orchestration/impl/k8s/index.ts +1 -0
  118. package/src/orchestration/index.ts +333 -0
  119. package/src/orchestration/message-consumer.ts +584 -0
  120. package/src/orchestration/scheduled-wakeup.ts +704 -0
  121. package/src/permissions/approval-policy.ts +36 -0
  122. package/src/permissions/grant-store.ts +219 -0
  123. package/src/platform/file-handler.ts +66 -0
  124. package/src/platform/link-buttons.ts +57 -0
  125. package/src/platform/renderer-utils.ts +44 -0
  126. package/src/platform/response-renderer.ts +84 -0
  127. package/src/platform/unified-thread-consumer.ts +187 -0
  128. package/src/platform.ts +318 -0
  129. package/src/proxy/http-proxy.ts +752 -0
  130. package/src/proxy/proxy-manager.ts +81 -0
  131. package/src/proxy/secret-proxy.ts +402 -0
  132. package/src/proxy/token-refresh-job.ts +143 -0
  133. package/src/routes/internal/audio.ts +141 -0
  134. package/src/routes/internal/device-auth.ts +566 -0
  135. package/src/routes/internal/files.ts +226 -0
  136. package/src/routes/internal/history.ts +69 -0
  137. package/src/routes/internal/images.ts +127 -0
  138. package/src/routes/internal/interactions.ts +84 -0
  139. package/src/routes/internal/middleware.ts +23 -0
  140. package/src/routes/internal/schedule.ts +226 -0
  141. package/src/routes/internal/types.ts +22 -0
  142. package/src/routes/openapi-auto.ts +239 -0
  143. package/src/routes/public/agent-access.ts +23 -0
  144. package/src/routes/public/agent-config.ts +675 -0
  145. package/src/routes/public/agent-history.ts +422 -0
  146. package/src/routes/public/agent-schedules.ts +296 -0
  147. package/src/routes/public/agent.ts +1086 -0
  148. package/src/routes/public/agents.ts +373 -0
  149. package/src/routes/public/channels.ts +191 -0
  150. package/src/routes/public/cli-auth.ts +883 -0
  151. package/src/routes/public/connections.ts +574 -0
  152. package/src/routes/public/landing.ts +16 -0
  153. package/src/routes/public/oauth.ts +147 -0
  154. package/src/routes/public/settings-auth.ts +104 -0
  155. package/src/routes/public/slack.ts +173 -0
  156. package/src/routes/shared/agent-ownership.ts +101 -0
  157. package/src/routes/shared/token-verifier.ts +34 -0
  158. package/src/services/core-services.ts +1053 -0
  159. package/src/services/image-generation-service.ts +257 -0
  160. package/src/services/instruction-service.ts +318 -0
  161. package/src/services/mcp-registry.ts +94 -0
  162. package/src/services/platform-helpers.ts +287 -0
  163. package/src/services/session-manager.ts +262 -0
  164. package/src/services/settings-resolver.ts +74 -0
  165. package/src/services/system-config-resolver.ts +90 -0
  166. package/src/services/system-skills-service.ts +229 -0
  167. package/src/services/transcription-service.ts +684 -0
  168. package/src/session.ts +110 -0
  169. package/src/spaces/index.ts +1 -0
  170. package/src/spaces/space-resolver.ts +17 -0
  171. package/src/stores/in-memory-agent-store.ts +403 -0
  172. package/src/stores/redis-agent-store.ts +279 -0
  173. package/src/utils/public-url.ts +44 -0
  174. package/src/utils/rate-limiter.ts +94 -0
  175. package/tsconfig.json +33 -0
@@ -0,0 +1,347 @@
1
+ // Set ENCRYPTION_KEY before any imports that use encryption
2
+ process.env.ENCRYPTION_KEY =
3
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { generateWorkerToken } from "@lobu/core";
7
+ import { McpConfigService } from "../auth/mcp/config-service";
8
+
9
+ function makeToken(agentId = "agent1") {
10
+ return generateWorkerToken("user1", "conv1", "deploy1", {
11
+ channelId: "ch1",
12
+ agentId,
13
+ });
14
+ }
15
+
16
+ const BASE_URL = "http://localhost:8080/mcp";
17
+
18
+ describe("McpConfigService", () => {
19
+ test("registerGlobalServers - HTTP + stdio", async () => {
20
+ const service = new McpConfigService();
21
+ service.registerGlobalServers({
22
+ "http-server": {
23
+ url: "https://upstream.example.com/mcp",
24
+ type: "sse",
25
+ },
26
+ "stdio-server": {
27
+ command: "node",
28
+ args: ["server.js"],
29
+ type: "stdio",
30
+ },
31
+ });
32
+
33
+ const config = await service.getWorkerConfig({
34
+ baseUrl: BASE_URL,
35
+ workerToken: makeToken(),
36
+ });
37
+
38
+ // HTTP server should be rewritten
39
+ expect(config.mcpServers["http-server"]).toBeDefined();
40
+ expect(config.mcpServers["http-server"].url).toBe(BASE_URL);
41
+ expect(config.mcpServers["http-server"].type).toBe("sse");
42
+
43
+ // Stdio server should be unchanged
44
+ expect(config.mcpServers["stdio-server"]).toBeDefined();
45
+ expect(config.mcpServers["stdio-server"].command).toBe("node");
46
+ expect(config.mcpServers["stdio-server"].args).toEqual(["server.js"]);
47
+ expect(config.mcpServers["stdio-server"].type).toBe("stdio");
48
+ });
49
+
50
+ test("registerGlobalServers - skip duplicates", async () => {
51
+ const service = new McpConfigService();
52
+ service.registerGlobalServers({
53
+ "my-mcp": { url: "https://first.example.com/mcp", type: "sse" },
54
+ });
55
+ service.registerGlobalServers({
56
+ "my-mcp": { url: "https://second.example.com/mcp", type: "sse" },
57
+ });
58
+
59
+ const config = await service.getWorkerConfig({
60
+ baseUrl: BASE_URL,
61
+ workerToken: makeToken(),
62
+ });
63
+
64
+ // Only first registration should stick (url gets rewritten to baseUrl,
65
+ // but the httpServer entry should reflect the first upstream)
66
+ expect(config.mcpServers["my-mcp"]).toBeDefined();
67
+ const servers = await service.getAllHttpServers();
68
+ expect(servers.get("my-mcp")?.upstreamUrl).toBe(
69
+ "https://first.example.com/mcp"
70
+ );
71
+ });
72
+
73
+ test("getWorkerConfig - rewrites HTTP URLs", async () => {
74
+ const service = new McpConfigService();
75
+ const token = makeToken();
76
+ service.registerGlobalServers({
77
+ "test-mcp": { url: "https://upstream.example.com/mcp", type: "sse" },
78
+ });
79
+
80
+ const config = await service.getWorkerConfig({
81
+ baseUrl: BASE_URL,
82
+ workerToken: token,
83
+ });
84
+
85
+ const mcp = config.mcpServers["test-mcp"];
86
+ expect(mcp.url).toBe(BASE_URL);
87
+ expect(mcp.type).toBe("sse");
88
+ expect(mcp.headers.Authorization).toBe(`Bearer ${token}`);
89
+ expect(mcp.headers["X-Mcp-Id"]).toBe("test-mcp");
90
+ });
91
+
92
+ test("getWorkerConfig - preserves custom headers", async () => {
93
+ const service = new McpConfigService();
94
+ const token = makeToken();
95
+ service.registerGlobalServers({
96
+ "custom-mcp": {
97
+ url: "https://upstream.example.com/mcp",
98
+ type: "sse",
99
+ headers: { "X-Custom": "value", "X-Another": "data" },
100
+ },
101
+ });
102
+
103
+ const config = await service.getWorkerConfig({
104
+ baseUrl: BASE_URL,
105
+ workerToken: token,
106
+ });
107
+
108
+ const headers = config.mcpServers["custom-mcp"].headers;
109
+ expect(headers["X-Custom"]).toBe("value");
110
+ expect(headers["X-Another"]).toBe("data");
111
+ expect(headers.Authorization).toBe(`Bearer ${token}`);
112
+ expect(headers["X-Mcp-Id"]).toBe("custom-mcp");
113
+ });
114
+
115
+ test("getWorkerConfig - includes stdio without rewriting", async () => {
116
+ const service = new McpConfigService();
117
+ service.registerGlobalServers({
118
+ "stdio-mcp": {
119
+ command: "python",
120
+ args: ["-m", "mcp_server"],
121
+ type: "stdio",
122
+ },
123
+ });
124
+
125
+ const config = await service.getWorkerConfig({
126
+ baseUrl: BASE_URL,
127
+ workerToken: makeToken(),
128
+ });
129
+
130
+ const mcp = config.mcpServers["stdio-mcp"];
131
+ expect(mcp.command).toBe("python");
132
+ expect(mcp.args).toEqual(["-m", "mcp_server"]);
133
+ expect(mcp.type).toBe("stdio");
134
+ expect(mcp.headers).toBeUndefined();
135
+ });
136
+
137
+ test("getWorkerConfig - merges per-agent MCPs", async () => {
138
+ const mockAgentSettingsStore = {
139
+ getSettings: async (agentId: string) => {
140
+ if (agentId === "agent1") {
141
+ return {
142
+ mcpServers: {
143
+ "agent-mcp": {
144
+ url: "https://agent-mcp.example.com/mcp",
145
+ type: "sse",
146
+ },
147
+ },
148
+ };
149
+ }
150
+ return null;
151
+ },
152
+ };
153
+
154
+ const service = new McpConfigService({
155
+ agentSettingsStore: mockAgentSettingsStore as any,
156
+ });
157
+ service.registerGlobalServers({
158
+ "global-mcp": { url: "https://global.example.com/mcp", type: "sse" },
159
+ });
160
+
161
+ const config = await service.getWorkerConfig({
162
+ baseUrl: BASE_URL,
163
+ workerToken: makeToken("agent1"),
164
+ });
165
+
166
+ expect(config.mcpServers["global-mcp"]).toBeDefined();
167
+ expect(config.mcpServers["agent-mcp"]).toBeDefined();
168
+ expect(config.mcpServers["agent-mcp"].url).toBe(BASE_URL);
169
+ expect(config.mcpServers["agent-mcp"].perAgent).toBe(true);
170
+ });
171
+
172
+ test("getWorkerConfig - rejects invalid tokens", async () => {
173
+ const service = new McpConfigService();
174
+ service.registerGlobalServers({
175
+ "test-mcp": { url: "https://upstream.example.com/mcp", type: "sse" },
176
+ });
177
+
178
+ const config = await service.getWorkerConfig({
179
+ baseUrl: BASE_URL,
180
+ workerToken: "invalid-garbage-token",
181
+ });
182
+
183
+ expect(Object.keys(config.mcpServers)).toHaveLength(0);
184
+ });
185
+
186
+ test("getWorkerConfig - skips disabled MCPs", async () => {
187
+ const mockAgentSettingsStore = {
188
+ getSettings: async () => ({
189
+ mcpServers: {
190
+ "disabled-mcp": {
191
+ url: "https://disabled.example.com/mcp",
192
+ type: "sse",
193
+ enabled: false,
194
+ },
195
+ "enabled-mcp": {
196
+ url: "https://enabled.example.com/mcp",
197
+ type: "sse",
198
+ },
199
+ },
200
+ }),
201
+ };
202
+
203
+ const service = new McpConfigService({
204
+ agentSettingsStore: mockAgentSettingsStore as any,
205
+ });
206
+
207
+ const config = await service.getWorkerConfig({
208
+ baseUrl: BASE_URL,
209
+ workerToken: makeToken("agent1"),
210
+ });
211
+
212
+ expect(config.mcpServers["disabled-mcp"]).toBeUndefined();
213
+ expect(config.mcpServers["enabled-mcp"]).toBeDefined();
214
+ });
215
+
216
+ test("getWorkerConfig - global takes precedence over per-agent", async () => {
217
+ const mockAgentSettingsStore = {
218
+ getSettings: async () => ({
219
+ mcpServers: {
220
+ "shared-mcp": {
221
+ url: "https://agent-version.example.com/mcp",
222
+ type: "sse",
223
+ },
224
+ },
225
+ }),
226
+ };
227
+
228
+ const service = new McpConfigService({
229
+ agentSettingsStore: mockAgentSettingsStore as any,
230
+ });
231
+ service.registerGlobalServers({
232
+ "shared-mcp": {
233
+ url: "https://global-version.example.com/mcp",
234
+ type: "sse",
235
+ },
236
+ });
237
+
238
+ const config = await service.getWorkerConfig({
239
+ baseUrl: BASE_URL,
240
+ workerToken: makeToken("agent1"),
241
+ });
242
+
243
+ // Global should win - no perAgent flag
244
+ expect(config.mcpServers["shared-mcp"]).toBeDefined();
245
+ expect(config.mcpServers["shared-mcp"].perAgent).toBeUndefined();
246
+ });
247
+
248
+ test("getMcpStatus - returns correct auth and input flags", async () => {
249
+ const service = new McpConfigService();
250
+ service.registerGlobalServers({
251
+ "oauth-mcp": {
252
+ url: "https://oauth.example.com/mcp",
253
+ type: "sse",
254
+ oauth: { clientId: "abc" },
255
+ },
256
+ "login-mcp": {
257
+ url: "https://login.example.com/mcp",
258
+ type: "sse",
259
+ loginUrl: "https://login.example.com/auth",
260
+ },
261
+ "input-mcp": {
262
+ url: "https://input.example.com/mcp",
263
+ type: "sse",
264
+ inputs: [
265
+ { type: "promptString", id: "api_key", description: "API key" },
266
+ ],
267
+ },
268
+ "plain-mcp": {
269
+ url: "https://plain.example.com/mcp",
270
+ type: "sse",
271
+ },
272
+ });
273
+
274
+ const statuses = await service.getMcpStatus("agent1");
275
+
276
+ const oauthStatus = statuses.find((s) => s.id === "oauth-mcp");
277
+ expect(oauthStatus?.requiresAuth).toBe(true);
278
+ expect(oauthStatus?.requiresInput).toBe(false);
279
+
280
+ const loginStatus = statuses.find((s) => s.id === "login-mcp");
281
+ expect(loginStatus?.requiresAuth).toBe(true);
282
+
283
+ const inputStatus = statuses.find((s) => s.id === "input-mcp");
284
+ expect(inputStatus?.requiresInput).toBe(true);
285
+ expect(inputStatus?.requiresAuth).toBe(false);
286
+
287
+ const plainStatus = statuses.find((s) => s.id === "plain-mcp");
288
+ expect(plainStatus?.requiresAuth).toBe(false);
289
+ expect(plainStatus?.requiresInput).toBe(false);
290
+ });
291
+
292
+ test("getAllHttpServers - merges global + per-agent, excludes disabled and non-HTTP", async () => {
293
+ const mockAgentSettingsStore = {
294
+ getSettings: async () => ({
295
+ mcpServers: {
296
+ "agent-http": {
297
+ url: "https://agent-http.example.com/mcp",
298
+ type: "sse",
299
+ },
300
+ "agent-stdio": {
301
+ command: "node",
302
+ args: ["server.js"],
303
+ type: "stdio",
304
+ },
305
+ "agent-disabled": {
306
+ url: "https://disabled.example.com/mcp",
307
+ type: "sse",
308
+ enabled: false,
309
+ },
310
+ },
311
+ }),
312
+ };
313
+
314
+ const service = new McpConfigService({
315
+ agentSettingsStore: mockAgentSettingsStore as any,
316
+ });
317
+ service.registerGlobalServers({
318
+ "global-http": { url: "https://global.example.com/mcp", type: "sse" },
319
+ });
320
+
321
+ const servers = await service.getAllHttpServers("agent1");
322
+
323
+ expect(servers.has("global-http")).toBe(true);
324
+ expect(servers.has("agent-http")).toBe(true);
325
+ expect(servers.has("agent-stdio")).toBe(false);
326
+ expect(servers.has("agent-disabled")).toBe(false);
327
+ });
328
+
329
+ test("getGlobalMcpServers - returns settings-compatible format", async () => {
330
+ const service = new McpConfigService();
331
+ service.registerGlobalServers({
332
+ "http-mcp": { url: "https://example.com/mcp", type: "sse" },
333
+ "stdio-mcp": { command: "node", args: ["s.js"], type: "stdio" },
334
+ });
335
+
336
+ const result = await service.getGlobalMcpServers();
337
+
338
+ expect(result["http-mcp"]).toEqual({
339
+ url: "https://example.com/mcp",
340
+ type: "sse",
341
+ });
342
+ expect(result["stdio-mcp"]).toEqual({
343
+ url: undefined,
344
+ type: "stdio",
345
+ });
346
+ });
347
+ });