@xalia/agent 0.5.7 → 0.6.0

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 (186) hide show
  1. package/README.md +23 -8
  2. package/dist/agent/src/agent/agent.js +176 -96
  3. package/dist/agent/src/agent/agentUtils.js +82 -59
  4. package/dist/agent/src/agent/compressingContextManager.js +102 -0
  5. package/dist/agent/src/agent/context.js +189 -0
  6. package/dist/agent/src/agent/dummyLLM.js +46 -5
  7. package/dist/agent/src/agent/mcpServerManager.js +23 -24
  8. package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
  9. package/dist/agent/src/agent/nullPlatform.js +14 -0
  10. package/dist/agent/src/agent/openAILLMStreaming.js +26 -14
  11. package/dist/agent/src/agent/promptProvider.js +63 -0
  12. package/dist/agent/src/agent/repeatLLM.js +5 -5
  13. package/dist/agent/src/agent/sudoMcpServerManager.js +23 -21
  14. package/dist/agent/src/agent/tokenAuth.js +7 -7
  15. package/dist/agent/src/agent/tools.js +1 -1
  16. package/dist/agent/src/chat/client/chatClient.js +733 -0
  17. package/dist/agent/src/chat/client/connection.js +209 -0
  18. package/dist/agent/src/chat/client/connection.test.js +188 -0
  19. package/dist/agent/src/chat/client/constants.js +5 -0
  20. package/dist/agent/src/chat/client/index.js +15 -0
  21. package/dist/agent/src/chat/client/interfaces.js +2 -0
  22. package/dist/agent/src/chat/client/responseHandler.js +105 -0
  23. package/dist/agent/src/chat/client/sessionClient.js +331 -0
  24. package/dist/agent/src/chat/client/teamManager.js +2 -0
  25. package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
  26. package/dist/agent/src/chat/data/dataModels.js +2 -0
  27. package/dist/agent/src/chat/data/database.js +749 -0
  28. package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
  29. package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
  30. package/dist/agent/src/chat/protocol/constants.js +50 -0
  31. package/dist/agent/src/chat/protocol/errors.js +22 -0
  32. package/dist/agent/src/chat/protocol/messages.js +110 -0
  33. package/dist/agent/src/chat/server/chatContextManager.js +405 -0
  34. package/dist/agent/src/chat/server/connectionManager.js +352 -0
  35. package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
  36. package/dist/agent/src/chat/server/conversation.js +198 -0
  37. package/dist/agent/src/chat/server/errorUtils.js +23 -0
  38. package/dist/agent/src/chat/server/openSession.js +869 -0
  39. package/dist/agent/src/chat/server/server.js +177 -0
  40. package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
  41. package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
  42. package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
  43. package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
  44. package/dist/agent/src/chat/server/tools.js +243 -0
  45. package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
  46. package/dist/agent/src/chat/utils/approvalManager.js +85 -0
  47. package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
  48. package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
  49. package/dist/agent/src/chat/utils/htmlToText.js +84 -0
  50. package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
  51. package/dist/agent/src/chat/utils/search.js +145 -0
  52. package/dist/agent/src/chat/utils/userResolver.js +46 -0
  53. package/dist/agent/src/chat/utils/websocket.js +16 -0
  54. package/dist/agent/src/test/agent.test.js +332 -0
  55. package/dist/agent/src/test/approvalManager.test.js +58 -0
  56. package/dist/agent/src/test/chatContextManager.test.js +392 -0
  57. package/dist/agent/src/test/clientServerConnection.test.js +158 -0
  58. package/dist/agent/src/test/compressingContextManager.test.js +65 -0
  59. package/dist/agent/src/test/context.test.js +83 -0
  60. package/dist/agent/src/test/conversation.test.js +89 -0
  61. package/dist/agent/src/test/db.test.js +271 -83
  62. package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
  63. package/dist/agent/src/test/dbTestTools.js +99 -0
  64. package/dist/agent/src/test/imageLoad.test.js +8 -7
  65. package/dist/agent/src/test/mcpServerManager.test.js +23 -20
  66. package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
  67. package/dist/agent/src/test/openaiStreaming.test.js +64 -35
  68. package/dist/agent/src/test/prompt.test.js +5 -4
  69. package/dist/agent/src/test/promptProvider.test.js +28 -0
  70. package/dist/agent/src/test/responseHandler.test.js +61 -0
  71. package/dist/agent/src/test/sudoMcpServerManager.test.js +24 -25
  72. package/dist/agent/src/test/testTools.js +109 -0
  73. package/dist/agent/src/test/tools.test.js +31 -0
  74. package/dist/agent/src/tool/agentChat.js +21 -10
  75. package/dist/agent/src/tool/agentMain.js +1 -1
  76. package/dist/agent/src/tool/chatMain.js +241 -58
  77. package/dist/agent/src/tool/commandPrompt.js +22 -17
  78. package/dist/agent/src/tool/files.js +20 -16
  79. package/dist/agent/src/tool/nodePlatform.js +47 -3
  80. package/dist/agent/src/tool/options.js +4 -4
  81. package/dist/agent/src/tool/prompt.js +19 -13
  82. package/eslint.config.mjs +14 -1
  83. package/package.json +14 -6
  84. package/scripts/chat_server +8 -0
  85. package/scripts/setup_chat +7 -2
  86. package/scripts/shutdown_chat_server +3 -0
  87. package/scripts/test_chat +135 -17
  88. package/src/agent/agent.ts +283 -138
  89. package/src/agent/agentUtils.ts +143 -108
  90. package/src/agent/compressingContextManager.ts +164 -0
  91. package/src/agent/context.ts +268 -0
  92. package/src/agent/dummyLLM.ts +76 -8
  93. package/src/agent/iAgentEventHandler.ts +54 -0
  94. package/src/agent/iplatform.ts +1 -0
  95. package/src/agent/mcpServerManager.ts +35 -31
  96. package/src/agent/nullAgentEventHandler.ts +20 -0
  97. package/src/agent/nullPlatform.ts +13 -0
  98. package/src/agent/openAILLMStreaming.ts +26 -13
  99. package/src/agent/promptProvider.ts +87 -0
  100. package/src/agent/repeatLLM.ts +5 -5
  101. package/src/agent/sudoMcpServerManager.ts +30 -29
  102. package/src/agent/tokenAuth.ts +7 -7
  103. package/src/agent/tools.ts +3 -1
  104. package/src/chat/client/chatClient.ts +900 -0
  105. package/src/chat/client/connection.test.ts +241 -0
  106. package/src/chat/client/connection.ts +276 -0
  107. package/src/chat/client/constants.ts +3 -0
  108. package/src/chat/client/index.ts +18 -0
  109. package/src/chat/client/interfaces.ts +34 -0
  110. package/src/chat/client/responseHandler.ts +131 -0
  111. package/src/chat/client/sessionClient.ts +443 -0
  112. package/src/chat/client/teamManager.ts +29 -0
  113. package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
  114. package/src/chat/data/dataModels.ts +85 -0
  115. package/src/chat/data/database.ts +982 -0
  116. package/src/chat/data/dbMcpServerConfigs.ts +59 -0
  117. package/src/chat/protocol/connectionMessages.ts +49 -0
  118. package/src/chat/protocol/constants.ts +55 -0
  119. package/src/chat/protocol/errors.ts +16 -0
  120. package/src/chat/protocol/messages.ts +682 -0
  121. package/src/chat/server/README.md +127 -0
  122. package/src/chat/server/chatContextManager.ts +612 -0
  123. package/src/chat/server/connectionManager.test.ts +266 -0
  124. package/src/chat/server/connectionManager.ts +541 -0
  125. package/src/chat/server/conversation.ts +269 -0
  126. package/src/chat/server/errorUtils.ts +28 -0
  127. package/src/chat/server/openSession.ts +1332 -0
  128. package/src/chat/server/server.ts +177 -0
  129. package/src/chat/server/sessionFileManager.ts +239 -0
  130. package/src/chat/server/sessionRegistry.test.ts +138 -0
  131. package/src/chat/server/sessionRegistry.ts +1064 -0
  132. package/src/chat/server/test-utils/mockFactories.ts +422 -0
  133. package/src/chat/server/tools.ts +265 -0
  134. package/src/chat/utils/agentSessionMap.ts +76 -0
  135. package/src/chat/utils/approvalManager.ts +111 -0
  136. package/src/{utils → chat/utils}/asyncLock.ts +3 -3
  137. package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
  138. package/src/chat/utils/htmlToText.ts +61 -0
  139. package/src/chat/utils/multiAsyncQueue.ts +52 -0
  140. package/src/chat/utils/search.ts +139 -0
  141. package/src/chat/utils/userResolver.ts +48 -0
  142. package/src/chat/utils/websocket.ts +16 -0
  143. package/src/test/agent.test.ts +487 -0
  144. package/src/test/approvalManager.test.ts +73 -0
  145. package/src/test/chatContextManager.test.ts +521 -0
  146. package/src/test/clientServerConnection.test.ts +207 -0
  147. package/src/test/compressingContextManager.test.ts +82 -0
  148. package/src/test/context.test.ts +105 -0
  149. package/src/test/conversation.test.ts +109 -0
  150. package/src/test/db.test.ts +358 -89
  151. package/src/test/dbMcpServerConfigs.test.ts +112 -0
  152. package/src/test/dbTestTools.ts +153 -0
  153. package/src/test/imageLoad.test.ts +7 -6
  154. package/src/test/mcpServerManager.test.ts +21 -16
  155. package/src/test/multiAsyncQueue.test.ts +125 -0
  156. package/src/test/openaiStreaming.test.ts +71 -36
  157. package/src/test/prompt.test.ts +4 -3
  158. package/src/test/promptProvider.test.ts +33 -0
  159. package/src/test/responseHandler.test.ts +78 -0
  160. package/src/test/sudoMcpServerManager.test.ts +32 -30
  161. package/src/test/testTools.ts +146 -0
  162. package/src/test/tools.test.ts +39 -0
  163. package/src/tool/agentChat.ts +26 -12
  164. package/src/tool/agentMain.ts +1 -1
  165. package/src/tool/chatMain.ts +292 -100
  166. package/src/tool/commandPrompt.ts +28 -19
  167. package/src/tool/files.ts +25 -19
  168. package/src/tool/nodePlatform.ts +52 -3
  169. package/src/tool/options.ts +4 -2
  170. package/src/tool/prompt.ts +22 -15
  171. package/test_data/dummyllm_script_crash.json +32 -0
  172. package/test_data/frog.png.b64 +1 -0
  173. package/vitest.config.ts +39 -0
  174. package/dist/agent/src/chat/client.js +0 -349
  175. package/dist/agent/src/chat/conversationManager.js +0 -392
  176. package/dist/agent/src/chat/db.js +0 -209
  177. package/dist/agent/src/chat/frontendClient.js +0 -74
  178. package/dist/agent/src/chat/server.js +0 -158
  179. package/src/chat/client.ts +0 -455
  180. package/src/chat/conversationManager.ts +0 -595
  181. package/src/chat/db.ts +0 -290
  182. package/src/chat/frontendClient.ts +0 -123
  183. package/src/chat/messages.ts +0 -235
  184. package/src/chat/server.ts +0 -177
  185. /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
  186. /package/{frog.png → test_data/frog.png} +0 -0
