@lobu/gateway 3.0.8 → 3.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/dist/api/platform.d.ts.map +1 -1
  2. package/dist/api/platform.js +8 -26
  3. package/dist/api/platform.js.map +1 -1
  4. package/dist/auth/mcp/proxy.d.ts +14 -0
  5. package/dist/auth/mcp/proxy.d.ts.map +1 -1
  6. package/dist/auth/mcp/proxy.js +149 -13
  7. package/dist/auth/mcp/proxy.js.map +1 -1
  8. package/dist/cli/gateway.d.ts.map +1 -1
  9. package/dist/cli/gateway.js +29 -0
  10. package/dist/cli/gateway.js.map +1 -1
  11. package/dist/cli/index.js +2 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/connections/chat-instance-manager.d.ts.map +1 -1
  14. package/dist/connections/chat-instance-manager.js +2 -1
  15. package/dist/connections/chat-instance-manager.js.map +1 -1
  16. package/dist/connections/interaction-bridge.d.ts +9 -2
  17. package/dist/connections/interaction-bridge.d.ts.map +1 -1
  18. package/dist/connections/interaction-bridge.js +132 -230
  19. package/dist/connections/interaction-bridge.js.map +1 -1
  20. package/dist/connections/message-handler-bridge.d.ts.map +1 -1
  21. package/dist/connections/message-handler-bridge.js +44 -26
  22. package/dist/connections/message-handler-bridge.js.map +1 -1
  23. package/dist/interactions.d.ts +9 -43
  24. package/dist/interactions.d.ts.map +1 -1
  25. package/dist/interactions.js +10 -52
  26. package/dist/interactions.js.map +1 -1
  27. package/dist/orchestration/base-deployment-manager.js +7 -7
  28. package/dist/orchestration/base-deployment-manager.js.map +1 -1
  29. package/dist/platform/unified-thread-consumer.d.ts.map +1 -1
  30. package/dist/platform/unified-thread-consumer.js +38 -34
  31. package/dist/platform/unified-thread-consumer.js.map +1 -1
  32. package/dist/routes/public/agent.d.ts +4 -0
  33. package/dist/routes/public/agent.d.ts.map +1 -1
  34. package/dist/routes/public/agent.js +21 -0
  35. package/dist/routes/public/agent.js.map +1 -1
  36. package/dist/services/core-services.d.ts.map +1 -1
  37. package/dist/services/core-services.js +4 -0
  38. package/dist/services/core-services.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/__tests__/agent-config-routes.test.ts +0 -254
  41. package/src/__tests__/agent-history-routes.test.ts +0 -72
  42. package/src/__tests__/agent-routes.test.ts +0 -68
  43. package/src/__tests__/agent-schedules-routes.test.ts +0 -59
  44. package/src/__tests__/agent-settings-store.test.ts +0 -323
  45. package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
  46. package/src/__tests__/bedrock-openai-service.test.ts +0 -157
  47. package/src/__tests__/bedrock-provider-module.test.ts +0 -56
  48. package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
  49. package/src/__tests__/chat-response-bridge.test.ts +0 -131
  50. package/src/__tests__/config-memory-plugins.test.ts +0 -92
  51. package/src/__tests__/config-request-store.test.ts +0 -127
  52. package/src/__tests__/connection-routes.test.ts +0 -144
  53. package/src/__tests__/core-services-store-selection.test.ts +0 -92
  54. package/src/__tests__/docker-deployment.test.ts +0 -1211
  55. package/src/__tests__/embedded-deployment.test.ts +0 -342
  56. package/src/__tests__/grant-store.test.ts +0 -148
  57. package/src/__tests__/http-proxy.test.ts +0 -281
  58. package/src/__tests__/instruction-service.test.ts +0 -37
  59. package/src/__tests__/link-buttons.test.ts +0 -112
  60. package/src/__tests__/lobu.test.ts +0 -32
  61. package/src/__tests__/mcp-config-service.test.ts +0 -347
  62. package/src/__tests__/mcp-proxy.test.ts +0 -694
  63. package/src/__tests__/message-handler-bridge.test.ts +0 -17
  64. package/src/__tests__/model-selection.test.ts +0 -172
  65. package/src/__tests__/oauth-templates.test.ts +0 -39
  66. package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
  67. package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
  68. package/src/__tests__/provider-inheritance.test.ts +0 -212
  69. package/src/__tests__/routes/cli-auth.test.ts +0 -337
  70. package/src/__tests__/routes/interactions.test.ts +0 -121
  71. package/src/__tests__/secret-proxy.test.ts +0 -85
  72. package/src/__tests__/session-manager.test.ts +0 -572
  73. package/src/__tests__/setup.ts +0 -133
  74. package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
  75. package/src/__tests__/slack-routes.test.ts +0 -161
  76. package/src/__tests__/system-config-resolver.test.ts +0 -75
  77. package/src/__tests__/system-message-limiter.test.ts +0 -89
  78. package/src/__tests__/system-skills-service.test.ts +0 -362
  79. package/src/__tests__/transcription-service.test.ts +0 -222
  80. package/src/__tests__/utils/rate-limiter.test.ts +0 -102
  81. package/src/__tests__/worker-connection-manager.test.ts +0 -497
  82. package/src/__tests__/worker-job-router.test.ts +0 -722
  83. package/src/api/index.ts +0 -1
  84. package/src/api/platform.ts +0 -292
  85. package/src/api/response-renderer.ts +0 -157
  86. package/src/auth/agent-metadata-store.ts +0 -168
  87. package/src/auth/api-auth-middleware.ts +0 -69
  88. package/src/auth/api-key-provider-module.ts +0 -213
  89. package/src/auth/base-provider-module.ts +0 -201
  90. package/src/auth/bedrock/provider-module.ts +0 -110
  91. package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
  92. package/src/auth/chatgpt/device-code-client.ts +0 -218
  93. package/src/auth/chatgpt/index.ts +0 -1
  94. package/src/auth/claude/oauth-module.ts +0 -280
  95. package/src/auth/cli/token-service.ts +0 -249
  96. package/src/auth/external/client.ts +0 -560
  97. package/src/auth/external/device-code-client.ts +0 -235
  98. package/src/auth/mcp/config-service.ts +0 -420
  99. package/src/auth/mcp/proxy.ts +0 -1086
  100. package/src/auth/mcp/string-substitution.ts +0 -17
  101. package/src/auth/mcp/tool-cache.ts +0 -90
  102. package/src/auth/oauth/base-client.ts +0 -267
  103. package/src/auth/oauth/client.ts +0 -153
  104. package/src/auth/oauth/credentials.ts +0 -7
  105. package/src/auth/oauth/providers.ts +0 -69
  106. package/src/auth/oauth/state-store.ts +0 -150
  107. package/src/auth/oauth-templates.ts +0 -179
  108. package/src/auth/provider-catalog.ts +0 -220
  109. package/src/auth/provider-model-options.ts +0 -41
  110. package/src/auth/settings/agent-settings-store.ts +0 -565
  111. package/src/auth/settings/auth-profiles-manager.ts +0 -216
  112. package/src/auth/settings/index.ts +0 -12
  113. package/src/auth/settings/model-preference-store.ts +0 -52
  114. package/src/auth/settings/model-selection.ts +0 -135
  115. package/src/auth/settings/resolved-settings-view.ts +0 -298
  116. package/src/auth/settings/template-utils.ts +0 -44
  117. package/src/auth/settings/token-service.ts +0 -88
  118. package/src/auth/system-env-store.ts +0 -98
  119. package/src/auth/user-agents-store.ts +0 -68
  120. package/src/channels/binding-service.ts +0 -214
  121. package/src/channels/index.ts +0 -4
  122. package/src/cli/gateway.ts +0 -1312
  123. package/src/cli/index.ts +0 -74
  124. package/src/commands/built-in-commands.ts +0 -80
  125. package/src/commands/command-dispatcher.ts +0 -94
  126. package/src/commands/command-reply-adapters.ts +0 -27
  127. package/src/config/file-loader.ts +0 -618
  128. package/src/config/index.ts +0 -588
  129. package/src/config/network-allowlist.ts +0 -71
  130. package/src/connections/chat-instance-manager.ts +0 -1284
  131. package/src/connections/chat-response-bridge.ts +0 -618
  132. package/src/connections/index.ts +0 -7
  133. package/src/connections/interaction-bridge.ts +0 -831
  134. package/src/connections/message-handler-bridge.ts +0 -415
  135. package/src/connections/platform-auth-methods.ts +0 -15
  136. package/src/connections/types.ts +0 -84
  137. package/src/gateway/connection-manager.ts +0 -291
  138. package/src/gateway/index.ts +0 -698
  139. package/src/gateway/job-router.ts +0 -201
  140. package/src/gateway-main.ts +0 -200
  141. package/src/index.ts +0 -41
  142. package/src/infrastructure/queue/index.ts +0 -12
  143. package/src/infrastructure/queue/queue-producer.ts +0 -148
  144. package/src/infrastructure/queue/redis-queue.ts +0 -361
  145. package/src/infrastructure/queue/types.ts +0 -133
  146. package/src/infrastructure/redis/system-message-limiter.ts +0 -94
  147. package/src/interactions/config-request-store.ts +0 -198
  148. package/src/interactions.ts +0 -363
  149. package/src/lobu.ts +0 -311
  150. package/src/metrics/prometheus.ts +0 -159
  151. package/src/modules/module-system.ts +0 -179
  152. package/src/orchestration/base-deployment-manager.ts +0 -900
  153. package/src/orchestration/deployment-utils.ts +0 -98
  154. package/src/orchestration/impl/docker-deployment.ts +0 -620
  155. package/src/orchestration/impl/embedded-deployment.ts +0 -268
  156. package/src/orchestration/impl/index.ts +0 -8
  157. package/src/orchestration/impl/k8s/deployment.ts +0 -1061
  158. package/src/orchestration/impl/k8s/helpers.ts +0 -610
  159. package/src/orchestration/impl/k8s/index.ts +0 -1
  160. package/src/orchestration/index.ts +0 -333
  161. package/src/orchestration/message-consumer.ts +0 -584
  162. package/src/orchestration/scheduled-wakeup.ts +0 -704
  163. package/src/permissions/approval-policy.ts +0 -36
  164. package/src/permissions/grant-store.ts +0 -219
  165. package/src/platform/file-handler.ts +0 -66
  166. package/src/platform/link-buttons.ts +0 -57
  167. package/src/platform/renderer-utils.ts +0 -44
  168. package/src/platform/response-renderer.ts +0 -84
  169. package/src/platform/unified-thread-consumer.ts +0 -187
  170. package/src/platform.ts +0 -318
  171. package/src/proxy/http-proxy.ts +0 -752
  172. package/src/proxy/proxy-manager.ts +0 -81
  173. package/src/proxy/secret-proxy.ts +0 -402
  174. package/src/proxy/token-refresh-job.ts +0 -143
  175. package/src/routes/internal/audio.ts +0 -141
  176. package/src/routes/internal/device-auth.ts +0 -652
  177. package/src/routes/internal/files.ts +0 -226
  178. package/src/routes/internal/history.ts +0 -69
  179. package/src/routes/internal/images.ts +0 -127
  180. package/src/routes/internal/interactions.ts +0 -84
  181. package/src/routes/internal/middleware.ts +0 -23
  182. package/src/routes/internal/schedule.ts +0 -226
  183. package/src/routes/internal/types.ts +0 -22
  184. package/src/routes/openapi-auto.ts +0 -239
  185. package/src/routes/public/agent-access.ts +0 -23
  186. package/src/routes/public/agent-config.ts +0 -675
  187. package/src/routes/public/agent-history.ts +0 -422
  188. package/src/routes/public/agent-schedules.ts +0 -296
  189. package/src/routes/public/agent.ts +0 -1086
  190. package/src/routes/public/agents.ts +0 -373
  191. package/src/routes/public/channels.ts +0 -191
  192. package/src/routes/public/cli-auth.ts +0 -896
  193. package/src/routes/public/connections.ts +0 -574
  194. package/src/routes/public/landing.ts +0 -16
  195. package/src/routes/public/oauth.ts +0 -147
  196. package/src/routes/public/settings-auth.ts +0 -104
  197. package/src/routes/public/slack.ts +0 -173
  198. package/src/routes/shared/agent-ownership.ts +0 -101
  199. package/src/routes/shared/token-verifier.ts +0 -34
  200. package/src/services/bedrock-model-catalog.ts +0 -217
  201. package/src/services/bedrock-openai-service.ts +0 -658
  202. package/src/services/core-services.ts +0 -1072
  203. package/src/services/image-generation-service.ts +0 -257
  204. package/src/services/instruction-service.ts +0 -318
  205. package/src/services/mcp-registry.ts +0 -94
  206. package/src/services/platform-helpers.ts +0 -287
  207. package/src/services/session-manager.ts +0 -262
  208. package/src/services/settings-resolver.ts +0 -74
  209. package/src/services/system-config-resolver.ts +0 -89
  210. package/src/services/system-skills-service.ts +0 -229
  211. package/src/services/transcription-service.ts +0 -684
  212. package/src/session.ts +0 -110
  213. package/src/spaces/index.ts +0 -1
  214. package/src/spaces/space-resolver.ts +0 -17
  215. package/src/stores/in-memory-agent-store.ts +0 -403
  216. package/src/stores/redis-agent-store.ts +0 -279
  217. package/src/utils/public-url.ts +0 -44
  218. package/src/utils/rate-limiter.ts +0 -94
  219. package/tsconfig.json +0 -33
@@ -1,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
- }