@lobu/gateway 3.0.9 → 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 (210) 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/interactions.d.ts +9 -43
  19. package/dist/interactions.d.ts.map +1 -1
  20. package/dist/interactions.js +10 -52
  21. package/dist/interactions.js.map +1 -1
  22. package/dist/routes/public/agent.d.ts +4 -0
  23. package/dist/routes/public/agent.d.ts.map +1 -1
  24. package/dist/routes/public/agent.js +21 -0
  25. package/dist/routes/public/agent.js.map +1 -1
  26. package/dist/services/core-services.d.ts.map +1 -1
  27. package/dist/services/core-services.js +4 -0
  28. package/dist/services/core-services.js.map +1 -1
  29. package/package.json +9 -9
  30. package/src/__tests__/agent-config-routes.test.ts +0 -254
  31. package/src/__tests__/agent-history-routes.test.ts +0 -72
  32. package/src/__tests__/agent-routes.test.ts +0 -68
  33. package/src/__tests__/agent-schedules-routes.test.ts +0 -59
  34. package/src/__tests__/agent-settings-store.test.ts +0 -323
  35. package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
  36. package/src/__tests__/bedrock-openai-service.test.ts +0 -157
  37. package/src/__tests__/bedrock-provider-module.test.ts +0 -56
  38. package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
  39. package/src/__tests__/chat-response-bridge.test.ts +0 -131
  40. package/src/__tests__/config-memory-plugins.test.ts +0 -92
  41. package/src/__tests__/config-request-store.test.ts +0 -127
  42. package/src/__tests__/connection-routes.test.ts +0 -144
  43. package/src/__tests__/core-services-store-selection.test.ts +0 -92
  44. package/src/__tests__/docker-deployment.test.ts +0 -1211
  45. package/src/__tests__/embedded-deployment.test.ts +0 -342
  46. package/src/__tests__/grant-store.test.ts +0 -148
  47. package/src/__tests__/http-proxy.test.ts +0 -281
  48. package/src/__tests__/instruction-service.test.ts +0 -37
  49. package/src/__tests__/link-buttons.test.ts +0 -112
  50. package/src/__tests__/lobu.test.ts +0 -32
  51. package/src/__tests__/mcp-config-service.test.ts +0 -347
  52. package/src/__tests__/mcp-proxy.test.ts +0 -694
  53. package/src/__tests__/message-handler-bridge.test.ts +0 -17
  54. package/src/__tests__/model-selection.test.ts +0 -172
  55. package/src/__tests__/oauth-templates.test.ts +0 -39
  56. package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
  57. package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
  58. package/src/__tests__/provider-inheritance.test.ts +0 -212
  59. package/src/__tests__/routes/cli-auth.test.ts +0 -337
  60. package/src/__tests__/routes/interactions.test.ts +0 -121
  61. package/src/__tests__/secret-proxy.test.ts +0 -85
  62. package/src/__tests__/session-manager.test.ts +0 -572
  63. package/src/__tests__/setup.ts +0 -133
  64. package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
  65. package/src/__tests__/slack-routes.test.ts +0 -161
  66. package/src/__tests__/system-config-resolver.test.ts +0 -75
  67. package/src/__tests__/system-message-limiter.test.ts +0 -89
  68. package/src/__tests__/system-skills-service.test.ts +0 -362
  69. package/src/__tests__/transcription-service.test.ts +0 -222
  70. package/src/__tests__/utils/rate-limiter.test.ts +0 -102
  71. package/src/__tests__/worker-connection-manager.test.ts +0 -497
  72. package/src/__tests__/worker-job-router.test.ts +0 -722
  73. package/src/api/index.ts +0 -1
  74. package/src/api/platform.ts +0 -292
  75. package/src/api/response-renderer.ts +0 -157
  76. package/src/auth/agent-metadata-store.ts +0 -168
  77. package/src/auth/api-auth-middleware.ts +0 -69
  78. package/src/auth/api-key-provider-module.ts +0 -213
  79. package/src/auth/base-provider-module.ts +0 -201
  80. package/src/auth/bedrock/provider-module.ts +0 -110
  81. package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
  82. package/src/auth/chatgpt/device-code-client.ts +0 -218
  83. package/src/auth/chatgpt/index.ts +0 -1
  84. package/src/auth/claude/oauth-module.ts +0 -280
  85. package/src/auth/cli/token-service.ts +0 -249
  86. package/src/auth/external/client.ts +0 -560
  87. package/src/auth/external/device-code-client.ts +0 -235
  88. package/src/auth/mcp/config-service.ts +0 -420
  89. package/src/auth/mcp/proxy.ts +0 -1086
  90. package/src/auth/mcp/string-substitution.ts +0 -17
  91. package/src/auth/mcp/tool-cache.ts +0 -90
  92. package/src/auth/oauth/base-client.ts +0 -267
  93. package/src/auth/oauth/client.ts +0 -153
  94. package/src/auth/oauth/credentials.ts +0 -7
  95. package/src/auth/oauth/providers.ts +0 -69
  96. package/src/auth/oauth/state-store.ts +0 -150
  97. package/src/auth/oauth-templates.ts +0 -179
  98. package/src/auth/provider-catalog.ts +0 -220
  99. package/src/auth/provider-model-options.ts +0 -41
  100. package/src/auth/settings/agent-settings-store.ts +0 -565
  101. package/src/auth/settings/auth-profiles-manager.ts +0 -216
  102. package/src/auth/settings/index.ts +0 -12
  103. package/src/auth/settings/model-preference-store.ts +0 -52
  104. package/src/auth/settings/model-selection.ts +0 -135
  105. package/src/auth/settings/resolved-settings-view.ts +0 -298
  106. package/src/auth/settings/template-utils.ts +0 -44
  107. package/src/auth/settings/token-service.ts +0 -88
  108. package/src/auth/system-env-store.ts +0 -98
  109. package/src/auth/user-agents-store.ts +0 -68
  110. package/src/channels/binding-service.ts +0 -214
  111. package/src/channels/index.ts +0 -4
  112. package/src/cli/gateway.ts +0 -1312
  113. package/src/cli/index.ts +0 -74
  114. package/src/commands/built-in-commands.ts +0 -80
  115. package/src/commands/command-dispatcher.ts +0 -94
  116. package/src/commands/command-reply-adapters.ts +0 -27
  117. package/src/config/file-loader.ts +0 -618
  118. package/src/config/index.ts +0 -588
  119. package/src/config/network-allowlist.ts +0 -71
  120. package/src/connections/chat-instance-manager.ts +0 -1284
  121. package/src/connections/chat-response-bridge.ts +0 -618
  122. package/src/connections/index.ts +0 -7
  123. package/src/connections/interaction-bridge.ts +0 -831
  124. package/src/connections/message-handler-bridge.ts +0 -440
  125. package/src/connections/platform-auth-methods.ts +0 -15
  126. package/src/connections/types.ts +0 -84
  127. package/src/gateway/connection-manager.ts +0 -291
  128. package/src/gateway/index.ts +0 -698
  129. package/src/gateway/job-router.ts +0 -201
  130. package/src/gateway-main.ts +0 -200
  131. package/src/index.ts +0 -41
  132. package/src/infrastructure/queue/index.ts +0 -12
  133. package/src/infrastructure/queue/queue-producer.ts +0 -148
  134. package/src/infrastructure/queue/redis-queue.ts +0 -361
  135. package/src/infrastructure/queue/types.ts +0 -133
  136. package/src/infrastructure/redis/system-message-limiter.ts +0 -94
  137. package/src/interactions/config-request-store.ts +0 -198
  138. package/src/interactions.ts +0 -363
  139. package/src/lobu.ts +0 -311
  140. package/src/metrics/prometheus.ts +0 -159
  141. package/src/modules/module-system.ts +0 -179
  142. package/src/orchestration/base-deployment-manager.ts +0 -900
  143. package/src/orchestration/deployment-utils.ts +0 -98
  144. package/src/orchestration/impl/docker-deployment.ts +0 -620
  145. package/src/orchestration/impl/embedded-deployment.ts +0 -268
  146. package/src/orchestration/impl/index.ts +0 -8
  147. package/src/orchestration/impl/k8s/deployment.ts +0 -1061
  148. package/src/orchestration/impl/k8s/helpers.ts +0 -610
  149. package/src/orchestration/impl/k8s/index.ts +0 -1
  150. package/src/orchestration/index.ts +0 -333
  151. package/src/orchestration/message-consumer.ts +0 -584
  152. package/src/orchestration/scheduled-wakeup.ts +0 -704
  153. package/src/permissions/approval-policy.ts +0 -36
  154. package/src/permissions/grant-store.ts +0 -219
  155. package/src/platform/file-handler.ts +0 -66
  156. package/src/platform/link-buttons.ts +0 -57
  157. package/src/platform/renderer-utils.ts +0 -44
  158. package/src/platform/response-renderer.ts +0 -84
  159. package/src/platform/unified-thread-consumer.ts +0 -194
  160. package/src/platform.ts +0 -318
  161. package/src/proxy/http-proxy.ts +0 -752
  162. package/src/proxy/proxy-manager.ts +0 -81
  163. package/src/proxy/secret-proxy.ts +0 -402
  164. package/src/proxy/token-refresh-job.ts +0 -143
  165. package/src/routes/internal/audio.ts +0 -141
  166. package/src/routes/internal/device-auth.ts +0 -652
  167. package/src/routes/internal/files.ts +0 -226
  168. package/src/routes/internal/history.ts +0 -69
  169. package/src/routes/internal/images.ts +0 -127
  170. package/src/routes/internal/interactions.ts +0 -84
  171. package/src/routes/internal/middleware.ts +0 -23
  172. package/src/routes/internal/schedule.ts +0 -226
  173. package/src/routes/internal/types.ts +0 -22
  174. package/src/routes/openapi-auto.ts +0 -239
  175. package/src/routes/public/agent-access.ts +0 -23
  176. package/src/routes/public/agent-config.ts +0 -675
  177. package/src/routes/public/agent-history.ts +0 -422
  178. package/src/routes/public/agent-schedules.ts +0 -296
  179. package/src/routes/public/agent.ts +0 -1086
  180. package/src/routes/public/agents.ts +0 -373
  181. package/src/routes/public/channels.ts +0 -191
  182. package/src/routes/public/cli-auth.ts +0 -896
  183. package/src/routes/public/connections.ts +0 -574
  184. package/src/routes/public/landing.ts +0 -16
  185. package/src/routes/public/oauth.ts +0 -147
  186. package/src/routes/public/settings-auth.ts +0 -104
  187. package/src/routes/public/slack.ts +0 -173
  188. package/src/routes/shared/agent-ownership.ts +0 -101
  189. package/src/routes/shared/token-verifier.ts +0 -34
  190. package/src/services/bedrock-model-catalog.ts +0 -217
  191. package/src/services/bedrock-openai-service.ts +0 -658
  192. package/src/services/core-services.ts +0 -1072
  193. package/src/services/image-generation-service.ts +0 -257
  194. package/src/services/instruction-service.ts +0 -318
  195. package/src/services/mcp-registry.ts +0 -94
  196. package/src/services/platform-helpers.ts +0 -287
  197. package/src/services/session-manager.ts +0 -262
  198. package/src/services/settings-resolver.ts +0 -74
  199. package/src/services/system-config-resolver.ts +0 -89
  200. package/src/services/system-skills-service.ts +0 -229
  201. package/src/services/transcription-service.ts +0 -684
  202. package/src/session.ts +0 -110
  203. package/src/spaces/index.ts +0 -1
  204. package/src/spaces/space-resolver.ts +0 -17
  205. package/src/stores/in-memory-agent-store.ts +0 -403
  206. package/src/stores/redis-agent-store.ts +0 -279
  207. package/src/utils/public-url.ts +0 -44
  208. package/src/utils/rate-limiter.ts +0 -94
  209. package/tsconfig.json +0 -33
  210. package/tsconfig.tsbuildinfo +0 -1
