@xalia/agent 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -8
- package/dist/agent/src/agent/agent.js +173 -96
- package/dist/agent/src/agent/agentUtils.js +82 -53
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/iAgentEventHandler.js +2 -0
- package/dist/agent/src/agent/mcpServerManager.js +22 -23
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +12 -7
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +11 -9
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/{websocket.js → utils/websocket.js} +2 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +262 -90
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +21 -18
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +12 -11
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +14 -12
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +235 -58
- package/dist/agent/src/tool/commandPrompt.js +15 -9
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +270 -135
- package/src/agent/agentUtils.ts +136 -95
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +32 -30
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +12 -6
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +13 -11
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/{websocket.ts → utils/websocket.ts} +2 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +351 -103
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +19 -14
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +11 -10
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +22 -15
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +283 -100
- package/src/tool/commandPrompt.ts +25 -9
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -310
- package/dist/agent/src/chat/conversationManager.js +0 -502
- package/dist/agent/src/chat/db.js +0 -218
- package/dist/agent/src/chat/messages.js +0 -29
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -445
- package/src/chat/conversationManager.ts +0 -730
- package/src/chat/db.ts +0 -304
- package/src/chat/messages.ts +0 -266
- package/src/chat/server.ts +0 -177
- /package/{frog.png → test_data/frog.png} +0 -0
|
@@ -0,0 +1,1332 @@
|
|
|
1
|
+
import { strict as assert } from "assert";
|
|
2
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AgentPreferences,
|
|
6
|
+
Configuration,
|
|
7
|
+
getLogger,
|
|
8
|
+
McpServerSettings,
|
|
9
|
+
prefsGetAutoApprove,
|
|
10
|
+
prefsSetAutoApprove,
|
|
11
|
+
SavedAgentProfile,
|
|
12
|
+
} from "@xalia/xmcp/sdk";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
Agent,
|
|
16
|
+
ChatCompletionAssistantMessageParam,
|
|
17
|
+
ChatCompletionMessageToolCall,
|
|
18
|
+
ChatCompletionToolMessageParam,
|
|
19
|
+
createUserMessage,
|
|
20
|
+
} from "../../agent/agent";
|
|
21
|
+
import { SkillManager } from "../../agent/sudoMcpServerManager";
|
|
22
|
+
import { McpServerInfo } from "../../agent/mcpServerManager";
|
|
23
|
+
import { IAgentEventHandler } from "../../agent/iAgentEventHandler";
|
|
24
|
+
import {
|
|
25
|
+
createAgentWithoutSkills,
|
|
26
|
+
DEFAULT_LLM_MODEL,
|
|
27
|
+
} from "../../agent/agentUtils";
|
|
28
|
+
import { IPlatform } from "../../agent/iplatform";
|
|
29
|
+
|
|
30
|
+
import type {
|
|
31
|
+
ServerToClient,
|
|
32
|
+
ServerMcpServerAdded,
|
|
33
|
+
ServerMcpServerRemoved,
|
|
34
|
+
ServerMcpServerToolEnabled,
|
|
35
|
+
ServerMcpServerToolDisabled,
|
|
36
|
+
ServerSystemPromptUpdated,
|
|
37
|
+
ServerModelUpdated,
|
|
38
|
+
ServerUserMessage,
|
|
39
|
+
ClientUserMessage,
|
|
40
|
+
ServerToolAutoApprovalSet,
|
|
41
|
+
ClientSessionMessage,
|
|
42
|
+
ServerSessionError,
|
|
43
|
+
ServerUserAdded,
|
|
44
|
+
ServerUserRemoved,
|
|
45
|
+
ServerSessionInfo,
|
|
46
|
+
ServerSessionUpdate,
|
|
47
|
+
ClientSetWorkspace,
|
|
48
|
+
ClientSetMcpServerConfig,
|
|
49
|
+
} from "../protocol/messages";
|
|
50
|
+
import { AsyncQueue } from "../utils/asyncQueue";
|
|
51
|
+
import { MultiAsyncQueue } from "../utils/multiAsyncQueue";
|
|
52
|
+
import {
|
|
53
|
+
SessionCheckpoint,
|
|
54
|
+
SessionMessage,
|
|
55
|
+
SessionParticipantMap,
|
|
56
|
+
SessionData,
|
|
57
|
+
TeamParticipant,
|
|
58
|
+
SessionCreateData,
|
|
59
|
+
UserMessageData,
|
|
60
|
+
} from "../data/dataModels";
|
|
61
|
+
import {
|
|
62
|
+
Database,
|
|
63
|
+
createSessionParticipantMap,
|
|
64
|
+
UserData,
|
|
65
|
+
} from "../data/database";
|
|
66
|
+
import { ApprovalManager } from "../utils/approvalManager";
|
|
67
|
+
import { ChatErrorMessage, ChatFatalError } from "../protocol/errors";
|
|
68
|
+
import { addDefaultChatTools } from "./tools";
|
|
69
|
+
import { ChatContextManager, ICheckpointWriter } from "./chatContextManager";
|
|
70
|
+
import {
|
|
71
|
+
llmUserMessageToUserMessageData,
|
|
72
|
+
MESSAGE_INDEX_START_VALUE,
|
|
73
|
+
} from "./conversation";
|
|
74
|
+
import { ChatSessionFileManager } from "./sessionFileManager";
|
|
75
|
+
import { IUserConnectionManager } from "./connectionManager";
|
|
76
|
+
import { getErrorString } from "./errorUtils";
|
|
77
|
+
import { NODE_PLATFORM } from "../../tool/nodePlatform";
|
|
78
|
+
import { DbMcpServerConfigs } from "../data/dbMcpServerConfigs";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Num messages to load at conversation startup.
|
|
82
|
+
*/
|
|
83
|
+
export const DEFAULT_NUM_MESSGAES = 500;
|
|
84
|
+
|
|
85
|
+
const logger = getLogger();
|
|
86
|
+
|
|
87
|
+
type QueuedClientMessage<
|
|
88
|
+
T extends ClientSessionMessage = ClientSessionMessage,
|
|
89
|
+
> = {
|
|
90
|
+
msg: T;
|
|
91
|
+
from: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Implementation of ICheckpointWriter that writes into the DB.
|
|
96
|
+
*/
|
|
97
|
+
class DBCheckpointWriter implements ICheckpointWriter {
|
|
98
|
+
constructor(
|
|
99
|
+
private db: Database,
|
|
100
|
+
private sessionId: string
|
|
101
|
+
) {}
|
|
102
|
+
|
|
103
|
+
writeCheckpoint(checkpoint: SessionCheckpoint): Promise<void> {
|
|
104
|
+
logger.info(
|
|
105
|
+
`[DBCheckpointWriter] writing (session ${this.sessionId}):\n` +
|
|
106
|
+
JSON.stringify(checkpoint)
|
|
107
|
+
);
|
|
108
|
+
return this.db.sessionCheckpointSet(this.sessionId, checkpoint);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Describes a Session (conversation) with connected participants.
|
|
114
|
+
*
|
|
115
|
+
* We maintain 2 queues - one for all incoming messages from clients, and
|
|
116
|
+
* another for user messages (to be gathered and sent to the Agent). This
|
|
117
|
+
* allows processing of user messages to depend on messages from clients
|
|
118
|
+
* (using a single queue would not allow this, since later messages could not
|
|
119
|
+
* be seen until user messages had been fully processed, which could block
|
|
120
|
+
* tool approvals and other interactions).
|
|
121
|
+
*/
|
|
122
|
+
export class OpenSession implements IAgentEventHandler, IPlatform {
|
|
123
|
+
private readonly db: Database;
|
|
124
|
+
private /* readonly */ agent: Agent;
|
|
125
|
+
private readonly sessionUUID: string;
|
|
126
|
+
private readonly teamUUID: string | undefined;
|
|
127
|
+
private readonly userUUID: string;
|
|
128
|
+
private readonly agentProfileUUID: string;
|
|
129
|
+
private readonly sessionParticipants: SessionParticipantMap;
|
|
130
|
+
private readonly agentProfilePreferences: AgentPreferences;
|
|
131
|
+
private /* readonly */ skillManager: SkillManager;
|
|
132
|
+
private readonly connectionManager: IUserConnectionManager<ServerToClient>;
|
|
133
|
+
private readonly messageQueue: AsyncQueue<QueuedClientMessage>;
|
|
134
|
+
private readonly userMessageQueue: MultiAsyncQueue<ServerUserMessage>;
|
|
135
|
+
private readonly contextManager: ChatContextManager;
|
|
136
|
+
private readonly approvalManager: ApprovalManager;
|
|
137
|
+
private readonly savedAgentProfile: SavedAgentProfile;
|
|
138
|
+
private isPersisted: boolean;
|
|
139
|
+
private sessionTitle: string;
|
|
140
|
+
private sessionUpdatedAt: string;
|
|
141
|
+
|
|
142
|
+
private constructor(
|
|
143
|
+
db: Database,
|
|
144
|
+
agent: Agent,
|
|
145
|
+
sessionData: SessionData,
|
|
146
|
+
savedAgentProfile: SavedAgentProfile,
|
|
147
|
+
isPersisted: boolean,
|
|
148
|
+
sessionParticipants: SessionParticipantMap,
|
|
149
|
+
agentProfilePreferences: AgentPreferences,
|
|
150
|
+
skillManager: SkillManager,
|
|
151
|
+
contextManager: ChatContextManager,
|
|
152
|
+
connectionManager: IUserConnectionManager<ServerToClient>,
|
|
153
|
+
approvalManager: ApprovalManager = new ApprovalManager()
|
|
154
|
+
) {
|
|
155
|
+
this.db = db;
|
|
156
|
+
this.agent = agent;
|
|
157
|
+
this.sessionUUID = sessionData.session_uuid;
|
|
158
|
+
this.teamUUID = sessionData.team_uuid;
|
|
159
|
+
this.userUUID = sessionData.user_uuid;
|
|
160
|
+
this.agentProfileUUID = sessionData.agent_profile_uuid;
|
|
161
|
+
this.sessionParticipants = sessionParticipants;
|
|
162
|
+
this.agentProfilePreferences = agentProfilePreferences;
|
|
163
|
+
this.skillManager = skillManager;
|
|
164
|
+
this.connectionManager = connectionManager;
|
|
165
|
+
this.messageQueue = new AsyncQueue<QueuedClientMessage>((m) =>
|
|
166
|
+
this.processMessage(m)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
this.userMessageQueue = new MultiAsyncQueue<ServerUserMessage>((m) =>
|
|
170
|
+
this.processUserMessages(m)
|
|
171
|
+
);
|
|
172
|
+
this.contextManager = contextManager;
|
|
173
|
+
this.approvalManager = approvalManager;
|
|
174
|
+
this.savedAgentProfile = savedAgentProfile;
|
|
175
|
+
this.isPersisted = isPersisted;
|
|
176
|
+
this.sessionTitle = sessionData.title;
|
|
177
|
+
this.sessionUpdatedAt = sessionData.updated_at;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private static async init(
|
|
181
|
+
db: Database,
|
|
182
|
+
sessionData: SessionData,
|
|
183
|
+
savedAgentProfile: SavedAgentProfile,
|
|
184
|
+
sessionMessages: SessionMessage[],
|
|
185
|
+
sessionParticipants: SessionParticipantMap,
|
|
186
|
+
ownerData: UserData,
|
|
187
|
+
ownerApiKey: string,
|
|
188
|
+
sessionCheckpoint: SessionCheckpoint | undefined,
|
|
189
|
+
llmUrl: string,
|
|
190
|
+
xmcpUrl: string,
|
|
191
|
+
connectionManager: IUserConnectionManager<ServerToClient>
|
|
192
|
+
): Promise<OpenSession> {
|
|
193
|
+
const sessionId = sessionData.session_uuid;
|
|
194
|
+
const fileManager = new ChatSessionFileManager(sessionId, db);
|
|
195
|
+
const contextManager = new ChatContextManager(
|
|
196
|
+
savedAgentProfile.profile.system_prompt,
|
|
197
|
+
sessionMessages,
|
|
198
|
+
sessionId,
|
|
199
|
+
ownerData.uuid,
|
|
200
|
+
sessionCheckpoint,
|
|
201
|
+
llmUrl,
|
|
202
|
+
savedAgentProfile.profile.model || DEFAULT_LLM_MODEL,
|
|
203
|
+
ownerApiKey,
|
|
204
|
+
new DBCheckpointWriter(db, sessionId),
|
|
205
|
+
fileManager
|
|
206
|
+
);
|
|
207
|
+
if (sessionData.workspace) {
|
|
208
|
+
const ws = sessionData.workspace;
|
|
209
|
+
contextManager.setWorkspace(createUserMessage(ws.message, ws.imageB64));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const openSession = new OpenSession(
|
|
213
|
+
db,
|
|
214
|
+
{} as Agent, // Placeholder - will be replaced after agent creation
|
|
215
|
+
sessionData,
|
|
216
|
+
savedAgentProfile,
|
|
217
|
+
false /* isPersisted */,
|
|
218
|
+
sessionParticipants,
|
|
219
|
+
savedAgentProfile.preferences,
|
|
220
|
+
{} as SkillManager, // Placeholder - will be replaced after agent creation
|
|
221
|
+
contextManager,
|
|
222
|
+
connectionManager
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Initialize an empty agent (to ensure there are no callbacks before
|
|
226
|
+
// the OpenSession and client are fully set up).
|
|
227
|
+
|
|
228
|
+
const xmcpConfig = Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
229
|
+
const [agent, skillManager] = await createAgentWithoutSkills(
|
|
230
|
+
llmUrl,
|
|
231
|
+
savedAgentProfile.profile,
|
|
232
|
+
openSession,
|
|
233
|
+
openSession,
|
|
234
|
+
contextManager,
|
|
235
|
+
ownerApiKey,
|
|
236
|
+
xmcpConfig,
|
|
237
|
+
undefined,
|
|
238
|
+
true
|
|
239
|
+
);
|
|
240
|
+
await addDefaultChatTools(agent, ownerData.timezone, openSession);
|
|
241
|
+
|
|
242
|
+
// Update OpenSession with real agent and skillManager
|
|
243
|
+
openSession.agent = agent;
|
|
244
|
+
openSession.skillManager = skillManager;
|
|
245
|
+
|
|
246
|
+
await openSession.restoreMcpSettings(
|
|
247
|
+
savedAgentProfile.profile.mcp_settings
|
|
248
|
+
);
|
|
249
|
+
return openSession;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static async initWithEmptySession(
|
|
253
|
+
db: Database,
|
|
254
|
+
sessionData: SessionData,
|
|
255
|
+
llmUrl: string,
|
|
256
|
+
xmcpUrl: string,
|
|
257
|
+
connectionManager: IUserConnectionManager<ServerToClient>
|
|
258
|
+
): Promise<OpenSession> {
|
|
259
|
+
const sessionMessages: SessionMessage[] = [];
|
|
260
|
+
const sessionCheckpoint = undefined;
|
|
261
|
+
|
|
262
|
+
const { savedAgentProfile, ownerData, ownerApiKey } =
|
|
263
|
+
await loadDataForEmptySession(
|
|
264
|
+
db,
|
|
265
|
+
sessionData.agent_profile_uuid,
|
|
266
|
+
sessionData.user_uuid
|
|
267
|
+
);
|
|
268
|
+
if (!ownerApiKey) {
|
|
269
|
+
throw new ChatErrorMessage("failed finding owners default api key");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const sessionParticipants = new Map<string, TeamParticipant>();
|
|
273
|
+
sessionParticipants.set(sessionData.user_uuid, {
|
|
274
|
+
user_uuid: sessionData.user_uuid,
|
|
275
|
+
nickname: ownerData.nickname || "",
|
|
276
|
+
email: ownerData.email,
|
|
277
|
+
role: "owner",
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return OpenSession.init(
|
|
281
|
+
db,
|
|
282
|
+
sessionData,
|
|
283
|
+
savedAgentProfile,
|
|
284
|
+
sessionMessages,
|
|
285
|
+
sessionParticipants,
|
|
286
|
+
ownerData,
|
|
287
|
+
ownerApiKey,
|
|
288
|
+
sessionCheckpoint,
|
|
289
|
+
llmUrl,
|
|
290
|
+
xmcpUrl,
|
|
291
|
+
connectionManager
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static async initWithExistingSession(
|
|
296
|
+
db: Database,
|
|
297
|
+
sessionId: string,
|
|
298
|
+
llmUrl: string,
|
|
299
|
+
xmcpUrl: string,
|
|
300
|
+
connectionManager: IUserConnectionManager<ServerToClient>
|
|
301
|
+
): Promise<OpenSession> {
|
|
302
|
+
// Load session data from database
|
|
303
|
+
const {
|
|
304
|
+
sessionData,
|
|
305
|
+
sessionMessages,
|
|
306
|
+
sessionCheckpoint,
|
|
307
|
+
savedAgentProfile,
|
|
308
|
+
sessionParticipants,
|
|
309
|
+
ownerData,
|
|
310
|
+
ownerApiKey,
|
|
311
|
+
} = await loadSessionData(db, sessionId);
|
|
312
|
+
return OpenSession.init(
|
|
313
|
+
db,
|
|
314
|
+
sessionData,
|
|
315
|
+
savedAgentProfile,
|
|
316
|
+
sessionMessages,
|
|
317
|
+
sessionParticipants,
|
|
318
|
+
ownerData,
|
|
319
|
+
ownerApiKey,
|
|
320
|
+
sessionCheckpoint,
|
|
321
|
+
llmUrl,
|
|
322
|
+
xmcpUrl,
|
|
323
|
+
connectionManager
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
onEmpty(): void {
|
|
328
|
+
logger.info(`[OpenSession.onEmpty] session ${this.sessionUUID}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
getParticipants(): SessionParticipantMap {
|
|
332
|
+
return this.sessionParticipants;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
sendSessionData(connectionId: string, clientMessageId: string): void {
|
|
336
|
+
logger.info(
|
|
337
|
+
`[SessionRegistry] sending session data for session ${this.sessionUUID}`
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const sessionInfo = this.serverSessionInfo(clientMessageId);
|
|
341
|
+
this.connectionManager.sendToConnection(connectionId, sessionInfo);
|
|
342
|
+
|
|
343
|
+
// send conversation history
|
|
344
|
+
const conversationMessages = this.contextManager.getConversationMessages();
|
|
345
|
+
conversationMessages.forEach((message) => {
|
|
346
|
+
this.connectionManager.sendToConnection(connectionId, message);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// add MCP settings
|
|
350
|
+
this.sendMcpSettings(connectionId);
|
|
351
|
+
|
|
352
|
+
// add system prompt and model
|
|
353
|
+
const agentProfile = this.agent.getAgentProfile();
|
|
354
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
355
|
+
type: "system_prompt_updated",
|
|
356
|
+
system_prompt: agentProfile.system_prompt,
|
|
357
|
+
session_id: this.sessionUUID,
|
|
358
|
+
});
|
|
359
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
360
|
+
type: "model_updated",
|
|
361
|
+
model: agentProfile.model || "",
|
|
362
|
+
session_id: this.sessionUUID,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Restore MCP settings from the database. We only broadcast when
|
|
368
|
+
* error occurs. The sending of MCP settings is handled by the
|
|
369
|
+
* the callers of `getMcpSettings` method.
|
|
370
|
+
* @param mcpServerSettings - MCP server settings from the database.
|
|
371
|
+
*/
|
|
372
|
+
async restoreMcpSettings(
|
|
373
|
+
mcpServerSettings: McpServerSettings
|
|
374
|
+
): Promise<void> {
|
|
375
|
+
// TODO: SkillManager.restoreSessings could support a callback to handle
|
|
376
|
+
// the errors, then we don't need to reimplement the logic.
|
|
377
|
+
|
|
378
|
+
// Add each server. We handle any errors so that conversations may
|
|
379
|
+
// continue even if not all MCP servers can be enabled (e.g. if the owner
|
|
380
|
+
// is not present to refresh an oauth token).
|
|
381
|
+
logger.info(
|
|
382
|
+
`[OpenSession] restoring MCP settings: ` +
|
|
383
|
+
JSON.stringify(mcpServerSettings)
|
|
384
|
+
);
|
|
385
|
+
const skillManager = this.skillManager;
|
|
386
|
+
for (const server_name in mcpServerSettings) {
|
|
387
|
+
try {
|
|
388
|
+
// TODO: the "[] => all-tools" logic is repeated here, in
|
|
389
|
+
// `sendSessionData` and in McpServerSettings. SkillManager could
|
|
390
|
+
// expose a method `expandMcpServerSettings` which removes missing
|
|
391
|
+
// elements and expands
|
|
392
|
+
|
|
393
|
+
// Handle the case where the mcp server no longer exists
|
|
394
|
+
if (!skillManager.hasServerBrief(server_name)) {
|
|
395
|
+
throw new Error(`unknown mcp server: ${server_name}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Handle `[]` meaning "all tools"
|
|
399
|
+
let enabled_tools = mcpServerSettings[server_name];
|
|
400
|
+
if (enabled_tools.length === 0) {
|
|
401
|
+
const allTools = await skillManager.getServerTools(server_name);
|
|
402
|
+
enabled_tools = allTools.map((t) => t.name);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
await skillManager.addMcpServer(server_name, false);
|
|
406
|
+
for (const enabled_tool of enabled_tools) {
|
|
407
|
+
skillManager.enableTool(server_name, enabled_tool);
|
|
408
|
+
}
|
|
409
|
+
} catch (e) {
|
|
410
|
+
this.broadcast({
|
|
411
|
+
type: "session_error",
|
|
412
|
+
message: `Error adding MCP server ${server_name}: ${String(e)}`,
|
|
413
|
+
session_id: this.sessionUUID,
|
|
414
|
+
});
|
|
415
|
+
logger.error(`Error adding MCP server ${server_name}: ${String(e)}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Intended to be called within specific `catch` blocks, defining a
|
|
422
|
+
* consistent error handling approach. If the exception can be handled it
|
|
423
|
+
* is and `true` is returned. Otherwise false is returned (and the caller
|
|
424
|
+
* is expected to re-throw if they are unable to handle it).
|
|
425
|
+
*/
|
|
426
|
+
private handleError(err: unknown, from?: string): boolean {
|
|
427
|
+
const sendError = (msg: ServerSessionError) => {
|
|
428
|
+
if (from) {
|
|
429
|
+
this.sendTo(from, msg);
|
|
430
|
+
} else {
|
|
431
|
+
this.broadcast(msg);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
if (err instanceof ChatFatalError) {
|
|
436
|
+
// ChatFatalError in session context should send error to specific user
|
|
437
|
+
// or broadcast if no specific user context
|
|
438
|
+
sendError({
|
|
439
|
+
type: "session_error",
|
|
440
|
+
message: err.message,
|
|
441
|
+
session_id: this.sessionUUID,
|
|
442
|
+
});
|
|
443
|
+
} else if (err instanceof ChatErrorMessage) {
|
|
444
|
+
sendError({
|
|
445
|
+
type: "session_error",
|
|
446
|
+
message: err.message,
|
|
447
|
+
session_id: this.sessionUUID,
|
|
448
|
+
});
|
|
449
|
+
} else {
|
|
450
|
+
const errString = getErrorString(err);
|
|
451
|
+
sendError({
|
|
452
|
+
type: "session_error",
|
|
453
|
+
message: errString,
|
|
454
|
+
session_id: this.sessionUUID,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private broadcast(msg: ServerToClient): void {
|
|
462
|
+
const users: Set<string> = new Set(this.sessionParticipants.keys());
|
|
463
|
+
this.connectionManager.sendToUsers(users, msg);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
sendTo(user_uuid: string, msg: ServerToClient): void {
|
|
467
|
+
this.connectionManager.sendToUsers(new Set([user_uuid]), msg);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// IPlatform.openUrl
|
|
471
|
+
openUrl(url: string, authResultP: Promise<boolean>, display_name: string) {
|
|
472
|
+
// These requests are always passed to the original owner, since it is
|
|
473
|
+
// their settings that will be used for all MCP servers.
|
|
474
|
+
this.broadcast({
|
|
475
|
+
type: "authentication_started",
|
|
476
|
+
session_id: this.sessionUUID,
|
|
477
|
+
url,
|
|
478
|
+
});
|
|
479
|
+
this.sendTo(this.userUUID, {
|
|
480
|
+
type: "authenticate",
|
|
481
|
+
session_id: this.sessionUUID,
|
|
482
|
+
url,
|
|
483
|
+
display_name,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// TODO: auth timeout
|
|
487
|
+
// Don't stall this function waiting for authentication
|
|
488
|
+
void authResultP.then((result) => {
|
|
489
|
+
this.sendTo(this.userUUID, {
|
|
490
|
+
type: "authentication_finished",
|
|
491
|
+
session_id: this.sessionUUID,
|
|
492
|
+
url,
|
|
493
|
+
result,
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// IPlatform.load
|
|
499
|
+
load(filename: string): Promise<string> {
|
|
500
|
+
if (process.env.DEVELOPMENT === "1") {
|
|
501
|
+
return NODE_PLATFORM.load(filename);
|
|
502
|
+
}
|
|
503
|
+
throw new ChatErrorMessage("Platform.load not implemented");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// IPlatform.renderHTML
|
|
507
|
+
renderHTML(html: string): Promise<void> {
|
|
508
|
+
return new Promise<void>((r) => {
|
|
509
|
+
this.broadcast({
|
|
510
|
+
type: "render_html",
|
|
511
|
+
html,
|
|
512
|
+
session_id: this.sessionUUID,
|
|
513
|
+
});
|
|
514
|
+
r();
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// IAgentEventHandler.onCompletion
|
|
519
|
+
onCompletion(result: ChatCompletionAssistantMessageParam): void {
|
|
520
|
+
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
521
|
+
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
522
|
+
this.contextManager.processAgentResponse(result);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// IAgentEventHandler.onToolCallResult
|
|
526
|
+
onToolCallResult(result: ChatCompletionToolMessageParam): void {
|
|
527
|
+
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
528
|
+
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
529
|
+
this.broadcast(toolCallMessage);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// IAgentEventHandler.onToolCall
|
|
533
|
+
async onToolCall(
|
|
534
|
+
toolCall: ChatCompletionMessageToolCall,
|
|
535
|
+
agentTool: boolean
|
|
536
|
+
): Promise<boolean> {
|
|
537
|
+
if (agentTool) {
|
|
538
|
+
// "Agent" tools are considered internal to the agent, and are always
|
|
539
|
+
// permitted. Inform all clients and immediately approve.
|
|
540
|
+
this.broadcast({
|
|
541
|
+
type: "tool_call",
|
|
542
|
+
tool_call: toolCall,
|
|
543
|
+
session_id: this.sessionUUID,
|
|
544
|
+
});
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
549
|
+
|
|
550
|
+
const [serverName, tool] = toolCall.function.name.split("__");
|
|
551
|
+
const autoApproved = prefsGetAutoApprove(
|
|
552
|
+
this.agentProfilePreferences,
|
|
553
|
+
serverName,
|
|
554
|
+
tool
|
|
555
|
+
);
|
|
556
|
+
if (!autoApproved) {
|
|
557
|
+
const { id, resultP } = this.approvalManager.startApproval(
|
|
558
|
+
toolCall.function.name
|
|
559
|
+
);
|
|
560
|
+
this.broadcast({
|
|
561
|
+
type: "approve_tool_call",
|
|
562
|
+
id,
|
|
563
|
+
tool_call: toolCall,
|
|
564
|
+
session_id: this.sessionUUID,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
logger.debug(`[OpenSession.onToolCall] awaiting approval ${id}`);
|
|
569
|
+
const { approved, auto_approve } = await resultP;
|
|
570
|
+
logger.debug(
|
|
571
|
+
`[OpenSession.onToolCall] approval ${id}: ${String(approved)}`
|
|
572
|
+
);
|
|
573
|
+
if (auto_approve) {
|
|
574
|
+
logger.debug(
|
|
575
|
+
"[OpenSession.onToolCall] auto_approve set. updated preferences"
|
|
576
|
+
);
|
|
577
|
+
const autoApprovalMsg = await this.onSetAutoApproval(
|
|
578
|
+
serverName,
|
|
579
|
+
tool,
|
|
580
|
+
true
|
|
581
|
+
);
|
|
582
|
+
if (autoApprovalMsg) {
|
|
583
|
+
this.broadcast(autoApprovalMsg);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return approved;
|
|
588
|
+
} catch (e) {
|
|
589
|
+
logger.debug(
|
|
590
|
+
`[OpenSession.onToolCall] error waiting for approval ${id}: ` +
|
|
591
|
+
String(e)
|
|
592
|
+
);
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
this.broadcast({
|
|
597
|
+
type: "tool_call",
|
|
598
|
+
tool_call: toolCall,
|
|
599
|
+
session_id: this.sessionUUID,
|
|
600
|
+
});
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// IAgentEventHandler.onAgentMessage
|
|
606
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
607
|
+
async onAgentMessage(msg: string, end: boolean): Promise<void> {
|
|
608
|
+
logger.debug(
|
|
609
|
+
`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
613
|
+
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
614
|
+
this.broadcast(agentMsgChunk);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Handle incoming session-related message from client.
|
|
619
|
+
* These messages include tool calls, MCP changes, etc.
|
|
620
|
+
* These messages are processed by `messageQueue`.
|
|
621
|
+
*/
|
|
622
|
+
onClientSessionMessage(from: string, message: ClientSessionMessage): void {
|
|
623
|
+
logger.info(
|
|
624
|
+
`[OpenSession] Message from ${from} in session ` +
|
|
625
|
+
`${this.sessionUUID}: ${JSON.stringify(message)}`
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
// Validate user is in session
|
|
629
|
+
if (!this.sessionParticipants.get(from)) {
|
|
630
|
+
logger.warn(
|
|
631
|
+
`User ${from} not in session ${this.sessionUUID} - ignoring message`
|
|
632
|
+
);
|
|
633
|
+
this.sendTo(from, {
|
|
634
|
+
type: "session_error",
|
|
635
|
+
message: "You are not a participant in this session",
|
|
636
|
+
session_id: this.sessionUUID,
|
|
637
|
+
client_message_id: message.client_message_id,
|
|
638
|
+
});
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Enqueue message for processing
|
|
643
|
+
if (!this.messageQueue.tryEnqueue({ msg: message, from })) {
|
|
644
|
+
this.sendTo(
|
|
645
|
+
from,
|
|
646
|
+
this.addSessionContext({
|
|
647
|
+
type: "session_error",
|
|
648
|
+
message: "message queue full. try again later",
|
|
649
|
+
client_message_id: message.client_message_id,
|
|
650
|
+
})
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Get MCP settings in the form of `ServerToClient` messages.
|
|
657
|
+
* This is used to restore MCP settings when a user joins a session.
|
|
658
|
+
*/
|
|
659
|
+
private sendMcpSettings(connectionId: string): void {
|
|
660
|
+
const servers = this.skillManager.getMcpServerNames();
|
|
661
|
+
servers.forEach((server_name) => {
|
|
662
|
+
const mcpServer = this.skillManager.getMcpServer(server_name);
|
|
663
|
+
const tools = mcpServer.getTools();
|
|
664
|
+
const enabled_tools = Array.from(mcpServer.getEnabledTools().keys());
|
|
665
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
666
|
+
type: "mcp_server_added",
|
|
667
|
+
server_name,
|
|
668
|
+
tools,
|
|
669
|
+
enabled_tools,
|
|
670
|
+
session_id: this.sessionUUID,
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Called once for each message.
|
|
677
|
+
*/
|
|
678
|
+
private async processMessage(
|
|
679
|
+
queuedMessage: QueuedClientMessage
|
|
680
|
+
): Promise<void> {
|
|
681
|
+
logger.debug(
|
|
682
|
+
`[onMessage]: processing (${queuedMessage.from}) ` +
|
|
683
|
+
JSON.stringify(queuedMessage.msg)
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
// In general, handlers return a message to be broadcast. Errors are
|
|
687
|
+
// handled by returning an error to just the sender. Handlers can
|
|
688
|
+
// also broadcast and send directly.
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
const msg = queuedMessage.msg;
|
|
692
|
+
let broadcastMsg: ServerToClient[] | ServerToClient | undefined =
|
|
693
|
+
undefined;
|
|
694
|
+
switch (msg.type) {
|
|
695
|
+
case "msg":
|
|
696
|
+
broadcastMsg = this.onUserMessage(msg, queuedMessage.from);
|
|
697
|
+
break;
|
|
698
|
+
case "add_mcp_server":
|
|
699
|
+
broadcastMsg = await this.onAddMcpServer(
|
|
700
|
+
msg.server_name,
|
|
701
|
+
msg.enable_all
|
|
702
|
+
);
|
|
703
|
+
break;
|
|
704
|
+
case "remove_mcp_server":
|
|
705
|
+
broadcastMsg = await this.onRemoveMcpServer(msg.server_name);
|
|
706
|
+
break;
|
|
707
|
+
case "enable_mcp_server_tool":
|
|
708
|
+
broadcastMsg = await this.onEnableMcpServerTool(
|
|
709
|
+
msg.server_name,
|
|
710
|
+
msg.tool
|
|
711
|
+
);
|
|
712
|
+
break;
|
|
713
|
+
case "disable_mcp_server_tool":
|
|
714
|
+
broadcastMsg = await this.onDisableMcpServerTool(
|
|
715
|
+
msg.server_name,
|
|
716
|
+
msg.tool
|
|
717
|
+
);
|
|
718
|
+
break;
|
|
719
|
+
case "enable_all_mcp_server_tools":
|
|
720
|
+
broadcastMsg = await this.onEnableAllMcpServerTools(msg.server_name);
|
|
721
|
+
break;
|
|
722
|
+
case "disable_all_mcp_server_tools":
|
|
723
|
+
broadcastMsg = await this.onDisableAllMcpServerTools(msg.server_name);
|
|
724
|
+
break;
|
|
725
|
+
case "tool_call_approval_result":
|
|
726
|
+
if (
|
|
727
|
+
this.approvalManager.approvalResult(
|
|
728
|
+
msg.id,
|
|
729
|
+
msg.result,
|
|
730
|
+
msg.auto_approve
|
|
731
|
+
)
|
|
732
|
+
) {
|
|
733
|
+
broadcastMsg = {
|
|
734
|
+
type: "tool_call_approval_result",
|
|
735
|
+
id: msg.id,
|
|
736
|
+
result: msg.result,
|
|
737
|
+
session_id: this.sessionUUID,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
break;
|
|
741
|
+
case "set_auto_approval":
|
|
742
|
+
broadcastMsg = await this.onSetAutoApproval(
|
|
743
|
+
msg.server_name,
|
|
744
|
+
msg.tool,
|
|
745
|
+
msg.auto_approve
|
|
746
|
+
);
|
|
747
|
+
break;
|
|
748
|
+
case "set_system_prompt":
|
|
749
|
+
broadcastMsg = await this.onSetSystemPrompt(msg.system_prompt);
|
|
750
|
+
break;
|
|
751
|
+
case "set_model":
|
|
752
|
+
broadcastMsg = await this.onSetModel(msg.model);
|
|
753
|
+
break;
|
|
754
|
+
case "set_workspace":
|
|
755
|
+
await this.onSetWorkspace(msg, queuedMessage.from);
|
|
756
|
+
break;
|
|
757
|
+
case "set_mcp_server_config":
|
|
758
|
+
await this.handleSetMcpServerConfig(msg, queuedMessage.from);
|
|
759
|
+
break;
|
|
760
|
+
default: {
|
|
761
|
+
const exhaustive: never = msg; // Error => non-exhaustive switch-case.
|
|
762
|
+
return exhaustive;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (broadcastMsg) {
|
|
767
|
+
if (broadcastMsg instanceof Array) {
|
|
768
|
+
broadcastMsg.map((msg) => {
|
|
769
|
+
this.broadcast(msg);
|
|
770
|
+
});
|
|
771
|
+
} else {
|
|
772
|
+
this.broadcast(broadcastMsg);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} catch (err: unknown) {
|
|
776
|
+
if (!this.handleError(err, queuedMessage.from)) {
|
|
777
|
+
throw err;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
private async onSetWorkspace(
|
|
783
|
+
msg: ClientSetWorkspace,
|
|
784
|
+
sender: string
|
|
785
|
+
): Promise<void> {
|
|
786
|
+
this.contextManager.setWorkspace(
|
|
787
|
+
createUserMessage(msg.message, msg.imageB64, sender)
|
|
788
|
+
);
|
|
789
|
+
const m = this.workspaceUserMessageData();
|
|
790
|
+
|
|
791
|
+
// It's possible that the session has not been written to the DB yet (if
|
|
792
|
+
// the workspace is updated before any messages are sent).
|
|
793
|
+
|
|
794
|
+
if (!this.isPersisted) {
|
|
795
|
+
await this.createSessionInDB();
|
|
796
|
+
} else {
|
|
797
|
+
await this.db.sessionUpdateWorkspace(this.sessionUUID, m);
|
|
798
|
+
}
|
|
799
|
+
assert(this.isPersisted);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
private async processUserMessages(msgs: ServerUserMessage[]): Promise<void> {
|
|
803
|
+
if (msgs.length === 0) {
|
|
804
|
+
logger.debug("ignoring empty messages");
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Begin an agent response using the accumulated user messages.
|
|
809
|
+
|
|
810
|
+
const { llmUserMessages, agentFirstChunk } =
|
|
811
|
+
this.contextManager.startAgentResponse(msgs);
|
|
812
|
+
this.broadcast(agentFirstChunk);
|
|
813
|
+
|
|
814
|
+
try {
|
|
815
|
+
await this.agent.userMessagesRaw(llmUserMessages);
|
|
816
|
+
} catch (e) {
|
|
817
|
+
logger.warn(
|
|
818
|
+
`[OpenSession.processUserMessages] agent error: ${String(e)}`
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
// Errors during agent replies must be turned into messages.
|
|
822
|
+
|
|
823
|
+
const errMsg = `error from LLM: ${String(e)}`;
|
|
824
|
+
await this.onAgentMessage(errMsg, true);
|
|
825
|
+
const err = this.contextManager.revertAgentResponse(errMsg);
|
|
826
|
+
this.broadcast(err);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const newSessionMessages = this.contextManager.endAgentResponse();
|
|
831
|
+
|
|
832
|
+
logger.debug(
|
|
833
|
+
"[processUserMessages] newSessionMessages: " +
|
|
834
|
+
JSON.stringify(newSessionMessages)
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
// Append to in-memory conversation and write to the DB
|
|
839
|
+
await this.db.sessionMessagesAppend(this.sessionUUID, newSessionMessages);
|
|
840
|
+
} catch (e) {
|
|
841
|
+
if (!this.handleError(e)) {
|
|
842
|
+
throw e;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private onUserMessage(
|
|
848
|
+
msg: ClientUserMessage,
|
|
849
|
+
from: string
|
|
850
|
+
): ServerUserMessage | undefined {
|
|
851
|
+
// Return a ServerUserMessage for broadcast. The actual message is places
|
|
852
|
+
// on a queue to be dealt with in another loop. This allows Agent
|
|
853
|
+
// processing of user messages to depend on other messages.
|
|
854
|
+
|
|
855
|
+
assert(msg);
|
|
856
|
+
assert(from);
|
|
857
|
+
|
|
858
|
+
// Assign the user message_idx and attempt to enqueue.
|
|
859
|
+
|
|
860
|
+
const userMessage = this.contextManager.processUserMessage(msg, from);
|
|
861
|
+
|
|
862
|
+
// Special case for the first message of the session
|
|
863
|
+
|
|
864
|
+
if (userMessage.message_idx === MESSAGE_INDEX_START_VALUE) {
|
|
865
|
+
// No need to wait for this to complete before broadcasting.
|
|
866
|
+
void this.onFirstMessage(userMessage);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (!this.userMessageQueue.tryEnqueue(userMessage)) {
|
|
870
|
+
// We failed to enqueue - revert the `getNextMessageIdx`
|
|
871
|
+
|
|
872
|
+
// NOTE: Nothing should await between `getNextMessageIdx` and
|
|
873
|
+
// `freeMessageIdx` here.
|
|
874
|
+
|
|
875
|
+
this.contextManager.unprocessUserMessage(userMessage);
|
|
876
|
+
|
|
877
|
+
this.sendTo(from, {
|
|
878
|
+
type: "session_error",
|
|
879
|
+
message: "failed to queue message. try again later.",
|
|
880
|
+
session_id: this.sessionUUID,
|
|
881
|
+
});
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Message is enqueued. Broadcast to all participants.
|
|
886
|
+
return userMessage;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private async createSessionInDB(): Promise<void> {
|
|
890
|
+
assert(!this.isPersisted);
|
|
891
|
+
const sessionCreateData: SessionCreateData = {
|
|
892
|
+
session_uuid: this.sessionUUID,
|
|
893
|
+
title: this.sessionTitle,
|
|
894
|
+
team_uuid: this.teamUUID,
|
|
895
|
+
agent_profile_uuid: this.agentProfileUUID,
|
|
896
|
+
workspace: this.workspaceUserMessageData(),
|
|
897
|
+
user_uuid: this.userUUID,
|
|
898
|
+
};
|
|
899
|
+
logger.info(
|
|
900
|
+
`[OpenSession.onFirstMessage] writing session ${this.sessionUUID}`
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
await this.db.sessionCreate(sessionCreateData);
|
|
904
|
+
this.isPersisted = true;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private async onFirstMessage(userMsg: ServerUserMessage): Promise<void> {
|
|
908
|
+
// Update title on the class before writing to the DB
|
|
909
|
+
|
|
910
|
+
this.sessionTitle = userMsg.message?.slice(0, 128) || "New Chat";
|
|
911
|
+
|
|
912
|
+
// The session may already have been saved (e.g. if the workspace is
|
|
913
|
+
// updated before any messages are sent).
|
|
914
|
+
|
|
915
|
+
if (!this.isPersisted) {
|
|
916
|
+
await this.createSessionInDB();
|
|
917
|
+
} else {
|
|
918
|
+
await this.db.sessionUpdateTitle(this.sessionUUID, this.sessionTitle);
|
|
919
|
+
}
|
|
920
|
+
assert(this.isPersisted);
|
|
921
|
+
|
|
922
|
+
// Broadcast the SessionUpdated message
|
|
923
|
+
|
|
924
|
+
const msg: ServerSessionUpdate = {
|
|
925
|
+
type: "session_update",
|
|
926
|
+
session_id: this.sessionUUID,
|
|
927
|
+
title: this.sessionTitle,
|
|
928
|
+
};
|
|
929
|
+
this.broadcast(msg);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
private async onAddMcpServer(
|
|
933
|
+
serverName: string,
|
|
934
|
+
enableAll: boolean
|
|
935
|
+
): Promise<ServerMcpServerAdded> {
|
|
936
|
+
logger.info(
|
|
937
|
+
`[onAddMcpServer]: Adding server ${serverName} ` +
|
|
938
|
+
`(enable_all: ${String(enableAll)})`
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
if (this.skillManager.hasMcpServer(serverName)) {
|
|
942
|
+
throw new ChatErrorMessage(`${serverName} already added`);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (!this.skillManager.hasServerBrief(serverName)) {
|
|
946
|
+
throw new ChatErrorMessage(`no such server: ${serverName}`);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
await this.skillManager.addMcpServer(serverName, enableAll);
|
|
950
|
+
this.skillManager.enableAllTools(serverName);
|
|
951
|
+
|
|
952
|
+
// Save changes to the AgentProfile
|
|
953
|
+
|
|
954
|
+
await this.updateAgentProfile();
|
|
955
|
+
|
|
956
|
+
// Broadcast the message to all participants.
|
|
957
|
+
|
|
958
|
+
const server = this.skillManager.getMcpServer(serverName);
|
|
959
|
+
const tools = server.getTools();
|
|
960
|
+
const enabled_tools = Array.from(server.getEnabledTools().keys());
|
|
961
|
+
return {
|
|
962
|
+
type: "mcp_server_added",
|
|
963
|
+
server_name: serverName,
|
|
964
|
+
tools,
|
|
965
|
+
enabled_tools,
|
|
966
|
+
session_id: this.sessionUUID,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
private async onRemoveMcpServer(
|
|
971
|
+
server_name: string
|
|
972
|
+
): Promise<ServerMcpServerRemoved> {
|
|
973
|
+
logger.info(`[onRemoveMcpServer]: Removing server ${server_name}`);
|
|
974
|
+
|
|
975
|
+
if (!this.skillManager.hasMcpServer(server_name)) {
|
|
976
|
+
throw new ChatErrorMessage(`${server_name} not enabled`);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
await this.skillManager.removeMcpServer(server_name);
|
|
980
|
+
await this.updateAgentProfile();
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
type: "mcp_server_removed",
|
|
984
|
+
server_name,
|
|
985
|
+
session_id: this.sessionUUID,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private async onEnableMcpServerTool(
|
|
990
|
+
server_name: string,
|
|
991
|
+
tool: string
|
|
992
|
+
): Promise<ServerMcpServerToolEnabled> {
|
|
993
|
+
this.ensureMcpServer(server_name);
|
|
994
|
+
this.skillManager.enableTool(server_name, tool);
|
|
995
|
+
|
|
996
|
+
await this.updateAgentProfile();
|
|
997
|
+
|
|
998
|
+
return {
|
|
999
|
+
type: "mcp_server_tool_enabled",
|
|
1000
|
+
server_name,
|
|
1001
|
+
tool,
|
|
1002
|
+
session_id: this.sessionUUID,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
private async onDisableMcpServerTool(
|
|
1007
|
+
server_name: string,
|
|
1008
|
+
tool: string
|
|
1009
|
+
): Promise<ServerMcpServerToolDisabled> {
|
|
1010
|
+
this.ensureMcpServerAndTool(server_name, tool);
|
|
1011
|
+
this.skillManager.disableTool(server_name, tool);
|
|
1012
|
+
|
|
1013
|
+
await this.updateAgentProfile();
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
type: "mcp_server_tool_disabled",
|
|
1017
|
+
server_name,
|
|
1018
|
+
tool,
|
|
1019
|
+
session_id: this.sessionUUID,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
private async onEnableAllMcpServerTools(
|
|
1024
|
+
server_name: string
|
|
1025
|
+
): Promise<ServerMcpServerToolEnabled[]> {
|
|
1026
|
+
// We reimplement the logic to enable any disabled tools so we can
|
|
1027
|
+
// construct messages along the way.
|
|
1028
|
+
|
|
1029
|
+
const server = this.ensureMcpServer(server_name);
|
|
1030
|
+
const enabledTools = server.getEnabledTools();
|
|
1031
|
+
const msgs: ServerMcpServerToolEnabled[] = [];
|
|
1032
|
+
for (const tool of server.getTools()) {
|
|
1033
|
+
if (!enabledTools.get(tool.name)) {
|
|
1034
|
+
this.skillManager.enableTool(server_name, tool.name);
|
|
1035
|
+
msgs.push({
|
|
1036
|
+
type: "mcp_server_tool_enabled",
|
|
1037
|
+
server_name,
|
|
1038
|
+
tool: tool.name,
|
|
1039
|
+
session_id: this.sessionUUID,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
await this.updateAgentProfile();
|
|
1045
|
+
|
|
1046
|
+
return msgs;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
private async onDisableAllMcpServerTools(
|
|
1050
|
+
server_name: string
|
|
1051
|
+
): Promise<ServerMcpServerToolDisabled[]> {
|
|
1052
|
+
// We reimplement the logic to disable all enabled tools so we can
|
|
1053
|
+
// construct messages along the way.
|
|
1054
|
+
|
|
1055
|
+
const server = this.ensureMcpServer(server_name);
|
|
1056
|
+
const enabledTools = server.getEnabledTools();
|
|
1057
|
+
const msgs: ServerMcpServerToolDisabled[] = [];
|
|
1058
|
+
for (const tool of enabledTools.keys()) {
|
|
1059
|
+
this.skillManager.disableTool(server_name, tool);
|
|
1060
|
+
msgs.push({
|
|
1061
|
+
type: "mcp_server_tool_disabled",
|
|
1062
|
+
server_name,
|
|
1063
|
+
tool,
|
|
1064
|
+
session_id: this.sessionUUID,
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
await this.updateAgentProfile();
|
|
1069
|
+
|
|
1070
|
+
return msgs;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
private async onSetSystemPrompt(
|
|
1074
|
+
system_prompt: string
|
|
1075
|
+
): Promise<ServerSystemPromptUpdated> {
|
|
1076
|
+
this.agent.setSystemPrompt(system_prompt);
|
|
1077
|
+
await this.updateAgentProfile();
|
|
1078
|
+
return {
|
|
1079
|
+
type: "system_prompt_updated",
|
|
1080
|
+
system_prompt,
|
|
1081
|
+
session_id: this.sessionUUID,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
private async onSetModel(model: string): Promise<ServerModelUpdated> {
|
|
1086
|
+
this.agent.setModel(model);
|
|
1087
|
+
await this.updateAgentProfile();
|
|
1088
|
+
return { type: "model_updated", model, session_id: this.sessionUUID };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
private async handleSetMcpServerConfig(
|
|
1092
|
+
msg: ClientSetMcpServerConfig,
|
|
1093
|
+
from: string
|
|
1094
|
+
): Promise<void> {
|
|
1095
|
+
if (!this.skillManager.hasServerBrief(msg.server_name)) {
|
|
1096
|
+
throw new Error(`unknown mcp server: ${msg.server_name}`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
const msc = this.db.createTypedClient(DbMcpServerConfigs);
|
|
1100
|
+
if (msg.config) {
|
|
1101
|
+
await msc.setConfigForUser(from, msg.server_name, msg.config);
|
|
1102
|
+
} else {
|
|
1103
|
+
await msc.deleteConfigForUser(from, msg.server_name);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// If the mcp server was enabled, disable and re-enable to ensure the new
|
|
1107
|
+
// settings are reflected. Note, we must track the set of enabled tools.
|
|
1108
|
+
|
|
1109
|
+
if (this.skillManager.hasMcpServer(msg.server_name)) {
|
|
1110
|
+
const enabledTools = this.skillManager
|
|
1111
|
+
.getMcpServer(msg.server_name)
|
|
1112
|
+
.getEnabledTools();
|
|
1113
|
+
await this.skillManager.removeMcpServer(msg.server_name);
|
|
1114
|
+
await this.skillManager.addMcpServer(msg.server_name, false);
|
|
1115
|
+
for (const tool of enabledTools.keys()) {
|
|
1116
|
+
this.skillManager.enableTool(msg.server_name, tool);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// TODO: Do we want to braodcast an "mcp_server_config_updated" message?
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
private async onSetAutoApproval(
|
|
1124
|
+
serverName: string,
|
|
1125
|
+
tool: string,
|
|
1126
|
+
autoApprove: boolean
|
|
1127
|
+
): Promise<ServerToolAutoApprovalSet | undefined> {
|
|
1128
|
+
if (
|
|
1129
|
+
prefsSetAutoApprove(
|
|
1130
|
+
this.agentProfilePreferences,
|
|
1131
|
+
serverName,
|
|
1132
|
+
tool,
|
|
1133
|
+
autoApprove
|
|
1134
|
+
)
|
|
1135
|
+
) {
|
|
1136
|
+
await this.db.updateAgentProfilePreferences(
|
|
1137
|
+
this.agentProfileUUID,
|
|
1138
|
+
this.agentProfilePreferences
|
|
1139
|
+
);
|
|
1140
|
+
|
|
1141
|
+
return {
|
|
1142
|
+
type: "tool_auto_approval_set",
|
|
1143
|
+
server_name: serverName,
|
|
1144
|
+
tool,
|
|
1145
|
+
auto_approve: autoApprove,
|
|
1146
|
+
session_id: this.sessionUUID,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
private ensureMcpServer(serverName: string): McpServerInfo {
|
|
1152
|
+
return this.skillManager.getMcpServer(serverName);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
private ensureMcpServerAndTool(serverName: string, toolName: string): Tool {
|
|
1156
|
+
const server = this.ensureMcpServer(serverName);
|
|
1157
|
+
const tool = server.getTool(toolName);
|
|
1158
|
+
if (!tool) {
|
|
1159
|
+
throw new ChatErrorMessage(`Tool ${toolName} on ${serverName} not found`);
|
|
1160
|
+
}
|
|
1161
|
+
return tool;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
private async updateAgentProfile(): Promise<void> {
|
|
1165
|
+
const profile = this.agent.getAgentProfile();
|
|
1166
|
+
logger.debug(
|
|
1167
|
+
`[updateAgentProfile]: uuid: ${this.agentProfileUUID} profile: ` +
|
|
1168
|
+
JSON.stringify(profile)
|
|
1169
|
+
);
|
|
1170
|
+
return this.db.updateAgentProfile(this.agentProfileUUID, profile);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Update participant in session (called by SessionRegistry).
|
|
1175
|
+
* This only updates the local participant map - actual membership
|
|
1176
|
+
* tracking is handled by SessionRegistry.
|
|
1177
|
+
*/
|
|
1178
|
+
addParticipant(userId: string, role: TeamParticipant): void {
|
|
1179
|
+
this.sessionParticipants.set(userId, role);
|
|
1180
|
+
// Broadcast result to all session participants
|
|
1181
|
+
const broadcastMessage: ServerUserAdded = {
|
|
1182
|
+
type: "user_added",
|
|
1183
|
+
user_uuid: userId,
|
|
1184
|
+
role: "participant",
|
|
1185
|
+
nickname: role.nickname,
|
|
1186
|
+
email: role.email,
|
|
1187
|
+
session_id: this.sessionUUID,
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
this.broadcast(broadcastMessage);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Remove participant from session (called by SessionRegistry).
|
|
1195
|
+
* This only updates the local participant map - actual membership
|
|
1196
|
+
* tracking is handled by SessionRegistry.
|
|
1197
|
+
*/
|
|
1198
|
+
removeParticipant(userId: string): void {
|
|
1199
|
+
this.sessionParticipants.delete(userId);
|
|
1200
|
+
// Broadcast result to all session participants
|
|
1201
|
+
const broadcastMessage: ServerUserRemoved = {
|
|
1202
|
+
type: "user_removed",
|
|
1203
|
+
user_uuid: userId,
|
|
1204
|
+
session_id: this.sessionUUID,
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
this.broadcast(broadcastMessage);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
private getSessionParticipants(): TeamParticipant[] {
|
|
1211
|
+
return Array.from(this.sessionParticipants.entries()).map(
|
|
1212
|
+
([userId, user]) => ({
|
|
1213
|
+
nickname: user.nickname,
|
|
1214
|
+
email: user.email,
|
|
1215
|
+
user_uuid: userId,
|
|
1216
|
+
role: user.role,
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
private workspaceUserMessageData(): UserMessageData | undefined {
|
|
1222
|
+
const workspaceMsg = this.contextManager.getWorkspace();
|
|
1223
|
+
return workspaceMsg
|
|
1224
|
+
? llmUserMessageToUserMessageData(workspaceMsg)
|
|
1225
|
+
: undefined;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
serverSessionInfo(clientMessageId: string): ServerSessionInfo {
|
|
1229
|
+
return {
|
|
1230
|
+
type: "session_info",
|
|
1231
|
+
owner_uuid: this.userUUID,
|
|
1232
|
+
title: this.sessionTitle,
|
|
1233
|
+
saved_agent_profile: this.savedAgentProfile,
|
|
1234
|
+
workspace: this.workspaceUserMessageData(),
|
|
1235
|
+
updated_at: this.sessionUpdatedAt,
|
|
1236
|
+
participants: this.getSessionParticipants(),
|
|
1237
|
+
mcp_server_briefs: this.skillManager.getServerBriefs(),
|
|
1238
|
+
agent_preferences: this.agentProfilePreferences,
|
|
1239
|
+
client_message_id: clientMessageId,
|
|
1240
|
+
session_id: this.sessionUUID,
|
|
1241
|
+
team_uuid: this.teamUUID,
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Helper method to add session context to messages
|
|
1246
|
+
private addSessionContext<T extends object>(
|
|
1247
|
+
msg: T
|
|
1248
|
+
): T & { session_id: string } {
|
|
1249
|
+
return {
|
|
1250
|
+
...msg,
|
|
1251
|
+
session_id: this.sessionUUID,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
async function loadDataForEmptySession(
|
|
1257
|
+
db: Database,
|
|
1258
|
+
agent_profile_uuid: string,
|
|
1259
|
+
owner_uuid: string
|
|
1260
|
+
): Promise<{
|
|
1261
|
+
savedAgentProfile: SavedAgentProfile;
|
|
1262
|
+
ownerData: UserData;
|
|
1263
|
+
ownerApiKey: string;
|
|
1264
|
+
}> {
|
|
1265
|
+
const [savedAgentProfile, ownerData, ownerApiKey] = await Promise.all([
|
|
1266
|
+
db.getSavedAgentProfileById(agent_profile_uuid),
|
|
1267
|
+
db.getUserFromUuid(owner_uuid),
|
|
1268
|
+
db.getUserApiKey(owner_uuid),
|
|
1269
|
+
]);
|
|
1270
|
+
if (!savedAgentProfile) {
|
|
1271
|
+
throw new ChatFatalError(`No such agent profile: ${agent_profile_uuid}`);
|
|
1272
|
+
}
|
|
1273
|
+
if (!ownerData) {
|
|
1274
|
+
throw new ChatFatalError(`No owner ${owner_uuid}`);
|
|
1275
|
+
}
|
|
1276
|
+
if (!ownerData.nickname) {
|
|
1277
|
+
throw new ChatFatalError(
|
|
1278
|
+
`User ${owner_uuid} has no username - cannot create session`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
if (!ownerApiKey) {
|
|
1282
|
+
throw new ChatFatalError(
|
|
1283
|
+
`User ${owner_uuid} has no api keys - cannot create session`
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return { savedAgentProfile, ownerData, ownerApiKey };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Loads session data, agent profile, and owner information from the database.
|
|
1292
|
+
*/
|
|
1293
|
+
async function loadSessionData(
|
|
1294
|
+
db: Database,
|
|
1295
|
+
sessionId: string
|
|
1296
|
+
): Promise<{
|
|
1297
|
+
sessionData: SessionData;
|
|
1298
|
+
sessionMessages: SessionMessage[];
|
|
1299
|
+
sessionCheckpoint: SessionCheckpoint | undefined;
|
|
1300
|
+
savedAgentProfile: SavedAgentProfile;
|
|
1301
|
+
ownerData: UserData;
|
|
1302
|
+
ownerApiKey: string;
|
|
1303
|
+
sessionParticipants: SessionParticipantMap;
|
|
1304
|
+
}> {
|
|
1305
|
+
const [sessionData, sessionMessages, sessionParticipants, sessionCheckpoint] =
|
|
1306
|
+
await Promise.all([
|
|
1307
|
+
db.sessionGetById(sessionId),
|
|
1308
|
+
db.sessionMessagesGetConversation(sessionId, DEFAULT_NUM_MESSGAES, 0),
|
|
1309
|
+
db.sessionGetParticipants(sessionId),
|
|
1310
|
+
db.sessionCheckpointGet(sessionId),
|
|
1311
|
+
]);
|
|
1312
|
+
if (!sessionData) {
|
|
1313
|
+
throw new ChatFatalError(`No such session: ${sessionId}`);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const { savedAgentProfile, ownerData, ownerApiKey } =
|
|
1317
|
+
await loadDataForEmptySession(
|
|
1318
|
+
db,
|
|
1319
|
+
sessionData.agent_profile_uuid,
|
|
1320
|
+
sessionData.user_uuid
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
return {
|
|
1324
|
+
sessionData,
|
|
1325
|
+
sessionMessages,
|
|
1326
|
+
sessionCheckpoint,
|
|
1327
|
+
savedAgentProfile,
|
|
1328
|
+
ownerData,
|
|
1329
|
+
ownerApiKey,
|
|
1330
|
+
sessionParticipants: createSessionParticipantMap(sessionParticipants),
|
|
1331
|
+
};
|
|
1332
|
+
}
|