@@ -0,0 +1,749 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Database = exports.DbClientBase = exports.SUPABASE_LOCAL_KEY = exports.SUPABASE_LOCAL_URL = exports.SESSION_ALLOWED_PARTICIPANTS = void 0;
7
+ exports.createSessionParticipantMap = createSessionParticipantMap;
8
+ exports.resolveCompoundName = resolveCompoundName;
9
+ const supabase_js_1 = require("@supabase/supabase-js");
10
+ const sdk_1 = require("@xalia/xmcp/sdk");
11
+ const logger = (0, sdk_1.getLogger)();
12
+ exports.SESSION_ALLOWED_PARTICIPANTS = ["owner", "participant"];
13
+ exports.SUPABASE_LOCAL_URL = "http://127.0.0.1:54321";
14
+ exports.SUPABASE_LOCAL_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiw" +
15
+ "icm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJz" +
16
+ "dJsyH-qQwv8Hdp7fsn3W0YpN81IU";
17
+ function createSessionParticipantMap(participants) {
18
+ const pmap = new Map();
19
+ participants.forEach((p) => {
20
+ pmap.set(p.user_uuid, p);
21
+ });
22
+ return pmap;
23
+ }
24
+ /**
25
+ * 'name' -> 'name'
26
+ * 'space/name' -> ['space', 'name']
27
+ */
28
+ function resolveCompoundName(name) {
29
+ const components = name.split("/");
30
+ if (components.length === 1) {
31
+ return name;
32
+ }
33
+ if (components.length !== 2) {
34
+ throw new Error("invalid compound name");
35
+ }
36
+ return components;
37
+ }
38
+ class DbClientBase {
39
+ constructor(client) {
40
+ this.client = client;
41
+ }
42
+ }
43
+ exports.DbClientBase = DbClientBase;
44
+ class Database {
45
+ constructor(supabaseUrl, supabaseKey) {
46
+ this.client = (0, supabase_js_1.createClient)(supabaseUrl, supabaseKey);
47
+ }
48
+ /**
49
+ * Get the underlying Supabase client for testing purposes only.
50
+ * DO NOT use in production code - use Database methods instead.
51
+ */
52
+ getClientForTesting() {
53
+ return this.client;
54
+ }
55
+ async getUserDataFromApiKey(apiKey) {
56
+ const { data, error } = await this.client
57
+ .from("api_keys")
58
+ .select("user_uuid, users ( nickname, timezone, email )")
59
+ .eq("api_key", apiKey)
60
+ .maybeSingle();
61
+ logger.debug(`[getUserDataFromApiKey]: got ${JSON.stringify({ data, error })}`);
62
+ if (error) {
63
+ throw error;
64
+ }
65
+ if (data === null) {
66
+ return undefined;
67
+ }
68
+ return {
69
+ uuid: data.user_uuid,
70
+ email: data.users.email,
71
+ nickname: data.users.nickname || `user ${data.user_uuid}`,
72
+ timezone: data.users.timezone || "UTC",
73
+ };
74
+ }
75
+ async getUserFromUuid(user_uuid) {
76
+ const { data, error } = await this.client
77
+ .from("users")
78
+ .select("*")
79
+ .eq("uuid", user_uuid)
80
+ .maybeSingle();
81
+ if (error) {
82
+ throw error;
83
+ }
84
+ return data;
85
+ }
86
+ async getUserByEmail(email) {
87
+ const { data, error } = await this.client
88
+ .from("users")
89
+ .select("*")
90
+ .eq("email", email)
91
+ .maybeSingle();
92
+ if (error) {
93
+ throw error;
94
+ }
95
+ return data;
96
+ }
97
+ async createUser(user_uuid, email, nickname, timezone) {
98
+ const payload = {
99
+ uuid: user_uuid,
100
+ email,
101
+ nickname,
102
+ timezone: timezone || "UTC",
103
+ };
104
+ const { error } = await this.client.from("users").insert(payload);
105
+ if (error) {
106
+ throw error;
107
+ }
108
+ }
109
+ async addApiKey(user_uuid, api_key, name, scopes, is_default = false) {
110
+ const payload = {
111
+ user_uuid,
112
+ api_key,
113
+ name,
114
+ scopes,
115
+ is_default,
116
+ };
117
+ const { data, error } = await this.client
118
+ .from("api_keys")
119
+ .insert(payload)
120
+ .select("*")
121
+ .maybeSingle();
122
+ if (error) {
123
+ throw error;
124
+ }
125
+ return data;
126
+ }
127
+ /**
128
+ * Get user's default API key from user UUID
129
+ * NOTE: this should only be used for accessing
130
+ * the session owner's API key.
131
+ * */
132
+ async getUserApiKey(user_uuid) {
133
+ const { data, error } = await this.client
134
+ .from("api_keys")
135
+ .select("api_key")
136
+ .eq("user_uuid", user_uuid)
137
+ .eq("is_default", true)
138
+ .maybeSingle();
139
+ if (error) {
140
+ throw error;
141
+ }
142
+ return data?.api_key;
143
+ }
144
+ async getSavedAgentProfileById(agentProfileId) {
145
+ const { data, error } = await this.client
146
+ .from("agent_profiles")
147
+ .select("*")
148
+ .eq("uuid", agentProfileId)
149
+ .maybeSingle();
150
+ if (error) {
151
+ throw error;
152
+ }
153
+ return data
154
+ ? sdk_1.SavedAgentProfile.fromJSONObj(data)
155
+ : undefined;
156
+ }
157
+ async getSavedAgentProfileByName(user_uuid, agentProfileName) {
158
+ const { data, error } = await this.client
159
+ .from("agent_profiles")
160
+ .select("*")
161
+ .eq("user_uuid", user_uuid)
162
+ .eq("profile_name", agentProfileName)
163
+ .maybeSingle();
164
+ if (error) {
165
+ throw error;
166
+ }
167
+ return data
168
+ ? sdk_1.SavedAgentProfile.fromJSONObj(data)
169
+ : undefined;
170
+ }
171
+ async getAgentProfileById(agentProfileId) {
172
+ const { data, error } = await this.client
173
+ .from("agent_profiles")
174
+ .select("profile")
175
+ .eq("uuid", agentProfileId)
176
+ .maybeSingle();
177
+ if (error) {
178
+ throw error;
179
+ }
180
+ return data
181
+ ? sdk_1.AgentProfile.fromJSONObj(data.profile)
182
+ : undefined;
183
+ }
184
+ async createAgentProfile(user_uuid, team_uuid, profileName, profile) {
185
+ const payload = {
186
+ profile: profile,
187
+ user_uuid,
188
+ team_uuid,
189
+ profile_name: profileName,
190
+ };
191
+ const { data, error } = await this.client
192
+ .from("agent_profiles")
193
+ .upsert(payload)
194
+ .select("*");
195
+ if (error) {
196
+ throw error;
197
+ }
198
+ return data[0]
199
+ ? sdk_1.SavedAgentProfile.fromJSONObj(data[0])
200
+ : undefined;
201
+ }
202
+ async updateAgentProfile(uuid, profile) {
203
+ const payload = {
204
+ profile: profile,
205
+ };
206
+ const { error } = await this.client
207
+ .from("agent_profiles")
208
+ .update(payload)
209
+ .eq("uuid", uuid);
210
+ if (error) {
211
+ throw error;
212
+ }
213
+ }
214
+ async getAgentProfilePreferences(agentProfileId) {
215
+ const { data, error } = await this.client
216
+ .from("agent_profiles")
217
+ .select("preferences")
218
+ .eq("uuid", agentProfileId)
219
+ .maybeSingle();
220
+ if (error) {
221
+ throw error;
222
+ }
223
+ return data ? data.preferences : undefined;
224
+ }
225
+ async updateAgentProfilePreferences(agentProfileId, preferences) {
226
+ const payload = { preferences };
227
+ const { error } = await this.client
228
+ .from("agent_profiles")
229
+ .update(payload)
230
+ .eq("uuid", agentProfileId);
231
+ if (error) {
232
+ throw error;
233
+ }
234
+ }
235
+ async clearAgentProfiles() {
236
+ await this.client.from("agent_profiles").delete().neq("uuid", "");
237
+ }
238
+ //
239
+ // sessions
240
+ //
241
+ static sessionNewUUID() {
242
+ const bytes = new Uint8Array(16);
243
+ crypto.getRandomValues(bytes);
244
+ return Array.from(bytes)
245
+ .map((b) => b.toString(16).padStart(2, "0"))
246
+ .join("")
247
+ .padStart(32, "0"); // in case of leading zeros
248
+ }
249
+ async sessionsGet() {
250
+ const { data, error } = await this.client.from("sessions").select("*");
251
+ if (error) {
252
+ throw error;
253
+ }
254
+ return data;
255
+ }
256
+ async sessionGetById(session_uuid) {
257
+ const { data, error } = await this.client
258
+ .from("sessions")
259
+ .select("*")
260
+ .eq("uuid", session_uuid)
261
+ .maybeSingle();
262
+ if (error) {
263
+ throw error;
264
+ }
265
+ if (!data) {
266
+ return undefined;
267
+ }
268
+ return {
269
+ ...data,
270
+ workspace: data.workspace || undefined,
271
+ owner_uuid: data.user_uuid,
272
+ session_uuid: data.uuid,
273
+ updated_at: data.updated_at || new Date().toISOString(),
274
+ };
275
+ }
276
+ async sessionGetByName(user_uuid, session_name) {
277
+ const { data, error } = await this.client
278
+ .from("sessions")
279
+ .select("*")
280
+ .eq("user_uuid", user_uuid)
281
+ .eq("title", session_name)
282
+ .maybeSingle();
283
+ if (error) {
284
+ logger.error(`[getSessionByName] error: ${JSON.stringify(error)}`);
285
+ throw error;
286
+ }
287
+ return {
288
+ ...data,
289
+ workspace: data.workspace || undefined,
290
+ owner_uuid: data.user_uuid,
291
+ session_uuid: data.uuid,
292
+ updated_at: data.updated_at || new Date().toISOString(),
293
+ };
294
+ }
295
+ async sessionCreate(session_data) {
296
+ const payload = {
297
+ uuid: session_data.session_uuid,
298
+ title: session_data.title,
299
+ agent_profile_uuid: session_data.agent_profile_uuid,
300
+ user_uuid: session_data.user_uuid,
301
+ team_uuid: session_data.team_uuid,
302
+ workspace: session_data.workspace,
303
+ };
304
+ const { error } = await this.client.from("sessions").insert(payload);
305
+ if (error) {
306
+ throw error;
307
+ }
308
+ }
309
+ async sessionUpdateTitle(session_uuid, title) {
310
+ const payload = {
311
+ title,
312
+ };
313
+ const { error } = await this.client
314
+ .from("sessions")
315
+ .update(payload)
316
+ .eq("uuid", session_uuid);
317
+ if (error) {
318
+ throw error;
319
+ }
320
+ }
321
+ async sessionUpdateWorkspace(session_uuid, workspace) {
322
+ const payload = {
323
+ workspace: workspace || null,
324
+ };
325
+ const { error } = await this.client
326
+ .from("sessions")
327
+ .update(payload)
328
+ .eq("uuid", session_uuid);
329
+ if (error) {
330
+ throw error;
331
+ }
332
+ }
333
+ async sessionDeleteById(session_uuid) {
334
+ await this.client.from("sessions").delete().eq("uuid", session_uuid);
335
+ }
336
+ async clearSessions() {
337
+ await this.client.from("sessions").delete().neq("uuid", "");
338
+ }
339
+ /**
340
+ * Get all user sessions (not including team sessions) for a user.
341
+ * @param user_uuid
342
+ * @returns SessionData[]
343
+ */
344
+ async getUserSessions(user_uuid) {
345
+ const { data: userSessions, error: userSessionsError } = await this.client
346
+ .from("sessions")
347
+ .select("uuid, title, agent_profile_uuid, workspace, updated_at, user_uuid")
348
+ .eq("user_uuid", user_uuid)
349
+ .is("team_uuid", null);
350
+ if (userSessionsError) {
351
+ throw userSessionsError;
352
+ }
353
+ return userSessions.map((s) => ({
354
+ session_uuid: s.uuid,
355
+ title: s.title,
356
+ team_uuid: undefined,
357
+ agent_profile_uuid: s.agent_profile_uuid,
358
+ workspace: s.workspace || undefined,
359
+ updated_at: s.updated_at,
360
+ user_uuid: s.user_uuid,
361
+ }));
362
+ }
363
+ async sessionGetParticipants(session_uuid) {
364
+ // check if session is a team session
365
+ const { data, error } = await this.client
366
+ .from("sessions")
367
+ .select("team_uuid, user_uuid")
368
+ .eq("uuid", session_uuid)
369
+ .maybeSingle();
370
+ if (error || !data) {
371
+ throw error || new Error("Session not found");
372
+ }
373
+ if (data.team_uuid) {
374
+ return this.teamGetMembers(data.team_uuid);
375
+ }
376
+ else {
377
+ const userData = await this.getUserFromUuid(data.user_uuid);
378
+ if (!userData) {
379
+ throw new Error("Cannot find user data");
380
+ }
381
+ return [
382
+ {
383
+ user_uuid: data.user_uuid,
384
+ nickname: userData.nickname || "",
385
+ email: userData.email,
386
+ role: "owner",
387
+ },
388
+ ];
389
+ }
390
+ }
391
+ async sessionGetTeamUuid(session_uuid) {
392
+ const data = await this.sessionGetById(session_uuid);
393
+ if (data) {
394
+ return data.team_uuid;
395
+ }
396
+ return undefined;
397
+ }
398
+ //
399
+ // session_messages
400
+ //
401
+ async sessionMessagesClearConversation(session_uuid) {
402
+ const { error } = await this.client
403
+ .from("session_messages")
404
+ .delete()
405
+ .eq("session_uuid", session_uuid);
406
+ if (error) {
407
+ throw error;
408
+ }
409
+ }
410
+ async sessionMessagesGetConversation(session_uuid, numEntries, beforeIndex) {
411
+ // Query all message for the given session, ordered high-to-low by
412
+ // message_idx, limited to `numEntries`. If `beforeIndex` is given, it
413
+ // means we get messages with `message_idx < beforeIndex`
414
+ let query = this.client
415
+ .from("session_messages")
416
+ .select("message_idx,sender_uuid,is_for_llm,content")
417
+ .eq("session_uuid", session_uuid);
418
+ if (beforeIndex) {
419
+ query = query.lt("message_idx", beforeIndex);
420
+ }
421
+ query = query.order("message_idx", { ascending: false }).limit(numEntries);
422
+ const { data, error } = await query;
423
+ if (error) {
424
+ throw error;
425
+ }
426
+ // To get the newest N messages, we've orded by index largest to smallest
427
+ // (newest first), but caller wants the message first to last, hence
428
+ // reverse the array.
429
+ return data
430
+ .map(({ sender_uuid, ...rest }) => {
431
+ return sender_uuid
432
+ ? {
433
+ sender_uuid,
434
+ ...rest,
435
+ }
436
+ : { ...rest };
437
+ })
438
+ .reverse();
439
+ }
440
+ async sessionMessagesAppend(session_uuid, messages) {
441
+ const payload = messages.map((m) => {
442
+ return { ...m, session_uuid };
443
+ });
444
+ const { error } = await this.client
445
+ .from("session_messages")
446
+ .insert(payload);
447
+ if (error) {
448
+ throw error;
449
+ }
450
+ }
451
+ //
452
+ // session_checkpoints
453
+ //
454
+ async sessionCheckpointsClear() {
455
+ const { error } = await this.client
456
+ .from("session_checkpoints")
457
+ .delete()
458
+ .neq("session_uuid", "");
459
+ if (error) {
460
+ throw error;
461
+ }
462
+ }
463
+ async sessionCheckpointGet(session_uuid) {
464
+ const { error, data } = await this.client
465
+ .from("session_checkpoints")
466
+ .select("message_idx,summary")
467
+ .eq("session_uuid", session_uuid)
468
+ .maybeSingle();
469
+ if (error) {
470
+ throw error;
471
+ }
472
+ if (!data) {
473
+ return undefined;
474
+ }
475
+ return {
476
+ message_idx: data.message_idx,
477
+ summary: data.summary,
478
+ };
479
+ }
480
+ async sessionCheckpointSet(session_uuid, checkpoint) {
481
+ const payload = {
482
+ session_uuid,
483
+ message_idx: checkpoint.message_idx,
484
+ summary: checkpoint.summary,
485
+ };
486
+ const { error } = await this.client
487
+ .from("session_checkpoints")
488
+ .upsert([payload], { onConflict: "session_uuid" });
489
+ if (error) {
490
+ throw error;
491
+ }
492
+ }
493
+ //
494
+ // agent_profiles
495
+ //
496
+ /**
497
+ * Get all agents belonging to a user.
498
+ * @param userUuid - UUID of the user
499
+ * @returns Array of agent profiles
500
+ */
501
+ async agentProfilesGetByUser(userUuid) {
502
+ const { data, error } = await this.client
503
+ .from("agent_profiles")
504
+ .select("*")
505
+ .eq("user_uuid", userUuid);
506
+ if (error) {
507
+ throw error;
508
+ }
509
+ return data.map((agent) => sdk_1.SavedAgentProfile.fromJSONObj(agent));
510
+ }
511
+ /**
512
+ * Get all agents belonging to a team.
513
+ * @param teamUuid - UUID of the team
514
+ * @returns Array of agent profiles
515
+ */
516
+ async AgentProfilesGetByTeam(teamUuid) {
517
+ const { data, error } = await this.client
518
+ .from("agent_profiles")
519
+ .select("*")
520
+ .eq("team_uuid", teamUuid);
521
+ if (error) {
522
+ throw error;
523
+ }
524
+ return data.map((agent) => sdk_1.SavedAgentProfile.fromJSONObj(agent));
525
+ }
526
+ async agentTemplateGetByName(templateName) {
527
+ const { data, error } = await this.client
528
+ .from("agent_templates")
529
+ .select("*")
530
+ .eq("name", templateName)
531
+ .maybeSingle();
532
+ if (error) {
533
+ throw error;
534
+ }
535
+ return data
536
+ ? sdk_1.AgentTemplate.fromJSONObj(data)
537
+ : undefined;
538
+ }
539
+ //
540
+ // teams
541
+ //
542
+ async createTeam(teamName, owner_uuid) {
543
+ const { data, error } = await this.client.rpc("create_team_with_owner", {
544
+ p_owner_uuid: owner_uuid,
545
+ p_name: teamName,
546
+ });
547
+ if (error) {
548
+ throw error;
549
+ }
550
+ // The SQL function returns a TEXT (team UUID)
551
+ return data;
552
+ }
553
+ /**
554
+ * Creates a team with initial participants.
555
+ * The owner is automatically added as a team member with 'owner' role.
556
+ * @param teamName - Name of the team to create
557
+ * @param ownerUuid - UUID of the team owner
558
+ * @param initialParticipants - Array of user UUIDs to add as initial members
559
+ * @returns The UUID of the created team
560
+ */
561
+ async createTeamWithParticipants(teamName, ownerUuid, initialParticipants) {
562
+ // Create the team first
563
+ const teamUuid = await this.createTeam(teamName, ownerUuid);
564
+ // If there are initial participants, add them to the team
565
+ if (initialParticipants.length > 0) {
566
+ // Filter out the owner if they're in the initial participants list
567
+ const participantsToAdd = initialParticipants.filter((uuid) => uuid !== ownerUuid);
568
+ if (participantsToAdd.length > 0) {
569
+ // Add all participants to the
570
+ const teamMemberInserts = participantsToAdd.map((userUuid) => ({
571
+ team_uuid: teamUuid,
572
+ user_uuid: userUuid,
573
+ role: "participant",
574
+ }));
575
+ const { error: membersError } = await this.client
576
+ .from("team_members")
577
+ .insert(teamMemberInserts);
578
+ if (membersError) {
579
+ // If adding members fails, we should clean up the team
580
+ logger.error(`Failed to add members to team ${teamUuid}: ${membersError.message}`);
581
+ // Optionally delete the team if member addition fails
582
+ await this.deleteTeam(teamUuid);
583
+ throw membersError;
584
+ }
585
+ }
586
+ }
587
+ logger.info(`Created team ${teamUuid}: ${String(initialParticipants.length)} members`);
588
+ return teamUuid;
589
+ }
590
+ /**
591
+ * Deletes a team and all associated data.
592
+ * This will also remove all team members and any team sessions.
593
+ * @param teamUuid - UUID of the team to delete
594
+ */
595
+ async deleteTeam(teamUuid) {
596
+ try {
597
+ // Delete the team - this should cascade to team_members and team_sessions
598
+ // due to foreign key constraints with ON DELETE CASCADE
599
+ const { error } = await this.client
600
+ .from("teams")
601
+ .delete()
602
+ .eq("uuid", teamUuid);
603
+ if (error) {
604
+ throw error;
605
+ }
606
+ logger.info(`Deleted team ${teamUuid}`);
607
+ }
608
+ catch (error) {
609
+ logger.error(`Failed to delete team ${teamUuid}:`, error);
610
+ throw new Error(`Failed to delete team: ${String(error)}`);
611
+ }
612
+ }
613
+ async getTeamInfosByUser(user_uuid) {
614
+ // Get all teams the user is a member of (including as owner)
615
+ const { data: teamMemberships, error: teamError } = await this.client
616
+ .from("team_members")
617
+ .select(`
618
+ team_uuid,
619
+ role,
620
+ teams!inner (
621
+ uuid,
622
+ name,
623
+ owner_uuid
624
+ )
625
+ `)
626
+ .eq("user_uuid", user_uuid);
627
+ if (teamError) {
628
+ throw teamError;
629
+ }
630
+ if (teamMemberships.length === 0) {
631
+ return [];
632
+ }
633
+ // Process all team memberships in parallel
634
+ const teamSessionPromises = teamMemberships.map(async (membership) => {
635
+ const teamUuid = membership.team_uuid;
636
+ const teamData = membership.teams;
637
+ // Use the role from team_members, sanity check against teams.owner_uuid
638
+ const memberRole = membership.role;
639
+ const isActualOwner = teamData.owner_uuid === user_uuid;
640
+ // Sanity check: if roles are inconsistent, throw an error
641
+ if ((memberRole === "owner" && !isActualOwner) ||
642
+ (memberRole !== "owner" && isActualOwner)) {
643
+ throw new Error(`Data inconsistency: user ${user_uuid} has role '${memberRole}' ` +
644
+ `in team ${teamUuid}, but owner is ${teamData.owner_uuid}`);
645
+ }
646
+ // Fetch team members and sessions in parallel using helper methods
647
+ const [participants, sessions, agents] = await Promise.all([
648
+ this.teamGetMembers(teamUuid),
649
+ this.teamGetSessions(teamUuid),
650
+ this.AgentProfilesGetByTeam(teamUuid),
651
+ ]);
652
+ return {
653
+ team_uuid: teamUuid,
654
+ team_name: teamData.name,
655
+ owner_uuid: teamData.owner_uuid,
656
+ participants,
657
+ sessions,
658
+ agents,
659
+ };
660
+ });
661
+ return await Promise.all(teamSessionPromises);
662
+ }
663
+ /**
664
+ * Get all members of a team.
665
+ * @param teamUuid - UUID of the team
666
+ * @returns Array of team members with their roles
667
+ */
668
+ async teamGetMembers(teamUuid) {
669
+ const { data, error } = await this.client
670
+ .from("team_members")
671
+ .select("user_uuid, role")
672
+ .eq("team_uuid", teamUuid);
673
+ if (error) {
674
+ throw error;
675
+ }
676
+ // get users' data in parallel
677
+ const usersData = await Promise.all(data.map((member) => this.getUserFromUuid(member.user_uuid)));
678
+ usersData.forEach((user) => {
679
+ if (!user) {
680
+ throw new Error("Cannot find user data");
681
+ }
682
+ });
683
+ const result = data.map((member, index) => ({
684
+ user_uuid: member.user_uuid,
685
+ nickname: usersData[index]?.nickname || "",
686
+ email: usersData[index]?.email || "",
687
+ role: member.role,
688
+ }));
689
+ return result;
690
+ }
691
+ /**
692
+ * Add a member to a team as a participant.
693
+ * @param teamUuid - UUID of the team
694
+ * @param userUuid - UUID of the user to add
695
+ */
696
+ async teamAddMember(teamUuid, userUuid) {
697
+ const { error } = await this.client.from("team_members").insert({
698
+ team_uuid: teamUuid,
699
+ user_uuid: userUuid,
700
+ role: "participant",
701
+ });
702
+ if (error) {
703
+ throw error;
704
+ }
705
+ }
706
+ /**
707
+ * Remove a member from a team.
708
+ * @param teamUuid - UUID of the team
709
+ * @param userUuid - UUID of the user to remove
710
+ */
711
+ async teamRemoveMember(teamUuid, userUuid) {
712
+ const { error } = await this.client
713
+ .from("team_members")
714
+ .delete()
715
+ .eq("team_uuid", teamUuid)
716
+ .eq("user_uuid", userUuid)
717
+ .eq("role", "participant");
718
+ if (error) {
719
+ throw error;
720
+ }
721
+ }
722
+ /**
723
+ * Get all sessions belonging to a team.
724
+ * @param teamUuid - UUID of the team
725
+ * @returns Array of session data
726
+ */
727
+ async teamGetSessions(teamUuid) {
728
+ const { data, error } = await this.client
729
+ .from("sessions")
730
+ .select("uuid, title, agent_profile_uuid, workspace, updated_at, user_uuid")
731
+ .eq("team_uuid", teamUuid);
732
+ if (error) {
733
+ throw error;
734
+ }
735
+ return data.map((session) => ({
736
+ session_uuid: session.uuid,
737
+ title: session.title,
738
+ team_uuid: teamUuid,
739
+ agent_profile_uuid: session.agent_profile_uuid,
740
+ workspace: session.workspace || undefined,
741
+ updated_at: session.updated_at || new Date().toISOString(),
742
+ user_uuid: session.user_uuid,
743
+ }));
744
+ }
745
+ createTypedClient(ctor) {
746
+ return new ctor(this.client);
747
+ }
748
+ }
749
+ exports.Database = Database;