@xalia/agent 0.6.0 → 0.6.2
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/dist/agent/src/agent/agent.js +103 -54
- package/dist/agent/src/agent/agentUtils.js +22 -21
- package/dist/agent/src/agent/compressingContextManager.js +3 -2
- package/dist/agent/src/agent/dummyLLM.js +1 -3
- package/dist/agent/src/agent/imageGenLLM.js +67 -0
- package/dist/agent/src/agent/imageGenerator.js +43 -0
- package/dist/agent/src/agent/llm.js +27 -0
- package/dist/agent/src/agent/mcpServerManager.js +18 -6
- package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
- package/dist/agent/src/agent/openAILLM.js +3 -3
- package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
- package/dist/agent/src/chat/client/chatClient.js +84 -13
- package/dist/agent/src/chat/client/sessionClient.js +47 -6
- package/dist/agent/src/chat/client/sessionFiles.js +102 -0
- package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
- package/dist/agent/src/chat/data/database.js +83 -70
- package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
- package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
- package/dist/agent/src/chat/data/mimeTypes.js +44 -0
- package/dist/agent/src/chat/protocol/messages.js +21 -0
- package/dist/agent/src/chat/server/chatContextManager.js +14 -7
- package/dist/agent/src/chat/server/connectionManager.js +14 -36
- package/dist/agent/src/chat/server/connectionManager.test.js +2 -16
- package/dist/agent/src/chat/server/conversation.js +69 -45
- package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +205 -43
- package/dist/agent/src/chat/server/server.js +5 -8
- package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
- package/dist/agent/src/chat/server/sessionRegistry.js +199 -32
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
- package/dist/agent/src/chat/server/tools.js +27 -6
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
- package/dist/agent/src/test/agent.test.js +15 -11
- package/dist/agent/src/test/chatContextManager.test.js +4 -0
- package/dist/agent/src/test/clientServerConnection.test.js +2 -2
- package/dist/agent/src/test/db.test.js +33 -70
- package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
- package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
- package/dist/agent/src/test/dbTestTools.js +6 -5
- package/dist/agent/src/test/imageLoad.test.js +1 -1
- package/dist/agent/src/test/mcpServerManager.test.js +1 -1
- package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
- package/dist/agent/src/test/testTools.js +12 -0
- package/dist/agent/src/tool/agentChat.js +25 -6
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +113 -4
- package/dist/agent/src/tool/commandPrompt.js +7 -3
- package/dist/agent/src/tool/files.js +23 -15
- package/dist/agent/src/tool/options.js +2 -2
- package/package.json +1 -1
- package/scripts/test_chat +124 -66
- package/src/agent/agent.ts +145 -38
- package/src/agent/agentUtils.ts +27 -21
- package/src/agent/compressingContextManager.ts +5 -4
- package/src/agent/context.ts +1 -1
- package/src/agent/dummyLLM.ts +1 -3
- package/src/agent/iAgentEventHandler.ts +15 -2
- package/src/agent/imageGenLLM.ts +99 -0
- package/src/agent/imageGenerator.ts +60 -0
- package/src/agent/llm.ts +128 -4
- package/src/agent/mcpServerManager.ts +26 -7
- package/src/agent/nullAgentEventHandler.ts +6 -0
- package/src/agent/openAILLM.ts +3 -8
- package/src/agent/openAILLMStreaming.ts +60 -14
- package/src/chat/client/chatClient.ts +119 -14
- package/src/chat/client/sessionClient.ts +75 -9
- package/src/chat/client/sessionFiles.ts +145 -0
- package/src/chat/data/apiKeyManager.ts +55 -7
- package/src/chat/data/dataModels.ts +16 -7
- package/src/chat/data/database.ts +107 -92
- package/src/chat/data/dbSessionFileModels.ts +91 -0
- package/src/chat/data/dbSessionFiles.ts +99 -0
- package/src/chat/data/dbSessionMessages.ts +68 -0
- package/src/chat/data/mimeTypes.ts +58 -0
- package/src/chat/protocol/messages.ts +127 -13
- package/src/chat/server/chatContextManager.ts +36 -13
- package/src/chat/server/connectionManager.test.ts +1 -22
- package/src/chat/server/connectionManager.ts +18 -53
- package/src/chat/server/conversation.ts +96 -57
- package/src/chat/server/imageGeneratorTools.ts +138 -0
- package/src/chat/server/openSession.ts +287 -49
- package/src/chat/server/server.ts +5 -11
- package/src/chat/server/sessionFileManager.ts +223 -63
- package/src/chat/server/sessionRegistry.ts +285 -41
- package/src/chat/server/test-utils/mockFactories.ts +13 -13
- package/src/chat/server/tools.ts +43 -8
- package/src/chat/utils/agentSessionMap.ts +2 -2
- package/src/chat/utils/multiAsyncQueue.ts +11 -1
- package/src/test/agent.test.ts +23 -14
- package/src/test/chatContextManager.test.ts +7 -2
- package/src/test/clientServerConnection.test.ts +3 -3
- package/src/test/compressingContextManager.test.ts +1 -1
- package/src/test/context.test.ts +2 -1
- package/src/test/conversation.test.ts +1 -1
- package/src/test/db.test.ts +41 -83
- package/src/test/dbSessionFiles.test.ts +258 -0
- package/src/test/dbSessionMessages.test.ts +85 -0
- package/src/test/dbTestTools.ts +9 -5
- package/src/test/imageLoad.test.ts +2 -2
- package/src/test/mcpServerManager.test.ts +3 -1
- package/src/test/multiAsyncQueue.test.ts +58 -0
- package/src/test/testTools.ts +15 -1
- package/src/tool/agentChat.ts +35 -7
- package/src/tool/agentMain.ts +7 -7
- package/src/tool/chatMain.ts +126 -5
- package/src/tool/commandPrompt.ts +10 -5
- package/src/tool/files.ts +30 -13
- package/src/tool/options.ts +1 -1
- package/test_data/dummyllm_script_image_gen.json +19 -0
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
- package/test_data/image_gen_test_profile.json +5 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SessionRegistry = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
4
|
+
exports.userSessionCreateData = userSessionCreateData;
|
|
5
|
+
exports.teamSessionCreateData = teamSessionCreateData;
|
|
6
6
|
const uuid_1 = require("uuid");
|
|
7
7
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
8
8
|
const messages_1 = require("../protocol/messages");
|
|
@@ -10,8 +10,8 @@ const openSession_1 = require("./openSession");
|
|
|
10
10
|
const database_1 = require("../data/database");
|
|
11
11
|
const userResolver_1 = require("../utils/userResolver");
|
|
12
12
|
const errors_1 = require("../protocol/errors");
|
|
13
|
-
const agentUtils_1 = require("../../agent/agentUtils");
|
|
14
13
|
const errorUtils_1 = require("./errorUtils");
|
|
14
|
+
const apiKeyManager_1 = require("../data/apiKeyManager");
|
|
15
15
|
const logger = (0, sdk_1.getLogger)();
|
|
16
16
|
class SessionRegistry {
|
|
17
17
|
constructor(db, connectionManager, llmUrl, xmcpUrl) {
|
|
@@ -29,6 +29,8 @@ class SessionRegistry {
|
|
|
29
29
|
// Session instances
|
|
30
30
|
// sessionId -> OpenSession
|
|
31
31
|
this.openSessions = new Map();
|
|
32
|
+
this.guests = new Map();
|
|
33
|
+
this.apiKeyManager = new apiKeyManager_1.ApiKeyManager(db);
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Add user to session membership.
|
|
@@ -112,6 +114,47 @@ class SessionRegistry {
|
|
|
112
114
|
getInMemoryUserSessions(userId) {
|
|
113
115
|
return this.userSessions.get(userId) || new Set();
|
|
114
116
|
}
|
|
117
|
+
// IMessageProcessor<ClientToServer>
|
|
118
|
+
async authenticate(token) {
|
|
119
|
+
// Parse the api key to determine the type of connection
|
|
120
|
+
const { prefix, apiKey, payload: sessionUUID, } = apiKeyManager_1.ApiKeyManager.parseToken(token);
|
|
121
|
+
if (prefix === openSession_1.GUEST_TOKEN_PREFIX) {
|
|
122
|
+
if (!sessionUUID) {
|
|
123
|
+
throw new Error(`invalid token ${token}`);
|
|
124
|
+
}
|
|
125
|
+
const descriptor = await this.getSessionDescriptor(sessionUUID);
|
|
126
|
+
if (!descriptor) {
|
|
127
|
+
throw new Error(`no such session ${sessionUUID}`);
|
|
128
|
+
}
|
|
129
|
+
if (descriptor.access_token !== token) {
|
|
130
|
+
throw new Error(`invalid guest key ${apiKey}`);
|
|
131
|
+
}
|
|
132
|
+
// For now, add a new user to the DB (so simplify the process of adding
|
|
133
|
+
// users).
|
|
134
|
+
const user_uuid = (0, uuid_1.v4)();
|
|
135
|
+
const email = `${user_uuid}@`;
|
|
136
|
+
const nickname = "Guest";
|
|
137
|
+
const timezone = "UTC";
|
|
138
|
+
await this.db.createUser(user_uuid, email, nickname, timezone);
|
|
139
|
+
const user = {
|
|
140
|
+
uuid: user_uuid,
|
|
141
|
+
nickname: "Guest",
|
|
142
|
+
email: "guest",
|
|
143
|
+
timezone: "UTC",
|
|
144
|
+
guest_for_session: sessionUUID,
|
|
145
|
+
};
|
|
146
|
+
this.guests.set(user_uuid, user);
|
|
147
|
+
return user.uuid;
|
|
148
|
+
// throw new Error("unimpl");
|
|
149
|
+
// // return "";
|
|
150
|
+
}
|
|
151
|
+
const userData = await this.apiKeyManager.verifyApiKey(apiKey);
|
|
152
|
+
if (!userData) {
|
|
153
|
+
throw new errors_1.ChatFatalError("invalid api key");
|
|
154
|
+
}
|
|
155
|
+
return userData.uuid;
|
|
156
|
+
}
|
|
157
|
+
// IMessageProcessor<ClientToServer>
|
|
115
158
|
async processMessage(connectionId, userId, message) {
|
|
116
159
|
if ((0, messages_1.isClientControlMessage)(message)) {
|
|
117
160
|
// handle connection level message
|
|
@@ -134,11 +177,11 @@ class SessionRegistry {
|
|
|
134
177
|
async processClientControlMessage(connectionId, userId, message) {
|
|
135
178
|
switch (message.type) {
|
|
136
179
|
case "control_agent_profile_create":
|
|
137
|
-
await this.handleAgentProfileCreate(
|
|
180
|
+
await this.handleAgentProfileCreate(userId, message);
|
|
138
181
|
break;
|
|
139
182
|
case "control_agent_profile_delete":
|
|
140
|
-
|
|
141
|
-
|
|
183
|
+
await this.handleAgentProfileDelete(userId, message);
|
|
184
|
+
break;
|
|
142
185
|
case "control_get_session_list":
|
|
143
186
|
await this.handleGetSessionList(connectionId, userId, message);
|
|
144
187
|
break;
|
|
@@ -173,6 +216,14 @@ class SessionRegistry {
|
|
|
173
216
|
async handleGetSessionList(connectionId, userId, message) {
|
|
174
217
|
try {
|
|
175
218
|
const [userSessions, teamSessions, userAgents] = await this.getAllAgentsAndSessionsByUser(userId);
|
|
219
|
+
// Mark any guest session as a user session
|
|
220
|
+
const guest = this.guests.get(userId);
|
|
221
|
+
if (guest) {
|
|
222
|
+
const guestSession = await this.db.sessionGetDescriptorById(guest.guest_for_session);
|
|
223
|
+
if (guestSession) {
|
|
224
|
+
userSessions.push(guestSession);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
176
227
|
const response = {
|
|
177
228
|
type: "control_session_list",
|
|
178
229
|
user_sessions: userSessions,
|
|
@@ -196,11 +247,11 @@ class SessionRegistry {
|
|
|
196
247
|
// validate the session access
|
|
197
248
|
await this.validateSessionAccess(sessionId, userId, "owner");
|
|
198
249
|
// get users/team-uuid before deletion
|
|
199
|
-
const [users,
|
|
250
|
+
const [users, sessionData] = await Promise.all([
|
|
200
251
|
this.getSessionUsers(sessionId),
|
|
201
|
-
this.db.
|
|
202
|
-
this.db.sessionGetById(sessionId),
|
|
252
|
+
this.db.sessionGetDescriptorById(sessionId),
|
|
203
253
|
]);
|
|
254
|
+
const teamUuid = sessionData?.team_uuid;
|
|
204
255
|
if (!sessionData) {
|
|
205
256
|
throw new errors_1.ChatFatalError(`No such session: ${sessionId}`);
|
|
206
257
|
}
|
|
@@ -244,9 +295,10 @@ class SessionRegistry {
|
|
|
244
295
|
const failedLookups = [];
|
|
245
296
|
const validMemberIds = [];
|
|
246
297
|
const participants = [];
|
|
247
|
-
// filter out the owner
|
|
298
|
+
// No need to filter out the owner here, that is done in
|
|
299
|
+
// `createTeamWithParticipants`
|
|
248
300
|
resolvedMemberIds.forEach((user, index) => {
|
|
249
|
-
if (user
|
|
301
|
+
if (user) {
|
|
250
302
|
validMemberIds.push(user.uuid);
|
|
251
303
|
participants.push({
|
|
252
304
|
user_uuid: user.uuid,
|
|
@@ -255,23 +307,44 @@ class SessionRegistry {
|
|
|
255
307
|
role: "participant",
|
|
256
308
|
});
|
|
257
309
|
}
|
|
258
|
-
else
|
|
310
|
+
else {
|
|
259
311
|
failedLookups.push(message.initial_members[index]);
|
|
260
312
|
}
|
|
261
313
|
});
|
|
262
314
|
// Create the team with initial participants
|
|
263
315
|
const teamUuid = await this.db.createTeamWithParticipants(message.team_name, userId, validMemberIds);
|
|
316
|
+
// Create a default agent for the new team
|
|
317
|
+
const defaultAgentProfile = await this.db.createAgentProfile(undefined, teamUuid, sdk_1.DEFAULT_AGENT_PROFILE_NAME, sdk_1.DEFAULT_AGENT_PROFILE);
|
|
318
|
+
if (!defaultAgentProfile) {
|
|
319
|
+
throw new Error(`Failed to create default agent profile for team ${teamUuid}`);
|
|
320
|
+
}
|
|
321
|
+
// Get owner's information and add to participants for the response
|
|
322
|
+
const ownerInfo = await this.db.getUserFromUuid(userId);
|
|
323
|
+
if (!ownerInfo) {
|
|
324
|
+
throw new Error(`Cannot find owner user data for user ${userId}`);
|
|
325
|
+
}
|
|
326
|
+
const ownerParticipant = {
|
|
327
|
+
user_uuid: userId,
|
|
328
|
+
nickname: ownerInfo.nickname || "",
|
|
329
|
+
email: ownerInfo.email,
|
|
330
|
+
role: "owner",
|
|
331
|
+
};
|
|
264
332
|
const response = {
|
|
265
333
|
type: "control_team_created",
|
|
266
334
|
team_uuid: teamUuid,
|
|
267
335
|
team_owner_uuid: userId,
|
|
268
336
|
team_name: message.team_name,
|
|
269
|
-
members: participants,
|
|
337
|
+
members: [ownerParticipant, ...participants],
|
|
270
338
|
failed_lookups: failedLookups,
|
|
271
339
|
};
|
|
272
340
|
const members = new Set(validMemberIds);
|
|
273
341
|
members.add(userId);
|
|
274
342
|
this.connectionManager.sendToUsers(members, response);
|
|
343
|
+
// Broadcast the default agent profile to all team members
|
|
344
|
+
this.connectionManager.sendToUsers(members, {
|
|
345
|
+
type: "control_agent_profile_created",
|
|
346
|
+
profile: defaultAgentProfile,
|
|
347
|
+
});
|
|
275
348
|
}
|
|
276
349
|
catch (error) {
|
|
277
350
|
logger.error(`[SessionRegistry] Error creating team: ${String(error)}`);
|
|
@@ -338,6 +411,13 @@ class SessionRegistry {
|
|
|
338
411
|
});
|
|
339
412
|
}
|
|
340
413
|
}
|
|
414
|
+
// Notify all team members about the updated member list
|
|
415
|
+
const updatedParticipants = await this.db.teamGetMembers(message.target_team_id);
|
|
416
|
+
this.connectionManager.sendToUsers(new Set(updatedParticipants.map((p) => p.user_uuid)), {
|
|
417
|
+
type: "control_team_members_updated",
|
|
418
|
+
team_uuid: message.target_team_id,
|
|
419
|
+
members: updatedParticipants,
|
|
420
|
+
});
|
|
341
421
|
logger.info(`[SessionRegistry] User ${user.uuid}` +
|
|
342
422
|
`added to team ${message.target_team_id}`);
|
|
343
423
|
}
|
|
@@ -388,6 +468,13 @@ class SessionRegistry {
|
|
|
388
468
|
this.removeUserFromSessionMemory(user.uuid, sessionData.session_uuid);
|
|
389
469
|
}
|
|
390
470
|
}
|
|
471
|
+
// Notify all remaining team members about the updated member list
|
|
472
|
+
const updatedParticipants = await this.db.teamGetMembers(message.target_team_id);
|
|
473
|
+
this.connectionManager.sendToUsers(new Set(updatedParticipants.map((p) => p.user_uuid)), {
|
|
474
|
+
type: "control_team_members_updated",
|
|
475
|
+
team_uuid: message.target_team_id,
|
|
476
|
+
members: updatedParticipants,
|
|
477
|
+
});
|
|
391
478
|
logger.info(`[SessionRegistry] User ${user.uuid} ` +
|
|
392
479
|
`removed from team ${message.target_team_id}`);
|
|
393
480
|
}
|
|
@@ -421,7 +508,7 @@ class SessionRegistry {
|
|
|
421
508
|
// Validate session access permissions
|
|
422
509
|
const access = await this.validateSessionAccess(sessionId, userId);
|
|
423
510
|
if (!access) {
|
|
424
|
-
throw new errors_1.ChatFatalError(`User ${userId} is not authorized to
|
|
511
|
+
throw new errors_1.ChatFatalError(`User ${userId} is not authorized to join session ${sessionId}`);
|
|
425
512
|
}
|
|
426
513
|
// get or create the session
|
|
427
514
|
const session = await this.getAndActivateSession(sessionId);
|
|
@@ -430,6 +517,15 @@ class SessionRegistry {
|
|
|
430
517
|
// since we have validated the access
|
|
431
518
|
throw new errors_1.ChatFatalError(`Server internal error: ` + `failed to load session ${sessionId}`);
|
|
432
519
|
}
|
|
520
|
+
const guest = this.guests.get(userId);
|
|
521
|
+
if (guest) {
|
|
522
|
+
session.addParticipant(userId, {
|
|
523
|
+
user_uuid: guest.uuid,
|
|
524
|
+
nickname: guest.nickname || "Guest",
|
|
525
|
+
email: guest.email,
|
|
526
|
+
role: "participant",
|
|
527
|
+
});
|
|
528
|
+
}
|
|
433
529
|
// To this point, there should be no error thrown.
|
|
434
530
|
// Update in-memory session-user/user-session mappings
|
|
435
531
|
this.addUserToSessionMemory(userId, sessionId);
|
|
@@ -445,7 +541,7 @@ class SessionRegistry {
|
|
|
445
541
|
this.sendControlError(connectionId, message.client_message_id, String(error));
|
|
446
542
|
}
|
|
447
543
|
}
|
|
448
|
-
async handleAgentProfileCreate(
|
|
544
|
+
async handleAgentProfileCreate(userId, message) {
|
|
449
545
|
// get agent profile from template
|
|
450
546
|
let agentProfileFromTemplate = undefined;
|
|
451
547
|
if (message.template_name) {
|
|
@@ -455,11 +551,8 @@ class SessionRegistry {
|
|
|
455
551
|
}
|
|
456
552
|
agentProfileFromTemplate = template.agent_profile;
|
|
457
553
|
}
|
|
458
|
-
const newAgentProfile = agentProfileFromTemplate ||
|
|
459
|
-
model
|
|
460
|
-
system_prompt: sdk_1.DEFAULT_AGENT_PROFILE_SYSTEM_PROMPT,
|
|
461
|
-
mcp_settings: {},
|
|
462
|
-
};
|
|
554
|
+
const newAgentProfile = agentProfileFromTemplate ||
|
|
555
|
+
new sdk_1.AgentProfile(sdk_1.DEFAULT_AGENT_PROFILE.model, sdk_1.DEFAULT_AGENT_PROFILE.system_prompt, sdk_1.DEFAULT_AGENT_PROFILE.mcp_settings);
|
|
463
556
|
const team_uuid = message.team_uuid || undefined;
|
|
464
557
|
if (team_uuid) {
|
|
465
558
|
// TODO: should be able to reconstruct the full SavedAgentProfile in one
|
|
@@ -474,17 +567,49 @@ class SessionRegistry {
|
|
|
474
567
|
return savedAgentProfile.uuid;
|
|
475
568
|
}
|
|
476
569
|
// User AgentProfile
|
|
477
|
-
const savedAgentProfile = await this.db.createAgentProfile(
|
|
570
|
+
const savedAgentProfile = await this.db.createAgentProfile(userId, undefined, message.title, newAgentProfile);
|
|
478
571
|
if (!savedAgentProfile) {
|
|
479
572
|
throw new Error("failed creating agent profile (createAgentProfile)");
|
|
480
573
|
}
|
|
481
574
|
// Send the new AgentProfile to the user
|
|
482
|
-
this.connectionManager.sendToUsers(new Set([
|
|
575
|
+
this.connectionManager.sendToUsers(new Set([userId]), {
|
|
483
576
|
type: "control_agent_profile_created",
|
|
484
577
|
profile: savedAgentProfile,
|
|
485
578
|
});
|
|
486
579
|
return savedAgentProfile.uuid;
|
|
487
580
|
}
|
|
581
|
+
async handleAgentProfileDelete(userId, message) {
|
|
582
|
+
const agentProfileUuid = message.agent_profile_uuid;
|
|
583
|
+
// Get the agent profile to determine if it's a team or user profile
|
|
584
|
+
const agentProfile = await this.db.getSavedAgentProfileById(agentProfileUuid);
|
|
585
|
+
if (!agentProfile) {
|
|
586
|
+
throw new Error(`Agent profile ${agentProfileUuid} not found`);
|
|
587
|
+
}
|
|
588
|
+
// Validate team access: user must own the profile or be in the team
|
|
589
|
+
// TODO: Only allow the owner to delete the profile?
|
|
590
|
+
if (agentProfile.team_uuid) {
|
|
591
|
+
const hasAccess = await this.validateTeamAccess(agentProfile.team_uuid, userId);
|
|
592
|
+
if (!hasAccess) {
|
|
593
|
+
throw new Error(`User ${userId} is not authorized to delete this agent profile`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Delete the agent profile from database
|
|
597
|
+
await this.db.deleteAgentProfile(agentProfileUuid);
|
|
598
|
+
// Send confirmation to all relevant users
|
|
599
|
+
if (agentProfile.team_uuid) {
|
|
600
|
+
const participants = await this.db.teamGetMembers(agentProfile.team_uuid);
|
|
601
|
+
this.connectionManager.sendToUsers(new Set(participants.map((p) => p.user_uuid)), {
|
|
602
|
+
type: "control_agent_profile_deleted",
|
|
603
|
+
profile_uuid: agentProfileUuid,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
this.connectionManager.sendToUsers(new Set([userId]), {
|
|
608
|
+
type: "control_agent_profile_deleted",
|
|
609
|
+
profile_uuid: agentProfileUuid,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
488
613
|
/**
|
|
489
614
|
* Create a new session for a user.
|
|
490
615
|
* - create session in database (via `newSession`)
|
|
@@ -497,12 +622,12 @@ class SessionRegistry {
|
|
|
497
622
|
if (!message.agent_profile_id) {
|
|
498
623
|
logger.info("[handleSessionCreate] creating new AgentProfile");
|
|
499
624
|
// Create AgentProfile and inform the client
|
|
500
|
-
message.agent_profile_id = await this.handleAgentProfileCreate({
|
|
625
|
+
message.agent_profile_id = await this.handleAgentProfileCreate(fromUserId, {
|
|
501
626
|
type: "control_agent_profile_create",
|
|
502
627
|
title: "New Agent " + (0, uuid_1.v4)(),
|
|
503
628
|
user_uuid: fromUserId,
|
|
504
629
|
team_uuid: message.team_id,
|
|
505
|
-
}
|
|
630
|
+
});
|
|
506
631
|
}
|
|
507
632
|
// Create new session and get its instance
|
|
508
633
|
const { openSession, sessionId } = message.team_id
|
|
@@ -531,7 +656,7 @@ class SessionRegistry {
|
|
|
531
656
|
// validate the agent profile
|
|
532
657
|
await this.validateSavedAgentProfile(agentProfileId);
|
|
533
658
|
const newSessionData = {
|
|
534
|
-
...
|
|
659
|
+
...userSessionCreateData(ownerId, title, agentProfileId),
|
|
535
660
|
updated_at: new Date().toISOString(),
|
|
536
661
|
};
|
|
537
662
|
const openSession = await openSession_1.OpenSession.initWithEmptySession(this.db, newSessionData, this.llmUrl, this.xmcpUrl, this.connectionManager);
|
|
@@ -550,7 +675,7 @@ class SessionRegistry {
|
|
|
550
675
|
throw new errors_1.ChatFatalError(`User ${fromUserId} is not a participant of team ${teamId}`);
|
|
551
676
|
}
|
|
552
677
|
const newSessionData = {
|
|
553
|
-
...
|
|
678
|
+
...teamSessionCreateData(teamId, fromUserId, title, agentProfileId),
|
|
554
679
|
updated_at: new Date().toISOString(),
|
|
555
680
|
};
|
|
556
681
|
// initialize the open session
|
|
@@ -589,8 +714,24 @@ class SessionRegistry {
|
|
|
589
714
|
* Handle user disconnect - clean up from all sessions.
|
|
590
715
|
* Called when a connection is closed to ensure proper cleanup.
|
|
591
716
|
*/
|
|
592
|
-
handleUserDisconnect(userId) {
|
|
717
|
+
async handleUserDisconnect(userId) {
|
|
593
718
|
logger.info(`[SessionRegistry] Handling disconnect for user ${userId}`);
|
|
719
|
+
// If the user is a guest, remove his DB entry.
|
|
720
|
+
const guest = this.guests.get(userId);
|
|
721
|
+
if (guest) {
|
|
722
|
+
const session = this.openSessions.get(guest.guest_for_session);
|
|
723
|
+
if (session) {
|
|
724
|
+
session.removeParticipant(userId);
|
|
725
|
+
}
|
|
726
|
+
this.guests.delete(userId);
|
|
727
|
+
try {
|
|
728
|
+
logger.info(`[SessionRegistry.handleUserDisconnect] deleting user ${userId}`);
|
|
729
|
+
await this.db.deleteUser(userId);
|
|
730
|
+
}
|
|
731
|
+
catch (e) {
|
|
732
|
+
logger.warn(`error removing guest ${userId}: ${(0, errorUtils_1.getErrorString)(e)}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
594
735
|
// Get all sessions the user is in (copy to avoid concurrent modification)
|
|
595
736
|
const userSessionIds = this.getInMemoryUserSessions(userId);
|
|
596
737
|
const sessionsToLeave = Array.from(userSessionIds);
|
|
@@ -607,11 +748,12 @@ class SessionRegistry {
|
|
|
607
748
|
* This will also create a default agent profile if none exists.
|
|
608
749
|
*/
|
|
609
750
|
async getAllAgentsAndSessionsByUser(userId) {
|
|
610
|
-
|
|
751
|
+
const [userSessions, teamInfos, userAgents] = await Promise.all([
|
|
611
752
|
this.db.getUserSessions(userId),
|
|
612
753
|
this.db.getTeamInfosByUser(userId),
|
|
613
754
|
this.agentProfilesGetByUserOrDefault(userId),
|
|
614
755
|
]);
|
|
756
|
+
return [userSessions, teamInfos, userAgents];
|
|
615
757
|
}
|
|
616
758
|
async agentProfilesGetByUserOrDefault(userId) {
|
|
617
759
|
const agentProfiles = await this.db.agentProfilesGetByUser(userId);
|
|
@@ -645,6 +787,12 @@ class SessionRegistry {
|
|
|
645
787
|
const participants = session.getParticipants();
|
|
646
788
|
const role = participants.get(userId);
|
|
647
789
|
if (!role) {
|
|
790
|
+
// Check for guest access
|
|
791
|
+
const guest = this.guests.get(userId);
|
|
792
|
+
if (guest && guest.guest_for_session === sessionId) {
|
|
793
|
+
return !accessType || accessType === "participant";
|
|
794
|
+
}
|
|
795
|
+
// Not authorized to join the session
|
|
648
796
|
return false;
|
|
649
797
|
}
|
|
650
798
|
else {
|
|
@@ -656,6 +804,12 @@ class SessionRegistry {
|
|
|
656
804
|
const participants = await this.db.sessionGetParticipants(sessionId);
|
|
657
805
|
const role = participants.find((p) => p.user_uuid === userId)?.role;
|
|
658
806
|
if (!role) {
|
|
807
|
+
// Check for guest access
|
|
808
|
+
const guest = this.guests.get(userId);
|
|
809
|
+
logger.info(`[validateSessionAccess] guest: ${JSON.stringify(guest)}`);
|
|
810
|
+
if (guest && guest.guest_for_session === sessionId) {
|
|
811
|
+
return !accessType || accessType === "participant";
|
|
812
|
+
}
|
|
659
813
|
return false;
|
|
660
814
|
}
|
|
661
815
|
else {
|
|
@@ -676,25 +830,38 @@ class SessionRegistry {
|
|
|
676
830
|
return !accessType || role === accessType;
|
|
677
831
|
}
|
|
678
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Read from DB if the session is not active
|
|
835
|
+
*/
|
|
836
|
+
async getSessionDescriptor(sessionId) {
|
|
837
|
+
const session = this.openSessions.get(sessionId);
|
|
838
|
+
if (session) {
|
|
839
|
+
return session.getDescriptor();
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
// fetch the session from database
|
|
843
|
+
return this.db.sessionGetDescriptorById(sessionId);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
679
846
|
}
|
|
680
847
|
exports.SessionRegistry = SessionRegistry;
|
|
681
|
-
function
|
|
848
|
+
function userSessionCreateData(ownerId, title, agentProfileId) {
|
|
682
849
|
return {
|
|
683
850
|
session_uuid: database_1.Database.sessionNewUUID(),
|
|
684
851
|
title,
|
|
685
852
|
team_uuid: undefined,
|
|
686
853
|
agent_profile_uuid: agentProfileId,
|
|
687
|
-
workspace: undefined,
|
|
688
854
|
user_uuid: ownerId,
|
|
855
|
+
agent_paused: false,
|
|
689
856
|
};
|
|
690
857
|
}
|
|
691
|
-
function
|
|
858
|
+
function teamSessionCreateData(teamId, ownerId, title, agentProfileId) {
|
|
692
859
|
return {
|
|
693
860
|
session_uuid: database_1.Database.sessionNewUUID(),
|
|
694
861
|
title,
|
|
695
862
|
team_uuid: teamId,
|
|
696
863
|
agent_profile_uuid: agentProfileId,
|
|
697
|
-
workspace: undefined,
|
|
698
864
|
user_uuid: ownerId,
|
|
865
|
+
agent_paused: false,
|
|
699
866
|
};
|
|
700
867
|
}
|
|
@@ -102,13 +102,11 @@ function createMockUserConnectionManager() {
|
|
|
102
102
|
sendToUsers: vitest_1.vi.fn(),
|
|
103
103
|
sendToConnection: vitest_1.vi.fn(),
|
|
104
104
|
sendServerError: vitest_1.vi.fn(),
|
|
105
|
-
getLiveUserApiKey: vitest_1.vi.fn(),
|
|
106
105
|
};
|
|
107
106
|
const mock = {
|
|
108
107
|
sendToUsers: spies.sendToUsers,
|
|
109
108
|
sendToConnection: spies.sendToConnection,
|
|
110
109
|
sendServerError: spies.sendServerError,
|
|
111
|
-
getLiveUserApiKey: spies.getLiveUserApiKey,
|
|
112
110
|
};
|
|
113
111
|
return { mock, spies };
|
|
114
112
|
}
|
|
@@ -117,10 +115,18 @@ function createMockUserConnectionManager() {
|
|
|
117
115
|
*/
|
|
118
116
|
function createMockSessionRegistry() {
|
|
119
117
|
const spies = {
|
|
118
|
+
authenticate: vitest_1.vi.fn().mockImplementation((apiKey) => {
|
|
119
|
+
if (apiKey === "valid-api-key")
|
|
120
|
+
return Promise.resolve(exports.MOCK_USERS.owner.uuid);
|
|
121
|
+
if (apiKey === "participant-api-key")
|
|
122
|
+
return Promise.resolve(exports.MOCK_USERS.participant.uuid);
|
|
123
|
+
return Promise.resolve(null);
|
|
124
|
+
}),
|
|
120
125
|
processMessage: vitest_1.vi.fn(),
|
|
121
126
|
handleUserDisconnect: vitest_1.vi.fn(),
|
|
122
127
|
};
|
|
123
128
|
const mock = {
|
|
129
|
+
authenticate: spies.authenticate,
|
|
124
130
|
processMessage: spies.processMessage,
|
|
125
131
|
handleUserDisconnect: spies.handleUserDisconnect,
|
|
126
132
|
};
|
|
@@ -189,6 +195,7 @@ function createMockSessionList() {
|
|
|
189
195
|
workspace: undefined,
|
|
190
196
|
updated_at: exports.MOCK_SESSIONS.active.updated_at || "",
|
|
191
197
|
user_uuid: exports.MOCK_SESSIONS.active.user_uuid,
|
|
198
|
+
agent_paused: false,
|
|
192
199
|
},
|
|
193
200
|
{
|
|
194
201
|
session_uuid: exports.MOCK_SESSIONS.secondary.uuid,
|
|
@@ -198,6 +205,7 @@ function createMockSessionList() {
|
|
|
198
205
|
workspace: undefined,
|
|
199
206
|
updated_at: exports.MOCK_SESSIONS.secondary.updated_at || "",
|
|
200
207
|
user_uuid: exports.MOCK_SESSIONS.secondary.user_uuid,
|
|
208
|
+
agent_paused: false,
|
|
201
209
|
},
|
|
202
210
|
];
|
|
203
211
|
}
|
|
@@ -295,13 +303,6 @@ function setupStandardMockBehaviors(mocks) {
|
|
|
295
303
|
});
|
|
296
304
|
}
|
|
297
305
|
// Setup user connection manager mocks
|
|
298
|
-
if (mocks.userConnectionManager) {
|
|
299
|
-
|
|
300
|
-
if (userId === exports.MOCK_USERS.owner.uuid)
|
|
301
|
-
return "valid-api-key";
|
|
302
|
-
if (userId === exports.MOCK_USERS.participant.uuid)
|
|
303
|
-
return "participant-api-key";
|
|
304
|
-
return undefined;
|
|
305
|
-
});
|
|
306
|
-
}
|
|
306
|
+
// if (mocks.userConnectionManager) {
|
|
307
|
+
// }
|
|
307
308
|
}
|
|
@@ -17,23 +17,34 @@ const expr_eval_1 = require("expr-eval");
|
|
|
17
17
|
const htmlToText_1 = require("../utils/htmlToText");
|
|
18
18
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
19
19
|
const search_1 = require("../utils/search");
|
|
20
|
+
const sessionFileManager_1 = require("./sessionFileManager");
|
|
21
|
+
const imageGeneratorTools_1 = require("./imageGeneratorTools");
|
|
20
22
|
const logger = (0, sdk_1.getLogger)();
|
|
21
23
|
/**
|
|
22
24
|
* Returns a function which parses an `args` struct and attempts to extract
|
|
23
25
|
* multiple string parameters with the given names. e.g.
|
|
24
26
|
*
|
|
25
|
-
* const parseFn = makeParseArgsFn(
|
|
27
|
+
* const parseFn = makeParseArgsFn(
|
|
28
|
+
* ["arg0", "arg1"] as const,
|
|
29
|
+
* ["opt0"] as const)
|
|
26
30
|
*
|
|
27
|
-
* creates
|
|
28
|
-
*
|
|
31
|
+
* creates
|
|
32
|
+
*
|
|
33
|
+
* parseFn: (args: unknown) => {
|
|
34
|
+
* arg0: string,
|
|
35
|
+
* arg1: string,
|
|
36
|
+
* opt0: string|undefined
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* which can be used to parse tool arguments.
|
|
29
40
|
*
|
|
30
41
|
* NOTE, the complex type parameters ensures that the name list is a
|
|
31
42
|
* compile-time value, which in turn ensures that the return value of this
|
|
32
43
|
* function is well-typed.
|
|
33
44
|
*/
|
|
34
|
-
function makeParseArgsFn(names) {
|
|
45
|
+
function makeParseArgsFn(names, optNames) {
|
|
35
46
|
return (args) => {
|
|
36
|
-
if (typeof args !== "object") {
|
|
47
|
+
if (!args || typeof args !== "object") {
|
|
37
48
|
throw new Error(`invalid args: ${typeof args}`);
|
|
38
49
|
}
|
|
39
50
|
const argsObj = args;
|
|
@@ -43,6 +54,14 @@ function makeParseArgsFn(names) {
|
|
|
43
54
|
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
44
55
|
}
|
|
45
56
|
}
|
|
57
|
+
if (optNames) {
|
|
58
|
+
for (const name of optNames) {
|
|
59
|
+
const val = argsObj[name];
|
|
60
|
+
if (typeof val !== "undefined" && typeof val !== "string") {
|
|
61
|
+
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
46
65
|
return argsObj;
|
|
47
66
|
};
|
|
48
67
|
}
|
|
@@ -234,10 +253,12 @@ function openURLTool() {
|
|
|
234
253
|
/**
|
|
235
254
|
* Add a set of agent tools for chat sessions.
|
|
236
255
|
*/
|
|
237
|
-
async function addDefaultChatTools(agent, timezone, platform) {
|
|
256
|
+
async function addDefaultChatTools(agent, timezone, platform, fileManager, llmUrl, llmApiKey) {
|
|
238
257
|
await agent.addAgentToolProvider(datetimeTool(timezone));
|
|
239
258
|
await agent.addAgentToolProvider(exports.calculatorTool);
|
|
240
259
|
await agent.addAgentToolProvider(renderTool(platform));
|
|
241
260
|
await agent.addAgentToolProvider(webSearchTool());
|
|
242
261
|
await agent.addAgentToolProvider(openURLTool());
|
|
262
|
+
await agent.addAgentToolProvider((0, sessionFileManager_1.fileManagerTool)(fileManager));
|
|
263
|
+
await agent.addAgentToolProvider(await (0, imageGeneratorTools_1.genImageFileTool)(llmUrl, llmApiKey, fileManager));
|
|
243
264
|
}
|
|
@@ -4,6 +4,7 @@ exports.MultiAsyncQueue = void 0;
|
|
|
4
4
|
class MultiAsyncQueue {
|
|
5
5
|
constructor(process, maxBacklog = 100) {
|
|
6
6
|
this.running = false;
|
|
7
|
+
this.paused = false;
|
|
7
8
|
this.queue = [];
|
|
8
9
|
this.process = process;
|
|
9
10
|
this.maxBacklog = maxBacklog;
|
|
@@ -22,8 +23,15 @@ class MultiAsyncQueue {
|
|
|
22
23
|
setTimeout(() => void this.tryNext(), 0);
|
|
23
24
|
return true;
|
|
24
25
|
}
|
|
26
|
+
pause() {
|
|
27
|
+
this.paused = true;
|
|
28
|
+
}
|
|
29
|
+
unpause() {
|
|
30
|
+
this.paused = false;
|
|
31
|
+
setTimeout(() => void this.tryNext(), 0);
|
|
32
|
+
}
|
|
25
33
|
async tryNext() {
|
|
26
|
-
if (this.running) {
|
|
34
|
+
if (this.running || this.paused) {
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
37
|
if (this.queue.length > 0) {
|
|
@@ -57,7 +57,10 @@ function createCallTestToolScript(tool_call_ids, param1, param2) {
|
|
|
57
57
|
const toolFn = async (_, args) => {
|
|
58
58
|
const { param1, param2 } = args;
|
|
59
59
|
return new Promise((r) => {
|
|
60
|
-
r({
|
|
60
|
+
r({
|
|
61
|
+
response: `tool_result: '${param1}' '${String(param2)}'`,
|
|
62
|
+
metadata: { type: "text/plain" },
|
|
63
|
+
});
|
|
61
64
|
});
|
|
62
65
|
};
|
|
63
66
|
// A script that uses the tool
|
|
@@ -103,11 +106,12 @@ function createCallTestToolScript(tool_call_ids, param1, param2) {
|
|
|
103
106
|
content: `tool_result: '${param1}' '${String(param2)}'`,
|
|
104
107
|
role: "tool",
|
|
105
108
|
tool_call_id: t_id,
|
|
109
|
+
metadata: { type: "text/plain" },
|
|
106
110
|
};
|
|
107
111
|
});
|
|
108
112
|
return {
|
|
109
113
|
script,
|
|
110
|
-
expectCompletions: script.map((s) => s.message),
|
|
114
|
+
expectCompletions: script.map((s) => (0, agent_1.completionToAssistantMessageParam)(s.message)),
|
|
111
115
|
expectAgentMessages,
|
|
112
116
|
expectToolResults,
|
|
113
117
|
toolDescriptor,
|
|
@@ -141,9 +145,9 @@ describe("Agent", () => {
|
|
|
141
145
|
(0, vitest_1.expect)(eventHandler.getToolCallResults()).eql(expectToolResults);
|
|
142
146
|
// Check the ordering
|
|
143
147
|
const allExpect = [
|
|
144
|
-
script[0].message,
|
|
148
|
+
(0, agent_1.completionToAssistantMessageParam)(script[0].message),
|
|
145
149
|
...expectToolResults,
|
|
146
|
-
script[1].message,
|
|
150
|
+
(0, agent_1.completionToAssistantMessageParam)(script[1].message),
|
|
147
151
|
];
|
|
148
152
|
const all = eventHandler.getAll();
|
|
149
153
|
(0, vitest_1.expect)(all).eql(allExpect);
|
|
@@ -213,9 +217,9 @@ describe("Agent", () => {
|
|
|
213
217
|
// Check the event handler was called with all completions and tool
|
|
214
218
|
// results, in the correct order.
|
|
215
219
|
const allExpect = [
|
|
216
|
-
script[0].message,
|
|
220
|
+
(0, agent_1.completionToAssistantMessageParam)(script[0].message),
|
|
217
221
|
...expectToolResults,
|
|
218
|
-
script[1].message,
|
|
222
|
+
(0, agent_1.completionToAssistantMessageParam)(script[1].message),
|
|
219
223
|
];
|
|
220
224
|
const all = eventHandler.getAll();
|
|
221
225
|
(0, vitest_1.expect)(all).eql(allExpect);
|
|
@@ -264,14 +268,14 @@ describe("Agent", () => {
|
|
|
264
268
|
};
|
|
265
269
|
};
|
|
266
270
|
(0, assert_1.strict)(scriptCopy[0].message.tool_calls);
|
|
267
|
-
const transformedFirstMsg = {
|
|
271
|
+
const transformedFirstMsg = (0, agent_1.completionToAssistantMessageParam)({
|
|
268
272
|
...scriptCopy[0].message,
|
|
269
273
|
tool_calls: scriptCopy[0].message.tool_calls.map(transformToolCall),
|
|
270
|
-
};
|
|
274
|
+
});
|
|
271
275
|
const allExpect = [
|
|
272
276
|
transformedFirstMsg,
|
|
273
277
|
...expectToolResults,
|
|
274
|
-
scriptCopy[1].message,
|
|
278
|
+
(0, agent_1.completionToAssistantMessageParam)(scriptCopy[1].message),
|
|
275
279
|
];
|
|
276
280
|
const all = eventHandler.getAll();
|
|
277
281
|
const conv = agent.getConversation();
|
|
@@ -311,9 +315,9 @@ describe("Agent", () => {
|
|
|
311
315
|
};
|
|
312
316
|
});
|
|
313
317
|
const allExpect = [
|
|
314
|
-
script[0].message,
|
|
318
|
+
(0, agent_1.completionToAssistantMessageParam)(script[0].message),
|
|
315
319
|
...expectToolCallResults,
|
|
316
|
-
script[1].message,
|
|
320
|
+
(0, agent_1.completionToAssistantMessageParam)(script[1].message),
|
|
317
321
|
];
|
|
318
322
|
const all = eventHandler.getAll();
|
|
319
323
|
const conv = agent.getConversation();
|