@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.
- package/package.json +2 -2
- package/src/__tests__/agent-config-routes.test.ts +254 -0
- package/src/__tests__/agent-history-routes.test.ts +72 -0
- package/src/__tests__/agent-routes.test.ts +68 -0
- package/src/__tests__/agent-schedules-routes.test.ts +59 -0
- package/src/__tests__/agent-settings-store.test.ts +323 -0
- package/src/__tests__/chat-instance-manager-slack.test.ts +204 -0
- package/src/__tests__/chat-response-bridge.test.ts +131 -0
- package/src/__tests__/config-memory-plugins.test.ts +92 -0
- package/src/__tests__/config-request-store.test.ts +127 -0
- package/src/__tests__/connection-routes.test.ts +144 -0
- package/src/__tests__/core-services-store-selection.test.ts +92 -0
- package/src/__tests__/docker-deployment.test.ts +1211 -0
- package/src/__tests__/embedded-deployment.test.ts +342 -0
- package/src/__tests__/grant-store.test.ts +148 -0
- package/src/__tests__/http-proxy.test.ts +281 -0
- package/src/__tests__/instruction-service.test.ts +37 -0
- package/src/__tests__/link-buttons.test.ts +112 -0
- package/src/__tests__/lobu.test.ts +32 -0
- package/src/__tests__/mcp-config-service.test.ts +347 -0
- package/src/__tests__/mcp-proxy.test.ts +696 -0
- package/src/__tests__/message-handler-bridge.test.ts +17 -0
- package/src/__tests__/model-selection.test.ts +172 -0
- package/src/__tests__/oauth-templates.test.ts +39 -0
- package/src/__tests__/platform-adapter-slack-send.test.ts +114 -0
- package/src/__tests__/platform-helpers-model-resolution.test.ts +253 -0
- package/src/__tests__/provider-inheritance.test.ts +212 -0
- package/src/__tests__/routes/cli-auth.test.ts +337 -0
- package/src/__tests__/routes/interactions.test.ts +121 -0
- package/src/__tests__/secret-proxy.test.ts +85 -0
- package/src/__tests__/session-manager.test.ts +572 -0
- package/src/__tests__/setup.ts +133 -0
- package/src/__tests__/skill-and-mcp-registry.test.ts +203 -0
- package/src/__tests__/slack-routes.test.ts +161 -0
- package/src/__tests__/system-config-resolver.test.ts +75 -0
- package/src/__tests__/system-message-limiter.test.ts +89 -0
- package/src/__tests__/system-skills-service.test.ts +362 -0
- package/src/__tests__/transcription-service.test.ts +222 -0
- package/src/__tests__/utils/rate-limiter.test.ts +102 -0
- package/src/__tests__/worker-connection-manager.test.ts +497 -0
- package/src/__tests__/worker-job-router.test.ts +722 -0
- package/src/api/index.ts +1 -0
- package/src/api/platform.ts +292 -0
- package/src/api/response-renderer.ts +157 -0
- package/src/auth/agent-metadata-store.ts +168 -0
- package/src/auth/api-auth-middleware.ts +69 -0
- package/src/auth/api-key-provider-module.ts +213 -0
- package/src/auth/base-provider-module.ts +201 -0
- package/src/auth/chatgpt/chatgpt-oauth-module.ts +185 -0
- package/src/auth/chatgpt/device-code-client.ts +218 -0
- package/src/auth/chatgpt/index.ts +1 -0
- package/src/auth/claude/oauth-module.ts +280 -0
- package/src/auth/cli/token-service.ts +249 -0
- package/src/auth/external/client.ts +560 -0
- package/src/auth/external/device-code-client.ts +225 -0
- package/src/auth/mcp/config-service.ts +392 -0
- package/src/auth/mcp/proxy.ts +1088 -0
- package/src/auth/mcp/string-substitution.ts +17 -0
- package/src/auth/mcp/tool-cache.ts +90 -0
- package/src/auth/oauth/base-client.ts +267 -0
- package/src/auth/oauth/client.ts +153 -0
- package/src/auth/oauth/credentials.ts +7 -0
- package/src/auth/oauth/providers.ts +69 -0
- package/src/auth/oauth/state-store.ts +150 -0
- package/src/auth/oauth-templates.ts +179 -0
- package/src/auth/provider-catalog.ts +220 -0
- package/src/auth/provider-model-options.ts +41 -0
- package/src/auth/settings/agent-settings-store.ts +565 -0
- package/src/auth/settings/auth-profiles-manager.ts +216 -0
- package/src/auth/settings/index.ts +12 -0
- package/src/auth/settings/model-preference-store.ts +52 -0
- package/src/auth/settings/model-selection.ts +135 -0
- package/src/auth/settings/resolved-settings-view.ts +298 -0
- package/src/auth/settings/template-utils.ts +44 -0
- package/src/auth/settings/token-service.ts +88 -0
- package/src/auth/system-env-store.ts +98 -0
- package/src/auth/user-agents-store.ts +68 -0
- package/src/channels/binding-service.ts +214 -0
- package/src/channels/index.ts +4 -0
- package/src/cli/gateway.ts +1304 -0
- package/src/cli/index.ts +74 -0
- package/src/commands/built-in-commands.ts +80 -0
- package/src/commands/command-dispatcher.ts +94 -0
- package/src/commands/command-reply-adapters.ts +27 -0
- package/src/config/file-loader.ts +618 -0
- package/src/config/index.ts +588 -0
- package/src/config/network-allowlist.ts +71 -0
- package/src/connections/chat-instance-manager.ts +1284 -0
- package/src/connections/chat-response-bridge.ts +618 -0
- package/src/connections/index.ts +7 -0
- package/src/connections/interaction-bridge.ts +831 -0
- package/src/connections/message-handler-bridge.ts +415 -0
- package/src/connections/platform-auth-methods.ts +15 -0
- package/src/connections/types.ts +84 -0
- package/src/gateway/connection-manager.ts +291 -0
- package/src/gateway/index.ts +700 -0
- package/src/gateway/job-router.ts +201 -0
- package/src/gateway-main.ts +200 -0
- package/src/index.ts +41 -0
- package/src/infrastructure/queue/index.ts +12 -0
- package/src/infrastructure/queue/queue-producer.ts +148 -0
- package/src/infrastructure/queue/redis-queue.ts +361 -0
- package/src/infrastructure/queue/types.ts +133 -0
- package/src/infrastructure/redis/system-message-limiter.ts +94 -0
- package/src/interactions/config-request-store.ts +198 -0
- package/src/interactions.ts +363 -0
- package/src/lobu.ts +311 -0
- package/src/metrics/prometheus.ts +159 -0
- package/src/modules/module-system.ts +179 -0
- package/src/orchestration/base-deployment-manager.ts +900 -0
- package/src/orchestration/deployment-utils.ts +98 -0
- package/src/orchestration/impl/docker-deployment.ts +620 -0
- package/src/orchestration/impl/embedded-deployment.ts +268 -0
- package/src/orchestration/impl/index.ts +8 -0
- package/src/orchestration/impl/k8s/deployment.ts +1061 -0
- package/src/orchestration/impl/k8s/helpers.ts +610 -0
- package/src/orchestration/impl/k8s/index.ts +1 -0
- package/src/orchestration/index.ts +333 -0
- package/src/orchestration/message-consumer.ts +584 -0
- package/src/orchestration/scheduled-wakeup.ts +704 -0
- package/src/permissions/approval-policy.ts +36 -0
- package/src/permissions/grant-store.ts +219 -0
- package/src/platform/file-handler.ts +66 -0
- package/src/platform/link-buttons.ts +57 -0
- package/src/platform/renderer-utils.ts +44 -0
- package/src/platform/response-renderer.ts +84 -0
- package/src/platform/unified-thread-consumer.ts +187 -0
- package/src/platform.ts +318 -0
- package/src/proxy/http-proxy.ts +752 -0
- package/src/proxy/proxy-manager.ts +81 -0
- package/src/proxy/secret-proxy.ts +402 -0
- package/src/proxy/token-refresh-job.ts +143 -0
- package/src/routes/internal/audio.ts +141 -0
- package/src/routes/internal/device-auth.ts +566 -0
- package/src/routes/internal/files.ts +226 -0
- package/src/routes/internal/history.ts +69 -0
- package/src/routes/internal/images.ts +127 -0
- package/src/routes/internal/interactions.ts +84 -0
- package/src/routes/internal/middleware.ts +23 -0
- package/src/routes/internal/schedule.ts +226 -0
- package/src/routes/internal/types.ts +22 -0
- package/src/routes/openapi-auto.ts +239 -0
- package/src/routes/public/agent-access.ts +23 -0
- package/src/routes/public/agent-config.ts +675 -0
- package/src/routes/public/agent-history.ts +422 -0
- package/src/routes/public/agent-schedules.ts +296 -0
- package/src/routes/public/agent.ts +1086 -0
- package/src/routes/public/agents.ts +373 -0
- package/src/routes/public/channels.ts +191 -0
- package/src/routes/public/cli-auth.ts +883 -0
- package/src/routes/public/connections.ts +574 -0
- package/src/routes/public/landing.ts +16 -0
- package/src/routes/public/oauth.ts +147 -0
- package/src/routes/public/settings-auth.ts +104 -0
- package/src/routes/public/slack.ts +173 -0
- package/src/routes/shared/agent-ownership.ts +101 -0
- package/src/routes/shared/token-verifier.ts +34 -0
- package/src/services/core-services.ts +1053 -0
- package/src/services/image-generation-service.ts +257 -0
- package/src/services/instruction-service.ts +318 -0
- package/src/services/mcp-registry.ts +94 -0
- package/src/services/platform-helpers.ts +287 -0
- package/src/services/session-manager.ts +262 -0
- package/src/services/settings-resolver.ts +74 -0
- package/src/services/system-config-resolver.ts +90 -0
- package/src/services/system-skills-service.ts +229 -0
- package/src/services/transcription-service.ts +684 -0
- package/src/session.ts +110 -0
- package/src/spaces/index.ts +1 -0
- package/src/spaces/space-resolver.ts +17 -0
- package/src/stores/in-memory-agent-store.ts +403 -0
- package/src/stores/redis-agent-store.ts +279 -0
- package/src/utils/public-url.ts +44 -0
- package/src/utils/rate-limiter.ts +94 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import type { AgentOptions, LogLevel, PluginConfig } from "@lobu/core";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULTS as CORE_DEFAULTS,
|
|
9
|
+
createLogger,
|
|
10
|
+
getOptionalBoolean,
|
|
11
|
+
getOptionalEnv,
|
|
12
|
+
getOptionalNumber,
|
|
13
|
+
getRequiredEnv,
|
|
14
|
+
TIME,
|
|
15
|
+
} from "@lobu/core";
|
|
16
|
+
import { config as dotenvConfig } from "dotenv";
|
|
17
|
+
import type { OrchestratorConfig } from "../orchestration/base-deployment-manager";
|
|
18
|
+
|
|
19
|
+
const logger = createLogger("cli-config");
|
|
20
|
+
const OWLETTO_PLUGIN_SOURCE = "@lobu/owletto-openclaw";
|
|
21
|
+
const NATIVE_MEMORY_PLUGIN_SOURCE = "@openclaw/native-memory";
|
|
22
|
+
const WORKER_PACKAGE_JSON_CANDIDATES = [
|
|
23
|
+
path.resolve(process.cwd(), "packages/worker/package.json"),
|
|
24
|
+
"/app/packages/worker/package.json",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// CONSTANTS
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gateway-specific constants
|
|
33
|
+
* Core constants (TIME, REDIS_KEYS, core DEFAULTS) are imported from @lobu/core
|
|
34
|
+
* This file contains gateway-specific configuration values
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// Gateway-specific default configuration values
|
|
38
|
+
const GATEWAY_DEFAULTS = {
|
|
39
|
+
/** Default HTTP server port */
|
|
40
|
+
HTTP_PORT: 3000,
|
|
41
|
+
/** Default public gateway URL */
|
|
42
|
+
PUBLIC_GATEWAY_URL: "",
|
|
43
|
+
/** Default queue names */
|
|
44
|
+
QUEUE_DIRECT_MESSAGE: "direct_message",
|
|
45
|
+
QUEUE_MESSAGE_QUEUE: "message_queue",
|
|
46
|
+
/** Default worker settings */
|
|
47
|
+
WORKER_IMAGE_REPOSITORY: "lobu-worker",
|
|
48
|
+
WORKER_IMAGE_TAG: "latest",
|
|
49
|
+
WORKER_IMAGE_DIGEST: "",
|
|
50
|
+
WORKER_IMAGE_PULL_POLICY: "Always",
|
|
51
|
+
WORKER_IMAGE_PULL_SECRETS: "",
|
|
52
|
+
WORKER_SERVICE_ACCOUNT_NAME: "lobu-worker",
|
|
53
|
+
WORKER_RUNTIME_CLASS_NAME: "kata",
|
|
54
|
+
WORKER_STARTUP_TIMEOUT_SECONDS: 90,
|
|
55
|
+
WORKER_CPU_REQUEST: "100m",
|
|
56
|
+
WORKER_MEMORY_REQUEST: "256Mi",
|
|
57
|
+
WORKER_CPU_LIMIT: "1000m",
|
|
58
|
+
WORKER_MEMORY_LIMIT: "2Gi",
|
|
59
|
+
WORKER_IDLE_CLEANUP_MINUTES: 60,
|
|
60
|
+
MAX_WORKER_DEPLOYMENTS: 100,
|
|
61
|
+
WORKER_STALE_TIMEOUT_MINUTES: 10,
|
|
62
|
+
/** Default Kubernetes namespace */
|
|
63
|
+
KUBERNETES_NAMESPACE: "lobu",
|
|
64
|
+
/** Default cleanup settings */
|
|
65
|
+
CLEANUP_INITIAL_DELAY_MS: TIME.FIVE_SECONDS_MS,
|
|
66
|
+
CLEANUP_INTERVAL_MS: 60000, // 1 minute
|
|
67
|
+
CLEANUP_VERY_OLD_DAYS: 7,
|
|
68
|
+
/** Default socket health settings */
|
|
69
|
+
SOCKET_HEALTH_CHECK_INTERVAL_MS: 5 * TIME.MINUTE_MS, // 5 minutes
|
|
70
|
+
SOCKET_STALE_THRESHOLD_MS: 15 * TIME.MINUTE_MS, // 15 minutes
|
|
71
|
+
SOCKET_PROTECT_ACTIVE_WORKERS: true,
|
|
72
|
+
/** Default deployment settings */
|
|
73
|
+
LOBU_DEV_PROJECT_PATH: "/app",
|
|
74
|
+
COMPOSE_PROJECT_NAME: "lobu",
|
|
75
|
+
DISPATCHER_SERVICE_NAME: "lobu-dispatcher",
|
|
76
|
+
/** Default log level */
|
|
77
|
+
LOG_LEVEL: "INFO" as const,
|
|
78
|
+
/** Default kubeconfig path */
|
|
79
|
+
KUBECONFIG: "~/.kube/config",
|
|
80
|
+
/** Embedded mode defaults */
|
|
81
|
+
EMBEDDED_MAX_CONCURRENT_SESSIONS: 100,
|
|
82
|
+
EMBEDDED_MAX_MEMORY_PER_SESSION_MB: 256,
|
|
83
|
+
EMBEDDED_BASH_MAX_COMMAND_COUNT: 50_000,
|
|
84
|
+
EMBEDDED_BASH_MAX_LOOP_ITERATIONS: 50_000,
|
|
85
|
+
EMBEDDED_BASH_MAX_CALL_DEPTH: 50,
|
|
86
|
+
} as const;
|
|
87
|
+
|
|
88
|
+
// Merged DEFAULTS with core and gateway-specific values (internal use only)
|
|
89
|
+
const DEFAULTS = {
|
|
90
|
+
...CORE_DEFAULTS,
|
|
91
|
+
...GATEWAY_DEFAULTS,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
// Display formatting (internal use only)
|
|
95
|
+
const DISPLAY = {
|
|
96
|
+
/** Horizontal separator length */
|
|
97
|
+
SEPARATOR_LENGTH: 50,
|
|
98
|
+
/** Token preview length for logging */
|
|
99
|
+
TOKEN_PREVIEW_LENGTH: 10,
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// TYPES
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
/** Recursively makes all properties optional */
|
|
107
|
+
export type DeepPartial<T> = {
|
|
108
|
+
[P in keyof T]?: T[P] extends (infer U)[]
|
|
109
|
+
? U[]
|
|
110
|
+
: T[P] extends object
|
|
111
|
+
? DeepPartial<T[P]>
|
|
112
|
+
: T[P];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Agent configuration passed programmatically via GatewayConfig.
|
|
117
|
+
* Used in embedded mode to provision agents at startup without API calls.
|
|
118
|
+
*/
|
|
119
|
+
export interface AgentConfig {
|
|
120
|
+
id: string;
|
|
121
|
+
name: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
identityMd?: string;
|
|
124
|
+
soulMd?: string;
|
|
125
|
+
userMd?: string;
|
|
126
|
+
providers?: Array<{ id: string; model?: string; key?: string }>;
|
|
127
|
+
connections?: Array<{ type: string; config: Record<string, string> }>;
|
|
128
|
+
skills?: { enabled?: string[]; mcp?: Record<string, any> };
|
|
129
|
+
network?: { allowed?: string[]; denied?: string[] };
|
|
130
|
+
nixPackages?: string[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Complete gateway configuration - single source of truth
|
|
135
|
+
* Platform-specific configs (like Slack) are built separately
|
|
136
|
+
*/
|
|
137
|
+
export interface GatewayConfig {
|
|
138
|
+
agents?: AgentConfig[];
|
|
139
|
+
agentDefaults: Partial<AgentOptions>;
|
|
140
|
+
sessionTimeoutMinutes: number;
|
|
141
|
+
logLevel: LogLevel;
|
|
142
|
+
queues: {
|
|
143
|
+
connectionString: string;
|
|
144
|
+
directMessage: string;
|
|
145
|
+
messageQueue: string;
|
|
146
|
+
retryLimit: number;
|
|
147
|
+
retryDelay: number;
|
|
148
|
+
expireInHours: number;
|
|
149
|
+
};
|
|
150
|
+
anthropicProxy: {
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
anthropicBaseUrl?: string;
|
|
153
|
+
};
|
|
154
|
+
orchestration: OrchestratorConfig;
|
|
155
|
+
mcp: {
|
|
156
|
+
publicGatewayUrl: string;
|
|
157
|
+
internalGatewayUrl: string;
|
|
158
|
+
};
|
|
159
|
+
health: {
|
|
160
|
+
checkIntervalMs: number;
|
|
161
|
+
staleThresholdMs: number;
|
|
162
|
+
protectActiveWorkers: boolean;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// CONFIGURATION BUILDERS
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Load environment variables from .env file if in non-production
|
|
172
|
+
*/
|
|
173
|
+
export function loadEnvFile(envPath?: string): void {
|
|
174
|
+
if (process.env.NODE_ENV === "production") {
|
|
175
|
+
logger.debug("Production mode - skipping .env file");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const envProvided = Boolean(envPath);
|
|
180
|
+
const resolvedPath = envProvided
|
|
181
|
+
? path.resolve(process.cwd(), envPath!)
|
|
182
|
+
: path.resolve(process.cwd(), ".env");
|
|
183
|
+
|
|
184
|
+
if (existsSync(resolvedPath)) {
|
|
185
|
+
dotenvConfig({ path: resolvedPath });
|
|
186
|
+
logger.debug(`Loaded environment variables from ${resolvedPath}`);
|
|
187
|
+
} else if (envProvided) {
|
|
188
|
+
logger.warn(
|
|
189
|
+
`Specified env file ${resolvedPath} was not found; continuing without it.`
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
logger.debug("No .env file found; relying on process environment.");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Derive the internal gateway URL for worker→gateway communication.
|
|
198
|
+
* In K8s, uses DISPATCHER_SERVICE_NAME + namespace. In Docker, defaults to "gateway".
|
|
199
|
+
*/
|
|
200
|
+
export function getInternalGatewayUrl(): string {
|
|
201
|
+
const dispatcherService = process.env.DISPATCHER_SERVICE_NAME;
|
|
202
|
+
if (dispatcherService) {
|
|
203
|
+
const namespace = process.env.KUBERNETES_NAMESPACE || "lobu";
|
|
204
|
+
return `http://${dispatcherService}.${namespace}.svc.cluster.local:8080`;
|
|
205
|
+
}
|
|
206
|
+
const port = process.env.GATEWAY_PORT || "8080";
|
|
207
|
+
return `http://gateway:${port}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Build the default memory plugin list based on MEMORY_URL env var.
|
|
212
|
+
* MEMORY_URL set → Owletto MCP plugin (connect to that URL) when installed
|
|
213
|
+
* MEMORY_URL empty → @openclaw/native-memory (filesystem-based)
|
|
214
|
+
*/
|
|
215
|
+
function isPluginInstalled(source: string): boolean {
|
|
216
|
+
const resolverPaths = new Set<string>([__filename]);
|
|
217
|
+
const packagePathParts = source.split("/");
|
|
218
|
+
|
|
219
|
+
for (const candidate of WORKER_PACKAGE_JSON_CANDIDATES) {
|
|
220
|
+
if (existsSync(candidate)) {
|
|
221
|
+
resolverPaths.add(candidate);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const resolverPath of resolverPaths) {
|
|
226
|
+
try {
|
|
227
|
+
createRequire(resolverPath).resolve(source);
|
|
228
|
+
return true;
|
|
229
|
+
} catch {
|
|
230
|
+
const packageDir = path.join(
|
|
231
|
+
path.dirname(resolverPath),
|
|
232
|
+
"node_modules",
|
|
233
|
+
...packagePathParts
|
|
234
|
+
);
|
|
235
|
+
if (existsSync(packageDir)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function buildMemoryPlugins(options?: {
|
|
245
|
+
hasOwlettoPlugin?: boolean;
|
|
246
|
+
hasNativeMemoryPlugin?: boolean;
|
|
247
|
+
}): PluginConfig[] {
|
|
248
|
+
const nativeMemoryPlugin: PluginConfig = {
|
|
249
|
+
source: NATIVE_MEMORY_PLUGIN_SOURCE,
|
|
250
|
+
slot: "memory",
|
|
251
|
+
enabled: true,
|
|
252
|
+
};
|
|
253
|
+
const hasNativeMemoryPlugin =
|
|
254
|
+
options?.hasNativeMemoryPlugin ??
|
|
255
|
+
isPluginInstalled(NATIVE_MEMORY_PLUGIN_SOURCE);
|
|
256
|
+
|
|
257
|
+
if (!process.env.MEMORY_URL) {
|
|
258
|
+
if (hasNativeMemoryPlugin) {
|
|
259
|
+
return [nativeMemoryPlugin];
|
|
260
|
+
}
|
|
261
|
+
logger.warn(
|
|
262
|
+
`${NATIVE_MEMORY_PLUGIN_SOURCE} is not installed; continuing without a memory plugin`
|
|
263
|
+
);
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const hasOwlettoPlugin =
|
|
268
|
+
options?.hasOwlettoPlugin ?? isPluginInstalled(OWLETTO_PLUGIN_SOURCE);
|
|
269
|
+
if (!hasOwlettoPlugin) {
|
|
270
|
+
if (hasNativeMemoryPlugin) {
|
|
271
|
+
logger.warn(
|
|
272
|
+
`${OWLETTO_PLUGIN_SOURCE} is not installed; falling back to ${NATIVE_MEMORY_PLUGIN_SOURCE}`
|
|
273
|
+
);
|
|
274
|
+
return [nativeMemoryPlugin];
|
|
275
|
+
}
|
|
276
|
+
logger.warn(
|
|
277
|
+
`${OWLETTO_PLUGIN_SOURCE} is not installed and ${NATIVE_MEMORY_PLUGIN_SOURCE} is unavailable; continuing without a memory plugin`
|
|
278
|
+
);
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const gatewayUrl = getInternalGatewayUrl();
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
source: OWLETTO_PLUGIN_SOURCE,
|
|
286
|
+
slot: "memory",
|
|
287
|
+
enabled: true,
|
|
288
|
+
config: {
|
|
289
|
+
mcpUrl: `${gatewayUrl}/mcp/owletto`,
|
|
290
|
+
gatewayAuthUrl: gatewayUrl,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Deep-merge utility: merges source into target, recursing into plain objects */
|
|
297
|
+
function deepMerge<T extends Record<string, any>>(
|
|
298
|
+
target: T,
|
|
299
|
+
source: Partial<T>
|
|
300
|
+
): T {
|
|
301
|
+
const result = { ...target };
|
|
302
|
+
for (const key of Object.keys(source) as (keyof T)[]) {
|
|
303
|
+
const srcVal = source[key];
|
|
304
|
+
if (srcVal === undefined) continue;
|
|
305
|
+
const tgtVal = result[key];
|
|
306
|
+
if (
|
|
307
|
+
tgtVal &&
|
|
308
|
+
srcVal &&
|
|
309
|
+
typeof tgtVal === "object" &&
|
|
310
|
+
typeof srcVal === "object" &&
|
|
311
|
+
!Array.isArray(tgtVal) &&
|
|
312
|
+
!Array.isArray(srcVal)
|
|
313
|
+
) {
|
|
314
|
+
result[key] = deepMerge(tgtVal as any, srcVal as any);
|
|
315
|
+
} else {
|
|
316
|
+
result[key] = srcVal as T[keyof T];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Build complete gateway configuration from environment variables,
|
|
324
|
+
* optionally deep-merged with explicit overrides.
|
|
325
|
+
*
|
|
326
|
+
* @param overrides - Partial config that takes precedence over env vars.
|
|
327
|
+
* Useful for embedded mode where the host provides config programmatically.
|
|
328
|
+
*/
|
|
329
|
+
export function buildGatewayConfig(
|
|
330
|
+
overrides?: DeepPartial<GatewayConfig>
|
|
331
|
+
): GatewayConfig {
|
|
332
|
+
logger.debug("Building gateway configuration from environment variables");
|
|
333
|
+
|
|
334
|
+
// Required variables (allow override to satisfy the requirement)
|
|
335
|
+
const connectionString =
|
|
336
|
+
overrides?.queues?.connectionString || getRequiredEnv("QUEUE_URL");
|
|
337
|
+
|
|
338
|
+
const defaultMemoryFlushEnabled = getOptionalBoolean(
|
|
339
|
+
"AGENT_DEFAULT_MEMORY_FLUSH_ENABLED",
|
|
340
|
+
true
|
|
341
|
+
);
|
|
342
|
+
const defaultMemoryFlushSoftThresholdTokens = getOptionalNumber(
|
|
343
|
+
"AGENT_DEFAULT_MEMORY_FLUSH_SOFT_THRESHOLD_TOKENS",
|
|
344
|
+
4000
|
|
345
|
+
);
|
|
346
|
+
const defaultMemoryFlushSystemPrompt = getOptionalEnv(
|
|
347
|
+
"AGENT_DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT",
|
|
348
|
+
"Session nearing compaction. Store durable memories now."
|
|
349
|
+
);
|
|
350
|
+
const defaultMemoryFlushPrompt = getOptionalEnv(
|
|
351
|
+
"AGENT_DEFAULT_MEMORY_FLUSH_PROMPT",
|
|
352
|
+
"Write any lasting notes to memory using available memory tools. Reply with NO_REPLY if nothing to store."
|
|
353
|
+
);
|
|
354
|
+
const publicGatewayUrl = getOptionalEnv(
|
|
355
|
+
"PUBLIC_GATEWAY_URL",
|
|
356
|
+
DEFAULTS.PUBLIC_GATEWAY_URL
|
|
357
|
+
);
|
|
358
|
+
// Build configuration
|
|
359
|
+
const config: GatewayConfig = {
|
|
360
|
+
agentDefaults: {
|
|
361
|
+
allowedTools: process.env.ALLOWED_TOOLS?.split(","),
|
|
362
|
+
disallowedTools: process.env.DISALLOWED_TOOLS?.split(","),
|
|
363
|
+
runtime: process.env.AGENT_RUNTIME || process.env.AGENT_DEFAULT_RUNTIME,
|
|
364
|
+
model: process.env.AGENT_DEFAULT_MODEL,
|
|
365
|
+
timeoutMinutes: process.env.TIMEOUT_MINUTES
|
|
366
|
+
? Number(process.env.TIMEOUT_MINUTES)
|
|
367
|
+
: undefined,
|
|
368
|
+
compaction: {
|
|
369
|
+
memoryFlush: {
|
|
370
|
+
enabled: defaultMemoryFlushEnabled,
|
|
371
|
+
softThresholdTokens: defaultMemoryFlushSoftThresholdTokens,
|
|
372
|
+
systemPrompt: defaultMemoryFlushSystemPrompt,
|
|
373
|
+
prompt: defaultMemoryFlushPrompt,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
pluginsConfig: {
|
|
377
|
+
plugins: buildMemoryPlugins(),
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
sessionTimeoutMinutes: getOptionalNumber(
|
|
381
|
+
"SESSION_TIMEOUT_MINUTES",
|
|
382
|
+
DEFAULTS.SESSION_TIMEOUT_MINUTES
|
|
383
|
+
),
|
|
384
|
+
logLevel: (process.env.LOG_LEVEL as LogLevel) || DEFAULTS.LOG_LEVEL,
|
|
385
|
+
queues: {
|
|
386
|
+
connectionString,
|
|
387
|
+
directMessage: getOptionalEnv(
|
|
388
|
+
"QUEUE_DIRECT_MESSAGE",
|
|
389
|
+
DEFAULTS.QUEUE_DIRECT_MESSAGE
|
|
390
|
+
),
|
|
391
|
+
messageQueue: getOptionalEnv(
|
|
392
|
+
"QUEUE_MESSAGE_QUEUE",
|
|
393
|
+
DEFAULTS.QUEUE_MESSAGE_QUEUE
|
|
394
|
+
),
|
|
395
|
+
retryLimit: getOptionalNumber(
|
|
396
|
+
"QUEUE_RETRY_LIMIT",
|
|
397
|
+
DEFAULTS.QUEUE_RETRY_LIMIT
|
|
398
|
+
),
|
|
399
|
+
retryDelay: getOptionalNumber(
|
|
400
|
+
"QUEUE_RETRY_DELAY",
|
|
401
|
+
DEFAULTS.QUEUE_RETRY_DELAY_SECONDS
|
|
402
|
+
),
|
|
403
|
+
expireInHours: getOptionalNumber(
|
|
404
|
+
"QUEUE_EXPIRE_HOURS",
|
|
405
|
+
DEFAULTS.QUEUE_EXPIRE_HOURS
|
|
406
|
+
),
|
|
407
|
+
},
|
|
408
|
+
anthropicProxy: {
|
|
409
|
+
enabled: true,
|
|
410
|
+
anthropicBaseUrl:
|
|
411
|
+
process.env.SECRET_PROXY_UPSTREAM_URL || process.env.ANTHROPIC_BASE_URL,
|
|
412
|
+
},
|
|
413
|
+
orchestration: {
|
|
414
|
+
deploymentMode: process.env.DEPLOYMENT_MODE as
|
|
415
|
+
| "embedded"
|
|
416
|
+
| "docker"
|
|
417
|
+
| "kubernetes"
|
|
418
|
+
| undefined,
|
|
419
|
+
queues: {
|
|
420
|
+
connectionString,
|
|
421
|
+
retryLimit: getOptionalNumber(
|
|
422
|
+
"QUEUE_RETRY_LIMIT",
|
|
423
|
+
DEFAULTS.QUEUE_RETRY_LIMIT
|
|
424
|
+
),
|
|
425
|
+
retryDelay: getOptionalNumber(
|
|
426
|
+
"QUEUE_RETRY_DELAY",
|
|
427
|
+
DEFAULTS.QUEUE_RETRY_DELAY_SECONDS
|
|
428
|
+
),
|
|
429
|
+
expireInSeconds:
|
|
430
|
+
getOptionalNumber("QUEUE_EXPIRE_HOURS", DEFAULTS.QUEUE_EXPIRE_HOURS) *
|
|
431
|
+
TIME.HOUR_SECONDS,
|
|
432
|
+
},
|
|
433
|
+
worker: {
|
|
434
|
+
image: {
|
|
435
|
+
repository: getOptionalEnv(
|
|
436
|
+
"WORKER_IMAGE_REPOSITORY",
|
|
437
|
+
DEFAULTS.WORKER_IMAGE_REPOSITORY
|
|
438
|
+
),
|
|
439
|
+
tag: getOptionalEnv("WORKER_IMAGE_TAG", DEFAULTS.WORKER_IMAGE_TAG),
|
|
440
|
+
digest: getOptionalEnv(
|
|
441
|
+
"WORKER_IMAGE_DIGEST",
|
|
442
|
+
DEFAULTS.WORKER_IMAGE_DIGEST
|
|
443
|
+
),
|
|
444
|
+
pullPolicy: getOptionalEnv(
|
|
445
|
+
"WORKER_IMAGE_PULL_POLICY",
|
|
446
|
+
DEFAULTS.WORKER_IMAGE_PULL_POLICY
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
imagePullSecrets: getOptionalEnv(
|
|
450
|
+
"WORKER_IMAGE_PULL_SECRETS",
|
|
451
|
+
DEFAULTS.WORKER_IMAGE_PULL_SECRETS
|
|
452
|
+
)
|
|
453
|
+
.split(",")
|
|
454
|
+
.map((secret) => secret.trim())
|
|
455
|
+
.filter(Boolean),
|
|
456
|
+
serviceAccountName: getOptionalEnv(
|
|
457
|
+
"WORKER_SERVICE_ACCOUNT_NAME",
|
|
458
|
+
DEFAULTS.WORKER_SERVICE_ACCOUNT_NAME
|
|
459
|
+
),
|
|
460
|
+
runtimeClassName: getOptionalEnv(
|
|
461
|
+
"WORKER_RUNTIME_CLASS_NAME",
|
|
462
|
+
DEFAULTS.WORKER_RUNTIME_CLASS_NAME
|
|
463
|
+
),
|
|
464
|
+
startupTimeoutSeconds: getOptionalNumber(
|
|
465
|
+
"WORKER_STARTUP_TIMEOUT_SECONDS",
|
|
466
|
+
DEFAULTS.WORKER_STARTUP_TIMEOUT_SECONDS
|
|
467
|
+
),
|
|
468
|
+
resources: {
|
|
469
|
+
requests: {
|
|
470
|
+
cpu: getOptionalEnv(
|
|
471
|
+
"WORKER_CPU_REQUEST",
|
|
472
|
+
DEFAULTS.WORKER_CPU_REQUEST
|
|
473
|
+
),
|
|
474
|
+
memory: getOptionalEnv(
|
|
475
|
+
"WORKER_MEMORY_REQUEST",
|
|
476
|
+
DEFAULTS.WORKER_MEMORY_REQUEST
|
|
477
|
+
),
|
|
478
|
+
},
|
|
479
|
+
limits: {
|
|
480
|
+
cpu: getOptionalEnv("WORKER_CPU_LIMIT", DEFAULTS.WORKER_CPU_LIMIT),
|
|
481
|
+
memory: getOptionalEnv(
|
|
482
|
+
"WORKER_MEMORY_LIMIT",
|
|
483
|
+
DEFAULTS.WORKER_MEMORY_LIMIT
|
|
484
|
+
),
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
idleCleanupMinutes: getOptionalNumber(
|
|
488
|
+
"WORKER_IDLE_CLEANUP_MINUTES",
|
|
489
|
+
DEFAULTS.WORKER_IDLE_CLEANUP_MINUTES
|
|
490
|
+
),
|
|
491
|
+
maxDeployments: getOptionalNumber(
|
|
492
|
+
"MAX_WORKER_DEPLOYMENTS",
|
|
493
|
+
DEFAULTS.MAX_WORKER_DEPLOYMENTS
|
|
494
|
+
),
|
|
495
|
+
},
|
|
496
|
+
kubernetes: {
|
|
497
|
+
namespace: getOptionalEnv(
|
|
498
|
+
"KUBERNETES_NAMESPACE",
|
|
499
|
+
DEFAULTS.KUBERNETES_NAMESPACE
|
|
500
|
+
),
|
|
501
|
+
},
|
|
502
|
+
cleanup: {
|
|
503
|
+
initialDelayMs: getOptionalNumber(
|
|
504
|
+
"CLEANUP_INITIAL_DELAY_MS",
|
|
505
|
+
DEFAULTS.CLEANUP_INITIAL_DELAY_MS
|
|
506
|
+
),
|
|
507
|
+
intervalMs: getOptionalNumber(
|
|
508
|
+
"CLEANUP_INTERVAL_MS",
|
|
509
|
+
DEFAULTS.CLEANUP_INTERVAL_MS
|
|
510
|
+
),
|
|
511
|
+
veryOldDays: getOptionalNumber(
|
|
512
|
+
"CLEANUP_VERY_OLD_DAYS",
|
|
513
|
+
DEFAULTS.CLEANUP_VERY_OLD_DAYS
|
|
514
|
+
),
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
mcp: {
|
|
518
|
+
publicGatewayUrl,
|
|
519
|
+
internalGatewayUrl: getInternalGatewayUrl(),
|
|
520
|
+
},
|
|
521
|
+
health: {
|
|
522
|
+
checkIntervalMs: getOptionalNumber(
|
|
523
|
+
"SOCKET_HEALTH_CHECK_INTERVAL_MS",
|
|
524
|
+
DEFAULTS.SOCKET_HEALTH_CHECK_INTERVAL_MS
|
|
525
|
+
),
|
|
526
|
+
staleThresholdMs: getOptionalNumber(
|
|
527
|
+
"SOCKET_STALE_THRESHOLD_MS",
|
|
528
|
+
DEFAULTS.SOCKET_STALE_THRESHOLD_MS
|
|
529
|
+
),
|
|
530
|
+
protectActiveWorkers: getOptionalBoolean(
|
|
531
|
+
"SOCKET_PROTECT_ACTIVE_WORKERS",
|
|
532
|
+
DEFAULTS.SOCKET_PROTECT_ACTIVE_WORKERS
|
|
533
|
+
),
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
logger.debug("Gateway configuration built successfully");
|
|
538
|
+
|
|
539
|
+
if (overrides) {
|
|
540
|
+
return deepMerge(config, overrides as Partial<GatewayConfig>);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return config;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Display gateway configuration (platform-agnostic parts only)
|
|
548
|
+
* Platform-specific display should be handled by platform modules
|
|
549
|
+
*/
|
|
550
|
+
export function displayGatewayConfig(config: GatewayConfig): void {
|
|
551
|
+
const separator = "=".repeat(DISPLAY.SEPARATOR_LENGTH);
|
|
552
|
+
|
|
553
|
+
console.log("Gateway Configuration:");
|
|
554
|
+
console.log(separator);
|
|
555
|
+
|
|
556
|
+
console.log("\nQueues:");
|
|
557
|
+
console.log(
|
|
558
|
+
` Connection: ${config.queues.connectionString.substring(0, 30)}...`
|
|
559
|
+
);
|
|
560
|
+
console.log(` Retry Limit: ${config.queues.retryLimit}`);
|
|
561
|
+
console.log(` Retry Delay: ${config.queues.retryDelay}s`);
|
|
562
|
+
|
|
563
|
+
console.log("\nMCP:");
|
|
564
|
+
console.log(
|
|
565
|
+
` Public Gateway: ${config.mcp.publicGatewayUrl || "(not set)"}`
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
console.log("\nOrchestration:");
|
|
569
|
+
console.log(
|
|
570
|
+
` Worker Image: ${config.orchestration.worker.image.repository}`
|
|
571
|
+
);
|
|
572
|
+
console.log(` Worker Tag: ${config.orchestration.worker.image.tag}`);
|
|
573
|
+
if (config.orchestration.worker.image.digest) {
|
|
574
|
+
console.log(` Worker Digest: ${config.orchestration.worker.image.digest}`);
|
|
575
|
+
}
|
|
576
|
+
console.log(
|
|
577
|
+
` Max Deployments: ${config.orchestration.worker.maxDeployments}`
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
console.log("\nHealth:");
|
|
581
|
+
console.log(` Socket Check Interval: ${config.health.checkIntervalMs}ms`);
|
|
582
|
+
console.log(` Socket Stale Threshold: ${config.health.staleThresholdMs}ms`);
|
|
583
|
+
console.log(
|
|
584
|
+
` Protect Active Workers: ${config.health.protectActiveWorkers}`
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
console.log(`\n${separator}`);
|
|
588
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createLogger } from "@lobu/core";
|
|
2
|
+
|
|
3
|
+
const logger = createLogger("network-allowlist");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load allowed domains from environment
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Not set: Complete isolation (deny all)
|
|
10
|
+
* - "*": Unrestricted access (allow all)
|
|
11
|
+
* - "domain1,domain2": Allowlist mode (deny by default, allow only these)
|
|
12
|
+
*/
|
|
13
|
+
export function loadAllowedDomains(): string[] {
|
|
14
|
+
const domains: string[] = [];
|
|
15
|
+
|
|
16
|
+
const allowedDomains = process.env.WORKER_ALLOWED_DOMAINS;
|
|
17
|
+
if (allowedDomains) {
|
|
18
|
+
const trimmed = allowedDomains.trim();
|
|
19
|
+
|
|
20
|
+
// Special case: * means unrestricted access
|
|
21
|
+
if (trimmed === "*") {
|
|
22
|
+
logger.debug("WORKER_ALLOWED_DOMAINS=* - unrestricted internet access");
|
|
23
|
+
return ["*"];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const parsed = trimmed
|
|
27
|
+
.split(",")
|
|
28
|
+
.map((d) => d.trim())
|
|
29
|
+
.filter((d) => d.length > 0);
|
|
30
|
+
|
|
31
|
+
domains.push(...parsed);
|
|
32
|
+
logger.debug(
|
|
33
|
+
`Loaded ${parsed.length} allowed domains from WORKER_ALLOWED_DOMAINS`
|
|
34
|
+
);
|
|
35
|
+
} else {
|
|
36
|
+
logger.warn(
|
|
37
|
+
"⚠️ WORKER_ALLOWED_DOMAINS not set - workers will have NO internet access (complete isolation)"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return domains;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if unrestricted mode is enabled
|
|
46
|
+
*/
|
|
47
|
+
export function isUnrestrictedMode(allowedDomains: string[]): boolean {
|
|
48
|
+
return allowedDomains.length === 1 && allowedDomains[0] === "*";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Load disallowed domains from environment
|
|
53
|
+
*/
|
|
54
|
+
export function loadDisallowedDomains(): string[] {
|
|
55
|
+
const domains: string[] = [];
|
|
56
|
+
|
|
57
|
+
const disallowedDomains = process.env.WORKER_DISALLOWED_DOMAINS;
|
|
58
|
+
if (disallowedDomains) {
|
|
59
|
+
const parsed = disallowedDomains
|
|
60
|
+
.split(",")
|
|
61
|
+
.map((d) => d.trim())
|
|
62
|
+
.filter((d) => d.length > 0);
|
|
63
|
+
|
|
64
|
+
domains.push(...parsed);
|
|
65
|
+
logger.debug(
|
|
66
|
+
`Loaded ${parsed.length} disallowed domains from WORKER_DISALLOWED_DOMAINS`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return domains;
|
|
71
|
+
}
|