@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,869 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenSession = exports.DEFAULT_NUM_MESSGAES = void 0;
|
|
4
|
+
const assert_1 = require("assert");
|
|
5
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
6
|
+
const agent_1 = require("../../agent/agent");
|
|
7
|
+
const agentUtils_1 = require("../../agent/agentUtils");
|
|
8
|
+
const asyncQueue_1 = require("../utils/asyncQueue");
|
|
9
|
+
const multiAsyncQueue_1 = require("../utils/multiAsyncQueue");
|
|
10
|
+
const database_1 = require("../data/database");
|
|
11
|
+
const approvalManager_1 = require("../utils/approvalManager");
|
|
12
|
+
const errors_1 = require("../protocol/errors");
|
|
13
|
+
const tools_1 = require("./tools");
|
|
14
|
+
const chatContextManager_1 = require("./chatContextManager");
|
|
15
|
+
const conversation_1 = require("./conversation");
|
|
16
|
+
const sessionFileManager_1 = require("./sessionFileManager");
|
|
17
|
+
const errorUtils_1 = require("./errorUtils");
|
|
18
|
+
const nodePlatform_1 = require("../../tool/nodePlatform");
|
|
19
|
+
const dbMcpServerConfigs_1 = require("../data/dbMcpServerConfigs");
|
|
20
|
+
/**
|
|
21
|
+
* Num messages to load at conversation startup.
|
|
22
|
+
*/
|
|
23
|
+
exports.DEFAULT_NUM_MESSGAES = 500;
|
|
24
|
+
const logger = (0, sdk_1.getLogger)();
|
|
25
|
+
/**
|
|
26
|
+
* Implementation of ICheckpointWriter that writes into the DB.
|
|
27
|
+
*/
|
|
28
|
+
class DBCheckpointWriter {
|
|
29
|
+
constructor(db, sessionId) {
|
|
30
|
+
this.db = db;
|
|
31
|
+
this.sessionId = sessionId;
|
|
32
|
+
}
|
|
33
|
+
writeCheckpoint(checkpoint) {
|
|
34
|
+
logger.info(`[DBCheckpointWriter] writing (session ${this.sessionId}):\n` +
|
|
35
|
+
JSON.stringify(checkpoint));
|
|
36
|
+
return this.db.sessionCheckpointSet(this.sessionId, checkpoint);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Describes a Session (conversation) with connected participants.
|
|
41
|
+
*
|
|
42
|
+
* We maintain 2 queues - one for all incoming messages from clients, and
|
|
43
|
+
* another for user messages (to be gathered and sent to the Agent). This
|
|
44
|
+
* allows processing of user messages to depend on messages from clients
|
|
45
|
+
* (using a single queue would not allow this, since later messages could not
|
|
46
|
+
* be seen until user messages had been fully processed, which could block
|
|
47
|
+
* tool approvals and other interactions).
|
|
48
|
+
*/
|
|
49
|
+
class OpenSession {
|
|
50
|
+
constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, connectionManager, approvalManager = new approvalManager_1.ApprovalManager()) {
|
|
51
|
+
this.db = db;
|
|
52
|
+
this.agent = agent;
|
|
53
|
+
this.sessionUUID = sessionData.session_uuid;
|
|
54
|
+
this.teamUUID = sessionData.team_uuid;
|
|
55
|
+
this.userUUID = sessionData.user_uuid;
|
|
56
|
+
this.agentProfileUUID = sessionData.agent_profile_uuid;
|
|
57
|
+
this.sessionParticipants = sessionParticipants;
|
|
58
|
+
this.agentProfilePreferences = agentProfilePreferences;
|
|
59
|
+
this.skillManager = skillManager;
|
|
60
|
+
this.connectionManager = connectionManager;
|
|
61
|
+
this.messageQueue = new asyncQueue_1.AsyncQueue((m) => this.processMessage(m));
|
|
62
|
+
this.userMessageQueue = new multiAsyncQueue_1.MultiAsyncQueue((m) => this.processUserMessages(m));
|
|
63
|
+
this.contextManager = contextManager;
|
|
64
|
+
this.approvalManager = approvalManager;
|
|
65
|
+
this.savedAgentProfile = savedAgentProfile;
|
|
66
|
+
this.isPersisted = isPersisted;
|
|
67
|
+
this.sessionTitle = sessionData.title;
|
|
68
|
+
this.sessionUpdatedAt = sessionData.updated_at;
|
|
69
|
+
}
|
|
70
|
+
static async init(db, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager) {
|
|
71
|
+
const sessionId = sessionData.session_uuid;
|
|
72
|
+
const fileManager = new sessionFileManager_1.ChatSessionFileManager(sessionId, db);
|
|
73
|
+
const contextManager = new chatContextManager_1.ChatContextManager(savedAgentProfile.profile.system_prompt, sessionMessages, sessionId, ownerData.uuid, sessionCheckpoint, llmUrl, savedAgentProfile.profile.model || agentUtils_1.DEFAULT_LLM_MODEL, ownerApiKey, new DBCheckpointWriter(db, sessionId), fileManager);
|
|
74
|
+
if (sessionData.workspace) {
|
|
75
|
+
const ws = sessionData.workspace;
|
|
76
|
+
contextManager.setWorkspace((0, agent_1.createUserMessage)(ws.message, ws.imageB64));
|
|
77
|
+
}
|
|
78
|
+
const openSession = new OpenSession(db, {}, // Placeholder - will be replaced after agent creation
|
|
79
|
+
sessionData, savedAgentProfile, false /* isPersisted */, sessionParticipants, savedAgentProfile.preferences, {}, // Placeholder - will be replaced after agent creation
|
|
80
|
+
contextManager, connectionManager);
|
|
81
|
+
// Initialize an empty agent (to ensure there are no callbacks before
|
|
82
|
+
// the OpenSession and client are fully set up).
|
|
83
|
+
const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
84
|
+
const [agent, skillManager] = await (0, agentUtils_1.createAgentWithoutSkills)(llmUrl, savedAgentProfile.profile, openSession, openSession, contextManager, ownerApiKey, xmcpConfig, undefined, true);
|
|
85
|
+
await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, openSession);
|
|
86
|
+
// Update OpenSession with real agent and skillManager
|
|
87
|
+
openSession.agent = agent;
|
|
88
|
+
openSession.skillManager = skillManager;
|
|
89
|
+
await openSession.restoreMcpSettings(savedAgentProfile.profile.mcp_settings);
|
|
90
|
+
return openSession;
|
|
91
|
+
}
|
|
92
|
+
static async initWithEmptySession(db, sessionData, llmUrl, xmcpUrl, connectionManager) {
|
|
93
|
+
const sessionMessages = [];
|
|
94
|
+
const sessionCheckpoint = undefined;
|
|
95
|
+
const { savedAgentProfile, ownerData, ownerApiKey } = await loadDataForEmptySession(db, sessionData.agent_profile_uuid, sessionData.user_uuid);
|
|
96
|
+
if (!ownerApiKey) {
|
|
97
|
+
throw new errors_1.ChatErrorMessage("failed finding owners default api key");
|
|
98
|
+
}
|
|
99
|
+
const sessionParticipants = new Map();
|
|
100
|
+
sessionParticipants.set(sessionData.user_uuid, {
|
|
101
|
+
user_uuid: sessionData.user_uuid,
|
|
102
|
+
nickname: ownerData.nickname || "",
|
|
103
|
+
email: ownerData.email,
|
|
104
|
+
role: "owner",
|
|
105
|
+
});
|
|
106
|
+
return OpenSession.init(db, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager);
|
|
107
|
+
}
|
|
108
|
+
static async initWithExistingSession(db, sessionId, llmUrl, xmcpUrl, connectionManager) {
|
|
109
|
+
// Load session data from database
|
|
110
|
+
const { sessionData, sessionMessages, sessionCheckpoint, savedAgentProfile, sessionParticipants, ownerData, ownerApiKey, } = await loadSessionData(db, sessionId);
|
|
111
|
+
return OpenSession.init(db, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager);
|
|
112
|
+
}
|
|
113
|
+
onEmpty() {
|
|
114
|
+
logger.info(`[OpenSession.onEmpty] session ${this.sessionUUID}`);
|
|
115
|
+
}
|
|
116
|
+
getParticipants() {
|
|
117
|
+
return this.sessionParticipants;
|
|
118
|
+
}
|
|
119
|
+
sendSessionData(connectionId, clientMessageId) {
|
|
120
|
+
logger.info(`[SessionRegistry] sending session data for session ${this.sessionUUID}`);
|
|
121
|
+
const sessionInfo = this.serverSessionInfo(clientMessageId);
|
|
122
|
+
this.connectionManager.sendToConnection(connectionId, sessionInfo);
|
|
123
|
+
// send conversation history
|
|
124
|
+
const conversationMessages = this.contextManager.getConversationMessages();
|
|
125
|
+
conversationMessages.forEach((message) => {
|
|
126
|
+
this.connectionManager.sendToConnection(connectionId, message);
|
|
127
|
+
});
|
|
128
|
+
// add MCP settings
|
|
129
|
+
this.sendMcpSettings(connectionId);
|
|
130
|
+
// add system prompt and model
|
|
131
|
+
const agentProfile = this.agent.getAgentProfile();
|
|
132
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
133
|
+
type: "system_prompt_updated",
|
|
134
|
+
system_prompt: agentProfile.system_prompt,
|
|
135
|
+
session_id: this.sessionUUID,
|
|
136
|
+
});
|
|
137
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
138
|
+
type: "model_updated",
|
|
139
|
+
model: agentProfile.model || "",
|
|
140
|
+
session_id: this.sessionUUID,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Restore MCP settings from the database. We only broadcast when
|
|
145
|
+
* error occurs. The sending of MCP settings is handled by the
|
|
146
|
+
* the callers of `getMcpSettings` method.
|
|
147
|
+
* @param mcpServerSettings - MCP server settings from the database.
|
|
148
|
+
*/
|
|
149
|
+
async restoreMcpSettings(mcpServerSettings) {
|
|
150
|
+
// TODO: SkillManager.restoreSessings could support a callback to handle
|
|
151
|
+
// the errors, then we don't need to reimplement the logic.
|
|
152
|
+
// Add each server. We handle any errors so that conversations may
|
|
153
|
+
// continue even if not all MCP servers can be enabled (e.g. if the owner
|
|
154
|
+
// is not present to refresh an oauth token).
|
|
155
|
+
logger.info(`[OpenSession] restoring MCP settings: ` +
|
|
156
|
+
JSON.stringify(mcpServerSettings));
|
|
157
|
+
const skillManager = this.skillManager;
|
|
158
|
+
for (const server_name in mcpServerSettings) {
|
|
159
|
+
try {
|
|
160
|
+
// TODO: the "[] => all-tools" logic is repeated here, in
|
|
161
|
+
// `sendSessionData` and in McpServerSettings. SkillManager could
|
|
162
|
+
// expose a method `expandMcpServerSettings` which removes missing
|
|
163
|
+
// elements and expands
|
|
164
|
+
// Handle the case where the mcp server no longer exists
|
|
165
|
+
if (!skillManager.hasServerBrief(server_name)) {
|
|
166
|
+
throw new Error(`unknown mcp server: ${server_name}`);
|
|
167
|
+
}
|
|
168
|
+
// Handle `[]` meaning "all tools"
|
|
169
|
+
let enabled_tools = mcpServerSettings[server_name];
|
|
170
|
+
if (enabled_tools.length === 0) {
|
|
171
|
+
const allTools = await skillManager.getServerTools(server_name);
|
|
172
|
+
enabled_tools = allTools.map((t) => t.name);
|
|
173
|
+
}
|
|
174
|
+
await skillManager.addMcpServer(server_name, false);
|
|
175
|
+
for (const enabled_tool of enabled_tools) {
|
|
176
|
+
skillManager.enableTool(server_name, enabled_tool);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
this.broadcast({
|
|
181
|
+
type: "session_error",
|
|
182
|
+
message: `Error adding MCP server ${server_name}: ${String(e)}`,
|
|
183
|
+
session_id: this.sessionUUID,
|
|
184
|
+
});
|
|
185
|
+
logger.error(`Error adding MCP server ${server_name}: ${String(e)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Intended to be called within specific `catch` blocks, defining a
|
|
191
|
+
* consistent error handling approach. If the exception can be handled it
|
|
192
|
+
* is and `true` is returned. Otherwise false is returned (and the caller
|
|
193
|
+
* is expected to re-throw if they are unable to handle it).
|
|
194
|
+
*/
|
|
195
|
+
handleError(err, from) {
|
|
196
|
+
const sendError = (msg) => {
|
|
197
|
+
if (from) {
|
|
198
|
+
this.sendTo(from, msg);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.broadcast(msg);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
if (err instanceof errors_1.ChatFatalError) {
|
|
205
|
+
// ChatFatalError in session context should send error to specific user
|
|
206
|
+
// or broadcast if no specific user context
|
|
207
|
+
sendError({
|
|
208
|
+
type: "session_error",
|
|
209
|
+
message: err.message,
|
|
210
|
+
session_id: this.sessionUUID,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else if (err instanceof errors_1.ChatErrorMessage) {
|
|
214
|
+
sendError({
|
|
215
|
+
type: "session_error",
|
|
216
|
+
message: err.message,
|
|
217
|
+
session_id: this.sessionUUID,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const errString = (0, errorUtils_1.getErrorString)(err);
|
|
222
|
+
sendError({
|
|
223
|
+
type: "session_error",
|
|
224
|
+
message: errString,
|
|
225
|
+
session_id: this.sessionUUID,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
broadcast(msg) {
|
|
231
|
+
const users = new Set(this.sessionParticipants.keys());
|
|
232
|
+
this.connectionManager.sendToUsers(users, msg);
|
|
233
|
+
}
|
|
234
|
+
sendTo(user_uuid, msg) {
|
|
235
|
+
this.connectionManager.sendToUsers(new Set([user_uuid]), msg);
|
|
236
|
+
}
|
|
237
|
+
// IPlatform.openUrl
|
|
238
|
+
openUrl(url, authResultP, display_name) {
|
|
239
|
+
// These requests are always passed to the original owner, since it is
|
|
240
|
+
// their settings that will be used for all MCP servers.
|
|
241
|
+
this.broadcast({
|
|
242
|
+
type: "authentication_started",
|
|
243
|
+
session_id: this.sessionUUID,
|
|
244
|
+
url,
|
|
245
|
+
});
|
|
246
|
+
this.sendTo(this.userUUID, {
|
|
247
|
+
type: "authenticate",
|
|
248
|
+
session_id: this.sessionUUID,
|
|
249
|
+
url,
|
|
250
|
+
display_name,
|
|
251
|
+
});
|
|
252
|
+
// TODO: auth timeout
|
|
253
|
+
// Don't stall this function waiting for authentication
|
|
254
|
+
void authResultP.then((result) => {
|
|
255
|
+
this.sendTo(this.userUUID, {
|
|
256
|
+
type: "authentication_finished",
|
|
257
|
+
session_id: this.sessionUUID,
|
|
258
|
+
url,
|
|
259
|
+
result,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
// IPlatform.load
|
|
264
|
+
load(filename) {
|
|
265
|
+
if (process.env.DEVELOPMENT === "1") {
|
|
266
|
+
return nodePlatform_1.NODE_PLATFORM.load(filename);
|
|
267
|
+
}
|
|
268
|
+
throw new errors_1.ChatErrorMessage("Platform.load not implemented");
|
|
269
|
+
}
|
|
270
|
+
// IPlatform.renderHTML
|
|
271
|
+
renderHTML(html) {
|
|
272
|
+
return new Promise((r) => {
|
|
273
|
+
this.broadcast({
|
|
274
|
+
type: "render_html",
|
|
275
|
+
html,
|
|
276
|
+
session_id: this.sessionUUID,
|
|
277
|
+
});
|
|
278
|
+
r();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// IAgentEventHandler.onCompletion
|
|
282
|
+
onCompletion(result) {
|
|
283
|
+
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
284
|
+
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
285
|
+
this.contextManager.processAgentResponse(result);
|
|
286
|
+
}
|
|
287
|
+
// IAgentEventHandler.onToolCallResult
|
|
288
|
+
onToolCallResult(result) {
|
|
289
|
+
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
290
|
+
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
291
|
+
this.broadcast(toolCallMessage);
|
|
292
|
+
}
|
|
293
|
+
// IAgentEventHandler.onToolCall
|
|
294
|
+
async onToolCall(toolCall, agentTool) {
|
|
295
|
+
if (agentTool) {
|
|
296
|
+
// "Agent" tools are considered internal to the agent, and are always
|
|
297
|
+
// permitted. Inform all clients and immediately approve.
|
|
298
|
+
this.broadcast({
|
|
299
|
+
type: "tool_call",
|
|
300
|
+
tool_call: toolCall,
|
|
301
|
+
session_id: this.sessionUUID,
|
|
302
|
+
});
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
306
|
+
const [serverName, tool] = toolCall.function.name.split("__");
|
|
307
|
+
const autoApproved = (0, sdk_1.prefsGetAutoApprove)(this.agentProfilePreferences, serverName, tool);
|
|
308
|
+
if (!autoApproved) {
|
|
309
|
+
const { id, resultP } = this.approvalManager.startApproval(toolCall.function.name);
|
|
310
|
+
this.broadcast({
|
|
311
|
+
type: "approve_tool_call",
|
|
312
|
+
id,
|
|
313
|
+
tool_call: toolCall,
|
|
314
|
+
session_id: this.sessionUUID,
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
logger.debug(`[OpenSession.onToolCall] awaiting approval ${id}`);
|
|
318
|
+
const { approved, auto_approve } = await resultP;
|
|
319
|
+
logger.debug(`[OpenSession.onToolCall] approval ${id}: ${String(approved)}`);
|
|
320
|
+
if (auto_approve) {
|
|
321
|
+
logger.debug("[OpenSession.onToolCall] auto_approve set. updated preferences");
|
|
322
|
+
const autoApprovalMsg = await this.onSetAutoApproval(serverName, tool, true);
|
|
323
|
+
if (autoApprovalMsg) {
|
|
324
|
+
this.broadcast(autoApprovalMsg);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return approved;
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
logger.debug(`[OpenSession.onToolCall] error waiting for approval ${id}: ` +
|
|
331
|
+
String(e));
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
this.broadcast({
|
|
337
|
+
type: "tool_call",
|
|
338
|
+
tool_call: toolCall,
|
|
339
|
+
session_id: this.sessionUUID,
|
|
340
|
+
});
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// IAgentEventHandler.onAgentMessage
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
346
|
+
async onAgentMessage(msg, end) {
|
|
347
|
+
logger.debug(`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`);
|
|
348
|
+
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
349
|
+
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
350
|
+
this.broadcast(agentMsgChunk);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Handle incoming session-related message from client.
|
|
354
|
+
* These messages include tool calls, MCP changes, etc.
|
|
355
|
+
* These messages are processed by `messageQueue`.
|
|
356
|
+
*/
|
|
357
|
+
onClientSessionMessage(from, message) {
|
|
358
|
+
logger.info(`[OpenSession] Message from ${from} in session ` +
|
|
359
|
+
`${this.sessionUUID}: ${JSON.stringify(message)}`);
|
|
360
|
+
// Validate user is in session
|
|
361
|
+
if (!this.sessionParticipants.get(from)) {
|
|
362
|
+
logger.warn(`User ${from} not in session ${this.sessionUUID} - ignoring message`);
|
|
363
|
+
this.sendTo(from, {
|
|
364
|
+
type: "session_error",
|
|
365
|
+
message: "You are not a participant in this session",
|
|
366
|
+
session_id: this.sessionUUID,
|
|
367
|
+
client_message_id: message.client_message_id,
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
// Enqueue message for processing
|
|
372
|
+
if (!this.messageQueue.tryEnqueue({ msg: message, from })) {
|
|
373
|
+
this.sendTo(from, this.addSessionContext({
|
|
374
|
+
type: "session_error",
|
|
375
|
+
message: "message queue full. try again later",
|
|
376
|
+
client_message_id: message.client_message_id,
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get MCP settings in the form of `ServerToClient` messages.
|
|
382
|
+
* This is used to restore MCP settings when a user joins a session.
|
|
383
|
+
*/
|
|
384
|
+
sendMcpSettings(connectionId) {
|
|
385
|
+
const servers = this.skillManager.getMcpServerNames();
|
|
386
|
+
servers.forEach((server_name) => {
|
|
387
|
+
const mcpServer = this.skillManager.getMcpServer(server_name);
|
|
388
|
+
const tools = mcpServer.getTools();
|
|
389
|
+
const enabled_tools = Array.from(mcpServer.getEnabledTools().keys());
|
|
390
|
+
this.connectionManager.sendToConnection(connectionId, {
|
|
391
|
+
type: "mcp_server_added",
|
|
392
|
+
server_name,
|
|
393
|
+
tools,
|
|
394
|
+
enabled_tools,
|
|
395
|
+
session_id: this.sessionUUID,
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Called once for each message.
|
|
401
|
+
*/
|
|
402
|
+
async processMessage(queuedMessage) {
|
|
403
|
+
logger.debug(`[onMessage]: processing (${queuedMessage.from}) ` +
|
|
404
|
+
JSON.stringify(queuedMessage.msg));
|
|
405
|
+
// In general, handlers return a message to be broadcast. Errors are
|
|
406
|
+
// handled by returning an error to just the sender. Handlers can
|
|
407
|
+
// also broadcast and send directly.
|
|
408
|
+
try {
|
|
409
|
+
const msg = queuedMessage.msg;
|
|
410
|
+
let broadcastMsg = undefined;
|
|
411
|
+
switch (msg.type) {
|
|
412
|
+
case "msg":
|
|
413
|
+
broadcastMsg = this.onUserMessage(msg, queuedMessage.from);
|
|
414
|
+
break;
|
|
415
|
+
case "add_mcp_server":
|
|
416
|
+
broadcastMsg = await this.onAddMcpServer(msg.server_name, msg.enable_all);
|
|
417
|
+
break;
|
|
418
|
+
case "remove_mcp_server":
|
|
419
|
+
broadcastMsg = await this.onRemoveMcpServer(msg.server_name);
|
|
420
|
+
break;
|
|
421
|
+
case "enable_mcp_server_tool":
|
|
422
|
+
broadcastMsg = await this.onEnableMcpServerTool(msg.server_name, msg.tool);
|
|
423
|
+
break;
|
|
424
|
+
case "disable_mcp_server_tool":
|
|
425
|
+
broadcastMsg = await this.onDisableMcpServerTool(msg.server_name, msg.tool);
|
|
426
|
+
break;
|
|
427
|
+
case "enable_all_mcp_server_tools":
|
|
428
|
+
broadcastMsg = await this.onEnableAllMcpServerTools(msg.server_name);
|
|
429
|
+
break;
|
|
430
|
+
case "disable_all_mcp_server_tools":
|
|
431
|
+
broadcastMsg = await this.onDisableAllMcpServerTools(msg.server_name);
|
|
432
|
+
break;
|
|
433
|
+
case "tool_call_approval_result":
|
|
434
|
+
if (this.approvalManager.approvalResult(msg.id, msg.result, msg.auto_approve)) {
|
|
435
|
+
broadcastMsg = {
|
|
436
|
+
type: "tool_call_approval_result",
|
|
437
|
+
id: msg.id,
|
|
438
|
+
result: msg.result,
|
|
439
|
+
session_id: this.sessionUUID,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case "set_auto_approval":
|
|
444
|
+
broadcastMsg = await this.onSetAutoApproval(msg.server_name, msg.tool, msg.auto_approve);
|
|
445
|
+
break;
|
|
446
|
+
case "set_system_prompt":
|
|
447
|
+
broadcastMsg = await this.onSetSystemPrompt(msg.system_prompt);
|
|
448
|
+
break;
|
|
449
|
+
case "set_model":
|
|
450
|
+
broadcastMsg = await this.onSetModel(msg.model);
|
|
451
|
+
break;
|
|
452
|
+
case "set_workspace":
|
|
453
|
+
await this.onSetWorkspace(msg, queuedMessage.from);
|
|
454
|
+
break;
|
|
455
|
+
case "set_mcp_server_config":
|
|
456
|
+
await this.handleSetMcpServerConfig(msg, queuedMessage.from);
|
|
457
|
+
break;
|
|
458
|
+
default: {
|
|
459
|
+
const exhaustive = msg; // Error => non-exhaustive switch-case.
|
|
460
|
+
return exhaustive;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (broadcastMsg) {
|
|
464
|
+
if (broadcastMsg instanceof Array) {
|
|
465
|
+
broadcastMsg.map((msg) => {
|
|
466
|
+
this.broadcast(msg);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
this.broadcast(broadcastMsg);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch (err) {
|
|
475
|
+
if (!this.handleError(err, queuedMessage.from)) {
|
|
476
|
+
throw err;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async onSetWorkspace(msg, sender) {
|
|
481
|
+
this.contextManager.setWorkspace((0, agent_1.createUserMessage)(msg.message, msg.imageB64, sender));
|
|
482
|
+
const m = this.workspaceUserMessageData();
|
|
483
|
+
// It's possible that the session has not been written to the DB yet (if
|
|
484
|
+
// the workspace is updated before any messages are sent).
|
|
485
|
+
if (!this.isPersisted) {
|
|
486
|
+
await this.createSessionInDB();
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
await this.db.sessionUpdateWorkspace(this.sessionUUID, m);
|
|
490
|
+
}
|
|
491
|
+
(0, assert_1.strict)(this.isPersisted);
|
|
492
|
+
}
|
|
493
|
+
async processUserMessages(msgs) {
|
|
494
|
+
if (msgs.length === 0) {
|
|
495
|
+
logger.debug("ignoring empty messages");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// Begin an agent response using the accumulated user messages.
|
|
499
|
+
const { llmUserMessages, agentFirstChunk } = this.contextManager.startAgentResponse(msgs);
|
|
500
|
+
this.broadcast(agentFirstChunk);
|
|
501
|
+
try {
|
|
502
|
+
await this.agent.userMessagesRaw(llmUserMessages);
|
|
503
|
+
}
|
|
504
|
+
catch (e) {
|
|
505
|
+
logger.warn(`[OpenSession.processUserMessages] agent error: ${String(e)}`);
|
|
506
|
+
// Errors during agent replies must be turned into messages.
|
|
507
|
+
const errMsg = `error from LLM: ${String(e)}`;
|
|
508
|
+
await this.onAgentMessage(errMsg, true);
|
|
509
|
+
const err = this.contextManager.revertAgentResponse(errMsg);
|
|
510
|
+
this.broadcast(err);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const newSessionMessages = this.contextManager.endAgentResponse();
|
|
514
|
+
logger.debug("[processUserMessages] newSessionMessages: " +
|
|
515
|
+
JSON.stringify(newSessionMessages));
|
|
516
|
+
try {
|
|
517
|
+
// Append to in-memory conversation and write to the DB
|
|
518
|
+
await this.db.sessionMessagesAppend(this.sessionUUID, newSessionMessages);
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
if (!this.handleError(e)) {
|
|
522
|
+
throw e;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
onUserMessage(msg, from) {
|
|
527
|
+
// Return a ServerUserMessage for broadcast. The actual message is places
|
|
528
|
+
// on a queue to be dealt with in another loop. This allows Agent
|
|
529
|
+
// processing of user messages to depend on other messages.
|
|
530
|
+
(0, assert_1.strict)(msg);
|
|
531
|
+
(0, assert_1.strict)(from);
|
|
532
|
+
// Assign the user message_idx and attempt to enqueue.
|
|
533
|
+
const userMessage = this.contextManager.processUserMessage(msg, from);
|
|
534
|
+
// Special case for the first message of the session
|
|
535
|
+
if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE) {
|
|
536
|
+
// No need to wait for this to complete before broadcasting.
|
|
537
|
+
void this.onFirstMessage(userMessage);
|
|
538
|
+
}
|
|
539
|
+
if (!this.userMessageQueue.tryEnqueue(userMessage)) {
|
|
540
|
+
// We failed to enqueue - revert the `getNextMessageIdx`
|
|
541
|
+
// NOTE: Nothing should await between `getNextMessageIdx` and
|
|
542
|
+
// `freeMessageIdx` here.
|
|
543
|
+
this.contextManager.unprocessUserMessage(userMessage);
|
|
544
|
+
this.sendTo(from, {
|
|
545
|
+
type: "session_error",
|
|
546
|
+
message: "failed to queue message. try again later.",
|
|
547
|
+
session_id: this.sessionUUID,
|
|
548
|
+
});
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
// Message is enqueued. Broadcast to all participants.
|
|
552
|
+
return userMessage;
|
|
553
|
+
}
|
|
554
|
+
async createSessionInDB() {
|
|
555
|
+
(0, assert_1.strict)(!this.isPersisted);
|
|
556
|
+
const sessionCreateData = {
|
|
557
|
+
session_uuid: this.sessionUUID,
|
|
558
|
+
title: this.sessionTitle,
|
|
559
|
+
team_uuid: this.teamUUID,
|
|
560
|
+
agent_profile_uuid: this.agentProfileUUID,
|
|
561
|
+
workspace: this.workspaceUserMessageData(),
|
|
562
|
+
user_uuid: this.userUUID,
|
|
563
|
+
};
|
|
564
|
+
logger.info(`[OpenSession.onFirstMessage] writing session ${this.sessionUUID}`);
|
|
565
|
+
await this.db.sessionCreate(sessionCreateData);
|
|
566
|
+
this.isPersisted = true;
|
|
567
|
+
}
|
|
568
|
+
async onFirstMessage(userMsg) {
|
|
569
|
+
// Update title on the class before writing to the DB
|
|
570
|
+
this.sessionTitle = userMsg.message?.slice(0, 128) || "New Chat";
|
|
571
|
+
// The session may already have been saved (e.g. if the workspace is
|
|
572
|
+
// updated before any messages are sent).
|
|
573
|
+
if (!this.isPersisted) {
|
|
574
|
+
await this.createSessionInDB();
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
await this.db.sessionUpdateTitle(this.sessionUUID, this.sessionTitle);
|
|
578
|
+
}
|
|
579
|
+
(0, assert_1.strict)(this.isPersisted);
|
|
580
|
+
// Broadcast the SessionUpdated message
|
|
581
|
+
const msg = {
|
|
582
|
+
type: "session_update",
|
|
583
|
+
session_id: this.sessionUUID,
|
|
584
|
+
title: this.sessionTitle,
|
|
585
|
+
};
|
|
586
|
+
this.broadcast(msg);
|
|
587
|
+
}
|
|
588
|
+
async onAddMcpServer(serverName, enableAll) {
|
|
589
|
+
logger.info(`[onAddMcpServer]: Adding server ${serverName} ` +
|
|
590
|
+
`(enable_all: ${String(enableAll)})`);
|
|
591
|
+
if (this.skillManager.hasMcpServer(serverName)) {
|
|
592
|
+
throw new errors_1.ChatErrorMessage(`${serverName} already added`);
|
|
593
|
+
}
|
|
594
|
+
if (!this.skillManager.hasServerBrief(serverName)) {
|
|
595
|
+
throw new errors_1.ChatErrorMessage(`no such server: ${serverName}`);
|
|
596
|
+
}
|
|
597
|
+
await this.skillManager.addMcpServer(serverName, enableAll);
|
|
598
|
+
this.skillManager.enableAllTools(serverName);
|
|
599
|
+
// Save changes to the AgentProfile
|
|
600
|
+
await this.updateAgentProfile();
|
|
601
|
+
// Broadcast the message to all participants.
|
|
602
|
+
const server = this.skillManager.getMcpServer(serverName);
|
|
603
|
+
const tools = server.getTools();
|
|
604
|
+
const enabled_tools = Array.from(server.getEnabledTools().keys());
|
|
605
|
+
return {
|
|
606
|
+
type: "mcp_server_added",
|
|
607
|
+
server_name: serverName,
|
|
608
|
+
tools,
|
|
609
|
+
enabled_tools,
|
|
610
|
+
session_id: this.sessionUUID,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async onRemoveMcpServer(server_name) {
|
|
614
|
+
logger.info(`[onRemoveMcpServer]: Removing server ${server_name}`);
|
|
615
|
+
if (!this.skillManager.hasMcpServer(server_name)) {
|
|
616
|
+
throw new errors_1.ChatErrorMessage(`${server_name} not enabled`);
|
|
617
|
+
}
|
|
618
|
+
await this.skillManager.removeMcpServer(server_name);
|
|
619
|
+
await this.updateAgentProfile();
|
|
620
|
+
return {
|
|
621
|
+
type: "mcp_server_removed",
|
|
622
|
+
server_name,
|
|
623
|
+
session_id: this.sessionUUID,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
async onEnableMcpServerTool(server_name, tool) {
|
|
627
|
+
this.ensureMcpServer(server_name);
|
|
628
|
+
this.skillManager.enableTool(server_name, tool);
|
|
629
|
+
await this.updateAgentProfile();
|
|
630
|
+
return {
|
|
631
|
+
type: "mcp_server_tool_enabled",
|
|
632
|
+
server_name,
|
|
633
|
+
tool,
|
|
634
|
+
session_id: this.sessionUUID,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async onDisableMcpServerTool(server_name, tool) {
|
|
638
|
+
this.ensureMcpServerAndTool(server_name, tool);
|
|
639
|
+
this.skillManager.disableTool(server_name, tool);
|
|
640
|
+
await this.updateAgentProfile();
|
|
641
|
+
return {
|
|
642
|
+
type: "mcp_server_tool_disabled",
|
|
643
|
+
server_name,
|
|
644
|
+
tool,
|
|
645
|
+
session_id: this.sessionUUID,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async onEnableAllMcpServerTools(server_name) {
|
|
649
|
+
// We reimplement the logic to enable any disabled tools so we can
|
|
650
|
+
// construct messages along the way.
|
|
651
|
+
const server = this.ensureMcpServer(server_name);
|
|
652
|
+
const enabledTools = server.getEnabledTools();
|
|
653
|
+
const msgs = [];
|
|
654
|
+
for (const tool of server.getTools()) {
|
|
655
|
+
if (!enabledTools.get(tool.name)) {
|
|
656
|
+
this.skillManager.enableTool(server_name, tool.name);
|
|
657
|
+
msgs.push({
|
|
658
|
+
type: "mcp_server_tool_enabled",
|
|
659
|
+
server_name,
|
|
660
|
+
tool: tool.name,
|
|
661
|
+
session_id: this.sessionUUID,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
await this.updateAgentProfile();
|
|
666
|
+
return msgs;
|
|
667
|
+
}
|
|
668
|
+
async onDisableAllMcpServerTools(server_name) {
|
|
669
|
+
// We reimplement the logic to disable all enabled tools so we can
|
|
670
|
+
// construct messages along the way.
|
|
671
|
+
const server = this.ensureMcpServer(server_name);
|
|
672
|
+
const enabledTools = server.getEnabledTools();
|
|
673
|
+
const msgs = [];
|
|
674
|
+
for (const tool of enabledTools.keys()) {
|
|
675
|
+
this.skillManager.disableTool(server_name, tool);
|
|
676
|
+
msgs.push({
|
|
677
|
+
type: "mcp_server_tool_disabled",
|
|
678
|
+
server_name,
|
|
679
|
+
tool,
|
|
680
|
+
session_id: this.sessionUUID,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
await this.updateAgentProfile();
|
|
684
|
+
return msgs;
|
|
685
|
+
}
|
|
686
|
+
async onSetSystemPrompt(system_prompt) {
|
|
687
|
+
this.agent.setSystemPrompt(system_prompt);
|
|
688
|
+
await this.updateAgentProfile();
|
|
689
|
+
return {
|
|
690
|
+
type: "system_prompt_updated",
|
|
691
|
+
system_prompt,
|
|
692
|
+
session_id: this.sessionUUID,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async onSetModel(model) {
|
|
696
|
+
this.agent.setModel(model);
|
|
697
|
+
await this.updateAgentProfile();
|
|
698
|
+
return { type: "model_updated", model, session_id: this.sessionUUID };
|
|
699
|
+
}
|
|
700
|
+
async handleSetMcpServerConfig(msg, from) {
|
|
701
|
+
if (!this.skillManager.hasServerBrief(msg.server_name)) {
|
|
702
|
+
throw new Error(`unknown mcp server: ${msg.server_name}`);
|
|
703
|
+
}
|
|
704
|
+
const msc = this.db.createTypedClient(dbMcpServerConfigs_1.DbMcpServerConfigs);
|
|
705
|
+
if (msg.config) {
|
|
706
|
+
await msc.setConfigForUser(from, msg.server_name, msg.config);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
await msc.deleteConfigForUser(from, msg.server_name);
|
|
710
|
+
}
|
|
711
|
+
// If the mcp server was enabled, disable and re-enable to ensure the new
|
|
712
|
+
// settings are reflected. Note, we must track the set of enabled tools.
|
|
713
|
+
if (this.skillManager.hasMcpServer(msg.server_name)) {
|
|
714
|
+
const enabledTools = this.skillManager
|
|
715
|
+
.getMcpServer(msg.server_name)
|
|
716
|
+
.getEnabledTools();
|
|
717
|
+
await this.skillManager.removeMcpServer(msg.server_name);
|
|
718
|
+
await this.skillManager.addMcpServer(msg.server_name, false);
|
|
719
|
+
for (const tool of enabledTools.keys()) {
|
|
720
|
+
this.skillManager.enableTool(msg.server_name, tool);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// TODO: Do we want to braodcast an "mcp_server_config_updated" message?
|
|
724
|
+
}
|
|
725
|
+
async onSetAutoApproval(serverName, tool, autoApprove) {
|
|
726
|
+
if ((0, sdk_1.prefsSetAutoApprove)(this.agentProfilePreferences, serverName, tool, autoApprove)) {
|
|
727
|
+
await this.db.updateAgentProfilePreferences(this.agentProfileUUID, this.agentProfilePreferences);
|
|
728
|
+
return {
|
|
729
|
+
type: "tool_auto_approval_set",
|
|
730
|
+
server_name: serverName,
|
|
731
|
+
tool,
|
|
732
|
+
auto_approve: autoApprove,
|
|
733
|
+
session_id: this.sessionUUID,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
ensureMcpServer(serverName) {
|
|
738
|
+
return this.skillManager.getMcpServer(serverName);
|
|
739
|
+
}
|
|
740
|
+
ensureMcpServerAndTool(serverName, toolName) {
|
|
741
|
+
const server = this.ensureMcpServer(serverName);
|
|
742
|
+
const tool = server.getTool(toolName);
|
|
743
|
+
if (!tool) {
|
|
744
|
+
throw new errors_1.ChatErrorMessage(`Tool ${toolName} on ${serverName} not found`);
|
|
745
|
+
}
|
|
746
|
+
return tool;
|
|
747
|
+
}
|
|
748
|
+
async updateAgentProfile() {
|
|
749
|
+
const profile = this.agent.getAgentProfile();
|
|
750
|
+
logger.debug(`[updateAgentProfile]: uuid: ${this.agentProfileUUID} profile: ` +
|
|
751
|
+
JSON.stringify(profile));
|
|
752
|
+
return this.db.updateAgentProfile(this.agentProfileUUID, profile);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Update participant in session (called by SessionRegistry).
|
|
756
|
+
* This only updates the local participant map - actual membership
|
|
757
|
+
* tracking is handled by SessionRegistry.
|
|
758
|
+
*/
|
|
759
|
+
addParticipant(userId, role) {
|
|
760
|
+
this.sessionParticipants.set(userId, role);
|
|
761
|
+
// Broadcast result to all session participants
|
|
762
|
+
const broadcastMessage = {
|
|
763
|
+
type: "user_added",
|
|
764
|
+
user_uuid: userId,
|
|
765
|
+
role: "participant",
|
|
766
|
+
nickname: role.nickname,
|
|
767
|
+
email: role.email,
|
|
768
|
+
session_id: this.sessionUUID,
|
|
769
|
+
};
|
|
770
|
+
this.broadcast(broadcastMessage);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Remove participant from session (called by SessionRegistry).
|
|
774
|
+
* This only updates the local participant map - actual membership
|
|
775
|
+
* tracking is handled by SessionRegistry.
|
|
776
|
+
*/
|
|
777
|
+
removeParticipant(userId) {
|
|
778
|
+
this.sessionParticipants.delete(userId);
|
|
779
|
+
// Broadcast result to all session participants
|
|
780
|
+
const broadcastMessage = {
|
|
781
|
+
type: "user_removed",
|
|
782
|
+
user_uuid: userId,
|
|
783
|
+
session_id: this.sessionUUID,
|
|
784
|
+
};
|
|
785
|
+
this.broadcast(broadcastMessage);
|
|
786
|
+
}
|
|
787
|
+
getSessionParticipants() {
|
|
788
|
+
return Array.from(this.sessionParticipants.entries()).map(([userId, user]) => ({
|
|
789
|
+
nickname: user.nickname,
|
|
790
|
+
email: user.email,
|
|
791
|
+
user_uuid: userId,
|
|
792
|
+
role: user.role,
|
|
793
|
+
}));
|
|
794
|
+
}
|
|
795
|
+
workspaceUserMessageData() {
|
|
796
|
+
const workspaceMsg = this.contextManager.getWorkspace();
|
|
797
|
+
return workspaceMsg
|
|
798
|
+
? (0, conversation_1.llmUserMessageToUserMessageData)(workspaceMsg)
|
|
799
|
+
: undefined;
|
|
800
|
+
}
|
|
801
|
+
serverSessionInfo(clientMessageId) {
|
|
802
|
+
return {
|
|
803
|
+
type: "session_info",
|
|
804
|
+
owner_uuid: this.userUUID,
|
|
805
|
+
title: this.sessionTitle,
|
|
806
|
+
saved_agent_profile: this.savedAgentProfile,
|
|
807
|
+
workspace: this.workspaceUserMessageData(),
|
|
808
|
+
updated_at: this.sessionUpdatedAt,
|
|
809
|
+
participants: this.getSessionParticipants(),
|
|
810
|
+
mcp_server_briefs: this.skillManager.getServerBriefs(),
|
|
811
|
+
agent_preferences: this.agentProfilePreferences,
|
|
812
|
+
client_message_id: clientMessageId,
|
|
813
|
+
session_id: this.sessionUUID,
|
|
814
|
+
team_uuid: this.teamUUID,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
// Helper method to add session context to messages
|
|
818
|
+
addSessionContext(msg) {
|
|
819
|
+
return {
|
|
820
|
+
...msg,
|
|
821
|
+
session_id: this.sessionUUID,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
exports.OpenSession = OpenSession;
|
|
826
|
+
async function loadDataForEmptySession(db, agent_profile_uuid, owner_uuid) {
|
|
827
|
+
const [savedAgentProfile, ownerData, ownerApiKey] = await Promise.all([
|
|
828
|
+
db.getSavedAgentProfileById(agent_profile_uuid),
|
|
829
|
+
db.getUserFromUuid(owner_uuid),
|
|
830
|
+
db.getUserApiKey(owner_uuid),
|
|
831
|
+
]);
|
|
832
|
+
if (!savedAgentProfile) {
|
|
833
|
+
throw new errors_1.ChatFatalError(`No such agent profile: ${agent_profile_uuid}`);
|
|
834
|
+
}
|
|
835
|
+
if (!ownerData) {
|
|
836
|
+
throw new errors_1.ChatFatalError(`No owner ${owner_uuid}`);
|
|
837
|
+
}
|
|
838
|
+
if (!ownerData.nickname) {
|
|
839
|
+
throw new errors_1.ChatFatalError(`User ${owner_uuid} has no username - cannot create session`);
|
|
840
|
+
}
|
|
841
|
+
if (!ownerApiKey) {
|
|
842
|
+
throw new errors_1.ChatFatalError(`User ${owner_uuid} has no api keys - cannot create session`);
|
|
843
|
+
}
|
|
844
|
+
return { savedAgentProfile, ownerData, ownerApiKey };
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Loads session data, agent profile, and owner information from the database.
|
|
848
|
+
*/
|
|
849
|
+
async function loadSessionData(db, sessionId) {
|
|
850
|
+
const [sessionData, sessionMessages, sessionParticipants, sessionCheckpoint] = await Promise.all([
|
|
851
|
+
db.sessionGetById(sessionId),
|
|
852
|
+
db.sessionMessagesGetConversation(sessionId, exports.DEFAULT_NUM_MESSGAES, 0),
|
|
853
|
+
db.sessionGetParticipants(sessionId),
|
|
854
|
+
db.sessionCheckpointGet(sessionId),
|
|
855
|
+
]);
|
|
856
|
+
if (!sessionData) {
|
|
857
|
+
throw new errors_1.ChatFatalError(`No such session: ${sessionId}`);
|
|
858
|
+
}
|
|
859
|
+
const { savedAgentProfile, ownerData, ownerApiKey } = await loadDataForEmptySession(db, sessionData.agent_profile_uuid, sessionData.user_uuid);
|
|
860
|
+
return {
|
|
861
|
+
sessionData,
|
|
862
|
+
sessionMessages,
|
|
863
|
+
sessionCheckpoint,
|
|
864
|
+
savedAgentProfile,
|
|
865
|
+
ownerData,
|
|
866
|
+
ownerApiKey,
|
|
867
|
+
sessionParticipants: (0, database_1.createSessionParticipantMap)(sessionParticipants),
|
|
868
|
+
};
|
|
869
|
+
}
|