@xalia/agent 0.6.9 → 0.6.11
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 +11 -0
- package/dist/agent/src/agent/agent.js +77 -18
- package/dist/agent/src/agent/agentUtils.js +3 -2
- package/dist/agent/src/agent/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -19
- package/dist/agent/src/agent/llm.js +1 -1
- package/dist/agent/src/agent/openAILLM.js +15 -12
- package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/tokenCounter.js +390 -0
- package/dist/agent/src/agent/tokenCounter.test.js +206 -0
- package/dist/agent/src/agent/toolSettings.js +17 -0
- package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
- package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
- package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
- package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
- package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
- package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
- package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
- package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
- package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
- package/dist/agent/src/agent/tools/index.js +64 -0
- package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
- package/dist/agent/src/agent/tools/renderTool.js +89 -0
- package/dist/agent/src/agent/tools/utils.js +61 -0
- package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
- package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
- package/dist/agent/src/chat/client/chatClient.js +28 -0
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -2
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/protocol/messages.js +5 -0
- package/dist/agent/src/chat/server/chatContextManager.js +45 -25
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
- package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
- package/dist/agent/src/chat/server/openSession.js +218 -55
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +5 -1
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
- package/dist/agent/src/chat/server/titleGenerator.js +112 -0
- package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
- package/dist/agent/src/chat/server/tools.js +63 -287
- package/dist/agent/src/chat/utils/approvalManager.js +6 -3
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
- package/dist/agent/src/test/agent.test.js +16 -17
- package/dist/agent/src/test/chatContextManager.test.js +15 -3
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
- package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
- package/dist/agent/src/test/testTools.js +6 -1
- package/dist/agent/src/test/tools.test.js +27 -9
- package/dist/agent/src/tool/agentChat.js +5 -2
- package/dist/agent/src/tool/chatMain.js +34 -7
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +8 -2
- package/.env.development +0 -1
- package/.prettierrc.json +0 -11
- package/dist/agent/src/agent/tools.js +0 -44
- package/eslint.config.mjs +0 -38
- package/scripts/chat_server +0 -8
- package/scripts/git_message +0 -31
- package/scripts/git_wip +0 -21
- package/scripts/pr_message +0 -18
- package/scripts/pr_review +0 -16
- package/scripts/setup_chat +0 -90
- package/scripts/shutdown_chat_server +0 -42
- package/scripts/start_chat_server +0 -24
- package/scripts/sudomcp_import +0 -23
- package/scripts/test_chat +0 -308
- package/src/agent/agent.ts +0 -624
- package/src/agent/agentUtils.ts +0 -285
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/dummyLLM.ts +0 -126
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -97
- package/src/agent/imageGenerator.ts +0 -45
- package/src/agent/iplatform.ts +0 -18
- package/src/agent/llm.ts +0 -74
- package/src/agent/mcpServerManager.ts +0 -541
- package/src/agent/nullAgentEventHandler.ts +0 -26
- package/src/agent/nullPlatform.ts +0 -13
- package/src/agent/openAI.ts +0 -123
- package/src/agent/openAILLM.ts +0 -95
- package/src/agent/openAILLMStreaming.ts +0 -609
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -50
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tools.ts +0 -57
- package/src/chat/client/chatClient.ts +0 -922
- package/src/chat/client/connection.test.ts +0 -241
- package/src/chat/client/connection.ts +0 -286
- package/src/chat/client/constants.ts +0 -1
- package/src/chat/client/index.ts +0 -18
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -537
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -101
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFileModels.ts +0 -113
- package/src/chat/data/dbSessionFiles.ts +0 -99
- package/src/chat/data/dbSessionMessages.ts +0 -102
- package/src/chat/data/mimeTypes.ts +0 -58
- package/src/chat/protocol/connectionMessages.ts +0 -49
- package/src/chat/protocol/constants.ts +0 -55
- package/src/chat/protocol/errors.ts +0 -16
- package/src/chat/protocol/messages.ts +0 -846
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -639
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -316
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -160
- package/src/chat/server/openAIRouterLLM.ts +0 -171
- package/src/chat/server/openSession.ts +0 -1689
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/server.ts +0 -175
- package/src/chat/server/sessionFileManager.ts +0 -422
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1425
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/tools.ts +0 -397
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -183
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/htmlToText.ts +0 -61
- package/src/chat/utils/multiAsyncQueue.ts +0 -62
- package/src/chat/utils/responseAwaiter.ts +0 -181
- package/src/chat/utils/search.ts +0 -139
- package/src/chat/utils/userResolver.ts +0 -48
- package/src/chat/utils/websocket.ts +0 -16
- package/src/index.ts +0 -0
- package/src/test/agent.test.ts +0 -590
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -527
- package/src/test/clientServerConnection.test.ts +0 -205
- package/src/test/compressingContextManager.test.ts +0 -77
- package/src/test/context.test.ts +0 -150
- package/src/test/contextTestTools.ts +0 -95
- package/src/test/conversation.test.ts +0 -109
- package/src/test/db.test.ts +0 -363
- package/src/test/dbMcpServerConfigs.test.ts +0 -112
- package/src/test/dbSessionFiles.test.ts +0 -258
- package/src/test/dbSessionMessages.test.ts +0 -85
- package/src/test/dbTestTools.ts +0 -157
- package/src/test/imageLoad.test.ts +0 -15
- package/src/test/mcpServerManager.test.ts +0 -114
- package/src/test/multiAsyncQueue.test.ts +0 -183
- package/src/test/openaiStreaming.test.ts +0 -177
- package/src/test/prompt.test.ts +0 -27
- package/src/test/promptProvider.test.ts +0 -33
- package/src/test/responseAwaiter.test.ts +0 -103
- package/src/test/sudoMcpServerManager.test.ts +0 -63
- package/src/test/testTools.ts +0 -171
- package/src/test/tools.test.ts +0 -39
- package/src/tool/agentChat.ts +0 -194
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -594
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -84
- package/src/tool/main.ts +0 -25
- package/src/tool/nodePlatform.ts +0 -73
- package/src/tool/options.ts +0 -144
- package/src/tool/prompt.ts +0 -101
- package/test_data/background_test_profile.json +0 -6
- package/test_data/background_test_script.json +0 -11
- package/test_data/dummyllm_script_crash.json +0 -32
- package/test_data/dummyllm_script_image_gen.json +0 -19
- package/test_data/dummyllm_script_image_gen_fe.json +0 -29
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
- package/test_data/dummyllm_script_render_tool.json +0 -29
- package/test_data/dummyllm_script_simplecalc.json +0 -28
- package/test_data/dummyllm_script_test_auto_approve.json +0 -81
- package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
- package/test_data/frog.png +0 -0
- package/test_data/frog.png.b64 +0 -1
- package/test_data/git_message_profile.json +0 -4
- package/test_data/git_wip_system.txt +0 -5
- package/test_data/image_gen_test_profile.json +0 -5
- package/test_data/pr_message_profile.json +0 -4
- package/test_data/pr_review_profile.json +0 -4
- package/test_data/prompt_simplecalc.txt +0 -1
- package/test_data/simplecalc_profile.json +0 -4
- package/test_data/sudomcp_import_profile.json +0 -4
- package/test_data/test_script_profile.json +0 -8
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -39
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
|
@@ -1,1425 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
AgentProfile,
|
|
5
|
-
getLogger,
|
|
6
|
-
SavedAgentProfile,
|
|
7
|
-
DEFAULT_AGENT_PROFILE_NAME,
|
|
8
|
-
DEFAULT_AGENT_PROFILE,
|
|
9
|
-
} from "@xalia/xmcp/sdk";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
ClientSessionMessage,
|
|
13
|
-
ClientToServer,
|
|
14
|
-
ServerControlSessionList,
|
|
15
|
-
ClientControlMessage,
|
|
16
|
-
isClientControlMessage,
|
|
17
|
-
ClientControlGetSessionList,
|
|
18
|
-
ClientControlSessionCreate,
|
|
19
|
-
ClientControlSessionJoin,
|
|
20
|
-
ServerControlError,
|
|
21
|
-
ClientControlSessionDelete,
|
|
22
|
-
ServerControlSessionDeleted,
|
|
23
|
-
ClientControlTeamCreate,
|
|
24
|
-
ServerControlTeamCreated,
|
|
25
|
-
ClientControlAddTeamUser,
|
|
26
|
-
ClientControlRemoveTeamUser,
|
|
27
|
-
ServerToClient,
|
|
28
|
-
ClientControlAgentProfileCreate,
|
|
29
|
-
ClientControlAgentProfileDelete,
|
|
30
|
-
ClientControlAddCustomMcpServer,
|
|
31
|
-
ClientControlRemoveCustomMcpServer,
|
|
32
|
-
} from "../protocol/messages";
|
|
33
|
-
import { GUEST_TOKEN_PREFIX, OpenSession } from "./openSession";
|
|
34
|
-
import {
|
|
35
|
-
CustomMcpServerDescriptor,
|
|
36
|
-
SessionCreateData,
|
|
37
|
-
SessionData,
|
|
38
|
-
SessionDescriptor,
|
|
39
|
-
TeamInfo,
|
|
40
|
-
TeamParticipant,
|
|
41
|
-
} from "../data/dataModels";
|
|
42
|
-
import { Database, UserData } from "../data/database";
|
|
43
|
-
import { resolveUserIdentifier } from "../utils/userResolver";
|
|
44
|
-
import { ChatFatalError } from "../protocol/errors";
|
|
45
|
-
import { IMessageProcessor, IUserConnectionManager } from "./connectionManager";
|
|
46
|
-
import { getErrorString } from "./errorUtils";
|
|
47
|
-
import { ApiKeyManager } from "../data/apiKeyManager";
|
|
48
|
-
|
|
49
|
-
const logger = getLogger();
|
|
50
|
-
|
|
51
|
-
export type GuestUser = UserData & { guest_for_session: string };
|
|
52
|
-
|
|
53
|
-
// server_name => url
|
|
54
|
-
type CustomMcpServersForUser = Map<string, CustomMcpServerDescriptor>;
|
|
55
|
-
|
|
56
|
-
export class CustomMcpServerManager {
|
|
57
|
-
private perUser: Map<string, CustomMcpServersForUser>;
|
|
58
|
-
|
|
59
|
-
constructor(_db: Database) {
|
|
60
|
-
this.perUser = new Map();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
getForUser(user_uuid: string): Record<string, CustomMcpServerDescriptor> {
|
|
64
|
-
const servers = this.perUser.get(user_uuid);
|
|
65
|
-
if (!servers) {
|
|
66
|
-
return {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return Object.fromEntries(servers.entries());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async addForUser(
|
|
73
|
-
user_uuid: string,
|
|
74
|
-
server_name: string,
|
|
75
|
-
description: string,
|
|
76
|
-
url: string
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
// TODO: persistence to DB
|
|
79
|
-
|
|
80
|
-
const userServers = this.getUserServers(user_uuid);
|
|
81
|
-
if (userServers.has(server_name)) {
|
|
82
|
-
throw new Error(`server ${server_name} already added`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
userServers.set(server_name, { name: server_name, description, url });
|
|
86
|
-
|
|
87
|
-
return Promise.resolve();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async removeForUser(user_uuid: string, server_name: string): Promise<void> {
|
|
91
|
-
const userServers = this.getUserServers(user_uuid);
|
|
92
|
-
if (userServers.has(server_name)) {
|
|
93
|
-
userServers.delete(server_name);
|
|
94
|
-
}
|
|
95
|
-
return Promise.resolve();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private getUserServers(user_uuid: string): CustomMcpServersForUser {
|
|
99
|
-
let userServers = this.perUser.get(user_uuid);
|
|
100
|
-
if (!userServers) {
|
|
101
|
-
userServers = new Map();
|
|
102
|
-
this.perUser.set(user_uuid, userServers);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return userServers;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export class SessionRegistry implements IMessageProcessor<ClientToServer> {
|
|
110
|
-
// In memory session-user/user-session mappings
|
|
111
|
-
// Note: this mappings ONLY trackes online users and will
|
|
112
|
-
// be cleaned up when the user disconnects.
|
|
113
|
-
// sessionId -> userIds
|
|
114
|
-
private sessionUsers: Map<string, Set<string>> = new Map();
|
|
115
|
-
// userId -> sessionIds
|
|
116
|
-
private userSessions: Map<string, Set<string>> = new Map();
|
|
117
|
-
|
|
118
|
-
// Session instances
|
|
119
|
-
// sessionId -> OpenSession
|
|
120
|
-
private openSessions: Map<string, OpenSession> = new Map();
|
|
121
|
-
|
|
122
|
-
private guests: Map<string, GuestUser> = new Map();
|
|
123
|
-
|
|
124
|
-
private apiKeyManager: ApiKeyManager;
|
|
125
|
-
|
|
126
|
-
private customMcpServerManager: CustomMcpServerManager;
|
|
127
|
-
|
|
128
|
-
constructor(
|
|
129
|
-
private db: Database,
|
|
130
|
-
private connectionManager: IUserConnectionManager<ServerToClient>,
|
|
131
|
-
private xmcpUrl: string
|
|
132
|
-
) {
|
|
133
|
-
this.apiKeyManager = new ApiKeyManager(db);
|
|
134
|
-
this.customMcpServerManager = new CustomMcpServerManager(db);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Add user to session membership.
|
|
139
|
-
* Creates session tracking if it doesn't exist.
|
|
140
|
-
*/
|
|
141
|
-
private addUserToSessionMemory(userId: string, sessionId: string): void {
|
|
142
|
-
logger.debug(
|
|
143
|
-
`[SessionRegistry] Adding user ${userId} to session ${sessionId}`
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Add to sessionUsers
|
|
147
|
-
if (!this.sessionUsers.has(sessionId)) {
|
|
148
|
-
this.sessionUsers.set(sessionId, new Set());
|
|
149
|
-
}
|
|
150
|
-
const sessionUserSet = this.sessionUsers.get(sessionId);
|
|
151
|
-
if (sessionUserSet) {
|
|
152
|
-
sessionUserSet.add(userId);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Add to userSessions
|
|
156
|
-
if (!this.userSessions.has(userId)) {
|
|
157
|
-
this.userSessions.set(userId, new Set());
|
|
158
|
-
}
|
|
159
|
-
const userSessionSet = this.userSessions.get(userId);
|
|
160
|
-
if (userSessionSet) {
|
|
161
|
-
userSessionSet.add(sessionId);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Remove user from session membership.
|
|
167
|
-
* Cleans up empty sessions and triggers session cleanup if needed.
|
|
168
|
-
*/
|
|
169
|
-
private removeUserFromSessionMemory(userId: string, sessionId: string): void {
|
|
170
|
-
logger.debug(
|
|
171
|
-
`[SessionRegistry] Removing user ${userId} from session ${sessionId}`
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
// Remove from sessionUsers
|
|
175
|
-
const sessionUserSet = this.sessionUsers.get(sessionId);
|
|
176
|
-
if (sessionUserSet) {
|
|
177
|
-
sessionUserSet.delete(userId);
|
|
178
|
-
if (sessionUserSet.size === 0) {
|
|
179
|
-
this.sessionUsers.delete(sessionId);
|
|
180
|
-
// Clean up session instance when empty
|
|
181
|
-
const session = this.openSessions.get(sessionId);
|
|
182
|
-
if (session) {
|
|
183
|
-
logger.debug(
|
|
184
|
-
`[SessionRegistry] Triggering cleanup for empty session ` +
|
|
185
|
-
sessionId
|
|
186
|
-
);
|
|
187
|
-
// The onEmpty callback will remove from openSessions map
|
|
188
|
-
|
|
189
|
-
logger.debug(
|
|
190
|
-
`[SessionRegistry] Session ${sessionId} empty. Removing`
|
|
191
|
-
);
|
|
192
|
-
session.onEmpty();
|
|
193
|
-
this.openSessions.delete(sessionId);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Remove from userSessions
|
|
199
|
-
const userSessionSet = this.userSessions.get(userId);
|
|
200
|
-
if (userSessionSet) {
|
|
201
|
-
userSessionSet.delete(sessionId);
|
|
202
|
-
if (userSessionSet.size === 0) {
|
|
203
|
-
this.userSessions.delete(userId);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
logger.info(
|
|
208
|
-
`[SessionRegistry] User ${userId} removed from session ${sessionId}`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Get all users in a session.
|
|
214
|
-
*/
|
|
215
|
-
async getSessionUsers(sessionId: string): Promise<Set<string>> {
|
|
216
|
-
const users = await this.db.sessionGetParticipants(sessionId);
|
|
217
|
-
return new Set(users.map((u) => u.user_uuid));
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Get all users in a session.
|
|
222
|
-
*/
|
|
223
|
-
getInMemorySessionUsers(sessionId: string): Set<string> {
|
|
224
|
-
return this.sessionUsers.get(sessionId) || new Set();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get all sessions a user belongs to.
|
|
229
|
-
*/
|
|
230
|
-
getUserSessions(userId: string): Set<string> {
|
|
231
|
-
return this.userSessions.get(userId) || new Set();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get all sessions a user belongs to in memory.
|
|
236
|
-
*/
|
|
237
|
-
getInMemoryUserSessions(userId: string): Set<string> {
|
|
238
|
-
return this.userSessions.get(userId) || new Set();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// IMessageProcessor<ClientToServer>
|
|
242
|
-
async authenticate(token: string): Promise<string | undefined> {
|
|
243
|
-
// Parse the api key to determine the type of connection
|
|
244
|
-
const {
|
|
245
|
-
prefix,
|
|
246
|
-
apiKey,
|
|
247
|
-
payload: sessionUUID,
|
|
248
|
-
} = ApiKeyManager.parseToken(token);
|
|
249
|
-
|
|
250
|
-
if (prefix === GUEST_TOKEN_PREFIX) {
|
|
251
|
-
if (!sessionUUID) {
|
|
252
|
-
throw new Error(`invalid token ${token}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const descriptor = await this.getSessionDescriptor(sessionUUID);
|
|
256
|
-
if (!descriptor) {
|
|
257
|
-
throw new Error(`no such session ${sessionUUID}`);
|
|
258
|
-
}
|
|
259
|
-
if (descriptor.access_token !== token) {
|
|
260
|
-
throw new Error(`invalid guest key ${apiKey}`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// For now, add a new user to the DB (so simplify the process of adding
|
|
264
|
-
// users).
|
|
265
|
-
|
|
266
|
-
const user_uuid = uuidv4();
|
|
267
|
-
const email = `${user_uuid}@`;
|
|
268
|
-
const nickname = "Guest";
|
|
269
|
-
const timezone = "UTC";
|
|
270
|
-
|
|
271
|
-
await this.db.createUser(user_uuid, email, nickname, timezone);
|
|
272
|
-
|
|
273
|
-
const user: GuestUser = {
|
|
274
|
-
uuid: user_uuid,
|
|
275
|
-
nickname: "Guest",
|
|
276
|
-
email: "guest",
|
|
277
|
-
timezone: "UTC",
|
|
278
|
-
guest_for_session: sessionUUID,
|
|
279
|
-
};
|
|
280
|
-
this.guests.set(user_uuid, user);
|
|
281
|
-
return user.uuid;
|
|
282
|
-
|
|
283
|
-
// throw new Error("unimpl");
|
|
284
|
-
// // return "";
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const userData = await this.apiKeyManager.verifyApiKey(apiKey);
|
|
288
|
-
if (!userData) {
|
|
289
|
-
throw new ChatFatalError("invalid api key");
|
|
290
|
-
}
|
|
291
|
-
return userData.uuid;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// IMessageProcessor<ClientToServer>
|
|
295
|
-
async processMessage(
|
|
296
|
-
connectionId: string,
|
|
297
|
-
userId: string,
|
|
298
|
-
message: ClientToServer
|
|
299
|
-
): Promise<void> {
|
|
300
|
-
if (isClientControlMessage(message)) {
|
|
301
|
-
// handle connection level message
|
|
302
|
-
await this.processClientControlMessage(connectionId, userId, message);
|
|
303
|
-
} else {
|
|
304
|
-
// handle session level message
|
|
305
|
-
this.processSessionMessage(userId, message as ClientSessionMessage);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private sendControlError(
|
|
310
|
-
connectionId: string,
|
|
311
|
-
clientMsgId: string,
|
|
312
|
-
errorMessage: string
|
|
313
|
-
): void {
|
|
314
|
-
// TODO: Why is this managed at the transport level?
|
|
315
|
-
|
|
316
|
-
const errorMsg: ServerControlError = {
|
|
317
|
-
type: "control_error",
|
|
318
|
-
message: errorMessage,
|
|
319
|
-
client_message_id: clientMsgId,
|
|
320
|
-
};
|
|
321
|
-
this.connectionManager.sendToConnection(connectionId, errorMsg);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
private async processClientControlMessage(
|
|
325
|
-
connectionId: string,
|
|
326
|
-
userId: string,
|
|
327
|
-
message: ClientControlMessage
|
|
328
|
-
): Promise<void> {
|
|
329
|
-
switch (message.type) {
|
|
330
|
-
case "control_agent_profile_create":
|
|
331
|
-
await this.handleAgentProfileCreate(userId, message);
|
|
332
|
-
break;
|
|
333
|
-
case "control_agent_profile_delete":
|
|
334
|
-
await this.handleAgentProfileDelete(userId, message);
|
|
335
|
-
break;
|
|
336
|
-
case "control_get_session_list":
|
|
337
|
-
await this.handleGetSessionList(connectionId, userId, message);
|
|
338
|
-
break;
|
|
339
|
-
case "control_session_create":
|
|
340
|
-
await this.handleSessionCreate(connectionId, userId, message);
|
|
341
|
-
break;
|
|
342
|
-
case "control_session_join":
|
|
343
|
-
await this.handleSessionJoin(connectionId, userId, message);
|
|
344
|
-
break;
|
|
345
|
-
case "control_session_delete":
|
|
346
|
-
await this.handleSessionDelete(connectionId, userId, message);
|
|
347
|
-
break;
|
|
348
|
-
case "control_team_create":
|
|
349
|
-
await this.handleTeamCreate(connectionId, userId, message);
|
|
350
|
-
break;
|
|
351
|
-
case "control_add_team_user":
|
|
352
|
-
await this.handleAddTeamUser(connectionId, userId, message);
|
|
353
|
-
break;
|
|
354
|
-
case "control_remove_team_user":
|
|
355
|
-
await this.handleRemoveTeamUser(connectionId, userId, message);
|
|
356
|
-
break;
|
|
357
|
-
case "control_add_custom_mcp_server":
|
|
358
|
-
await this.handleAddCustomMcpServer(userId, message);
|
|
359
|
-
break;
|
|
360
|
-
case "control_remove_custom_mcp_server":
|
|
361
|
-
await this.handleRemoveCustomMcpServer(userId, message);
|
|
362
|
-
break;
|
|
363
|
-
default: {
|
|
364
|
-
const exhaustive: never = message;
|
|
365
|
-
// unknown connection type should be a fatal error
|
|
366
|
-
throw new ChatFatalError(
|
|
367
|
-
`Unknown connection message type: ${String(exhaustive)}`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private async handleAddCustomMcpServer(
|
|
374
|
-
userId: string,
|
|
375
|
-
message: ClientControlAddCustomMcpServer
|
|
376
|
-
): Promise<void> {
|
|
377
|
-
await this.customMcpServerManager.addForUser(
|
|
378
|
-
userId,
|
|
379
|
-
message.server_name,
|
|
380
|
-
message.description,
|
|
381
|
-
message.url
|
|
382
|
-
);
|
|
383
|
-
this.connectionManager.sendToUsers([userId], {
|
|
384
|
-
type: "control_custom_mcp_server_added",
|
|
385
|
-
server_name: message.server_name,
|
|
386
|
-
url: message.url,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private async handleRemoveCustomMcpServer(
|
|
391
|
-
userId: string,
|
|
392
|
-
message: ClientControlRemoveCustomMcpServer
|
|
393
|
-
): Promise<void> {
|
|
394
|
-
await this.customMcpServerManager.removeForUser(
|
|
395
|
-
userId,
|
|
396
|
-
message.server_name
|
|
397
|
-
);
|
|
398
|
-
this.connectionManager.sendToUsers([userId], {
|
|
399
|
-
type: "control_custom_mcp_server_removed",
|
|
400
|
-
server_name: message.server_name,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Handle session_list request
|
|
406
|
-
*/
|
|
407
|
-
private async handleGetSessionList(
|
|
408
|
-
connectionId: string,
|
|
409
|
-
userId: string,
|
|
410
|
-
message: ClientControlGetSessionList
|
|
411
|
-
): Promise<void> {
|
|
412
|
-
try {
|
|
413
|
-
const [userSessions, teamSessions, userAgents] =
|
|
414
|
-
await this.getAllAgentsAndSessionsByUser(userId);
|
|
415
|
-
|
|
416
|
-
// Mark any guest session as a user session
|
|
417
|
-
|
|
418
|
-
const guest = this.guests.get(userId);
|
|
419
|
-
if (guest) {
|
|
420
|
-
const guestSession = await this.db.sessionGetDescriptorById(
|
|
421
|
-
guest.guest_for_session
|
|
422
|
-
);
|
|
423
|
-
if (guestSession) {
|
|
424
|
-
userSessions.push(guestSession);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const response: ServerControlSessionList = {
|
|
429
|
-
type: "control_session_list",
|
|
430
|
-
user_sessions: userSessions,
|
|
431
|
-
team_sessions: teamSessions,
|
|
432
|
-
user_agents: userAgents,
|
|
433
|
-
client_message_id: message.client_message_id,
|
|
434
|
-
custom_mcp_servers: this.customMcpServerManager.getForUser(userId),
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
this.connectionManager.sendToConnection(connectionId, response);
|
|
438
|
-
logger.info(
|
|
439
|
-
`[ConnectionManager] Sent ` +
|
|
440
|
-
`${String(userSessions.length)} user sessions and ` +
|
|
441
|
-
`${String(teamSessions.length)} team sessions to user ${userId}`
|
|
442
|
-
);
|
|
443
|
-
} catch (error) {
|
|
444
|
-
logger.error(`[ConnectionManager] Failed to get session list:`, error);
|
|
445
|
-
this.sendControlError(
|
|
446
|
-
connectionId,
|
|
447
|
-
message.client_message_id,
|
|
448
|
-
JSON.stringify(error)
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private async handleSessionDelete(
|
|
454
|
-
connectionId: string,
|
|
455
|
-
userId: string,
|
|
456
|
-
message: ClientControlSessionDelete
|
|
457
|
-
): Promise<void> {
|
|
458
|
-
const sessionId = message.target_session_id;
|
|
459
|
-
try {
|
|
460
|
-
// validate the session access
|
|
461
|
-
await this.validateSessionAccess(sessionId, userId, "owner");
|
|
462
|
-
|
|
463
|
-
// get users/team-uuid before deletion
|
|
464
|
-
const [users, sessionData] = await Promise.all([
|
|
465
|
-
this.getSessionUsers(sessionId),
|
|
466
|
-
this.db.sessionGetDescriptorById(sessionId),
|
|
467
|
-
]);
|
|
468
|
-
const teamUuid = sessionData?.team_uuid;
|
|
469
|
-
|
|
470
|
-
if (!sessionData) {
|
|
471
|
-
throw new ChatFatalError(`No such session: ${sessionId}`);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// delete the session from database
|
|
475
|
-
await this.db.sessionDeleteById(sessionId);
|
|
476
|
-
|
|
477
|
-
// remove the session from memory (should be no thrown from here)
|
|
478
|
-
const session = this.openSessions.get(sessionId);
|
|
479
|
-
if (session) {
|
|
480
|
-
const users = this.getInMemorySessionUsers(sessionId);
|
|
481
|
-
for (const user of users) {
|
|
482
|
-
this.removeUserFromSessionMemory(user, sessionId);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
this.openSessions.delete(sessionId);
|
|
486
|
-
|
|
487
|
-
// send confirmation to the client
|
|
488
|
-
const response: ServerControlSessionDeleted = {
|
|
489
|
-
type: "control_session_deleted",
|
|
490
|
-
session_id: sessionId,
|
|
491
|
-
team_uuid: teamUuid,
|
|
492
|
-
agent_profile_uuid: sessionData.agent_profile_uuid,
|
|
493
|
-
client_message_id: message.client_message_id,
|
|
494
|
-
};
|
|
495
|
-
this.connectionManager.sendToUsers(users, response);
|
|
496
|
-
} catch (error) {
|
|
497
|
-
logger.error(
|
|
498
|
-
`[SessionRegistry] Error delete session ${sessionId}:`,
|
|
499
|
-
error
|
|
500
|
-
);
|
|
501
|
-
this.sendControlError(
|
|
502
|
-
connectionId,
|
|
503
|
-
message.client_message_id,
|
|
504
|
-
String(error)
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Handle team_create_request message - creates a new team.
|
|
511
|
-
* @param connectionId connection id
|
|
512
|
-
* @param userId user id
|
|
513
|
-
* @param message team create request message
|
|
514
|
-
*/
|
|
515
|
-
private async handleTeamCreate(
|
|
516
|
-
connectionId: string,
|
|
517
|
-
userId: string,
|
|
518
|
-
message: ClientControlTeamCreate
|
|
519
|
-
): Promise<void> {
|
|
520
|
-
try {
|
|
521
|
-
// Resolve all initial members in parallel
|
|
522
|
-
// this contains undefined to mark failed lookups
|
|
523
|
-
const resolvedMemberIds = await Promise.all(
|
|
524
|
-
message.initial_members.map((member) =>
|
|
525
|
-
resolveUserIdentifier(this.db, member)
|
|
526
|
-
)
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
const failedLookups: string[] = [];
|
|
530
|
-
const validMemberIds: string[] = [];
|
|
531
|
-
const participants: TeamParticipant[] = [];
|
|
532
|
-
|
|
533
|
-
// No need to filter out the owner here, that is done in
|
|
534
|
-
// `createTeamWithParticipants`
|
|
535
|
-
resolvedMemberIds.forEach((user, index) => {
|
|
536
|
-
if (user) {
|
|
537
|
-
validMemberIds.push(user.uuid);
|
|
538
|
-
participants.push({
|
|
539
|
-
user_uuid: user.uuid,
|
|
540
|
-
nickname: user.nickname || "",
|
|
541
|
-
email: user.email,
|
|
542
|
-
role: "participant",
|
|
543
|
-
});
|
|
544
|
-
} else {
|
|
545
|
-
failedLookups.push(message.initial_members[index]);
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// Create the team with initial participants
|
|
550
|
-
const teamUuid = await this.db.createTeamWithParticipants(
|
|
551
|
-
message.team_name,
|
|
552
|
-
userId,
|
|
553
|
-
validMemberIds
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
// Create a default agent for the new team
|
|
557
|
-
const defaultAgentProfile = await this.db.createAgentProfile(
|
|
558
|
-
undefined,
|
|
559
|
-
teamUuid,
|
|
560
|
-
DEFAULT_AGENT_PROFILE_NAME,
|
|
561
|
-
DEFAULT_AGENT_PROFILE
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
if (!defaultAgentProfile) {
|
|
565
|
-
throw new Error(
|
|
566
|
-
`Failed to create default agent profile for team ${teamUuid}`
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Get owner's information and add to participants for the response
|
|
571
|
-
const ownerInfo = await this.db.getUserFromUuid(userId);
|
|
572
|
-
if (!ownerInfo) {
|
|
573
|
-
throw new Error(`Cannot find owner user data for user ${userId}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const ownerParticipant: TeamParticipant = {
|
|
577
|
-
user_uuid: userId,
|
|
578
|
-
nickname: ownerInfo.nickname || "",
|
|
579
|
-
email: ownerInfo.email,
|
|
580
|
-
role: "owner",
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
const response: ServerControlTeamCreated = {
|
|
584
|
-
type: "control_team_created",
|
|
585
|
-
team_uuid: teamUuid,
|
|
586
|
-
team_owner_uuid: userId,
|
|
587
|
-
team_name: message.team_name,
|
|
588
|
-
members: [ownerParticipant, ...participants],
|
|
589
|
-
failed_lookups: failedLookups,
|
|
590
|
-
};
|
|
591
|
-
const members = new Set(validMemberIds);
|
|
592
|
-
members.add(userId);
|
|
593
|
-
this.connectionManager.sendToUsers(members, response);
|
|
594
|
-
|
|
595
|
-
// Broadcast the default agent profile to all team members
|
|
596
|
-
this.connectionManager.sendToUsers(members, {
|
|
597
|
-
type: "control_agent_profile_created",
|
|
598
|
-
profile: defaultAgentProfile,
|
|
599
|
-
});
|
|
600
|
-
} catch (error) {
|
|
601
|
-
logger.error(`[SessionRegistry] Error creating team: ${String(error)}`);
|
|
602
|
-
this.sendControlError(
|
|
603
|
-
connectionId,
|
|
604
|
-
message.client_message_id,
|
|
605
|
-
String(error)
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Process session-scoped client message.
|
|
612
|
-
* Handles membership messages here, others to OpenSession.
|
|
613
|
-
*/
|
|
614
|
-
private processSessionMessage(
|
|
615
|
-
userId: string,
|
|
616
|
-
message: ClientSessionMessage
|
|
617
|
-
): void {
|
|
618
|
-
const sessionId = message.session_id;
|
|
619
|
-
logger.info(
|
|
620
|
-
`[SessionRegistry] Processing message from user ${userId} in session ` +
|
|
621
|
-
sessionId
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
const session = this.openSessions.get(sessionId);
|
|
625
|
-
if (!session) {
|
|
626
|
-
throw new ChatFatalError(`Internal error: No such session ${sessionId}`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Forward all other messages to OpenSession
|
|
630
|
-
session.onClientSessionMessage(userId, message);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Handle add_user message - adds user to team.
|
|
635
|
-
*/
|
|
636
|
-
private async handleAddTeamUser(
|
|
637
|
-
connectionId: string,
|
|
638
|
-
fromUserId: string,
|
|
639
|
-
message: ClientControlAddTeamUser
|
|
640
|
-
): Promise<void> {
|
|
641
|
-
// Validate permissions - only owner can add users
|
|
642
|
-
const access = await this.validateTeamAccess(
|
|
643
|
-
message.target_team_id,
|
|
644
|
-
fromUserId,
|
|
645
|
-
"owner"
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
if (!access) {
|
|
649
|
-
this.sendControlError(
|
|
650
|
-
connectionId,
|
|
651
|
-
message.client_message_id,
|
|
652
|
-
"Only team owner can add users"
|
|
653
|
-
);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
// Resolve user identifier
|
|
657
|
-
const user = await resolveUserIdentifier(
|
|
658
|
-
this.db,
|
|
659
|
-
message.user_uuid_or_email
|
|
660
|
-
);
|
|
661
|
-
if (!user) {
|
|
662
|
-
this.sendControlError(
|
|
663
|
-
connectionId,
|
|
664
|
-
message.client_message_id,
|
|
665
|
-
"User not found"
|
|
666
|
-
);
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Check if user is already a participant
|
|
671
|
-
const participants = await this.db.teamGetMembers(message.target_team_id);
|
|
672
|
-
if (participants.some((p) => p.user_uuid === user.uuid)) {
|
|
673
|
-
this.sendControlError(
|
|
674
|
-
connectionId,
|
|
675
|
-
message.client_message_id,
|
|
676
|
-
"User is already a participant"
|
|
677
|
-
);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Update database
|
|
682
|
-
try {
|
|
683
|
-
await this.db.teamAddMember(message.target_team_id, user.uuid);
|
|
684
|
-
} catch (error) {
|
|
685
|
-
this.sendControlError(
|
|
686
|
-
connectionId,
|
|
687
|
-
message.client_message_id,
|
|
688
|
-
"Server Internal Error: cannot add user."
|
|
689
|
-
);
|
|
690
|
-
logger.error(
|
|
691
|
-
`[SessionRegistry] Error adding user ${user.uuid}` +
|
|
692
|
-
` to team ${message.target_team_id}:`,
|
|
693
|
-
error
|
|
694
|
-
);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// add user to related active sessions
|
|
699
|
-
const sessions = await this.db.teamGetSessions(message.target_team_id);
|
|
700
|
-
for (const sessionData of sessions) {
|
|
701
|
-
const session = this.openSessions.get(sessionData.session_uuid);
|
|
702
|
-
if (session) {
|
|
703
|
-
session.addParticipant(user.uuid, {
|
|
704
|
-
user_uuid: user.uuid,
|
|
705
|
-
nickname: user.nickname || "",
|
|
706
|
-
email: user.email,
|
|
707
|
-
role: "participant",
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Notify all team members about the updated member list
|
|
713
|
-
const updatedParticipants = await this.db.teamGetMembers(
|
|
714
|
-
message.target_team_id
|
|
715
|
-
);
|
|
716
|
-
this.connectionManager.sendToUsers(
|
|
717
|
-
new Set(updatedParticipants.map((p) => p.user_uuid)),
|
|
718
|
-
{
|
|
719
|
-
type: "control_team_members_updated",
|
|
720
|
-
team_uuid: message.target_team_id,
|
|
721
|
-
members: updatedParticipants,
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
logger.info(
|
|
726
|
-
`[SessionRegistry] User ${user.uuid}` +
|
|
727
|
-
`added to team ${message.target_team_id}`
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* Handle remove_user message - removes user from session membership.
|
|
733
|
-
* Only session owner can remove users.
|
|
734
|
-
*/
|
|
735
|
-
private async handleRemoveTeamUser(
|
|
736
|
-
connectionId: string,
|
|
737
|
-
fromUserId: string,
|
|
738
|
-
message: ClientControlRemoveTeamUser
|
|
739
|
-
): Promise<void> {
|
|
740
|
-
// Validate permissions - only owner can remove users
|
|
741
|
-
const access = await this.validateTeamAccess(
|
|
742
|
-
message.target_team_id,
|
|
743
|
-
fromUserId,
|
|
744
|
-
"owner"
|
|
745
|
-
);
|
|
746
|
-
if (!access) {
|
|
747
|
-
this.sendControlError(
|
|
748
|
-
connectionId,
|
|
749
|
-
message.client_message_id,
|
|
750
|
-
"Only team owner can remove users"
|
|
751
|
-
);
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Resolve user identifier
|
|
756
|
-
const user = await resolveUserIdentifier(
|
|
757
|
-
this.db,
|
|
758
|
-
message.user_uuid_or_email
|
|
759
|
-
);
|
|
760
|
-
if (!user) {
|
|
761
|
-
this.sendControlError(
|
|
762
|
-
connectionId,
|
|
763
|
-
message.client_message_id,
|
|
764
|
-
"User not found"
|
|
765
|
-
);
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// owner cannot remove her/himself
|
|
770
|
-
if (user.uuid === fromUserId) {
|
|
771
|
-
this.sendControlError(
|
|
772
|
-
connectionId,
|
|
773
|
-
message.client_message_id,
|
|
774
|
-
"Owner cannot remove herself/himself"
|
|
775
|
-
);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Check if user is actually a participant
|
|
780
|
-
const participants = await this.db.teamGetMembers(message.target_team_id);
|
|
781
|
-
if (!participants.some((p) => p.user_uuid === user.uuid)) {
|
|
782
|
-
this.sendControlError(
|
|
783
|
-
connectionId,
|
|
784
|
-
message.client_message_id,
|
|
785
|
-
"User is not a participant"
|
|
786
|
-
);
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// Remove from database
|
|
791
|
-
try {
|
|
792
|
-
await this.db.teamRemoveMember(message.target_team_id, user.uuid);
|
|
793
|
-
} catch (error) {
|
|
794
|
-
this.sendControlError(
|
|
795
|
-
connectionId,
|
|
796
|
-
message.client_message_id,
|
|
797
|
-
"Server Internal Error: cannot remove user."
|
|
798
|
-
);
|
|
799
|
-
logger.error(
|
|
800
|
-
`[SessionRegistry] Error removing user ${user.uuid}` +
|
|
801
|
-
` from team ${message.target_team_id}:`,
|
|
802
|
-
error
|
|
803
|
-
);
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Update OpenSession's participant map and in memory tracking
|
|
808
|
-
const sessions = await this.db.teamGetSessions(message.target_team_id);
|
|
809
|
-
for (const sessionData of sessions) {
|
|
810
|
-
const session = this.openSessions.get(sessionData.session_uuid);
|
|
811
|
-
if (session) {
|
|
812
|
-
session.removeParticipant(user.uuid);
|
|
813
|
-
this.removeUserFromSessionMemory(user.uuid, sessionData.session_uuid);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Notify all remaining team members about the updated member list
|
|
818
|
-
const updatedParticipants = await this.db.teamGetMembers(
|
|
819
|
-
message.target_team_id
|
|
820
|
-
);
|
|
821
|
-
this.connectionManager.sendToUsers(
|
|
822
|
-
new Set(updatedParticipants.map((p) => p.user_uuid)),
|
|
823
|
-
{
|
|
824
|
-
type: "control_team_members_updated",
|
|
825
|
-
team_uuid: message.target_team_id,
|
|
826
|
-
members: updatedParticipants,
|
|
827
|
-
}
|
|
828
|
-
);
|
|
829
|
-
|
|
830
|
-
logger.info(
|
|
831
|
-
`[SessionRegistry] User ${user.uuid} ` +
|
|
832
|
-
`removed from team ${message.target_team_id}`
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Get session instance, if the session has not been initialized,
|
|
838
|
-
* it will be.
|
|
839
|
-
*/
|
|
840
|
-
private async getAndActivateSession(
|
|
841
|
-
sessionId: string
|
|
842
|
-
): Promise<{ session: OpenSession; isNew: boolean } | undefined> {
|
|
843
|
-
if (this.openSessions.has(sessionId)) {
|
|
844
|
-
logger.info(`[SessionRegistry] Session ${sessionId} already exists`);
|
|
845
|
-
const session = this.openSessions.get(sessionId);
|
|
846
|
-
if (!session) {
|
|
847
|
-
throw new ChatFatalError(
|
|
848
|
-
`Internal error: No such session: ${sessionId}`
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
return { session, isNew: false };
|
|
852
|
-
} else {
|
|
853
|
-
logger.info(`[SessionRegistry] loading session ${sessionId}`);
|
|
854
|
-
const session = await OpenSession.initWithExistingSession(
|
|
855
|
-
this.db,
|
|
856
|
-
sessionId,
|
|
857
|
-
this.xmcpUrl,
|
|
858
|
-
this.connectionManager
|
|
859
|
-
);
|
|
860
|
-
return { session, isNew: true };
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Handle user joining a session.
|
|
866
|
-
* Activates the session if not already active.
|
|
867
|
-
* return the session info to the client joining the session.
|
|
868
|
-
*/
|
|
869
|
-
async handleSessionJoin(
|
|
870
|
-
connectionId: string,
|
|
871
|
-
userId: string,
|
|
872
|
-
message: ClientControlSessionJoin
|
|
873
|
-
): Promise<void> {
|
|
874
|
-
const sessionId = message.target_session_id;
|
|
875
|
-
logger.info(
|
|
876
|
-
`[SessionRegistry] Joining session ${sessionId} for user ${userId}`
|
|
877
|
-
);
|
|
878
|
-
try {
|
|
879
|
-
// Validate session access permissions
|
|
880
|
-
const access = await this.validateSessionAccess(sessionId, userId);
|
|
881
|
-
if (!access) {
|
|
882
|
-
throw new ChatFatalError(
|
|
883
|
-
`User ${userId} is not authorized to join session ${sessionId}`
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// get or create the session
|
|
888
|
-
const sessionInfo = await this.getAndActivateSession(sessionId);
|
|
889
|
-
if (!sessionInfo) {
|
|
890
|
-
// this in theory should not happen
|
|
891
|
-
// since we have validated the access
|
|
892
|
-
throw new ChatFatalError(
|
|
893
|
-
`Server internal error: ` + `failed to load session ${sessionId}`
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
|
-
const { session, isNew } = sessionInfo;
|
|
897
|
-
|
|
898
|
-
const guest = this.guests.get(userId);
|
|
899
|
-
if (guest) {
|
|
900
|
-
session.addParticipant(userId, {
|
|
901
|
-
user_uuid: guest.uuid,
|
|
902
|
-
nickname: guest.nickname || "Guest",
|
|
903
|
-
email: guest.email,
|
|
904
|
-
role: "participant",
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
// To this point, there should be no error thrown.
|
|
908
|
-
// Update in-memory session-user/user-session mappings
|
|
909
|
-
this.addUserToSessionMemory(userId, sessionId);
|
|
910
|
-
|
|
911
|
-
// Register session immediately
|
|
912
|
-
if (!this.openSessions.has(sessionId)) {
|
|
913
|
-
this.openSessions.set(sessionId, session);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// pass the message to the session to handle the rest
|
|
917
|
-
await session.sendSessionData(
|
|
918
|
-
connectionId,
|
|
919
|
-
message.client_message_id,
|
|
920
|
-
isNew
|
|
921
|
-
);
|
|
922
|
-
} catch (error) {
|
|
923
|
-
logger.error(
|
|
924
|
-
`[SessionRegistry] Error handling user join: ${String(error)}`
|
|
925
|
-
);
|
|
926
|
-
this.sendControlError(
|
|
927
|
-
connectionId,
|
|
928
|
-
message.client_message_id,
|
|
929
|
-
String(error)
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
async handleAgentProfileCreate(
|
|
935
|
-
userId: string,
|
|
936
|
-
message: ClientControlAgentProfileCreate
|
|
937
|
-
): Promise<string> {
|
|
938
|
-
// get agent profile from template
|
|
939
|
-
let agentProfileFromTemplate: AgentProfile | undefined = undefined;
|
|
940
|
-
if (message.template_name) {
|
|
941
|
-
const template = await this.db.agentTemplateGetByName(
|
|
942
|
-
message.template_name
|
|
943
|
-
);
|
|
944
|
-
if (!template) {
|
|
945
|
-
throw new Error(`template ${message.template_name} not found`);
|
|
946
|
-
}
|
|
947
|
-
agentProfileFromTemplate = template.agent_profile;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
const newAgentProfile: AgentProfile =
|
|
951
|
-
agentProfileFromTemplate ||
|
|
952
|
-
new AgentProfile(
|
|
953
|
-
message.model || DEFAULT_AGENT_PROFILE.model,
|
|
954
|
-
DEFAULT_AGENT_PROFILE.system_prompt,
|
|
955
|
-
DEFAULT_AGENT_PROFILE.mcp_settings
|
|
956
|
-
);
|
|
957
|
-
const team_uuid = message.team_uuid || undefined;
|
|
958
|
-
|
|
959
|
-
if (team_uuid) {
|
|
960
|
-
// TODO: should be able to reconstruct the full SavedAgentProfile in one
|
|
961
|
-
// call.
|
|
962
|
-
const savedAgentProfile = await this.db.createAgentProfile(
|
|
963
|
-
undefined,
|
|
964
|
-
team_uuid,
|
|
965
|
-
message.title,
|
|
966
|
-
newAgentProfile
|
|
967
|
-
);
|
|
968
|
-
if (!savedAgentProfile) {
|
|
969
|
-
throw new Error(
|
|
970
|
-
"failed creating team agent profile (createAgentProfile)"
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Broadcast the new AgentProfile to all participants
|
|
975
|
-
|
|
976
|
-
const participants = await this.db.teamGetMembers(team_uuid);
|
|
977
|
-
this.connectionManager.sendToUsers(
|
|
978
|
-
new Set(participants.map((p) => p.user_uuid)),
|
|
979
|
-
{ type: "control_agent_profile_created", profile: savedAgentProfile }
|
|
980
|
-
);
|
|
981
|
-
return savedAgentProfile.uuid;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// User AgentProfile
|
|
985
|
-
|
|
986
|
-
const savedAgentProfile = await this.db.createAgentProfile(
|
|
987
|
-
userId,
|
|
988
|
-
undefined,
|
|
989
|
-
message.title,
|
|
990
|
-
newAgentProfile
|
|
991
|
-
);
|
|
992
|
-
if (!savedAgentProfile) {
|
|
993
|
-
throw new Error("failed creating agent profile (createAgentProfile)");
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Send the new AgentProfile to the user
|
|
997
|
-
this.connectionManager.sendToUsers(new Set([userId]), {
|
|
998
|
-
type: "control_agent_profile_created",
|
|
999
|
-
profile: savedAgentProfile,
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
return savedAgentProfile.uuid;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
async handleAgentProfileDelete(
|
|
1006
|
-
userId: string,
|
|
1007
|
-
message: ClientControlAgentProfileDelete
|
|
1008
|
-
): Promise<void> {
|
|
1009
|
-
const agentProfileUuid = message.agent_profile_uuid;
|
|
1010
|
-
|
|
1011
|
-
// Get the agent profile to determine if it's a team or user profile
|
|
1012
|
-
const agentProfile =
|
|
1013
|
-
await this.db.getSavedAgentProfileById(agentProfileUuid);
|
|
1014
|
-
if (!agentProfile) {
|
|
1015
|
-
throw new Error(`Agent profile ${agentProfileUuid} not found`);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// Validate team access: user must own the profile or be in the team
|
|
1019
|
-
// TODO: Only allow the owner to delete the profile?
|
|
1020
|
-
if (agentProfile.team_uuid) {
|
|
1021
|
-
const hasAccess = await this.validateTeamAccess(
|
|
1022
|
-
agentProfile.team_uuid,
|
|
1023
|
-
userId
|
|
1024
|
-
);
|
|
1025
|
-
if (!hasAccess) {
|
|
1026
|
-
throw new Error(
|
|
1027
|
-
`User ${userId} is not authorized to delete this agent profile`
|
|
1028
|
-
);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Delete the agent profile from database
|
|
1033
|
-
await this.db.deleteAgentProfile(agentProfileUuid);
|
|
1034
|
-
|
|
1035
|
-
// Send confirmation to all relevant users
|
|
1036
|
-
if (agentProfile.team_uuid) {
|
|
1037
|
-
const participants = await this.db.teamGetMembers(agentProfile.team_uuid);
|
|
1038
|
-
this.connectionManager.sendToUsers(
|
|
1039
|
-
new Set(participants.map((p) => p.user_uuid)),
|
|
1040
|
-
{
|
|
1041
|
-
type: "control_agent_profile_deleted",
|
|
1042
|
-
profile_uuid: agentProfileUuid,
|
|
1043
|
-
}
|
|
1044
|
-
);
|
|
1045
|
-
} else {
|
|
1046
|
-
this.connectionManager.sendToUsers(new Set([userId]), {
|
|
1047
|
-
type: "control_agent_profile_deleted",
|
|
1048
|
-
profile_uuid: agentProfileUuid,
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
/**
|
|
1054
|
-
* Create a new session for a user.
|
|
1055
|
-
* - create session in database (via `newSession`)
|
|
1056
|
-
* - create an OpenSession instance
|
|
1057
|
-
* - return the session info
|
|
1058
|
-
*/
|
|
1059
|
-
async handleSessionCreate(
|
|
1060
|
-
connectionId: string,
|
|
1061
|
-
fromUserId: string,
|
|
1062
|
-
message: ClientControlSessionCreate
|
|
1063
|
-
): Promise<void> {
|
|
1064
|
-
try {
|
|
1065
|
-
// If agent not given, create one and inform the client
|
|
1066
|
-
|
|
1067
|
-
if (!message.agent_profile_id) {
|
|
1068
|
-
logger.info("[handleSessionCreate] creating new AgentProfile");
|
|
1069
|
-
|
|
1070
|
-
// Create AgentProfile and inform the client
|
|
1071
|
-
message.agent_profile_id = await this.handleAgentProfileCreate(
|
|
1072
|
-
fromUserId,
|
|
1073
|
-
{
|
|
1074
|
-
type: "control_agent_profile_create",
|
|
1075
|
-
title: "New Agent " + uuidv4(),
|
|
1076
|
-
user_uuid: fromUserId,
|
|
1077
|
-
team_uuid: message.team_id,
|
|
1078
|
-
}
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Create new session and get its instance
|
|
1083
|
-
const { openSession, sessionId } = message.team_id
|
|
1084
|
-
? await this.newTeamSession(
|
|
1085
|
-
fromUserId,
|
|
1086
|
-
message.team_id,
|
|
1087
|
-
message.title,
|
|
1088
|
-
message.agent_profile_id
|
|
1089
|
-
)
|
|
1090
|
-
: await this.newUserSession(
|
|
1091
|
-
fromUserId,
|
|
1092
|
-
message.title,
|
|
1093
|
-
message.agent_profile_id
|
|
1094
|
-
);
|
|
1095
|
-
|
|
1096
|
-
// there should be no further error thrown from now.
|
|
1097
|
-
// Register session immediately
|
|
1098
|
-
this.openSessions.set(sessionId, openSession);
|
|
1099
|
-
|
|
1100
|
-
// add owner to session memory
|
|
1101
|
-
this.addUserToSessionMemory(fromUserId, sessionId);
|
|
1102
|
-
|
|
1103
|
-
// send session info to the connection. It has just been created so we
|
|
1104
|
-
// must also restore the mcp servers.
|
|
1105
|
-
await openSession.sendSessionData(
|
|
1106
|
-
connectionId,
|
|
1107
|
-
message.client_message_id,
|
|
1108
|
-
true
|
|
1109
|
-
);
|
|
1110
|
-
|
|
1111
|
-
logger.info(
|
|
1112
|
-
`[SessionRegistry] new session ${sessionId}:` +
|
|
1113
|
-
` ${message.title} for ${fromUserId}`
|
|
1114
|
-
);
|
|
1115
|
-
} catch (error) {
|
|
1116
|
-
const errStr = getErrorString(error);
|
|
1117
|
-
logger.error(`[SessionRegistry] Error in session create: ${errStr}`);
|
|
1118
|
-
this.sendControlError(connectionId, message.client_message_id, errStr);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Create a new user session.
|
|
1124
|
-
*/
|
|
1125
|
-
private async newUserSession(
|
|
1126
|
-
ownerId: string,
|
|
1127
|
-
title: string,
|
|
1128
|
-
agentProfileId: string
|
|
1129
|
-
): Promise<{ openSession: OpenSession; sessionId: string }> {
|
|
1130
|
-
// validate the agent profile
|
|
1131
|
-
await this.validateSavedAgentProfile(agentProfileId);
|
|
1132
|
-
|
|
1133
|
-
const newSessionData: SessionDescriptor = {
|
|
1134
|
-
...userSessionCreateData(ownerId, title, agentProfileId),
|
|
1135
|
-
updated_at: new Date().toISOString(),
|
|
1136
|
-
};
|
|
1137
|
-
const openSession = await OpenSession.initWithEmptySession(
|
|
1138
|
-
this.db,
|
|
1139
|
-
newSessionData,
|
|
1140
|
-
this.xmcpUrl,
|
|
1141
|
-
this.connectionManager
|
|
1142
|
-
);
|
|
1143
|
-
return { sessionId: newSessionData.session_uuid, openSession };
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
/**
|
|
1147
|
-
* Create a new team session.
|
|
1148
|
-
*/
|
|
1149
|
-
private async newTeamSession(
|
|
1150
|
-
fromUserId: string,
|
|
1151
|
-
teamId: string,
|
|
1152
|
-
title: string,
|
|
1153
|
-
agentProfileId: string
|
|
1154
|
-
): Promise<{ openSession: OpenSession; sessionId: string }> {
|
|
1155
|
-
// validate agent profile and team access
|
|
1156
|
-
const [_savedAgentProfile, access, participants] = await Promise.all([
|
|
1157
|
-
this.validateSavedAgentProfile(agentProfileId),
|
|
1158
|
-
this.validateTeamAccess(teamId, fromUserId),
|
|
1159
|
-
this.db.teamGetMembers(teamId),
|
|
1160
|
-
]);
|
|
1161
|
-
|
|
1162
|
-
if (!access) {
|
|
1163
|
-
throw new ChatFatalError(
|
|
1164
|
-
`User ${fromUserId} is not a participant of team ${teamId}`
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const newSessionData: SessionDescriptor = {
|
|
1169
|
-
...teamSessionCreateData(
|
|
1170
|
-
teamId,
|
|
1171
|
-
participants,
|
|
1172
|
-
fromUserId,
|
|
1173
|
-
title,
|
|
1174
|
-
agentProfileId
|
|
1175
|
-
),
|
|
1176
|
-
updated_at: new Date().toISOString(),
|
|
1177
|
-
};
|
|
1178
|
-
|
|
1179
|
-
// initialize the open session
|
|
1180
|
-
const openSession = await OpenSession.initWithEmptySession(
|
|
1181
|
-
this.db,
|
|
1182
|
-
newSessionData,
|
|
1183
|
-
this.xmcpUrl,
|
|
1184
|
-
this.connectionManager
|
|
1185
|
-
);
|
|
1186
|
-
return { sessionId: newSessionData.session_uuid, openSession };
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
/**
|
|
1190
|
-
* Gracefully shutdown all sessions and clean up resources.
|
|
1191
|
-
*/
|
|
1192
|
-
shutdown(): void {
|
|
1193
|
-
logger.info(
|
|
1194
|
-
`[SessionRegistry] Shutting down ` +
|
|
1195
|
-
String(this.openSessions.size) +
|
|
1196
|
-
` sessions`
|
|
1197
|
-
);
|
|
1198
|
-
|
|
1199
|
-
// Create list of sessions to avoid concurrent modification
|
|
1200
|
-
const sessionsToClose = Array.from(this.openSessions.keys());
|
|
1201
|
-
|
|
1202
|
-
for (const sessionId of sessionsToClose) {
|
|
1203
|
-
const session = this.openSessions.get(sessionId);
|
|
1204
|
-
if (session) {
|
|
1205
|
-
logger.debug(`[SessionRegistry] Closing session ${sessionId}`);
|
|
1206
|
-
try {
|
|
1207
|
-
// Trigger the session's cleanup
|
|
1208
|
-
session.onEmpty();
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
logger.error(
|
|
1211
|
-
`[SessionRegistry] Error closing session ${sessionId}:`,
|
|
1212
|
-
error
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// Clear all maps
|
|
1219
|
-
this.sessionUsers.clear();
|
|
1220
|
-
this.userSessions.clear();
|
|
1221
|
-
this.openSessions.clear();
|
|
1222
|
-
|
|
1223
|
-
logger.info(`[SessionRegistry] Shutdown complete`);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
* Handle user disconnect - clean up from all sessions.
|
|
1228
|
-
* Called when a connection is closed to ensure proper cleanup.
|
|
1229
|
-
*/
|
|
1230
|
-
async handleUserDisconnect(userId: string): Promise<void> {
|
|
1231
|
-
logger.info(`[SessionRegistry] Handling disconnect for user ${userId}`);
|
|
1232
|
-
|
|
1233
|
-
// If the user is a guest, remove his DB entry.
|
|
1234
|
-
const guest = this.guests.get(userId);
|
|
1235
|
-
if (guest) {
|
|
1236
|
-
const session = this.openSessions.get(guest.guest_for_session);
|
|
1237
|
-
if (session) {
|
|
1238
|
-
session.removeParticipant(userId);
|
|
1239
|
-
}
|
|
1240
|
-
this.guests.delete(userId);
|
|
1241
|
-
try {
|
|
1242
|
-
logger.info(
|
|
1243
|
-
`[SessionRegistry.handleUserDisconnect] deleting user ${userId}`
|
|
1244
|
-
);
|
|
1245
|
-
await this.db.deleteUser(userId);
|
|
1246
|
-
} catch (e) {
|
|
1247
|
-
logger.warn(`error removing guest ${userId}: ${getErrorString(e)}`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// Get all sessions the user is in (copy to avoid concurrent modification)
|
|
1252
|
-
const userSessionIds = this.getInMemoryUserSessions(userId);
|
|
1253
|
-
const sessionsToLeave = Array.from(userSessionIds);
|
|
1254
|
-
|
|
1255
|
-
// Remove user from each session
|
|
1256
|
-
for (const sessionId of sessionsToLeave) {
|
|
1257
|
-
this.removeUserFromSessionMemory(userId, sessionId);
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
logger.info(
|
|
1261
|
-
`[SessionRegistry] User ${userId} removed` +
|
|
1262
|
-
` from ${String(sessionsToLeave.length)} sessions`
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* Get all sessions for a user,
|
|
1268
|
-
* including user solo sessions and team sessions.
|
|
1269
|
-
* This will also create a default agent profile if none exists.
|
|
1270
|
-
*/
|
|
1271
|
-
private async getAllAgentsAndSessionsByUser(
|
|
1272
|
-
userId: string
|
|
1273
|
-
): Promise<[SessionData[], TeamInfo[], SavedAgentProfile[]]> {
|
|
1274
|
-
const [userSessions, teamInfos, userAgents] = await Promise.all([
|
|
1275
|
-
this.db.getUserSessions(userId),
|
|
1276
|
-
this.db.getTeamInfosByUser(userId),
|
|
1277
|
-
this.agentProfilesGetByUserOrDefault(userId),
|
|
1278
|
-
]);
|
|
1279
|
-
|
|
1280
|
-
return [userSessions, teamInfos, userAgents];
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
private async agentProfilesGetByUserOrDefault(
|
|
1284
|
-
userId: string
|
|
1285
|
-
): Promise<SavedAgentProfile[]> {
|
|
1286
|
-
const agentProfiles = await this.db.agentProfilesGetByUser(userId);
|
|
1287
|
-
if (agentProfiles.length === 0) {
|
|
1288
|
-
const profileName = DEFAULT_AGENT_PROFILE_NAME;
|
|
1289
|
-
const profile = await this.db.createAgentProfile(
|
|
1290
|
-
userId,
|
|
1291
|
-
undefined,
|
|
1292
|
-
profileName,
|
|
1293
|
-
DEFAULT_AGENT_PROFILE
|
|
1294
|
-
);
|
|
1295
|
-
if (!profile) {
|
|
1296
|
-
throw new Error(`No such agent profile: ${profileName}`);
|
|
1297
|
-
}
|
|
1298
|
-
return [profile];
|
|
1299
|
-
}
|
|
1300
|
-
return agentProfiles;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Validates that an agent profile exists in the database.
|
|
1305
|
-
*/
|
|
1306
|
-
private async validateSavedAgentProfile(
|
|
1307
|
-
agentProfileId: string
|
|
1308
|
-
): Promise<SavedAgentProfile> {
|
|
1309
|
-
const savedAgentProfile =
|
|
1310
|
-
await this.db.getSavedAgentProfileById(agentProfileId);
|
|
1311
|
-
if (!savedAgentProfile) {
|
|
1312
|
-
throw new ChatFatalError(`No such agent profile: ${agentProfileId}`);
|
|
1313
|
-
}
|
|
1314
|
-
return savedAgentProfile;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
/**
|
|
1318
|
-
* Validates that a user has permission to access a session.
|
|
1319
|
-
*/
|
|
1320
|
-
async validateSessionAccess(
|
|
1321
|
-
sessionId: string,
|
|
1322
|
-
userId: string,
|
|
1323
|
-
accessType?: "participant" | "owner"
|
|
1324
|
-
): Promise<boolean> {
|
|
1325
|
-
const session = this.openSessions.get(sessionId);
|
|
1326
|
-
if (session) {
|
|
1327
|
-
// in memory session
|
|
1328
|
-
const participants = session.getParticipants();
|
|
1329
|
-
const role = participants.get(userId);
|
|
1330
|
-
if (!role) {
|
|
1331
|
-
// Check for guest access
|
|
1332
|
-
const guest = this.guests.get(userId);
|
|
1333
|
-
if (guest && guest.guest_for_session === sessionId) {
|
|
1334
|
-
return !accessType || accessType === "participant";
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Not authorized to join the session
|
|
1338
|
-
return false;
|
|
1339
|
-
} else {
|
|
1340
|
-
return !accessType || role.role === accessType;
|
|
1341
|
-
}
|
|
1342
|
-
} else {
|
|
1343
|
-
// fetch the session from database
|
|
1344
|
-
const participants = await this.db.sessionGetParticipants(sessionId);
|
|
1345
|
-
const role = participants.find((p) => p.user_uuid === userId)?.role;
|
|
1346
|
-
if (!role) {
|
|
1347
|
-
// Check for guest access
|
|
1348
|
-
const guest = this.guests.get(userId);
|
|
1349
|
-
logger.info(`[validateSessionAccess] guest: ${JSON.stringify(guest)}`);
|
|
1350
|
-
if (guest && guest.guest_for_session === sessionId) {
|
|
1351
|
-
return !accessType || accessType === "participant";
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
return false;
|
|
1355
|
-
} else {
|
|
1356
|
-
return !accessType || role === accessType;
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
/**
|
|
1362
|
-
* Validates that a user has permission to access a team.
|
|
1363
|
-
*/
|
|
1364
|
-
private async validateTeamAccess(
|
|
1365
|
-
teamId: string,
|
|
1366
|
-
userId: string,
|
|
1367
|
-
accessType?: "participant" | "owner"
|
|
1368
|
-
): Promise<boolean> {
|
|
1369
|
-
const participants = await this.db.teamGetMembers(teamId);
|
|
1370
|
-
const role = participants.find((p) => p.user_uuid === userId)?.role;
|
|
1371
|
-
if (!role) {
|
|
1372
|
-
return false;
|
|
1373
|
-
} else {
|
|
1374
|
-
return !accessType || role === accessType;
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
* Read from DB if the session is not active
|
|
1380
|
-
*/
|
|
1381
|
-
private async getSessionDescriptor(
|
|
1382
|
-
sessionId: string
|
|
1383
|
-
): Promise<SessionDescriptor | undefined> {
|
|
1384
|
-
const session = this.openSessions.get(sessionId);
|
|
1385
|
-
if (session) {
|
|
1386
|
-
return session.getDescriptor();
|
|
1387
|
-
} else {
|
|
1388
|
-
// fetch the session from database
|
|
1389
|
-
return this.db.sessionGetDescriptorById(sessionId);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
export function userSessionCreateData(
|
|
1395
|
-
ownerId: string,
|
|
1396
|
-
title: string,
|
|
1397
|
-
agentProfileId: string
|
|
1398
|
-
): SessionCreateData {
|
|
1399
|
-
return {
|
|
1400
|
-
session_uuid: Database.sessionNewUUID(),
|
|
1401
|
-
title,
|
|
1402
|
-
team_uuid: undefined,
|
|
1403
|
-
agent_profile_uuid: agentProfileId,
|
|
1404
|
-
user_uuid: ownerId,
|
|
1405
|
-
agent_paused: false,
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
export function teamSessionCreateData(
|
|
1410
|
-
teamId: string,
|
|
1411
|
-
participants: TeamParticipant[],
|
|
1412
|
-
ownerId: string,
|
|
1413
|
-
title: string,
|
|
1414
|
-
agentProfileId: string
|
|
1415
|
-
): SessionCreateData {
|
|
1416
|
-
return {
|
|
1417
|
-
session_uuid: Database.sessionNewUUID(),
|
|
1418
|
-
title,
|
|
1419
|
-
team_uuid: teamId,
|
|
1420
|
-
participants,
|
|
1421
|
-
agent_profile_uuid: agentProfileId,
|
|
1422
|
-
user_uuid: ownerId,
|
|
1423
|
-
agent_paused: false,
|
|
1424
|
-
};
|
|
1425
|
-
}
|