@@ -1,85 +0,0 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
2
- import { MockRedisClient } from "@lobu/core/testing";
3
- import {
4
- generatePlaceholder,
5
- type SecretMapping,
6
- storeSecretMapping,
7
- } from "../proxy/secret-proxy";
8
-
9
- describe("storeSecretMapping", () => {
10
- let redis: MockRedisClient;
11
-
12
- beforeEach(() => {
13
- redis = new MockRedisClient();
14
- });
15
-
16
- test("stores mapping at expected key", async () => {
17
- const mapping: SecretMapping = {
18
- agentId: "agent-1",
19
- envVarName: "API_KEY",
20
- value: "real-secret",
21
- deploymentName: "deploy-1",
22
- };
23
- await storeSecretMapping(redis as any, "test-uuid", mapping);
24
- const raw = await redis.get("lobu:secret:test-uuid");
25
- expect(raw).not.toBeNull();
26
- const parsed = JSON.parse(raw!);
27
- expect(parsed.agentId).toBe("agent-1");
28
- expect(parsed.value).toBe("real-secret");
29
- });
30
-
31
- test("uses custom TTL", async () => {
32
- const mapping: SecretMapping = {
33
- agentId: "agent-1",
34
- envVarName: "KEY",
35
- value: "val",
36
- deploymentName: "deploy-1",
37
- };
38
- await storeSecretMapping(redis as any, "uuid-2", mapping, 3600);
39
- const raw = await redis.get("lobu:secret:uuid-2");
40
- expect(raw).not.toBeNull();
41
- });
42
- });
43
-
44
- describe("generatePlaceholder", () => {
45
- let redis: MockRedisClient;
46
-
47
- beforeEach(() => {
48
- redis = new MockRedisClient();
49
- });
50
-
51
- test("returns placeholder with prefix", async () => {
52
- const placeholder = await generatePlaceholder(
53
- redis as any,
54
- "agent-1",
55
- "API_KEY",
56
- "real-value",
57
- "deploy-1"
58
- );
59
- expect(placeholder).toStartWith("lobu_secret_");
60
- });
61
-
62
- test("stores mapping in Redis", async () => {
63
- const placeholder = await generatePlaceholder(
64
- redis as any,
65
- "agent-1",
66
- "API_KEY",
67
- "real-value",
68
- "deploy-1"
69
- );
70
- const uuid = placeholder.replace("lobu_secret_", "");
71
- const raw = await redis.get(`lobu:secret:${uuid}`);
72
- expect(raw).not.toBeNull();
73
- const mapping = JSON.parse(raw!);
74
- expect(mapping.agentId).toBe("agent-1");
75
- expect(mapping.envVarName).toBe("API_KEY");
76
- expect(mapping.value).toBe("real-value");
77
- expect(mapping.deploymentName).toBe("deploy-1");
78
- });
79
-
80
- test("generates unique placeholders", async () => {
81
- const p1 = await generatePlaceholder(redis as any, "a", "K", "v1", "d");
82
- const p2 = await generatePlaceholder(redis as any, "a", "K", "v2", "d");
83
- expect(p1).not.toBe(p2);
84
- });
85
- });
@@ -1,572 +0,0 @@
1
- /**
2
- * Tests for SessionManager and RedisSessionStore
3
- * Tests session storage, retrieval, and thread ownership validation
4
- */
5
-
6
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
7
- import { RedisSessionStore, SessionManager } from "../services/session-manager";
8
- import { computeSessionKey, type ThreadSession } from "../session";
9
- import { cleanupTestEnv, MockMessageQueue, setupTestEnv } from "./setup";
10
-
11
- describe("SessionManager", () => {
12
- let mockQueue: MockMessageQueue;
13
- let store: RedisSessionStore;
14
- let manager: SessionManager;
15
-
16
- beforeEach(() => {
17
- setupTestEnv();
18
- mockQueue = new MockMessageQueue();
19
- store = new RedisSessionStore(mockQueue as any);
20
- manager = new SessionManager(store);
21
- });
22
-
23
- afterEach(() => {
24
- cleanupTestEnv();
25
- });
26
-
27
- describe("Session Creation and Retrieval", () => {
28
- test("creates and retrieves session", async () => {
29
- const session: ThreadSession = {
30
- channelId: "C123",
31
- userId: "U123",
32
- conversationId: "1234567890.123456",
33
- threadCreator: "U123",
34
- status: "pending",
35
- createdAt: Date.now(),
36
- lastActivity: Date.now(),
37
- };
38
-
39
- await manager.setSession(session);
40
-
41
- const sessionKey = computeSessionKey(session);
42
- const retrieved = await manager.getSession(sessionKey);
43
- expect(retrieved).not.toBeNull();
44
- expect(retrieved?.userId).toBe("U123");
45
- expect(retrieved?.channelId).toBe("C123");
46
- expect(retrieved?.conversationId).toBe("1234567890.123456");
47
- });
48
-
49
- test("returns null for non-existent session", async () => {
50
- const session = await manager.getSession("non-existent");
51
- expect(session).toBeNull();
52
- });
53
-
54
- test("stores all session fields correctly", async () => {
55
- const session: ThreadSession = {
56
- channelId: "C456",
57
- userId: "U456",
58
- conversationId: "9876543210.654321",
59
- threadCreator: "U456",
60
- status: "running",
61
- createdAt: Date.now(),
62
- lastActivity: Date.now(),
63
- };
64
-
65
- await manager.setSession(session);
66
-
67
- const sessionKey = computeSessionKey(session);
68
- const retrieved = await manager.getSession(sessionKey);
69
- expect(retrieved).toMatchObject({
70
- channelId: "C456",
71
- userId: "U456",
72
- conversationId: "9876543210.654321",
73
- threadCreator: "U456",
74
- status: "running",
75
- });
76
- });
77
- });
78
-
79
- describe("Session Deletion", () => {
80
- test("deletes existing session", async () => {
81
- const session: ThreadSession = {
82
- channelId: "C123",
83
- userId: "U123",
84
- conversationId: "1234567890.123456",
85
- status: "completed",
86
- createdAt: Date.now(),
87
- lastActivity: Date.now(),
88
- };
89
-
90
- await manager.setSession(session);
91
- const sessionKey = computeSessionKey(session);
92
- expect(await manager.getSession(sessionKey)).not.toBeNull();
93
-
94
- await manager.deleteSession(sessionKey);
95
- expect(await manager.getSession(sessionKey)).toBeNull();
96
- });
97
-
98
- test("handles deleting non-existent session gracefully", async () => {
99
- // Should not throw
100
- await manager.deleteSession("non-existent");
101
- expect(true).toBe(true); // Test passes if no error thrown
102
- });
103
-
104
- test("deletes both session and thread index", async () => {
105
- const session: ThreadSession = {
106
- channelId: "C123",
107
- userId: "U123",
108
- conversationId: "1234567890.123456",
109
- status: "completed",
110
- createdAt: Date.now(),
111
- lastActivity: Date.now(),
112
- };
113
-
114
- await manager.setSession(session);
115
- const sessionKey = computeSessionKey(session);
116
-
117
- // Should be able to find by thread
118
- const byThread = await manager.findSessionByThread(
119
- "C123",
120
- "1234567890.123456"
121
- );
122
- expect(byThread).not.toBeNull();
123
-
124
- // Delete
125
- await manager.deleteSession(sessionKey);
126
-
127
- // Should no longer find by thread
128
- const afterDelete = await manager.findSessionByThread(
129
- "C123",
130
- "1234567890.123456"
131
- );
132
- expect(afterDelete).toBeNull();
133
- });
134
- });
135
-
136
- describe("Thread Index Lookup", () => {
137
- test("finds session by thread ID", async () => {
138
- const session: ThreadSession = {
139
- channelId: "C789",
140
- userId: "U789",
141
- conversationId: "1111111111.111111",
142
- status: "pending",
143
- createdAt: Date.now(),
144
- lastActivity: Date.now(),
145
- };
146
-
147
- await manager.setSession(session);
148
-
149
- const found = await manager.findSessionByThread(
150
- "C789",
151
- "1111111111.111111"
152
- );
153
- expect(found).not.toBeNull();
154
- expect(found?.userId).toBe("U789");
155
- });
156
-
157
- test("returns null when thread not found", async () => {
158
- const found = await manager.findSessionByThread(
159
- "C999",
160
- "9999999999.999999"
161
- );
162
- expect(found).toBeNull();
163
- });
164
-
165
- test("handles multiple sessions in different threads", async () => {
166
- const session1: ThreadSession = {
167
- channelId: "C111",
168
- userId: "U111",
169
- conversationId: "1111111111.111111",
170
- status: "pending",
171
- createdAt: Date.now(),
172
- lastActivity: Date.now(),
173
- };
174
-
175
- const session2: ThreadSession = {
176
- channelId: "C222",
177
- userId: "U222",
178
- conversationId: "2222222222.222222",
179
- status: "running",
180
- createdAt: Date.now(),
181
- lastActivity: Date.now(),
182
- };
183
-
184
- await manager.setSession(session1);
185
- await manager.setSession(session2);
186
-
187
- const found1 = await manager.findSessionByThread(
188
- "C111",
189
- "1111111111.111111"
190
- );
191
- const found2 = await manager.findSessionByThread(
192
- "C222",
193
- "2222222222.222222"
194
- );
195
-
196
- expect(found1?.channelId).toBe("C111");
197
- expect(found2?.channelId).toBe("C222");
198
- });
199
-
200
- test("updates session when thread index already exists", async () => {
201
- const session: ThreadSession = {
202
- channelId: "C123",
203
- userId: "U123",
204
- conversationId: "1234567890.123456",
205
- status: "pending",
206
- createdAt: Date.now(),
207
- lastActivity: Date.now(),
208
- };
209
-
210
- await manager.setSession(session);
211
-
212
- // Update the same session
213
- const updatedSession = { ...session, status: "completed" as const };
214
- await manager.setSession(updatedSession);
215
-
216
- const found = await manager.findSessionByThread(
217
- "C123",
218
- "1234567890.123456"
219
- );
220
- expect(found?.status).toBe("completed");
221
- });
222
- });
223
-
224
- describe("Thread Ownership Validation", () => {
225
- test("allows access when no session exists", async () => {
226
- const result = await manager.validateThreadOwnership(
227
- "C123",
228
- "1234567890.123456",
229
- "U123"
230
- );
231
-
232
- expect(result.allowed).toBe(true);
233
- expect(result.owner).toBeUndefined();
234
- });
235
-
236
- test("allows access when no thread creator is set", async () => {
237
- const session: ThreadSession = {
238
- channelId: "C123",
239
- userId: "U123",
240
- conversationId: "1234567890.123456",
241
- // No threadCreator set
242
- status: "pending",
243
- createdAt: Date.now(),
244
- lastActivity: Date.now(),
245
- };
246
-
247
- await manager.setSession(session);
248
-
249
- const result = await manager.validateThreadOwnership(
250
- "C123",
251
- "1234567890.123456",
252
- "U456" // Different user
253
- );
254
-
255
- expect(result.allowed).toBe(true);
256
- });
257
-
258
- test("allows access when user is thread creator", async () => {
259
- const session: ThreadSession = {
260
- channelId: "C123",
261
- userId: "U123",
262
- conversationId: "1234567890.123456",
263
- threadCreator: "U123",
264
- status: "running",
265
- createdAt: Date.now(),
266
- lastActivity: Date.now(),
267
- };
268
-
269
- await manager.setSession(session);
270
-
271
- const result = await manager.validateThreadOwnership(
272
- "C123",
273
- "1234567890.123456",
274
- "U123"
275
- );
276
-
277
- expect(result.allowed).toBe(true);
278
- expect(result.owner).toBe("U123");
279
- });
280
-
281
- test("denies access when user is not thread creator", async () => {
282
- const session: ThreadSession = {
283
- channelId: "C123",
284
- userId: "U123",
285
- conversationId: "1234567890.123456",
286
- threadCreator: "U123",
287
- status: "running",
288
- createdAt: Date.now(),
289
- lastActivity: Date.now(),
290
- };
291
-
292
- await manager.setSession(session);
293
-
294
- const result = await manager.validateThreadOwnership(
295
- "C123",
296
- "1234567890.123456",
297
- "U456" // Different user
298
- );
299
-
300
- expect(result.allowed).toBe(false);
301
- expect(result.owner).toBe("U123");
302
- });
303
-
304
- test("validates ownership across multiple threads correctly", async () => {
305
- const session1: ThreadSession = {
306
- channelId: "C111",
307
- userId: "U111",
308
- conversationId: "1111111111.111111",
309
- threadCreator: "U111",
310
- status: "running",
311
- createdAt: Date.now(),
312
- lastActivity: Date.now(),
313
- };
314
-
315
- const session2: ThreadSession = {
316
- channelId: "C222",
317
- userId: "U222",
318
- conversationId: "2222222222.222222",
319
- threadCreator: "U222",
320
- status: "running",
321
- createdAt: Date.now(),
322
- lastActivity: Date.now(),
323
- };
324
-
325
- await manager.setSession(session1);
326
- await manager.setSession(session2);
327
-
328
- // User 111 can access their thread
329
- const result1 = await manager.validateThreadOwnership(
330
- "C111",
331
- "1111111111.111111",
332
- "U111"
333
- );
334
- expect(result1.allowed).toBe(true);
335
-
336
- // User 111 cannot access user 222's thread
337
- const result2 = await manager.validateThreadOwnership(
338
- "C222",
339
- "2222222222.222222",
340
- "U111"
341
- );
342
- expect(result2.allowed).toBe(false);
343
- expect(result2.owner).toBe("U222");
344
- });
345
- });
346
-
347
- describe("Session Activity Tracking", () => {
348
- test("updates lastActivity timestamp", async () => {
349
- const session: ThreadSession = {
350
- channelId: "C123",
351
- userId: "U123",
352
- conversationId: "activity.123456",
353
- status: "running",
354
- createdAt: Date.now(),
355
- lastActivity: Date.now() - 1000, // 1 second ago
356
- };
357
-
358
- await manager.setSession(session);
359
- const sessionKey = computeSessionKey(session);
360
-
361
- const before = await manager.getSession(sessionKey);
362
- const beforeActivity = before?.lastActivity;
363
-
364
- // Wait a bit and touch
365
- await new Promise((resolve) => setTimeout(resolve, 10));
366
- await manager.touchSession(sessionKey);
367
-
368
- const after = await manager.getSession(sessionKey);
369
- expect(after?.lastActivity).toBeGreaterThan(beforeActivity!);
370
- });
371
-
372
- test("handles touching non-existent session gracefully", async () => {
373
- // Should not throw
374
- await manager.touchSession("non-existent");
375
- expect(true).toBe(true); // Test passes if no error thrown
376
- });
377
-
378
- test("preserves other fields when touching", async () => {
379
- const session: ThreadSession = {
380
- channelId: "C123",
381
- userId: "U123",
382
- conversationId: "preserve.123456",
383
- threadCreator: "U123",
384
- status: "running",
385
- createdAt: Date.now(),
386
- lastActivity: Date.now(),
387
- };
388
-
389
- await manager.setSession(session);
390
- const sessionKey = computeSessionKey(session);
391
- await manager.touchSession(sessionKey);
392
-
393
- const updated = await manager.getSession(sessionKey);
394
- expect(updated?.channelId).toBe("C123");
395
- expect(updated?.userId).toBe("U123");
396
- expect(updated?.conversationId).toBe("preserve.123456");
397
- expect(updated?.status).toBe("running");
398
- });
399
- });
400
-
401
- describe("Session Status Updates", () => {
402
- test("updates session status", async () => {
403
- const session: ThreadSession = {
404
- channelId: "C123",
405
- userId: "U123",
406
- conversationId: "status.123456",
407
- status: "pending",
408
- createdAt: Date.now(),
409
- lastActivity: Date.now(),
410
- };
411
-
412
- await manager.setSession(session);
413
- const sessionKey = computeSessionKey(session);
414
-
415
- const updated = { ...session, status: "completed" as const };
416
- await manager.setSession(updated);
417
-
418
- const retrieved = await manager.getSession(sessionKey);
419
- expect(retrieved?.status).toBe("completed");
420
- });
421
-
422
- test("handles all valid status transitions", async () => {
423
- const statuses: Array<ThreadSession["status"]> = [
424
- "pending",
425
- "starting",
426
- "running",
427
- "completed",
428
- "error",
429
- "timeout",
430
- ];
431
-
432
- for (const status of statuses) {
433
- const session: ThreadSession = {
434
- channelId: "C123",
435
- userId: "U123",
436
- conversationId: `status-${status}.123456`,
437
- status,
438
- createdAt: Date.now(),
439
- lastActivity: Date.now(),
440
- };
441
-
442
- await manager.setSession(session);
443
- const sessionKey = computeSessionKey(session);
444
-
445
- const retrieved = await manager.getSession(sessionKey);
446
- expect(retrieved?.status).toBe(status);
447
- }
448
- });
449
- });
450
-
451
- describe("Redis TTL Behavior", () => {
452
- test("cleanup returns 0 for Redis-based store", async () => {
453
- const count = await manager.cleanupExpired(3600);
454
- expect(count).toBe(0); // Redis handles TTL automatically
455
- });
456
- });
457
-
458
- describe("Error Handling", () => {
459
- test("handles invalid JSON in Redis gracefully", async () => {
460
- // Store invalid JSON directly in Redis
461
- const redis = mockQueue.getRedisClient();
462
- await redis.set("session:invalid-json", "{invalid json}", 3600);
463
-
464
- const session = await manager.getSession("invalid-json");
465
- expect(session).toBeNull(); // Should return null on parse error
466
- });
467
-
468
- test("handles concurrent session updates", async () => {
469
- const session: ThreadSession = {
470
- channelId: "C123",
471
- userId: "U123",
472
- conversationId: "concurrent.123456",
473
- status: "pending",
474
- createdAt: Date.now(),
475
- lastActivity: Date.now(),
476
- };
477
-
478
- const sessionKey = computeSessionKey(session);
479
-
480
- // Simulate concurrent updates
481
- await Promise.all([
482
- manager.setSession({ ...session, status: "running" }),
483
- manager.setSession({ ...session, status: "completed" }),
484
- ]);
485
-
486
- // Last write wins
487
- const retrieved = await manager.getSession(sessionKey);
488
- expect(retrieved).not.toBeNull();
489
- expect(["running", "completed"]).toContain(retrieved?.status);
490
- });
491
- });
492
-
493
- describe("Session Store Key Formatting", () => {
494
- test("uses correct key prefix for sessions", async () => {
495
- const session: ThreadSession = {
496
- channelId: "C123",
497
- userId: "U123",
498
- conversationId: "keyformat.123456",
499
- status: "pending",
500
- createdAt: Date.now(),
501
- lastActivity: Date.now(),
502
- };
503
-
504
- await manager.setSession(session);
505
-
506
- const redis = mockQueue.getRedisClient();
507
- // Key format: session:{channelId}:{conversationId}
508
- const hasKey = redis.has("session:C123:keyformat.123456");
509
- expect(hasKey).toBe(true);
510
- });
511
-
512
- test("uses correct key format for thread index", async () => {
513
- const session: ThreadSession = {
514
- channelId: "C123",
515
- userId: "U123",
516
- conversationId: "1234567890.123456",
517
- status: "pending",
518
- createdAt: Date.now(),
519
- lastActivity: Date.now(),
520
- };
521
-
522
- await manager.setSession(session);
523
-
524
- const redis = mockQueue.getRedisClient();
525
- // Thread index should use format: conversation_index:{channelId}:{conversationId}
526
- const hasIndex = redis.has("conversation_index:C123:1234567890.123456");
527
- expect(hasIndex).toBe(true);
528
- });
529
- });
530
-
531
- describe("Edge Cases", () => {
532
- test("handles API platform sessions (channelId equals conversationId)", async () => {
533
- const agentId = "agent-123";
534
- const session: ThreadSession = {
535
- channelId: agentId,
536
- userId: "U123",
537
- conversationId: agentId,
538
- status: "pending",
539
- createdAt: Date.now(),
540
- lastActivity: Date.now(),
541
- };
542
-
543
- await manager.setSession(session);
544
-
545
- // For API platform, key should just be conversationId
546
- const sessionKey = computeSessionKey(session);
547
- expect(sessionKey).toBe(agentId);
548
-
549
- const retrieved = await manager.getSession(sessionKey);
550
- expect(retrieved).not.toBeNull();
551
- expect(retrieved?.conversationId).toBe(agentId);
552
- });
553
-
554
- test("handles special characters in IDs", async () => {
555
- const session: ThreadSession = {
556
- channelId: "C-123-test",
557
- userId: "U_special_123",
558
- conversationId: "1234567890.123456-extra",
559
- status: "pending",
560
- createdAt: Date.now(),
561
- lastActivity: Date.now(),
562
- };
563
-
564
- await manager.setSession(session);
565
- const sessionKey = computeSessionKey(session);
566
-
567
- const retrieved = await manager.getSession(sessionKey);
568
- expect(retrieved).not.toBeNull();
569
- expect(retrieved?.channelId).toBe("C-123-test");
570
- });
571
- });
572
- });