@xalia/agent 0.6.1 → 0.6.3
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 +109 -57
- package/dist/agent/src/agent/agentUtils.js +24 -26
- 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 +154 -235
- package/dist/agent/src/chat/client/constants.js +1 -2
- package/dist/agent/src/chat/client/sessionClient.js +47 -15
- 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 -1
- package/dist/agent/src/chat/server/chatContextManager.js +19 -16
- package/dist/agent/src/chat/server/connectionManager.js +14 -36
- package/dist/agent/src/chat/server/connectionManager.test.js +3 -16
- package/dist/agent/src/chat/server/conversation.js +73 -44
- package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +398 -233
- package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
- 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 +214 -42
- 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/approvalManager.js +82 -64
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
- package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
- package/dist/agent/src/test/agent.test.js +104 -63
- package/dist/agent/src/test/approvalManager.test.js +79 -35
- package/dist/agent/src/test/chatContextManager.test.js +16 -17
- 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/responseAwaiter.test.js +74 -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 +115 -6
- 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/setup_chat +2 -2
- package/scripts/test_chat +95 -36
- package/src/agent/agent.ts +152 -41
- package/src/agent/agentUtils.ts +34 -41
- 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 +262 -286
- package/src/chat/client/constants.ts +0 -2
- package/src/chat/client/sessionClient.ts +82 -20
- package/src/chat/client/sessionFiles.ts +142 -0
- package/src/chat/data/apiKeyManager.ts +55 -7
- package/src/chat/data/dataModels.ts +17 -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 +136 -25
- package/src/chat/server/chatContextManager.ts +42 -24
- package/src/chat/server/connectionManager.test.ts +2 -22
- package/src/chat/server/connectionManager.ts +18 -53
- package/src/chat/server/conversation.ts +106 -59
- package/src/chat/server/imageGeneratorTools.ts +138 -0
- package/src/chat/server/openSession.ts +606 -325
- package/src/chat/server/openSessionMessageSender.ts +4 -0
- package/src/chat/server/server.ts +5 -11
- package/src/chat/server/sessionFileManager.ts +223 -63
- package/src/chat/server/sessionRegistry.ts +317 -52
- 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/approvalManager.ts +153 -81
- package/src/chat/utils/multiAsyncQueue.ts +11 -1
- package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
- package/src/test/agent.test.ts +152 -75
- package/src/test/approvalManager.test.ts +108 -40
- package/src/test/chatContextManager.test.ts +26 -22
- 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/responseAwaiter.test.ts +103 -0
- package/src/test/testTools.ts +15 -1
- package/src/tool/agentChat.ts +36 -8
- package/src/tool/agentMain.ts +7 -7
- package/src/tool/chatMain.ts +128 -7
- 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
- package/dist/agent/src/test/responseHandler.test.js +0 -61
- package/src/test/responseHandler.test.ts +0 -78
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { strict as assert } from "assert";
|
|
2
3
|
|
|
3
4
|
import { getLogger, SavedAgentProfile } from "@xalia/xmcp/sdk";
|
|
4
5
|
|
|
@@ -6,10 +7,10 @@ import { Connection } from "./connection";
|
|
|
6
7
|
import { SessionClient } from "./sessionClient";
|
|
7
8
|
import { IChatClientEventHandler } from "./interfaces";
|
|
8
9
|
import {
|
|
9
|
-
SessionData,
|
|
10
10
|
TeamInfo,
|
|
11
11
|
TeamParticipant,
|
|
12
12
|
AgentSessionData,
|
|
13
|
+
SessionDescriptor,
|
|
13
14
|
} from "../data/dataModels";
|
|
14
15
|
import { createSessionParticipantMap } from "../data/database";
|
|
15
16
|
import {
|
|
@@ -21,17 +22,34 @@ import {
|
|
|
21
22
|
ServerControlMessage,
|
|
22
23
|
ServerControlSessionDeleted,
|
|
23
24
|
ServerControlTeamCreated,
|
|
25
|
+
ServerControlTeamMembersUpdated,
|
|
24
26
|
ServerToClient,
|
|
25
27
|
ClientToServer,
|
|
26
28
|
ServerControlAgentProfileCreated,
|
|
27
29
|
ServerControlAgentProfileDeleted,
|
|
30
|
+
ClientControlSessionCreate,
|
|
28
31
|
} from "../protocol/messages";
|
|
29
|
-
import {
|
|
32
|
+
import { SESSION_JOIN_TIMEOUT } from "./constants";
|
|
30
33
|
import { ITeamManager } from "./teamManager";
|
|
31
34
|
import { buildAgentSessionMap } from "../utils/agentSessionMap";
|
|
35
|
+
import { ResponseAwaiter } from "../utils/responseAwaiter";
|
|
32
36
|
|
|
33
37
|
const logger = getLogger();
|
|
34
38
|
|
|
39
|
+
/// A fake message which includes an actual client. This avoids a peculiarity
|
|
40
|
+
/// related to handling of async resolves.
|
|
41
|
+
|
|
42
|
+
type CreatedClient = {
|
|
43
|
+
client: SessionClient;
|
|
44
|
+
sessionInfo: ServerSessionInfo;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type JoinCreateResponse = ServerSessionInfo | ServerControlError;
|
|
48
|
+
|
|
49
|
+
function idExtractor(msg: JoinCreateResponse): string | undefined {
|
|
50
|
+
return msg.client_message_id;
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
// client team info, used for session tracking etc.
|
|
36
54
|
export type ClientTeamInfo = {
|
|
37
55
|
team_uuid: string;
|
|
@@ -41,37 +59,54 @@ export type ClientTeamInfo = {
|
|
|
41
59
|
agentSessionMap: Map<string, AgentSessionData>;
|
|
42
60
|
};
|
|
43
61
|
|
|
44
|
-
type SessionJoinOrCreateResolver = {
|
|
45
|
-
resolve: (
|
|
46
|
-
sessionClient: SessionClient,
|
|
47
|
-
sessionInfo: ServerSessionInfo
|
|
48
|
-
) => void;
|
|
49
|
-
reject: (error: Error) => void;
|
|
50
|
-
sessionId: string;
|
|
51
|
-
clientMessageId: string;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
62
|
export class ChatClient implements ITeamManager {
|
|
63
|
+
private connection: Connection<ClientToServer, ServerToClient>;
|
|
64
|
+
private eventHandler: IChatClientEventHandler;
|
|
65
|
+
// agent_uuid -> agent session data
|
|
66
|
+
private userAgentSessionMap: Map<string, AgentSessionData>;
|
|
67
|
+
// team_uuid -> team info
|
|
68
|
+
private teams: Map<string, ClientTeamInfo>;
|
|
69
|
+
// Whether this client is in guest mode
|
|
70
|
+
private isGuestMode: boolean;
|
|
71
|
+
private closed: boolean;
|
|
72
|
+
private currentSessionId: string | undefined;
|
|
73
|
+
// note: currentTeamId will not be reset when swtiching to a user session.
|
|
74
|
+
private currentTeamId: string | undefined;
|
|
75
|
+
// session_uuid -> session client
|
|
76
|
+
private activeSessions: Map<string, SessionClient>;
|
|
77
|
+
// session_uuid -> true if there is a new message.
|
|
78
|
+
private newMessage: Map<string, boolean>;
|
|
79
|
+
// Track pending session join requests.
|
|
80
|
+
private sessionJoinAwaiter: ResponseAwaiter<
|
|
81
|
+
JoinCreateResponse,
|
|
82
|
+
CreatedClient
|
|
83
|
+
>;
|
|
84
|
+
|
|
55
85
|
private constructor(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
connection: Connection<ClientToServer, ServerToClient>,
|
|
87
|
+
eventHandler: IChatClientEventHandler,
|
|
88
|
+
userAgentSessionMap: Map<string, AgentSessionData>,
|
|
89
|
+
teams: Map<string, ClientTeamInfo>,
|
|
90
|
+
isGuestMode: boolean
|
|
91
|
+
) {
|
|
92
|
+
this.connection = connection;
|
|
93
|
+
this.eventHandler = eventHandler;
|
|
94
|
+
this.userAgentSessionMap = userAgentSessionMap;
|
|
95
|
+
this.teams = teams;
|
|
96
|
+
this.isGuestMode = isGuestMode;
|
|
97
|
+
|
|
98
|
+
this.closed = false;
|
|
99
|
+
this.currentSessionId = undefined;
|
|
100
|
+
this.currentTeamId = undefined;
|
|
101
|
+
this.activeSessions = new Map();
|
|
102
|
+
this.newMessage = new Map();
|
|
103
|
+
this.sessionJoinAwaiter = ResponseAwaiter.initWithImmediate(
|
|
104
|
+
"control_error",
|
|
105
|
+
idExtractor,
|
|
106
|
+
this.onJoinCreateResponse.bind(this),
|
|
107
|
+
Math.max(SESSION_JOIN_TIMEOUT)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
75
110
|
|
|
76
111
|
/**
|
|
77
112
|
* Connect to server and create ChatClient instance
|
|
@@ -81,6 +116,9 @@ export class ChatClient implements ITeamManager {
|
|
|
81
116
|
token: string,
|
|
82
117
|
eventHandler: IChatClientEventHandler
|
|
83
118
|
): Promise<ChatClient> {
|
|
119
|
+
// Determine if this is a guest token (format: guest_<hash>_<session-id>)
|
|
120
|
+
const isGuestMode = token.startsWith("guest_");
|
|
121
|
+
|
|
84
122
|
const connection = new Connection<ClientToServer, ServerToClient>({
|
|
85
123
|
url,
|
|
86
124
|
token,
|
|
@@ -92,7 +130,7 @@ export class ChatClient implements ITeamManager {
|
|
|
92
130
|
// Register session_info handler for initialization
|
|
93
131
|
connection.on("control_session_list", (msg) => {
|
|
94
132
|
// get user sessions, user agents, and team sessions
|
|
95
|
-
const userSessions = new Map<string,
|
|
133
|
+
const userSessions = new Map<string, SessionDescriptor>();
|
|
96
134
|
msg.user_sessions.forEach((session) => {
|
|
97
135
|
userSessions.set(session.session_uuid, session);
|
|
98
136
|
});
|
|
@@ -127,7 +165,8 @@ export class ChatClient implements ITeamManager {
|
|
|
127
165
|
connection,
|
|
128
166
|
eventHandler,
|
|
129
167
|
userAgentSessionMap,
|
|
130
|
-
teams
|
|
168
|
+
teams,
|
|
169
|
+
isGuestMode
|
|
131
170
|
);
|
|
132
171
|
resolveClient(client);
|
|
133
172
|
} else {
|
|
@@ -291,7 +330,6 @@ export class ChatClient implements ITeamManager {
|
|
|
291
330
|
this.teams.clear();
|
|
292
331
|
this.currentSessionId = undefined;
|
|
293
332
|
this.currentTeamId = undefined;
|
|
294
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
public async createNewSession(
|
|
@@ -303,74 +341,24 @@ export class ChatClient implements ITeamManager {
|
|
|
303
341
|
throw new Error("ChatClient is closed");
|
|
304
342
|
}
|
|
305
343
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`profile id ${JSON.stringify(agentProfileId)}`
|
|
310
|
-
);
|
|
311
|
-
throw new Error("Session join/create is already in progress");
|
|
312
|
-
}
|
|
344
|
+
const clientMessageId = uuidv4();
|
|
345
|
+
const createdSessionP =
|
|
346
|
+
this.sessionJoinAwaiter.waitForResponse(clientMessageId);
|
|
313
347
|
|
|
314
|
-
|
|
315
|
-
|
|
348
|
+
const createMsg: ClientControlSessionCreate = {
|
|
349
|
+
type: "control_session_create",
|
|
350
|
+
client_message_id: clientMessageId,
|
|
351
|
+
title: newTitle,
|
|
352
|
+
agent_profile_id: agentProfileId,
|
|
353
|
+
team_id: teamId,
|
|
354
|
+
};
|
|
355
|
+
this.connection.send(createMsg);
|
|
356
|
+
logger.info(`[ChatClient] Sent ${JSON.stringify(createMsg)}`);
|
|
316
357
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}, SESSION_CREATE_TIMEOUT);
|
|
322
|
-
|
|
323
|
-
// Track this pending request
|
|
324
|
-
this.sessionJoinOrCreateRes = {
|
|
325
|
-
resolve: (
|
|
326
|
-
sessionClient: SessionClient,
|
|
327
|
-
sessionInfo: ServerSessionInfo
|
|
328
|
-
) => {
|
|
329
|
-
const sessionId = this.sessionJoinOrCreateRes?.sessionId;
|
|
330
|
-
if (!sessionId) {
|
|
331
|
-
throw new Error("Session id is not set in resolving.");
|
|
332
|
-
}
|
|
333
|
-
if (sessionId !== sessionInfo.session_id) {
|
|
334
|
-
throw new Error("SessionInfo id mismatch");
|
|
335
|
-
}
|
|
336
|
-
clearTimeout(timeoutId);
|
|
337
|
-
this.activeSessions.set(sessionId, sessionClient);
|
|
338
|
-
this.currentSessionId = sessionId;
|
|
339
|
-
if (sessionInfo.team_uuid) {
|
|
340
|
-
this.currentTeamId = sessionInfo.team_uuid;
|
|
341
|
-
}
|
|
342
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
343
|
-
this.addSessionToAgentSessionMap(sessionInfo);
|
|
344
|
-
resolve(sessionClient);
|
|
345
|
-
},
|
|
346
|
-
reject: (error: Error) => {
|
|
347
|
-
clearTimeout(timeoutId);
|
|
348
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
349
|
-
reject(error);
|
|
350
|
-
},
|
|
351
|
-
sessionId: "",
|
|
352
|
-
clientMessageId,
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
// Send session_create message
|
|
356
|
-
try {
|
|
357
|
-
this.connection.send({
|
|
358
|
-
type: "control_session_create",
|
|
359
|
-
client_message_id: clientMessageId,
|
|
360
|
-
title: newTitle,
|
|
361
|
-
agent_profile_id: agentProfileId,
|
|
362
|
-
team_id: teamId,
|
|
363
|
-
});
|
|
364
|
-
logger.info(
|
|
365
|
-
`[ChatClient] Sent session_create for session ${newTitle}` +
|
|
366
|
-
` client message id: ${clientMessageId}`
|
|
367
|
-
);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
370
|
-
clearTimeout(timeoutId);
|
|
371
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
372
|
-
}
|
|
373
|
-
});
|
|
358
|
+
const createdSession = await createdSessionP;
|
|
359
|
+
const sessionId = createdSession.client.getSessionUUID();
|
|
360
|
+
logger.info(`[ChatClient] joined session ${sessionId}`);
|
|
361
|
+
return createdSession.client;
|
|
374
362
|
}
|
|
375
363
|
|
|
376
364
|
/**
|
|
@@ -382,66 +370,26 @@ export class ChatClient implements ITeamManager {
|
|
|
382
370
|
throw new Error("ChatClient is closed");
|
|
383
371
|
}
|
|
384
372
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// TODO: we can directly return a session client if we cache conversation.
|
|
391
|
-
// For now, we go through the join process.
|
|
392
|
-
return new Promise((resolve, reject) => {
|
|
393
|
-
const clientMessageId = uuidv4();
|
|
373
|
+
const clientMessageId = uuidv4();
|
|
374
|
+
const createdSessionP =
|
|
375
|
+
this.sessionJoinAwaiter.waitForResponse(clientMessageId);
|
|
394
376
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}, SESSION_JOIN_TIMEOUT);
|
|
400
|
-
|
|
401
|
-
// Track this pending request
|
|
402
|
-
this.sessionJoinOrCreateRes = {
|
|
403
|
-
resolve: (
|
|
404
|
-
sessionClient: SessionClient,
|
|
405
|
-
sessionInfo: ServerSessionInfo
|
|
406
|
-
) => {
|
|
407
|
-
clearTimeout(timeoutId);
|
|
408
|
-
this.activeSessions.set(sessionId, sessionClient);
|
|
409
|
-
this.currentSessionId = sessionId;
|
|
410
|
-
if (sessionInfo.team_uuid) {
|
|
411
|
-
this.currentTeamId = sessionInfo.team_uuid;
|
|
412
|
-
}
|
|
413
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
414
|
-
this.updateSessionInfo(sessionInfo);
|
|
415
|
-
logger.info(`[ChatClient] joined session ${sessionId}`);
|
|
416
|
-
resolve(sessionClient);
|
|
417
|
-
},
|
|
418
|
-
reject: (error: Error) => {
|
|
419
|
-
clearTimeout(timeoutId);
|
|
420
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
421
|
-
logger.error(
|
|
422
|
-
`[ChatClient] failed to join session` +
|
|
423
|
-
` ${sessionId}: ${error.message}`
|
|
424
|
-
);
|
|
425
|
-
reject(error);
|
|
426
|
-
},
|
|
427
|
-
sessionId,
|
|
428
|
-
clientMessageId,
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Send session_join message
|
|
432
|
-
try {
|
|
433
|
-
this.connection.send({
|
|
434
|
-
type: "control_session_join",
|
|
435
|
-
client_message_id: clientMessageId,
|
|
436
|
-
target_session_id: sessionId,
|
|
437
|
-
});
|
|
438
|
-
logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
|
|
439
|
-
} catch (error) {
|
|
440
|
-
this.sessionJoinOrCreateRes = undefined;
|
|
441
|
-
clearTimeout(timeoutId);
|
|
442
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
443
|
-
}
|
|
377
|
+
this.connection.send({
|
|
378
|
+
type: "control_session_join",
|
|
379
|
+
client_message_id: clientMessageId,
|
|
380
|
+
target_session_id: sessionId,
|
|
444
381
|
});
|
|
382
|
+
logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
|
|
383
|
+
|
|
384
|
+
// Await the response
|
|
385
|
+
|
|
386
|
+
const createdSession = await createdSessionP;
|
|
387
|
+
const newSessionId = createdSession.client.getSessionUUID();
|
|
388
|
+
if (newSessionId !== sessionId) {
|
|
389
|
+
throw new Error(`unexpected session id ${newSessionId}`);
|
|
390
|
+
}
|
|
391
|
+
logger.info(`[ChatClient] joined session ${sessionId}`);
|
|
392
|
+
return createdSession.client;
|
|
445
393
|
}
|
|
446
394
|
|
|
447
395
|
/**
|
|
@@ -476,6 +424,27 @@ export class ChatClient implements ITeamManager {
|
|
|
476
424
|
);
|
|
477
425
|
}
|
|
478
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Delete an agent profile by sending control_agent_profile_delete message
|
|
429
|
+
* @param agentProfileUuid - The UUID of the agent profile to delete
|
|
430
|
+
* @returns void
|
|
431
|
+
*/
|
|
432
|
+
deleteAgentProfile(agentProfileUuid: string): void {
|
|
433
|
+
if (this.closed) {
|
|
434
|
+
throw new Error("ChatClient is closed");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
this.connection.send({
|
|
438
|
+
type: "control_agent_profile_delete",
|
|
439
|
+
agent_profile_uuid: agentProfileUuid,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
logger.debug(
|
|
443
|
+
`[ChatClient] Sent control_agent_profile_delete for profile ` +
|
|
444
|
+
agentProfileUuid
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
479
448
|
/**
|
|
480
449
|
* Create a new team with initial members
|
|
481
450
|
* @param teamName
|
|
@@ -644,6 +613,9 @@ export class ChatClient implements ITeamManager {
|
|
|
644
613
|
case "control_team_created":
|
|
645
614
|
this.handleTeamCreatedMessage(msg);
|
|
646
615
|
break;
|
|
616
|
+
case "control_team_members_updated":
|
|
617
|
+
this.handleTeamMembersUpdated(msg);
|
|
618
|
+
break;
|
|
647
619
|
default: {
|
|
648
620
|
const _exhaustive: never = msg;
|
|
649
621
|
throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
|
|
@@ -676,8 +648,36 @@ export class ChatClient implements ITeamManager {
|
|
|
676
648
|
void this.eventHandler.onMessage(msg, this);
|
|
677
649
|
}
|
|
678
650
|
|
|
679
|
-
private handleAgentProfileDeleted(
|
|
680
|
-
|
|
651
|
+
private handleAgentProfileDeleted(msg: ServerControlAgentProfileDeleted) {
|
|
652
|
+
logger.debug(
|
|
653
|
+
`[ChatClient.handleAgentProfileDeleted] msg: ${JSON.stringify(msg)}`
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
const profileUuid = msg.profile_uuid;
|
|
657
|
+
let found = false;
|
|
658
|
+
|
|
659
|
+
// Try to remove from user agent session map
|
|
660
|
+
if (this.userAgentSessionMap.has(profileUuid)) {
|
|
661
|
+
this.userAgentSessionMap.delete(profileUuid);
|
|
662
|
+
found = true;
|
|
663
|
+
} else {
|
|
664
|
+
// Try to remove from team agent session maps
|
|
665
|
+
for (const [_teamId, team] of this.teams.entries()) {
|
|
666
|
+
if (team.agentSessionMap.has(profileUuid)) {
|
|
667
|
+
team.agentSessionMap.delete(profileUuid);
|
|
668
|
+
found = true;
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!found) {
|
|
675
|
+
logger.warn(
|
|
676
|
+
`[ChatClient] Agent profile ${profileUuid} not found in any session map`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
void this.eventHandler.onMessage(msg, this);
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
private handleSessionMessage(msg: ServerSessionScopedMessage): void {
|
|
@@ -725,119 +725,71 @@ export class ChatClient implements ITeamManager {
|
|
|
725
725
|
return;
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
+
private handleTeamMembersUpdated(msg: ServerControlTeamMembersUpdated): void {
|
|
729
|
+
const team = this.teams.get(msg.team_uuid);
|
|
730
|
+
if (!team) {
|
|
731
|
+
logger.warn(
|
|
732
|
+
`[ChatClient] Received team_members_updated for unknown team ` +
|
|
733
|
+
msg.team_uuid
|
|
734
|
+
);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Update the participants map
|
|
739
|
+
team.participants = new Map(
|
|
740
|
+
msg.members.map((participant) => [participant.user_uuid, participant])
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
logger.info(
|
|
744
|
+
`[ChatClient] Updated team members for team ${msg.team_uuid}, ` +
|
|
745
|
+
`now has ${String(msg.members.length)} members`
|
|
746
|
+
);
|
|
747
|
+
void this.eventHandler.onMessage(msg, this);
|
|
748
|
+
}
|
|
749
|
+
|
|
728
750
|
/**
|
|
729
751
|
* Handle session_info message which can be for:
|
|
730
752
|
* 1. A pending session join/create request
|
|
731
753
|
* 2. An update to an existing active session
|
|
754
|
+
* 3. A new session notification
|
|
732
755
|
* We update the session list here as well.
|
|
733
756
|
*/
|
|
734
757
|
private handleSessionInfoMessage(msg: ServerSessionInfo): void {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
|
|
741
|
-
) {
|
|
742
|
-
const { resolve, reject } = this.sessionJoinOrCreateRes;
|
|
743
|
-
|
|
744
|
-
try {
|
|
745
|
-
logger.info(
|
|
746
|
-
`[ChatClient] Creating SessionClient for session ${sessionId}`
|
|
747
|
-
);
|
|
748
|
-
logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
|
|
749
|
-
const sessionClient = new SessionClient(
|
|
750
|
-
sessionId,
|
|
751
|
-
msg.saved_agent_profile,
|
|
752
|
-
this.connection,
|
|
753
|
-
msg.mcp_server_briefs,
|
|
754
|
-
createSessionParticipantMap(msg.participants)
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
// we need to pass the session id if this is a new session
|
|
758
|
-
if (this.sessionJoinOrCreateRes.sessionId === "") {
|
|
759
|
-
this.sessionJoinOrCreateRes.sessionId = sessionId;
|
|
760
|
-
} else if (this.sessionJoinOrCreateRes.sessionId !== sessionId) {
|
|
761
|
-
throw new Error(
|
|
762
|
-
`[ChatClient] session id mismatch: ` +
|
|
763
|
-
`${this.sessionJoinOrCreateRes.sessionId} !== ${sessionId}`
|
|
764
|
-
);
|
|
765
|
-
}
|
|
758
|
+
// We intercept messages for th Awaiter here so that we can create and
|
|
759
|
+
// register the client immediately, before other messages for this session
|
|
760
|
+
// are handled. Relying on Awaiter alone (currently) schedules the resolv
|
|
761
|
+
// to be called later (potentially AFTER further messages for the session
|
|
762
|
+
// have been received).
|
|
766
763
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const errorMsg =
|
|
770
|
-
error instanceof Error ? error : new Error(String(error));
|
|
771
|
-
logger.error(
|
|
772
|
-
`[ChatClient] Failed to create SessionClient: ${errorMsg.message}`
|
|
773
|
-
);
|
|
774
|
-
reject(errorMsg);
|
|
775
|
-
}
|
|
776
|
-
} else {
|
|
777
|
-
this.updateSessionInfo(msg);
|
|
764
|
+
if (this.sessionJoinAwaiter.onMessage(msg)) {
|
|
765
|
+
return;
|
|
778
766
|
}
|
|
767
|
+
|
|
768
|
+
logger.debug(
|
|
769
|
+
`[ChatClient.handleSessionInfoMessage] not handled by awaiter - updating`
|
|
770
|
+
);
|
|
771
|
+
this.upsertSessionToAgentSessionMap(msg);
|
|
779
772
|
}
|
|
780
773
|
|
|
781
774
|
handleControlError(msg: ServerControlError): void {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
this.sessionJoinOrCreateRes &&
|
|
785
|
-
msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
|
|
786
|
-
) {
|
|
787
|
-
this.sessionJoinOrCreateRes.reject(new Error(msg.message));
|
|
775
|
+
if (this.sessionJoinAwaiter.onMessage(msg)) {
|
|
776
|
+
return;
|
|
788
777
|
}
|
|
789
|
-
this.eventHandler.onError(`Server error: ${msg.message}`);
|
|
790
|
-
}
|
|
791
778
|
|
|
792
|
-
|
|
793
|
-
* Update the session list. This is called when a session is updated.
|
|
794
|
-
* An error is thrown if the session id is not found.
|
|
795
|
-
* TODO: we might want to resync session map, for now, throw error to
|
|
796
|
-
* expose problems in the code.
|
|
797
|
-
*/
|
|
798
|
-
private updateAgentSessionMap(sessionInfo: ServerSessionInfo): void {
|
|
799
|
-
const sessionId = sessionInfo.session_id;
|
|
800
|
-
if (sessionInfo.team_uuid) {
|
|
801
|
-
const teamInfo = this.teams.get(sessionInfo.team_uuid);
|
|
802
|
-
if (!teamInfo) {
|
|
803
|
-
throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
|
|
804
|
-
}
|
|
805
|
-
//teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
|
|
806
|
-
const agentSessionMap = teamInfo.agentSessionMap;
|
|
807
|
-
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
808
|
-
throw new Error(
|
|
809
|
-
`[updateAgentSessionMap] team session ${sessionId}` +
|
|
810
|
-
` not found in agent session map`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
} else {
|
|
814
|
-
const agentSessionMap = this.userAgentSessionMap;
|
|
815
|
-
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
816
|
-
throw new Error(
|
|
817
|
-
`[updateAgentSessionMap] user session ${sessionId}` +
|
|
818
|
-
` not found in agent session map`
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
779
|
+
this.eventHandler.onError(`Server error: ${msg.message}`);
|
|
822
780
|
}
|
|
823
781
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
if (sessionClient) {
|
|
834
|
-
sessionClient.updateSessionInfo(msg);
|
|
835
|
-
} else {
|
|
836
|
-
throw new Error(`Session client not found for session ${sessionId}`);
|
|
782
|
+
private upsertSessionToAgentSessionMap(msg: ServerSessionInfo): void {
|
|
783
|
+
// Skip agent session map updates for guest sessions since they don't
|
|
784
|
+
// need session organization
|
|
785
|
+
if (this.isGuestMode) {
|
|
786
|
+
logger.info(
|
|
787
|
+
`[ChatClient] Skipping agent session map add for ` +
|
|
788
|
+
`guest session ${msg.session_id}`
|
|
789
|
+
);
|
|
790
|
+
return;
|
|
837
791
|
}
|
|
838
|
-
}
|
|
839
792
|
|
|
840
|
-
private addSessionToAgentSessionMap(msg: ServerSessionInfo): void {
|
|
841
793
|
// get the correct agent session map
|
|
842
794
|
const agentSessionMap = msg.team_uuid
|
|
843
795
|
? this.teams.get(msg.team_uuid)?.agentSessionMap
|
|
@@ -854,47 +806,71 @@ export class ChatClient implements ITeamManager {
|
|
|
854
806
|
` not found in agent session map`
|
|
855
807
|
);
|
|
856
808
|
}
|
|
857
|
-
|
|
809
|
+
|
|
810
|
+
// Check if session already exists
|
|
811
|
+
const existingSessionIndex = agentSession.sessions.findIndex(
|
|
812
|
+
(session) => session.session_uuid === msg.session_id
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
if (existingSessionIndex !== -1) {
|
|
816
|
+
// Update existing session
|
|
817
|
+
agentSession.sessions[existingSessionIndex] =
|
|
818
|
+
sessionInfoToSessionDescriptor(msg);
|
|
819
|
+
logger.info(
|
|
820
|
+
`[ChatClient] Updated existing session ${msg.session_id} in ` +
|
|
821
|
+
`agent session map`
|
|
822
|
+
);
|
|
823
|
+
} else {
|
|
824
|
+
// Add new session
|
|
825
|
+
agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
|
|
826
|
+
logger.info(
|
|
827
|
+
`[ChatClient] Added new session ${msg.session_id} to agent session map`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
858
831
|
agentSession.updated_at = new Date(msg.updated_at).getTime();
|
|
859
832
|
}
|
|
833
|
+
|
|
834
|
+
// This is the immediate callback of the ResponseAwaiter for session
|
|
835
|
+
// joining. It must create and register the client.
|
|
836
|
+
private onJoinCreateResponse(msg: JoinCreateResponse): CreatedClient {
|
|
837
|
+
// Errors should not get through here.
|
|
838
|
+
assert(msg.type === "session_info");
|
|
839
|
+
|
|
840
|
+
const sessionId = msg.session_id;
|
|
841
|
+
const client = new SessionClient(
|
|
842
|
+
sessionId,
|
|
843
|
+
msg.saved_agent_profile,
|
|
844
|
+
this.connection,
|
|
845
|
+
msg.mcp_server_briefs,
|
|
846
|
+
createSessionParticipantMap(msg.participants),
|
|
847
|
+
msg.agent_paused
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
this.activeSessions.set(sessionId, client);
|
|
851
|
+
this.currentSessionId = sessionId;
|
|
852
|
+
if (msg.team_uuid) {
|
|
853
|
+
this.currentTeamId = msg.team_uuid;
|
|
854
|
+
}
|
|
855
|
+
this.upsertSessionToAgentSessionMap(msg);
|
|
856
|
+
|
|
857
|
+
return {
|
|
858
|
+
client,
|
|
859
|
+
sessionInfo: msg,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
860
862
|
}
|
|
861
863
|
|
|
862
|
-
function
|
|
864
|
+
function sessionInfoToSessionDescriptor(
|
|
865
|
+
msg: ServerSessionInfo
|
|
866
|
+
): SessionDescriptor {
|
|
863
867
|
return {
|
|
864
868
|
session_uuid: msg.session_id,
|
|
865
869
|
title: msg.title,
|
|
866
870
|
team_uuid: msg.team_uuid,
|
|
867
871
|
agent_profile_uuid: msg.saved_agent_profile.uuid,
|
|
868
|
-
workspace: msg.workspace,
|
|
869
872
|
updated_at: msg.updated_at,
|
|
870
873
|
user_uuid: msg.owner_uuid,
|
|
874
|
+
agent_paused: msg.agent_paused,
|
|
871
875
|
};
|
|
872
876
|
}
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* Update the agent session map
|
|
876
|
-
* @param agentSessionMap - the agent session map
|
|
877
|
-
* @param sessionInfo - the session info
|
|
878
|
-
* @returns true if the session is updated, false otherwise
|
|
879
|
-
*/
|
|
880
|
-
function doUpdateAgentSessionMap(
|
|
881
|
-
agentSessionMap: Map<string, AgentSessionData>,
|
|
882
|
-
sessionInfo: ServerSessionInfo
|
|
883
|
-
): boolean {
|
|
884
|
-
const sessionId = sessionInfo.session_id;
|
|
885
|
-
const agentUuid = sessionInfo.saved_agent_profile.uuid;
|
|
886
|
-
const agent = agentSessionMap.get(agentUuid);
|
|
887
|
-
if (!agent) {
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
let updated = false;
|
|
891
|
-
agent.sessions.map((session) => {
|
|
892
|
-
if (session.session_uuid === sessionId) {
|
|
893
|
-
updated = true;
|
|
894
|
-
return sessionInfoToSessionData(sessionInfo);
|
|
895
|
-
} else {
|
|
896
|
-
return session;
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
return updated;
|
|
900
|
-
}
|