@lobu/gateway 2.8.0 → 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,268 @@
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { createLogger, ErrorCode, OrchestratorError } from "@lobu/core";
5
+ import type { ModelProviderModule } from "../../modules/module-system";
6
+ import {
7
+ BaseDeploymentManager,
8
+ type DeploymentInfo,
9
+ type MessagePayload,
10
+ type ModuleEnvVarsBuilder,
11
+ type OrchestratorConfig,
12
+ } from "../base-deployment-manager";
13
+ import {
14
+ buildDeploymentInfoSummary,
15
+ getVeryOldThresholdDays,
16
+ } from "../deployment-utils";
17
+
18
+ const logger = createLogger("orchestrator");
19
+
20
+ /** Timeout (ms) to wait for graceful shutdown before SIGKILL. */
21
+ const KILL_TIMEOUT_MS = 5_000;
22
+ const WORKER_BIN_DIR_CANDIDATES = [
23
+ path.resolve("node_modules/.bin"),
24
+ path.resolve("packages/worker/node_modules/.bin"),
25
+ "/app/node_modules/.bin",
26
+ "/app/packages/worker/node_modules/.bin",
27
+ ] as const;
28
+
29
+ interface EmbeddedWorkerEntry {
30
+ process: ChildProcess;
31
+ env: Record<string, string>;
32
+ lastActivity: Date;
33
+ workspaceDir: string;
34
+ }
35
+
36
+ function buildEmbeddedWorkerPath(existingPath?: string): string | undefined {
37
+ const segments = (existingPath || "").split(":").filter(Boolean);
38
+
39
+ for (const candidate of [...WORKER_BIN_DIR_CANDIDATES].reverse()) {
40
+ if (!fs.existsSync(candidate)) continue;
41
+ if (segments.includes(candidate)) continue;
42
+ segments.unshift(candidate);
43
+ }
44
+
45
+ return segments.length > 0 ? segments.join(":") : existingPath;
46
+ }
47
+
48
+ function getBunExecutable(): string {
49
+ return path.basename(process.execPath).startsWith("bun")
50
+ ? process.execPath
51
+ : "bun";
52
+ }
53
+
54
+ export class EmbeddedDeploymentManager extends BaseDeploymentManager {
55
+ private workers: Map<string, EmbeddedWorkerEntry> = new Map();
56
+
57
+ constructor(
58
+ config: OrchestratorConfig,
59
+ moduleEnvVarsBuilder?: ModuleEnvVarsBuilder,
60
+ providerModules: ModelProviderModule[] = []
61
+ ) {
62
+ super(config, moduleEnvVarsBuilder, providerModules);
63
+ }
64
+
65
+ protected getDispatcherHost(): string {
66
+ return "localhost";
67
+ }
68
+
69
+ async validateWorkerImage(): Promise<void> {
70
+ const entryPoint = path.resolve("packages/worker/src/index.ts");
71
+ if (!fs.existsSync(entryPoint)) {
72
+ throw new OrchestratorError(
73
+ ErrorCode.DEPLOYMENT_CREATE_FAILED,
74
+ `Worker entry point not found: ${entryPoint}. Run from the project root.`
75
+ );
76
+ }
77
+ logger.debug(`Worker entry point verified: ${entryPoint}`);
78
+ }
79
+
80
+ async createDeployment(
81
+ ...args: Parameters<BaseDeploymentManager["createDeployment"]>
82
+ ): Promise<void> {
83
+ const [deploymentName, username, userId, messageDataRaw] = args;
84
+ const messageData = messageDataRaw as MessagePayload | undefined;
85
+
86
+ const agentId = messageData?.agentId;
87
+ if (!agentId) {
88
+ throw new OrchestratorError(
89
+ ErrorCode.DEPLOYMENT_CREATE_FAILED,
90
+ "Missing agentId in message payload"
91
+ );
92
+ }
93
+ const workspaceDir = path.resolve(`workspaces/${agentId}`);
94
+ fs.mkdirSync(workspaceDir, { recursive: true });
95
+
96
+ const commonEnvVars = await this.generateEnvironmentVariables(
97
+ username,
98
+ userId,
99
+ deploymentName,
100
+ messageData,
101
+ true
102
+ );
103
+
104
+ commonEnvVars.WORKSPACE_DIR = workspaceDir;
105
+ commonEnvVars.DEPLOYMENT_MODE = "embedded";
106
+ const embeddedPath = buildEmbeddedWorkerPath(
107
+ commonEnvVars.PATH || process.env.PATH
108
+ );
109
+ if (embeddedPath) {
110
+ commonEnvVars.PATH = embeddedPath;
111
+ }
112
+
113
+ // Serialize allowed domains for worker-side just-bash bootstrap
114
+ const allowedDomains = messageData?.networkConfig?.allowedDomains ?? [];
115
+ if (allowedDomains.length > 0) {
116
+ commonEnvVars.JUST_BASH_ALLOWED_DOMAINS = JSON.stringify(allowedDomains);
117
+ }
118
+
119
+ // Determine spawn command based on nix packages
120
+ const nixPackages = messageData?.nixConfig?.packages ?? [];
121
+ const workerEntryPoint = path.resolve("packages/worker/src/index.ts");
122
+ const bunExecutable = getBunExecutable();
123
+
124
+ let command: string;
125
+ let spawnArgs: string[];
126
+
127
+ if (nixPackages.length > 0) {
128
+ // Wrap in nix-shell so nix binaries are on PATH
129
+ command = "nix-shell";
130
+ spawnArgs = [
131
+ "-p",
132
+ ...nixPackages,
133
+ "--run",
134
+ `${bunExecutable} run ${workerEntryPoint}`,
135
+ ];
136
+ logger.info(
137
+ `Spawning embedded worker ${deploymentName} with nix packages: ${nixPackages.join(", ")}`
138
+ );
139
+ } else {
140
+ command = bunExecutable;
141
+ spawnArgs = ["run", workerEntryPoint];
142
+ }
143
+
144
+ const child = spawn(command, spawnArgs, {
145
+ env: { ...process.env, ...commonEnvVars },
146
+ cwd: workspaceDir,
147
+ stdio: ["ignore", "pipe", "pipe"],
148
+ });
149
+
150
+ // Pipe child stdout/stderr to gateway logger
151
+ child.stdout?.on("data", (data: Buffer) => {
152
+ for (const line of data.toString().trimEnd().split("\n")) {
153
+ logger.info({ worker: deploymentName }, line);
154
+ }
155
+ });
156
+ child.stderr?.on("data", (data: Buffer) => {
157
+ for (const line of data.toString().trimEnd().split("\n")) {
158
+ logger.warn({ worker: deploymentName }, line);
159
+ }
160
+ });
161
+
162
+ // Handle child exit (use once to prevent duplicate handler invocations)
163
+ child.once("exit", (code, signal) => {
164
+ const entry = this.workers.get(deploymentName);
165
+ if (entry) {
166
+ this.workers.delete(deploymentName);
167
+ if (signal) {
168
+ logger.info(
169
+ `Embedded worker ${deploymentName} exited with signal ${signal}`
170
+ );
171
+ } else if (code !== 0) {
172
+ logger.error(
173
+ `Embedded worker ${deploymentName} exited with code ${code}`
174
+ );
175
+ } else {
176
+ logger.info(`Embedded worker ${deploymentName} exited cleanly`);
177
+ }
178
+ }
179
+ });
180
+
181
+ this.workers.set(deploymentName, {
182
+ process: child,
183
+ env: commonEnvVars,
184
+ lastActivity: new Date(),
185
+ workspaceDir,
186
+ });
187
+
188
+ logger.info(
189
+ `Started embedded worker subprocess for ${deploymentName} (pid=${child.pid})`
190
+ );
191
+ }
192
+
193
+ async scaleDeployment(
194
+ deploymentName: string,
195
+ replicas: number
196
+ ): Promise<void> {
197
+ const entry = this.workers.get(deploymentName);
198
+
199
+ if (replicas === 0 && entry) {
200
+ this.killWorker(entry, deploymentName);
201
+ logger.info(`Stopped embedded worker ${deploymentName}`);
202
+ } else if (replicas === 1 && !entry) {
203
+ logger.warn(
204
+ `Cannot scale up ${deploymentName} — use createDeployment to re-spawn`
205
+ );
206
+ }
207
+ }
208
+
209
+ async deleteDeployment(deploymentName: string): Promise<void> {
210
+ const entry = this.workers.get(deploymentName);
211
+ if (entry) {
212
+ this.killWorker(entry, deploymentName);
213
+ logger.info(`Stopped embedded worker: ${deploymentName}`);
214
+ }
215
+ }
216
+
217
+ async listDeployments(): Promise<DeploymentInfo[]> {
218
+ const now = Date.now();
219
+ const idleThresholdMinutes = this.config.worker.idleCleanupMinutes;
220
+ const veryOldDays = getVeryOldThresholdDays(this.config);
221
+
222
+ const results: DeploymentInfo[] = [];
223
+ for (const [deploymentName, entry] of this.workers) {
224
+ results.push(
225
+ buildDeploymentInfoSummary({
226
+ deploymentName,
227
+ lastActivity: entry.lastActivity,
228
+ now,
229
+ idleThresholdMinutes,
230
+ veryOldDays,
231
+ replicas: 1,
232
+ })
233
+ );
234
+ }
235
+ return results;
236
+ }
237
+
238
+ async updateDeploymentActivity(deploymentName: string): Promise<void> {
239
+ const entry = this.workers.get(deploymentName);
240
+ if (entry) {
241
+ entry.lastActivity = new Date();
242
+ }
243
+ }
244
+
245
+ /** Send SIGTERM, then SIGKILL after timeout. */
246
+ private killWorker(entry: EmbeddedWorkerEntry, deploymentName: string): void {
247
+ const child = entry.process;
248
+
249
+ // Delete from map first to prevent race with exit handler
250
+ this.workers.delete(deploymentName);
251
+
252
+ // Check if already exited after map deletion
253
+ if (child.exitCode !== null || child.killed) return;
254
+
255
+ child.kill("SIGTERM");
256
+
257
+ const killTimer = setTimeout(() => {
258
+ if (child.exitCode === null && !child.killed) {
259
+ logger.warn(
260
+ `Embedded worker ${deploymentName} did not exit after SIGTERM, sending SIGKILL`
261
+ );
262
+ child.kill("SIGKILL");
263
+ }
264
+ }, KILL_TIMEOUT_MS);
265
+
266
+ child.once("exit", () => clearTimeout(killTimer));
267
+ }
268
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Deployment manager implementations
3
+ * Add new deployment targets here (e.g., CloudflareDeploymentManager, LambdaDeploymentManager)
4
+ */
5
+
6
+ export { DockerDeploymentManager } from "./docker-deployment";
7
+ export { EmbeddedDeploymentManager } from "./embedded-deployment";
8
+ export { K8sDeploymentManager } from "./k8s";