@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.
- package/README.md +23 -8
- package/dist/agent/src/agent/agent.js +176 -96
- package/dist/agent/src/agent/agentUtils.js +82 -59
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/mcpServerManager.js +23 -24
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +26 -14
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +23 -21
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/utils/websocket.js +16 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +271 -83
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +23 -20
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +64 -35
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +24 -25
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +241 -58
- package/dist/agent/src/tool/commandPrompt.js +22 -17
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +283 -138
- package/src/agent/agentUtils.ts +143 -108
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +35 -31
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +26 -13
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +30 -29
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/utils/websocket.ts +16 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +358 -89
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +21 -16
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +71 -36
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +32 -30
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +292 -100
- package/src/tool/commandPrompt.ts +28 -19
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -349
- package/dist/agent/src/chat/conversationManager.js +0 -392
- package/dist/agent/src/chat/db.js +0 -209
- package/dist/agent/src/chat/frontendClient.js +0 -74
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -455
- package/src/chat/conversationManager.ts +0 -595
- package/src/chat/db.ts +0 -290
- package/src/chat/frontendClient.ts +0 -123
- package/src/chat/messages.ts +0 -235
- package/src/chat/server.ts +0 -177
- /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
- /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;
|