@lobu/gateway 3.0.9 → 3.0.13

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 (212) hide show
  1. package/dist/api/platform.d.ts.map +1 -1
  2. package/dist/api/platform.js +7 -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/connections/chat-instance-manager.d.ts.map +1 -1
  12. package/dist/connections/chat-instance-manager.js +2 -1
  13. package/dist/connections/chat-instance-manager.js.map +1 -1
  14. package/dist/connections/interaction-bridge.d.ts +9 -2
  15. package/dist/connections/interaction-bridge.d.ts.map +1 -1
  16. package/dist/connections/interaction-bridge.js +121 -261
  17. package/dist/connections/interaction-bridge.js.map +1 -1
  18. package/dist/gateway/index.js +1 -1
  19. package/dist/gateway/index.js.map +1 -1
  20. package/dist/interactions.d.ts +9 -43
  21. package/dist/interactions.d.ts.map +1 -1
  22. package/dist/interactions.js +10 -52
  23. package/dist/interactions.js.map +1 -1
  24. package/dist/routes/public/agent.d.ts +4 -0
  25. package/dist/routes/public/agent.d.ts.map +1 -1
  26. package/dist/routes/public/agent.js +21 -0
  27. package/dist/routes/public/agent.js.map +1 -1
  28. package/dist/services/core-services.d.ts.map +1 -1
  29. package/dist/services/core-services.js +4 -0
  30. package/dist/services/core-services.js.map +1 -1
  31. package/package.json +9 -9
  32. package/src/__tests__/agent-config-routes.test.ts +0 -254
  33. package/src/__tests__/agent-history-routes.test.ts +0 -72
  34. package/src/__tests__/agent-routes.test.ts +0 -68
  35. package/src/__tests__/agent-schedules-routes.test.ts +0 -59
  36. package/src/__tests__/agent-settings-store.test.ts +0 -323
  37. package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
  38. package/src/__tests__/bedrock-openai-service.test.ts +0 -157
  39. package/src/__tests__/bedrock-provider-module.test.ts +0 -56
  40. package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
  41. package/src/__tests__/chat-response-bridge.test.ts +0 -131
  42. package/src/__tests__/config-memory-plugins.test.ts +0 -92
  43. package/src/__tests__/config-request-store.test.ts +0 -127
  44. package/src/__tests__/connection-routes.test.ts +0 -144
  45. package/src/__tests__/core-services-store-selection.test.ts +0 -92
  46. package/src/__tests__/docker-deployment.test.ts +0 -1211
  47. package/src/__tests__/embedded-deployment.test.ts +0 -342
  48. package/src/__tests__/grant-store.test.ts +0 -148
  49. package/src/__tests__/http-proxy.test.ts +0 -281
  50. package/src/__tests__/instruction-service.test.ts +0 -37
  51. package/src/__tests__/link-buttons.test.ts +0 -112
  52. package/src/__tests__/lobu.test.ts +0 -32
  53. package/src/__tests__/mcp-config-service.test.ts +0 -347
  54. package/src/__tests__/mcp-proxy.test.ts +0 -694
  55. package/src/__tests__/message-handler-bridge.test.ts +0 -17
  56. package/src/__tests__/model-selection.test.ts +0 -172
  57. package/src/__tests__/oauth-templates.test.ts +0 -39
  58. package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
  59. package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
  60. package/src/__tests__/provider-inheritance.test.ts +0 -212
  61. package/src/__tests__/routes/cli-auth.test.ts +0 -337
  62. package/src/__tests__/routes/interactions.test.ts +0 -121
  63. package/src/__tests__/secret-proxy.test.ts +0 -85
  64. package/src/__tests__/session-manager.test.ts +0 -572
  65. package/src/__tests__/setup.ts +0 -133
  66. package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
  67. package/src/__tests__/slack-routes.test.ts +0 -161
  68. package/src/__tests__/system-config-resolver.test.ts +0 -75
  69. package/src/__tests__/system-message-limiter.test.ts +0 -89
  70. package/src/__tests__/system-skills-service.test.ts +0 -362
  71. package/src/__tests__/transcription-service.test.ts +0 -222
  72. package/src/__tests__/utils/rate-limiter.test.ts +0 -102
  73. package/src/__tests__/worker-connection-manager.test.ts +0 -497
  74. package/src/__tests__/worker-job-router.test.ts +0 -722
  75. package/src/api/index.ts +0 -1
  76. package/src/api/platform.ts +0 -292
  77. package/src/api/response-renderer.ts +0 -157
  78. package/src/auth/agent-metadata-store.ts +0 -168
  79. package/src/auth/api-auth-middleware.ts +0 -69
  80. package/src/auth/api-key-provider-module.ts +0 -213
  81. package/src/auth/base-provider-module.ts +0 -201
  82. package/src/auth/bedrock/provider-module.ts +0 -110
  83. package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
  84. package/src/auth/chatgpt/device-code-client.ts +0 -218
  85. package/src/auth/chatgpt/index.ts +0 -1
  86. package/src/auth/claude/oauth-module.ts +0 -280
  87. package/src/auth/cli/token-service.ts +0 -249
  88. package/src/auth/external/client.ts +0 -560
  89. package/src/auth/external/device-code-client.ts +0 -235
  90. package/src/auth/mcp/config-service.ts +0 -420
  91. package/src/auth/mcp/proxy.ts +0 -1086
  92. package/src/auth/mcp/string-substitution.ts +0 -17
  93. package/src/auth/mcp/tool-cache.ts +0 -90
  94. package/src/auth/oauth/base-client.ts +0 -267
  95. package/src/auth/oauth/client.ts +0 -153
  96. package/src/auth/oauth/credentials.ts +0 -7
  97. package/src/auth/oauth/providers.ts +0 -69
  98. package/src/auth/oauth/state-store.ts +0 -150
  99. package/src/auth/oauth-templates.ts +0 -179
  100. package/src/auth/provider-catalog.ts +0 -220
  101. package/src/auth/provider-model-options.ts +0 -41
  102. package/src/auth/settings/agent-settings-store.ts +0 -565
  103. package/src/auth/settings/auth-profiles-manager.ts +0 -216
  104. package/src/auth/settings/index.ts +0 -12
  105. package/src/auth/settings/model-preference-store.ts +0 -52
  106. package/src/auth/settings/model-selection.ts +0 -135
  107. package/src/auth/settings/resolved-settings-view.ts +0 -298
  108. package/src/auth/settings/template-utils.ts +0 -44
  109. package/src/auth/settings/token-service.ts +0 -88
  110. package/src/auth/system-env-store.ts +0 -98
  111. package/src/auth/user-agents-store.ts +0 -68
  112. package/src/channels/binding-service.ts +0 -214
  113. package/src/channels/index.ts +0 -4
  114. package/src/cli/gateway.ts +0 -1312
  115. package/src/cli/index.ts +0 -74
  116. package/src/commands/built-in-commands.ts +0 -80
  117. package/src/commands/command-dispatcher.ts +0 -94
  118. package/src/commands/command-reply-adapters.ts +0 -27
  119. package/src/config/file-loader.ts +0 -618
  120. package/src/config/index.ts +0 -588
  121. package/src/config/network-allowlist.ts +0 -71
  122. package/src/connections/chat-instance-manager.ts +0 -1284
  123. package/src/connections/chat-response-bridge.ts +0 -618
  124. package/src/connections/index.ts +0 -7
  125. package/src/connections/interaction-bridge.ts +0 -831
  126. package/src/connections/message-handler-bridge.ts +0 -440
  127. package/src/connections/platform-auth-methods.ts +0 -15
  128. package/src/connections/types.ts +0 -84
  129. package/src/gateway/connection-manager.ts +0 -291
  130. package/src/gateway/index.ts +0 -698
  131. package/src/gateway/job-router.ts +0 -201
  132. package/src/gateway-main.ts +0 -200
  133. package/src/index.ts +0 -41
  134. package/src/infrastructure/queue/index.ts +0 -12
  135. package/src/infrastructure/queue/queue-producer.ts +0 -148
  136. package/src/infrastructure/queue/redis-queue.ts +0 -361
  137. package/src/infrastructure/queue/types.ts +0 -133
  138. package/src/infrastructure/redis/system-message-limiter.ts +0 -94
  139. package/src/interactions/config-request-store.ts +0 -198
  140. package/src/interactions.ts +0 -363
  141. package/src/lobu.ts +0 -311
  142. package/src/metrics/prometheus.ts +0 -159
  143. package/src/modules/module-system.ts +0 -179
  144. package/src/orchestration/base-deployment-manager.ts +0 -900
  145. package/src/orchestration/deployment-utils.ts +0 -98
  146. package/src/orchestration/impl/docker-deployment.ts +0 -620
  147. package/src/orchestration/impl/embedded-deployment.ts +0 -268
  148. package/src/orchestration/impl/index.ts +0 -8
  149. package/src/orchestration/impl/k8s/deployment.ts +0 -1061
  150. package/src/orchestration/impl/k8s/helpers.ts +0 -610
  151. package/src/orchestration/impl/k8s/index.ts +0 -1
  152. package/src/orchestration/index.ts +0 -333
  153. package/src/orchestration/message-consumer.ts +0 -584
  154. package/src/orchestration/scheduled-wakeup.ts +0 -704
  155. package/src/permissions/approval-policy.ts +0 -36
  156. package/src/permissions/grant-store.ts +0 -219
  157. package/src/platform/file-handler.ts +0 -66
  158. package/src/platform/link-buttons.ts +0 -57
  159. package/src/platform/renderer-utils.ts +0 -44
  160. package/src/platform/response-renderer.ts +0 -84
  161. package/src/platform/unified-thread-consumer.ts +0 -194
  162. package/src/platform.ts +0 -318
  163. package/src/proxy/http-proxy.ts +0 -752
  164. package/src/proxy/proxy-manager.ts +0 -81
  165. package/src/proxy/secret-proxy.ts +0 -402
  166. package/src/proxy/token-refresh-job.ts +0 -143
  167. package/src/routes/internal/audio.ts +0 -141
  168. package/src/routes/internal/device-auth.ts +0 -652
  169. package/src/routes/internal/files.ts +0 -226
  170. package/src/routes/internal/history.ts +0 -69
  171. package/src/routes/internal/images.ts +0 -127
  172. package/src/routes/internal/interactions.ts +0 -84
  173. package/src/routes/internal/middleware.ts +0 -23
  174. package/src/routes/internal/schedule.ts +0 -226
  175. package/src/routes/internal/types.ts +0 -22
  176. package/src/routes/openapi-auto.ts +0 -239
  177. package/src/routes/public/agent-access.ts +0 -23
  178. package/src/routes/public/agent-config.ts +0 -675
  179. package/src/routes/public/agent-history.ts +0 -422
  180. package/src/routes/public/agent-schedules.ts +0 -296
  181. package/src/routes/public/agent.ts +0 -1086
  182. package/src/routes/public/agents.ts +0 -373
  183. package/src/routes/public/channels.ts +0 -191
  184. package/src/routes/public/cli-auth.ts +0 -896
  185. package/src/routes/public/connections.ts +0 -574
  186. package/src/routes/public/landing.ts +0 -16
  187. package/src/routes/public/oauth.ts +0 -147
  188. package/src/routes/public/settings-auth.ts +0 -104
  189. package/src/routes/public/slack.ts +0 -173
  190. package/src/routes/shared/agent-ownership.ts +0 -101
  191. package/src/routes/shared/token-verifier.ts +0 -34
  192. package/src/services/bedrock-model-catalog.ts +0 -217
  193. package/src/services/bedrock-openai-service.ts +0 -658
  194. package/src/services/core-services.ts +0 -1072
  195. package/src/services/image-generation-service.ts +0 -257
  196. package/src/services/instruction-service.ts +0 -318
  197. package/src/services/mcp-registry.ts +0 -94
  198. package/src/services/platform-helpers.ts +0 -287
  199. package/src/services/session-manager.ts +0 -262
  200. package/src/services/settings-resolver.ts +0 -74
  201. package/src/services/system-config-resolver.ts +0 -89
  202. package/src/services/system-skills-service.ts +0 -229
  203. package/src/services/transcription-service.ts +0 -684
  204. package/src/session.ts +0 -110
  205. package/src/spaces/index.ts +0 -1
  206. package/src/spaces/space-resolver.ts +0 -17
  207. package/src/stores/in-memory-agent-store.ts +0 -403
  208. package/src/stores/redis-agent-store.ts +0 -279
  209. package/src/utils/public-url.ts +0 -44
  210. package/src/utils/rate-limiter.ts +0 -94
  211. package/tsconfig.json +0 -33
  212. package/tsconfig.tsbuildinfo +0 -1
@@ -1,287 +0,0 @@
1
- /**
2
- * Shared platform helpers.
3
- * Extracts common logic duplicated across Slack, Telegram, and WhatsApp message handlers.
4
- */
5
-
6
- import {
7
- createLogger,
8
- type PluginConfig,
9
- type PluginsConfig,
10
- } from "@lobu/core";
11
- import type { AgentSettingsStore } from "../auth/settings";
12
- import { resolveEffectiveModelRef } from "../auth/settings/model-selection";
13
- import type { ChannelBindingService } from "../channels";
14
- import { buildMemoryPlugins, getInternalGatewayUrl } from "../config";
15
- import type { MessagePayload } from "../infrastructure/queue/queue-producer";
16
- import { getModelProviderModules } from "../modules/module-system";
17
- import { platformAgentId } from "../spaces";
18
-
19
- const logger = createLogger("platform-helpers");
20
- const OWLETTO_PLUGIN_SOURCE = "@lobu/owletto-openclaw";
21
-
22
- function readOwlettoRuntimeDefaults(): PluginConfig | null {
23
- const configuredPlugin = buildMemoryPlugins().find(
24
- (plugin) =>
25
- plugin.source === OWLETTO_PLUGIN_SOURCE && plugin.slot === "memory"
26
- );
27
- if (configuredPlugin) {
28
- return configuredPlugin;
29
- }
30
-
31
- const gatewayUrl = getInternalGatewayUrl();
32
- return {
33
- source: OWLETTO_PLUGIN_SOURCE,
34
- slot: "memory",
35
- enabled: true,
36
- config: {
37
- mcpUrl: `${gatewayUrl}/mcp/owletto`,
38
- gatewayAuthUrl: gatewayUrl,
39
- },
40
- };
41
- }
42
-
43
- function normalizeOwlettoPluginConfig(
44
- plugin: PluginConfig,
45
- runtimeDefault: PluginConfig | null
46
- ): PluginConfig {
47
- if (
48
- plugin.source !== OWLETTO_PLUGIN_SOURCE ||
49
- plugin.slot !== "memory" ||
50
- !runtimeDefault?.config ||
51
- !plugin.config
52
- ) {
53
- return plugin;
54
- }
55
-
56
- const storedMcpUrl = plugin.config.mcpUrl;
57
- const storedGatewayAuthUrl = plugin.config.gatewayAuthUrl;
58
- const runtimeMcpUrl = runtimeDefault.config.mcpUrl;
59
- const runtimeGatewayAuthUrl = runtimeDefault.config.gatewayAuthUrl;
60
-
61
- const shouldReplaceMcpUrl =
62
- typeof storedMcpUrl === "string" &&
63
- typeof runtimeMcpUrl === "string" &&
64
- runtimeMcpUrl !== storedMcpUrl &&
65
- /^https?:\/\/gateway(?::\d+)?\/mcp\/owletto\/?$/.test(storedMcpUrl);
66
- const shouldReplaceGatewayAuthUrl =
67
- typeof storedGatewayAuthUrl === "string" &&
68
- typeof runtimeGatewayAuthUrl === "string" &&
69
- runtimeGatewayAuthUrl !== storedGatewayAuthUrl &&
70
- /^https?:\/\/gateway(?::\d+)?\/?$/.test(storedGatewayAuthUrl);
71
-
72
- if (!shouldReplaceMcpUrl && !shouldReplaceGatewayAuthUrl) {
73
- return plugin;
74
- }
75
-
76
- return {
77
- ...plugin,
78
- config: {
79
- ...plugin.config,
80
- ...(shouldReplaceMcpUrl ? { mcpUrl: runtimeMcpUrl } : {}),
81
- ...(shouldReplaceGatewayAuthUrl
82
- ? { gatewayAuthUrl: runtimeGatewayAuthUrl }
83
- : {}),
84
- },
85
- };
86
- }
87
-
88
- function normalizePluginsConfig(
89
- pluginsConfig: PluginsConfig | undefined
90
- ): PluginsConfig | undefined {
91
- if (!pluginsConfig?.plugins?.length) {
92
- return pluginsConfig;
93
- }
94
-
95
- const runtimeDefault = readOwlettoRuntimeDefaults();
96
- let changed = false;
97
- const plugins = pluginsConfig.plugins.map((plugin) => {
98
- const normalized = normalizeOwlettoPluginConfig(plugin, runtimeDefault);
99
- if (normalized !== plugin) {
100
- changed = true;
101
- }
102
- return normalized;
103
- });
104
-
105
- return changed ? { ...pluginsConfig, plugins } : pluginsConfig;
106
- }
107
-
108
- /**
109
- * Resolve agent options by merging base options with per-agent settings.
110
- * Priority: agent settings > config defaults.
111
- */
112
- export async function resolveAgentOptions(
113
- agentId: string,
114
- baseOptions: Record<string, any>,
115
- agentSettingsStore?: AgentSettingsStore
116
- ): Promise<Record<string, any>> {
117
- if (!agentSettingsStore) {
118
- return { ...baseOptions };
119
- }
120
-
121
- const settings = await agentSettingsStore.getEffectiveSettings(agentId);
122
- if (!settings) {
123
- return { ...baseOptions };
124
- }
125
-
126
- const effectiveProviders = settings.installedProviders || [];
127
-
128
- const mergedOptions: Record<string, any> = { ...baseOptions };
129
- const effectiveModelRef = resolveEffectiveModelRef(settings);
130
- logger.info(
131
- {
132
- agentId,
133
- configuredModel: settings.model,
134
- effectiveModel: effectiveModelRef,
135
- },
136
- "Applying agent settings"
137
- );
138
-
139
- if (effectiveModelRef) {
140
- mergedOptions.model = effectiveModelRef;
141
- } else if (effectiveProviders.length > 0) {
142
- // Auto mode with installed providers: let worker resolve default model.
143
- delete mergedOptions.model;
144
- }
145
-
146
- if (settings.networkConfig) {
147
- mergedOptions.networkConfig = settings.networkConfig;
148
- }
149
- if (settings.nixConfig) {
150
- mergedOptions.nixConfig = settings.nixConfig;
151
- }
152
- if (settings.toolsConfig) {
153
- mergedOptions.toolsConfig = settings.toolsConfig;
154
- }
155
- if (settings.mcpServers) {
156
- mergedOptions.mcpServers = settings.mcpServers;
157
- }
158
- if (settings.pluginsConfig) {
159
- mergedOptions.pluginsConfig = normalizePluginsConfig(
160
- settings.pluginsConfig
161
- );
162
- }
163
- // Apply default memory plugins if no pluginsConfig from settings or baseOptions
164
- if (!mergedOptions.pluginsConfig) {
165
- mergedOptions.pluginsConfig = { plugins: buildMemoryPlugins() };
166
- }
167
- if (settings.verboseLogging !== undefined) {
168
- mergedOptions.verboseLogging = settings.verboseLogging;
169
- }
170
-
171
- return mergedOptions;
172
- }
173
-
174
- export async function hasConfiguredProvider(
175
- agentId: string,
176
- agentSettingsStore?: AgentSettingsStore
177
- ): Promise<boolean> {
178
- if (!agentSettingsStore) {
179
- return true;
180
- }
181
-
182
- const settings = await agentSettingsStore.getEffectiveSettings(agentId);
183
- const installedProviderIds = new Set(
184
- (settings?.installedProviders || []).map((provider) => provider.providerId)
185
- );
186
-
187
- if ((settings?.authProfiles?.length || 0) > 0) {
188
- return true;
189
- }
190
-
191
- const modules = getModelProviderModules();
192
- if (installedProviderIds.size > 0) {
193
- return modules.some(
194
- (module) =>
195
- installedProviderIds.has(module.providerId) && module.hasSystemKey()
196
- );
197
- }
198
-
199
- return modules.some((module) => module.hasSystemKey());
200
- }
201
-
202
- /**
203
- * Build a MessagePayload from common fields.
204
- * Extracts networkConfig, nixConfig, mcpServers from agentOptions before constructing the payload.
205
- */
206
- export function buildMessagePayload(params: {
207
- platform: string;
208
- userId: string;
209
- botId: string;
210
- conversationId: string;
211
- teamId: string;
212
- agentId: string;
213
- messageId: string;
214
- messageText: string;
215
- channelId: string;
216
- platformMetadata: Record<string, any>;
217
- agentOptions: Record<string, any>;
218
- }): MessagePayload {
219
- const { networkConfig, nixConfig, mcpServers, ...remainingOptions } =
220
- params.agentOptions;
221
-
222
- return {
223
- platform: params.platform,
224
- userId: params.userId,
225
- botId: params.botId,
226
- conversationId: params.conversationId,
227
- teamId: params.teamId,
228
- agentId: params.agentId,
229
- messageId: params.messageId,
230
- messageText: params.messageText,
231
- channelId: params.channelId,
232
- platformMetadata: params.platformMetadata,
233
- agentOptions: remainingOptions,
234
- networkConfig,
235
- nixConfig,
236
- mcpConfig: mcpServers ? { mcpServers } : undefined,
237
- };
238
- }
239
-
240
- /**
241
- * Resolve agent ID. Deterministic for all platforms.
242
- * Channel binding is checked first for Slack (multi-tenant), then falls back to platformAgentId.
243
- */
244
- export async function resolveAgentId(params: {
245
- platform: string;
246
- userId: string;
247
- channelId: string;
248
- isGroup: boolean;
249
- teamId?: string;
250
- channelBindingService?: ChannelBindingService;
251
- sendConfigPrompt?: () => Promise<boolean>;
252
- }): Promise<{ agentId: string; promptSent: boolean }> {
253
- const {
254
- platform,
255
- userId,
256
- channelId,
257
- isGroup,
258
- teamId,
259
- channelBindingService,
260
- sendConfigPrompt,
261
- } = params;
262
-
263
- // Check channel binding first (Slack multi-tenant)
264
- if (channelBindingService) {
265
- const binding = await channelBindingService.getBinding(
266
- platform,
267
- channelId,
268
- teamId
269
- );
270
- if (binding) {
271
- logger.info({ agentId: binding.agentId, channelId }, "Using bound agent");
272
- return { agentId: binding.agentId, promptSent: false };
273
- }
274
-
275
- if (sendConfigPrompt) {
276
- const sent = await sendConfigPrompt();
277
- if (sent) return { agentId: "", promptSent: true };
278
- }
279
- }
280
-
281
- const agentId = platformAgentId(platform, userId, channelId, isGroup);
282
- logger.info(
283
- { agentId, platform, channelId },
284
- "Deterministic agent ID resolved"
285
- );
286
- return { agentId, promptSent: false };
287
- }
@@ -1,262 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { createLogger, DEFAULTS, REDIS_KEYS } from "@lobu/core";
4
- import type Redis from "ioredis";
5
- import type { IMessageQueue } from "../infrastructure/queue";
6
- import {
7
- computeSessionKey,
8
- type ISessionManager,
9
- type SessionStore,
10
- type ThreadSession,
11
- } from "../session";
12
-
13
- const logger = createLogger("session-manager");
14
-
15
- /**
16
- * Redis-based session storage
17
- * Sessions are stored with automatic TTL expiration
18
- */
19
- export class RedisSessionStore implements SessionStore {
20
- private readonly SESSION_PREFIX = REDIS_KEYS.SESSION;
21
- private readonly THREAD_INDEX_PREFIX = "conversation_index:";
22
- private readonly DEFAULT_TTL_SECONDS = DEFAULTS.SESSION_TTL_SECONDS;
23
- private redis: Redis;
24
-
25
- constructor(queue: IMessageQueue) {
26
- // Get Redis client from queue connection pool
27
- this.redis = queue.getRedisClient();
28
- }
29
-
30
- private getSessionKey(sessionKey: string): string {
31
- return `${this.SESSION_PREFIX}${sessionKey}`;
32
- }
33
-
34
- private getThreadIndexKey(channelId: string, threadTs: string): string {
35
- return `${this.THREAD_INDEX_PREFIX}${channelId}:${threadTs}`;
36
- }
37
-
38
- async get(sessionKey: string): Promise<ThreadSession | null> {
39
- try {
40
- const key = this.getSessionKey(sessionKey);
41
- const data = await this.redis.get(key);
42
-
43
- if (!data) {
44
- return null;
45
- }
46
-
47
- // Parse JSON
48
- return JSON.parse(data) as ThreadSession;
49
- } catch (error) {
50
- logger.error(`Failed to get session ${sessionKey}:`, error);
51
- return null;
52
- }
53
- }
54
-
55
- async set(sessionKey: string, session: ThreadSession): Promise<void> {
56
- try {
57
- const key = this.getSessionKey(sessionKey);
58
- const indexKey = this.getThreadIndexKey(
59
- session.channelId,
60
- session.conversationId
61
- );
62
-
63
- // Atomically set both session and thread index keys
64
- const pipeline = this.redis.pipeline();
65
- pipeline.setex(key, this.DEFAULT_TTL_SECONDS, JSON.stringify(session));
66
- pipeline.setex(
67
- indexKey,
68
- this.DEFAULT_TTL_SECONDS,
69
- JSON.stringify({ sessionKey })
70
- );
71
- await pipeline.exec();
72
-
73
- logger.debug(`Stored session ${sessionKey}`);
74
- } catch (error) {
75
- logger.error(`Failed to set session ${sessionKey}:`, error);
76
- throw error;
77
- }
78
- }
79
-
80
- async delete(sessionKey: string): Promise<void> {
81
- try {
82
- // Get session first to clean up thread index
83
- const session = await this.get(sessionKey);
84
-
85
- const key = this.getSessionKey(sessionKey);
86
-
87
- // Atomically delete both session and thread index keys
88
- if (session?.conversationId) {
89
- const indexKey = this.getThreadIndexKey(
90
- session.channelId,
91
- session.conversationId
92
- );
93
- const pipeline = this.redis.pipeline();
94
- pipeline.del(key);
95
- pipeline.del(indexKey);
96
- await pipeline.exec();
97
- } else {
98
- await this.redis.del(key);
99
- }
100
-
101
- logger.debug(`Deleted session ${sessionKey}`);
102
- } catch (error) {
103
- logger.error(`Failed to delete session ${sessionKey}:`, error);
104
- throw error;
105
- }
106
- }
107
-
108
- async getByThread(
109
- channelId: string,
110
- threadTs: string
111
- ): Promise<ThreadSession | null> {
112
- try {
113
- const indexKey = this.getThreadIndexKey(channelId, threadTs);
114
- const indexData = await this.redis.get(indexKey);
115
-
116
- if (!indexData) {
117
- return null;
118
- }
119
-
120
- const index = JSON.parse(indexData) as { sessionKey: string };
121
- return await this.get(index.sessionKey);
122
- } catch (error) {
123
- logger.error(
124
- `Failed to get session by thread ${channelId}:${threadTs}:`,
125
- error
126
- );
127
- return null;
128
- }
129
- }
130
-
131
- /** Optional cleanup - Redis handles this via TTL */
132
- async cleanup?(): Promise<number> {
133
- logger.debug("Redis TTL handles automatic cleanup");
134
- return 0;
135
- }
136
- }
137
-
138
- /**
139
- * Session manager that abstracts session storage
140
- * Provides thread ownership validation and session lifecycle management
141
- */
142
- export class SessionManager implements ISessionManager {
143
- private store: SessionStore;
144
-
145
- constructor(store: SessionStore) {
146
- this.store = store;
147
- }
148
-
149
- /**
150
- * Create a new session
151
- */
152
- async createSession(
153
- channelId: string,
154
- userId: string,
155
- conversationId?: string,
156
- threadCreator?: string
157
- ): Promise<ThreadSession> {
158
- const effectiveConversationId = conversationId || userId;
159
- const session: ThreadSession = {
160
- conversationId: effectiveConversationId,
161
- channelId,
162
- userId,
163
- threadCreator: threadCreator || userId,
164
- lastActivity: Date.now(),
165
- createdAt: Date.now(),
166
- };
167
- const sessionKey = computeSessionKey(session);
168
- await this.store.set(sessionKey, session);
169
- return session;
170
- }
171
-
172
- /**
173
- * Update session
174
- */
175
- async updateSession(
176
- sessionKey: string,
177
- updates: Partial<ThreadSession>
178
- ): Promise<void> {
179
- const session = await this.getSession(sessionKey);
180
- if (session) {
181
- const updated = { ...session, ...updates };
182
- await this.store.set(sessionKey, updated);
183
- }
184
- }
185
-
186
- /**
187
- * Get session by session key
188
- */
189
- async getSession(sessionKey: string): Promise<ThreadSession | null> {
190
- return await this.store.get(sessionKey);
191
- }
192
-
193
- /**
194
- * Create or update a session
195
- */
196
- async setSession(session: ThreadSession): Promise<void> {
197
- const sessionKey = computeSessionKey(session);
198
- await this.store.set(sessionKey, session);
199
- }
200
-
201
- /**
202
- * Delete a session
203
- */
204
- async deleteSession(sessionKey: string): Promise<void> {
205
- await this.store.delete(sessionKey);
206
- }
207
-
208
- /**
209
- * Find session by thread
210
- */
211
- async findSessionByThread(
212
- channelId: string,
213
- threadTs: string
214
- ): Promise<ThreadSession | null> {
215
- return await this.store.getByThread(channelId, threadTs);
216
- }
217
-
218
- /**
219
- * Validate thread ownership
220
- * Returns true if the user is the thread creator or no session exists
221
- */
222
- async validateThreadOwnership(
223
- channelId: string,
224
- threadTs: string,
225
- userId: string
226
- ): Promise<{ allowed: boolean; owner?: string }> {
227
- const session = await this.findSessionByThread(channelId, threadTs);
228
-
229
- if (!session) {
230
- return { allowed: true }; // No session, allow creation
231
- }
232
-
233
- if (!session.threadCreator) {
234
- return { allowed: true }; // No owner set, allow
235
- }
236
-
237
- if (session.threadCreator === userId) {
238
- return { allowed: true, owner: session.threadCreator };
239
- }
240
-
241
- return { allowed: false, owner: session.threadCreator };
242
- }
243
-
244
- /**
245
- * Update session activity timestamp
246
- */
247
- async touchSession(sessionKey: string): Promise<void> {
248
- const session = await this.getSession(sessionKey);
249
- if (session) {
250
- session.lastActivity = Date.now();
251
- await this.setSession(session);
252
- }
253
- }
254
-
255
- /**
256
- * Cleanup expired sessions (for in-memory stores)
257
- * Note: Redis-based stores handle this automatically via TTL
258
- */
259
- async cleanupExpired(ttl: number): Promise<number> {
260
- return (await this.store.cleanup?.(ttl)) || 0;
261
- }
262
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * SettingsResolver — resolves effective agent settings with template fallback.
3
- *
4
- * Extracted from the store layer so sub-stores stay single-domain.
5
- * Orchestrates across AgentConfigStore (settings + metadata) and
6
- * AgentConnectionStore (connections) for template resolution.
7
- */
8
-
9
- import type {
10
- AgentConfigStore,
11
- AgentConnectionStore,
12
- AgentSettings,
13
- } from "@lobu/core";
14
-
15
- export class SettingsResolver {
16
- constructor(
17
- private readonly config: AgentConfigStore,
18
- private readonly connections: AgentConnectionStore
19
- ) {}
20
-
21
- /**
22
- * Get effective settings for an agent, with template agent fallback.
23
- * For sandbox agents, inherits from the template agent when own settings
24
- * are missing or have no providers configured.
25
- */
26
- async getEffectiveSettings(agentId: string): Promise<AgentSettings | null> {
27
- const settings = await this.config.getSettings(agentId);
28
-
29
- // If settings exist and have providers, use them directly
30
- if (settings?.installedProviders?.length) return settings;
31
-
32
- // Resolve template agent ID
33
- const templateAgentId = await this.resolveTemplateAgentId(
34
- agentId,
35
- settings
36
- );
37
- if (!templateAgentId) return settings;
38
-
39
- const templateSettings = await this.config.getSettings(templateAgentId);
40
- if (!templateSettings) return settings;
41
-
42
- // Merge: own settings override template, but inherit missing fields
43
- if (!settings) {
44
- return { ...templateSettings, templateAgentId };
45
- }
46
-
47
- return {
48
- ...templateSettings,
49
- ...Object.fromEntries(
50
- Object.entries(settings).filter(([, v]) => v !== undefined)
51
- ),
52
- templateAgentId,
53
- } as AgentSettings;
54
- }
55
-
56
- /**
57
- * Resolve the template agent ID for a sandbox agent.
58
- * Chain: settings.templateAgentId → metadata.parentConnectionId → connection.templateAgentId
59
- */
60
- private async resolveTemplateAgentId(
61
- agentId: string,
62
- settings: AgentSettings | null
63
- ): Promise<string | undefined> {
64
- if (settings?.templateAgentId) return settings.templateAgentId;
65
-
66
- const metadata = await this.config.getMetadata(agentId);
67
- if (!metadata?.parentConnectionId) return undefined;
68
-
69
- const conn = await this.connections.getConnection(
70
- metadata.parentConnectionId
71
- );
72
- return conn?.templateAgentId;
73
- }
74
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * Resolves system config from system-skills.json.
3
- * Handles provider configs and MCP server resolution.
4
- *
5
- * NOTE: Integration OAuth config resolution (getIntegrationConfig, isOAuthConfigured,
6
- * overlayAgentOAuthCredentials, getSkillScopesForIntegration) has been removed.
7
- * OAuth for third-party APIs is now handled by Owletto.
8
- */
9
- import type { ProviderConfigEntry } from "@lobu/core";
10
- import type { SystemSkillsService } from "./system-skills-service";
11
-
12
- export interface ResolvedMcpRegistryServer {
13
- id: string;
14
- name: string;
15
- description: string;
16
- type: "oauth" | "stdio" | "sse" | "api-key";
17
- config: Record<string, unknown>;
18
- }
19
-
20
- export class SystemConfigResolver {
21
- constructor(private readonly systemSkillsService: SystemSkillsService) {}
22
-
23
- async getProviderConfigs(): Promise<Record<string, ProviderConfigEntry>> {
24
- return this.systemSkillsService.getProviderConfigs();
25
- }
26
-
27
- async getGlobalMcpServers(): Promise<
28
- Record<string, Record<string, unknown>>
29
- > {
30
- const systemSkills = await this.systemSkillsService.getSystemSkills();
31
- const mcpServers: Record<string, Record<string, unknown>> = {};
32
-
33
- for (const skill of systemSkills) {
34
- for (const mcp of skill.mcpServers || []) {
35
- if (!mcp?.id || mcpServers[mcp.id]) continue;
36
-
37
- const type = mcp.type || (mcp.command ? "stdio" : "sse");
38
- const config: Record<string, unknown> = { type };
39
-
40
- if (mcp.url) config.url = mcp.url;
41
- if (mcp.command) config.command = mcp.command;
42
- if (Array.isArray(mcp.args) && mcp.args.length > 0) {
43
- config.args = [...mcp.args];
44
- }
45
- if (mcp.oauth) config.oauth = mcp.oauth;
46
- if (mcp.inputs) config.inputs = mcp.inputs;
47
- if (mcp.headers) config.headers = mcp.headers;
48
-
49
- mcpServers[mcp.id] = config;
50
- }
51
- }
52
-
53
- return mcpServers;
54
- }
55
-
56
- async getMcpRegistryServers(): Promise<ResolvedMcpRegistryServer[]> {
57
- const systemSkills = await this.systemSkillsService.getSystemSkills();
58
- const entries: ResolvedMcpRegistryServer[] = [];
59
- const seenIds = new Set<string>();
60
-
61
- for (const skill of systemSkills) {
62
- for (const mcp of skill.mcpServers || []) {
63
- if (!mcp?.id || seenIds.has(mcp.id)) continue;
64
- seenIds.add(mcp.id);
65
-
66
- const type = mcp.type || (mcp.command ? "stdio" : "sse");
67
- const config: Record<string, unknown> = {
68
- type,
69
- };
70
-
71
- if (mcp.url) config.url = mcp.url;
72
- if (mcp.command) config.command = mcp.command;
73
- if (Array.isArray(mcp.args) && mcp.args.length > 0) {
74
- config.args = [...mcp.args];
75
- }
76
-
77
- entries.push({
78
- id: mcp.id,
79
- name: mcp.name || mcp.id,
80
- description: skill.description || `${skill.name} MCP server`,
81
- type,
82
- config,
83
- });
84
- }
85
- }
86
-
87
- return entries;
88
- }
89
- }