@xalia/agent 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/src/agent/agent.js +16 -9
- package/dist/agent/src/agent/agentUtils.js +24 -4
- package/dist/agent/src/agent/mcpServerManager.js +19 -9
- package/dist/agent/src/agent/openAILLM.js +3 -1
- package/dist/agent/src/agent/openAILLMStreaming.js +24 -25
- package/dist/agent/src/agent/repeatLLM.js +43 -0
- package/dist/agent/src/agent/sudoMcpServerManager.js +12 -6
- package/dist/agent/src/chat/client.js +259 -36
- package/dist/agent/src/chat/conversationManager.js +243 -24
- package/dist/agent/src/chat/db.js +24 -1
- package/dist/agent/src/chat/frontendClient.js +74 -0
- package/dist/agent/src/chat/server.js +3 -3
- package/dist/agent/src/test/db.test.js +25 -2
- package/dist/agent/src/test/openaiStreaming.test.js +133 -0
- package/dist/agent/src/test/prompt.test.js +2 -2
- package/dist/agent/src/test/sudoMcpServerManager.test.js +1 -1
- package/dist/agent/src/tool/agentChat.js +7 -197
- package/dist/agent/src/tool/chatMain.js +18 -23
- package/dist/agent/src/tool/commandPrompt.js +248 -0
- package/dist/agent/src/tool/prompt.js +27 -31
- package/package.json +1 -1
- package/scripts/test_chat +17 -1
- package/src/agent/agent.ts +34 -11
- package/src/agent/agentUtils.ts +52 -3
- package/src/agent/mcpServerManager.ts +43 -13
- package/src/agent/openAILLM.ts +3 -1
- package/src/agent/openAILLMStreaming.ts +28 -27
- package/src/agent/repeatLLM.ts +51 -0
- package/src/agent/sudoMcpServerManager.ts +41 -12
- package/src/chat/client.ts +353 -40
- package/src/chat/conversationManager.ts +345 -33
- package/src/chat/db.ts +28 -2
- package/src/chat/frontendClient.ts +123 -0
- package/src/chat/messages.ts +146 -2
- package/src/chat/server.ts +3 -3
- package/src/test/db.test.ts +35 -2
- package/src/test/openaiStreaming.test.ts +142 -0
- package/src/test/prompt.test.ts +1 -1
- package/src/test/sudoMcpServerManager.test.ts +1 -1
- package/src/tool/agentChat.ts +13 -211
- package/src/tool/chatMain.ts +28 -43
- package/src/tool/commandPrompt.ts +252 -0
- package/src/tool/prompt.ts +33 -32
@@ -34,26 +34,184 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
34
34
|
})();
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
36
36
|
exports.ChatClient = void 0;
|
37
|
-
const dotenv = __importStar(require("dotenv"));
|
38
|
-
const sdk_1 = require("@xalia/xmcp/sdk");
|
39
37
|
const assert_1 = require("assert");
|
40
38
|
const websocket = __importStar(require("ws"));
|
41
|
-
|
39
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
40
|
+
const mcpServerManager_1 = require("../agent/mcpServerManager");
|
42
41
|
const logger = (0, sdk_1.getLogger)();
|
42
|
+
class RemoteMcpServerManager {
|
43
|
+
constructor(sender) {
|
44
|
+
this.sender = sender;
|
45
|
+
this.mcpServers = {};
|
46
|
+
this.mcpServers = {};
|
47
|
+
}
|
48
|
+
hasMcpServer(mcpServerName) {
|
49
|
+
return !!this.mcpServers[mcpServerName];
|
50
|
+
}
|
51
|
+
getMcpServerNames() {
|
52
|
+
return Object.keys(this.mcpServers);
|
53
|
+
}
|
54
|
+
getMcpServer(mcpServerName) {
|
55
|
+
const server = this.mcpServers[mcpServerName];
|
56
|
+
if (server) {
|
57
|
+
return server;
|
58
|
+
}
|
59
|
+
throw Error(`unknown server ${mcpServerName}`);
|
60
|
+
}
|
61
|
+
async removeMcpServer(mcpServerName) {
|
62
|
+
if (!this.mcpServers[mcpServerName]) {
|
63
|
+
throw Error(`no server ${mcpServerName} (removeMcpServer)`);
|
64
|
+
}
|
65
|
+
this.sender.sendMessage({
|
66
|
+
type: "remove_mcp_server",
|
67
|
+
server_name: mcpServerName,
|
68
|
+
});
|
69
|
+
}
|
70
|
+
enableAllTools(mcpServerName) {
|
71
|
+
if (!this.mcpServers[mcpServerName]) {
|
72
|
+
throw Error(`no server ${mcpServerName} (enableAllTools)`);
|
73
|
+
}
|
74
|
+
this.sender.sendMessage({
|
75
|
+
type: "enable_all_mcp_server_tools",
|
76
|
+
server_name: mcpServerName,
|
77
|
+
});
|
78
|
+
}
|
79
|
+
disableAllTools(mcpServerName) {
|
80
|
+
if (!this.mcpServers[mcpServerName]) {
|
81
|
+
throw Error(`no server ${mcpServerName} (disableAllTools)`);
|
82
|
+
}
|
83
|
+
this.sender.sendMessage({
|
84
|
+
type: "disable_all_mcp_server_tools",
|
85
|
+
server_name: mcpServerName,
|
86
|
+
});
|
87
|
+
}
|
88
|
+
enableTool(mcpServerName, toolName) {
|
89
|
+
const server = this.mcpServers[mcpServerName];
|
90
|
+
if (!server) {
|
91
|
+
throw Error(`no server ${mcpServerName} (enableTool)`);
|
92
|
+
}
|
93
|
+
const tools = server.getTool(toolName);
|
94
|
+
if (!tools) {
|
95
|
+
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
96
|
+
}
|
97
|
+
this.sender.sendMessage({
|
98
|
+
type: "enable_mcp_server_tool",
|
99
|
+
server_name: mcpServerName,
|
100
|
+
tool: toolName,
|
101
|
+
});
|
102
|
+
}
|
103
|
+
disableTool(mcpServerName, toolName) {
|
104
|
+
const server = this.mcpServers[mcpServerName];
|
105
|
+
if (!server) {
|
106
|
+
throw Error(`no server ${mcpServerName} (disableTool)`);
|
107
|
+
}
|
108
|
+
const tools = server.getTool(toolName);
|
109
|
+
if (!tools) {
|
110
|
+
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
111
|
+
}
|
112
|
+
this.sender.sendMessage({
|
113
|
+
type: "disable_mcp_server_tool",
|
114
|
+
server_name: mcpServerName,
|
115
|
+
tool: toolName,
|
116
|
+
});
|
117
|
+
}
|
118
|
+
onMcpServerAdded(mcpServerName, tools, enabled_tools) {
|
119
|
+
logger.debug(`[onMcpServerAdded]: ${mcpServerName}, tools: ${JSON.stringify(tools)}` +
|
120
|
+
`, enabled: ${JSON.stringify(enabled_tools)}`);
|
121
|
+
const mcpServerInfo = new mcpServerManager_1.McpServerInfoRW(tools);
|
122
|
+
for (const tool of enabled_tools) {
|
123
|
+
mcpServerInfo.enableTool(tool);
|
124
|
+
}
|
125
|
+
this.mcpServers[mcpServerName] = mcpServerInfo;
|
126
|
+
}
|
127
|
+
onMcpServerRemoved(mcpServerName) {
|
128
|
+
delete this.mcpServers[mcpServerName];
|
129
|
+
}
|
130
|
+
onMcpServerToolEnabled(mcpServerName, toolName) {
|
131
|
+
const server = this.mcpServers[mcpServerName];
|
132
|
+
if (!server) {
|
133
|
+
throw Error(`no server ${mcpServerName} (onMcpServerToolEnabled)`);
|
134
|
+
}
|
135
|
+
const tools = server.getTool(toolName);
|
136
|
+
if (!tools) {
|
137
|
+
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
138
|
+
}
|
139
|
+
server.enableTool(toolName);
|
140
|
+
}
|
141
|
+
onMcpServerToolDisabled(mcpServerName, toolName) {
|
142
|
+
const server = this.mcpServers[mcpServerName];
|
143
|
+
if (!server) {
|
144
|
+
throw Error(`no server ${mcpServerName} (onMcpServerToolDisabled)`);
|
145
|
+
}
|
146
|
+
const tools = server.getTool(toolName);
|
147
|
+
if (!tools) {
|
148
|
+
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
149
|
+
}
|
150
|
+
server.disableTool(toolName);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
class RemoteSudoMcpServerManager {
|
154
|
+
constructor(sender, briefs, msm) {
|
155
|
+
this.sender = sender;
|
156
|
+
this.briefs = briefs;
|
157
|
+
this.msm = msm;
|
158
|
+
this.briefsMap = {};
|
159
|
+
briefs.forEach((b) => {
|
160
|
+
this.briefsMap[b.name] = b;
|
161
|
+
});
|
162
|
+
}
|
163
|
+
getMcpServerManager() {
|
164
|
+
return this.msm;
|
165
|
+
}
|
166
|
+
getServerBriefs() {
|
167
|
+
return this.briefs;
|
168
|
+
}
|
169
|
+
async addMcpServer(server_name, enable_all) {
|
170
|
+
if (!this.briefsMap[server_name]) {
|
171
|
+
throw Error(`no such server ${server_name} (addMcpServer)`);
|
172
|
+
}
|
173
|
+
this.sender.sendMessage({
|
174
|
+
type: "add_mcp_server",
|
175
|
+
server_name,
|
176
|
+
enable_all,
|
177
|
+
});
|
178
|
+
}
|
179
|
+
}
|
43
180
|
class ChatClient {
|
44
|
-
constructor(ws, onMessageCB, onConnectionClosedCB) {
|
181
|
+
constructor(platform, ws, onMessageCB, onConnectionClosedCB, serverBriefs) {
|
182
|
+
this.platform = platform;
|
45
183
|
this.ws = ws;
|
46
184
|
this.onMessageCB = onMessageCB;
|
47
185
|
this.onConnectionClosedCB = onConnectionClosedCB;
|
48
186
|
this.closed = false;
|
187
|
+
this.msm = new RemoteMcpServerManager(this);
|
188
|
+
this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs, this.msm);
|
189
|
+
this.systemPrompt = "";
|
190
|
+
this.model = "";
|
49
191
|
}
|
50
|
-
static async initWithParams(host, port, token, params, onMessageCB, onConnectionClosedCB) {
|
51
|
-
return new Promise((
|
192
|
+
static async initWithParams(host, port, token, params, onMessageCB, onConnectionClosedCB, platform) {
|
193
|
+
return new Promise((resolveClient, e) => {
|
52
194
|
const urlParams = new URLSearchParams(params);
|
53
195
|
const url = `ws://${host}:${port}?${urlParams}`;
|
54
196
|
const ws = new websocket.WebSocket(url, [token]);
|
55
197
|
logger.info("created ws");
|
56
|
-
|
198
|
+
let client = undefined;
|
199
|
+
const onMsg = (msg) => {
|
200
|
+
if (msg.type === "mcp_server_briefs") {
|
201
|
+
// This should be received once, as the first message at startup.
|
202
|
+
(0, assert_1.strict)(!client);
|
203
|
+
client = new ChatClient(platform, ws, onMessageCB, onConnectionClosedCB, msg.server_briefs);
|
204
|
+
resolveClient(client);
|
205
|
+
}
|
206
|
+
else {
|
207
|
+
(0, assert_1.strict)(client);
|
208
|
+
// Pass all messages to our internal handler (to update mcp state,
|
209
|
+
// etc) before sending to the client code.
|
210
|
+
// logger.debug(`[ChatClient.init(onMsg)]: ${JSON.stringify(msg)}`);
|
211
|
+
client.handleMessageInternal(msg);
|
212
|
+
client.onMessageCB(msg);
|
213
|
+
}
|
214
|
+
};
|
57
215
|
ws.onopen = async () => {
|
58
216
|
logger.info("opened");
|
59
217
|
ws.onmessage = (ev) => {
|
@@ -63,57 +221,80 @@ class ChatClient {
|
|
63
221
|
throw `expected "string" data, got ${typeof msgData}`;
|
64
222
|
}
|
65
223
|
logger.debug(`[client.onmessage]: ${msgData}`);
|
66
|
-
|
67
|
-
client.onMessageCB(msg);
|
224
|
+
onMsg(JSON.parse(msgData));
|
68
225
|
}
|
69
226
|
catch (e) {
|
70
|
-
client
|
227
|
+
if (client) {
|
228
|
+
client.close();
|
229
|
+
}
|
71
230
|
throw e;
|
72
231
|
}
|
73
232
|
};
|
74
|
-
r(client);
|
75
233
|
};
|
76
|
-
ws.onclose = (err) => {
|
234
|
+
ws.onclose = async (err) => {
|
77
235
|
logger.info("closed");
|
78
236
|
logger.info(`[client] WebSocket connection closed: ${JSON.stringify(err)}`);
|
79
|
-
client
|
80
|
-
|
237
|
+
if (client) {
|
238
|
+
client.closed = true;
|
239
|
+
}
|
240
|
+
await onConnectionClosedCB();
|
81
241
|
};
|
82
|
-
ws.onerror = (error) => {
|
242
|
+
ws.onerror = async (error) => {
|
83
243
|
logger.error("[client] WebSocket error:", JSON.stringify(error));
|
84
244
|
e(error);
|
85
|
-
client
|
86
|
-
|
245
|
+
if (client) {
|
246
|
+
client.closed = true;
|
247
|
+
}
|
248
|
+
await onConnectionClosedCB();
|
87
249
|
};
|
88
250
|
});
|
89
251
|
}
|
90
|
-
|
91
|
-
// host: string,
|
92
|
-
// port: number,
|
93
|
-
// token: string,
|
94
|
-
// sessionId: string,
|
95
|
-
// onMessageCB: OnMessageCallback,
|
96
|
-
// onConnectionClosedCB: OnConnectionClosedCallback
|
97
|
-
// ): Promise<ChatClient> {
|
98
|
-
// return ChatClient.initWithParams(
|
99
|
-
// host,
|
100
|
-
// port,
|
101
|
-
// token,
|
102
|
-
// { session_id: sessionId },
|
103
|
-
// onMessageCB,
|
104
|
-
// onConnectionClosedCB
|
105
|
-
// );
|
106
|
-
// }
|
107
|
-
static async init(host, port, token, onMessageCB, onConnectionClosedCB, sessionId = "untitled", agentProfileId = undefined) {
|
252
|
+
static async init(host, port, token, onMessageCB, onConnectionClosedCB, platform, sessionId = "untitled", agentProfileId = undefined) {
|
108
253
|
const params = { session_id: sessionId };
|
109
254
|
if (agentProfileId) {
|
110
255
|
params["agent_profile_id"] = agentProfileId;
|
111
256
|
}
|
112
|
-
return ChatClient.initWithParams(host, port, token, params, onMessageCB, onConnectionClosedCB);
|
257
|
+
return ChatClient.initWithParams(host, port, token, params, onMessageCB, onConnectionClosedCB, platform);
|
258
|
+
}
|
259
|
+
getSudoMcpServerManager() {
|
260
|
+
return this.smsm;
|
261
|
+
}
|
262
|
+
getConversation() {
|
263
|
+
throw "unimpl";
|
264
|
+
}
|
265
|
+
getAgentProfile() {
|
266
|
+
throw "unimpl";
|
267
|
+
}
|
268
|
+
getSystemPrompt() {
|
269
|
+
return this.systemPrompt;
|
270
|
+
}
|
271
|
+
setSystemPrompt(system_prompt) {
|
272
|
+
// Don't set system prompt here. Wait until we get confirmation from the
|
273
|
+
// server.
|
274
|
+
this.sendMessage({ type: "set_system_prompt", system_prompt });
|
275
|
+
}
|
276
|
+
getModel() {
|
277
|
+
return this.model;
|
278
|
+
}
|
279
|
+
setModel(model) {
|
280
|
+
// Don't set model here. Wait until we get confirmation from the server.
|
281
|
+
this.sendMessage({ type: "set_model", model });
|
282
|
+
}
|
283
|
+
userMessage(msg, imageB64) {
|
284
|
+
(0, assert_1.strict)(msg);
|
285
|
+
(0, assert_1.strict)(!imageB64, "images not supported in Chat yet");
|
286
|
+
this.sendMessage({
|
287
|
+
type: "msg",
|
288
|
+
message: msg,
|
289
|
+
});
|
290
|
+
}
|
291
|
+
resetConversation() {
|
292
|
+
throw "resetConversation not implemented for ChatClient";
|
113
293
|
}
|
114
294
|
sendMessage(message) {
|
115
295
|
(0, assert_1.strict)(!this.closed);
|
116
296
|
const data = JSON.stringify(message);
|
297
|
+
logger.debug(`[ChatClient.sendMessage] ${data}`);
|
117
298
|
this.ws.send(data);
|
118
299
|
}
|
119
300
|
close() {
|
@@ -121,6 +302,48 @@ class ChatClient {
|
|
121
302
|
this.onConnectionClosedCB();
|
122
303
|
this.ws.close();
|
123
304
|
}
|
305
|
+
handleMessageInternal(message) {
|
306
|
+
switch (message.type) {
|
307
|
+
//
|
308
|
+
// State updates
|
309
|
+
//
|
310
|
+
case "mcp_server_added":
|
311
|
+
this.msm.onMcpServerAdded(message.server_name, message.tools, message.enabled_tools);
|
312
|
+
break;
|
313
|
+
case "mcp_server_removed":
|
314
|
+
this.msm.onMcpServerRemoved(message.server_name);
|
315
|
+
break;
|
316
|
+
case "mcp_server_tool_enabled":
|
317
|
+
this.msm.onMcpServerToolEnabled(message.server_name, message.tool);
|
318
|
+
break;
|
319
|
+
case "mcp_server_tool_disabled":
|
320
|
+
this.msm.onMcpServerToolDisabled(message.server_name, message.tool);
|
321
|
+
break;
|
322
|
+
case "system_prompt_updated":
|
323
|
+
this.systemPrompt = message.system_prompt;
|
324
|
+
break;
|
325
|
+
case "model_updated":
|
326
|
+
this.model = message.model;
|
327
|
+
break;
|
328
|
+
//
|
329
|
+
// Actions
|
330
|
+
//
|
331
|
+
case "open_url":
|
332
|
+
this.platform.openUrl(message.url, new Promise(() => { }), // TODO: Why do we need this? Remove it.
|
333
|
+
message.display_name);
|
334
|
+
break;
|
335
|
+
case "approve_tool_call":
|
336
|
+
throw "unimpl approve_tool_call";
|
337
|
+
// break;
|
338
|
+
//
|
339
|
+
// Ignore other messages - the owner (the UI layer) can handle them at
|
340
|
+
// its discretion.
|
341
|
+
//
|
342
|
+
default:
|
343
|
+
logger.debug(`[handleMessageInternal]: ignoring message: ${message.type}`);
|
344
|
+
break;
|
345
|
+
}
|
346
|
+
}
|
124
347
|
}
|
125
348
|
exports.ChatClient = ChatClient;
|
126
349
|
// TODO: remove this
|
@@ -18,9 +18,11 @@ exports.UserAlreadyConnected = UserAlreadyConnected;
|
|
18
18
|
* Describes a Session (conversation) with connected participants.
|
19
19
|
*/
|
20
20
|
class OpenSession {
|
21
|
-
constructor(agent, sudoMcpServerManager, onEmpty) {
|
22
|
-
|
21
|
+
constructor(db, agent, sessionUUID, agentProfileUUID, sudoMcpServerManager, onEmpty) {
|
22
|
+
this.db = db;
|
23
23
|
this.agent = agent;
|
24
|
+
this.sessionUUID = sessionUUID;
|
25
|
+
this.agentProfileUUID = agentProfileUUID;
|
24
26
|
this.skillManager = sudoMcpServerManager;
|
25
27
|
this.onEmpty = onEmpty;
|
26
28
|
this.connections = {};
|
@@ -34,6 +36,22 @@ class OpenSession {
|
|
34
36
|
// Inform any other participants, and add the WebSocket to the map.
|
35
37
|
this.broadcast({ type: "user_joined", user: userName });
|
36
38
|
this.connections[userName] = ws;
|
39
|
+
// TODO:
|
40
|
+
//
|
41
|
+
// - send conversation state (ServerHistory)
|
42
|
+
const briefs = this.skillManager.getServerBriefs();
|
43
|
+
this.sendTo(userName, { type: "mcp_server_briefs", server_briefs: briefs });
|
44
|
+
const msm = this.agent.getMcpServerManager();
|
45
|
+
const mcpServerNames = msm.getMcpServerNames();
|
46
|
+
for (const serverName of mcpServerNames) {
|
47
|
+
const info = msm.getMcpServer(serverName);
|
48
|
+
this.sendTo(userName, {
|
49
|
+
type: "mcp_server_added",
|
50
|
+
server_name: serverName,
|
51
|
+
tools: info.getTools(),
|
52
|
+
enabled_tools: Object.keys(info.getEnabledTools()),
|
53
|
+
});
|
54
|
+
}
|
37
55
|
ws.on("message", async (message) => {
|
38
56
|
logger.debug(`[convMgr]: got message: (from ${userName}): ${message}`);
|
39
57
|
const msgStr = message.toString();
|
@@ -57,12 +75,59 @@ class OpenSession {
|
|
57
75
|
async onMessage(queuedMessage) {
|
58
76
|
logger.debug(`[onMessage]: processing (${queuedMessage.from}) ` +
|
59
77
|
`${JSON.stringify(queuedMessage.msg)}`);
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
78
|
+
// In general, handlers return a message to be broadcast. Errors are
|
79
|
+
// handled by returning an error to just the sender. Handlers can
|
80
|
+
// also broadcast and send directly.
|
81
|
+
try {
|
82
|
+
const msg = queuedMessage.msg;
|
83
|
+
let broadcastMsg = undefined;
|
84
|
+
switch (msg.type) {
|
85
|
+
case "msg":
|
86
|
+
broadcastMsg = await this.onChatMessage(msg.message, queuedMessage.from);
|
87
|
+
break;
|
88
|
+
case "add_mcp_server":
|
89
|
+
broadcastMsg = await this.onAddMcpServer(msg.server_name, msg.enable_all);
|
90
|
+
break;
|
91
|
+
case "remove_mcp_server":
|
92
|
+
broadcastMsg = await this.onRemoveMcpServer(msg.server_name);
|
93
|
+
break;
|
94
|
+
case "enable_mcp_server_tool":
|
95
|
+
broadcastMsg = await this.onEnableMcpServerTool(msg.server_name, msg.tool);
|
96
|
+
break;
|
97
|
+
case "disable_mcp_server_tool":
|
98
|
+
broadcastMsg = await this.onDisableMcpServerTool(msg.server_name, msg.tool);
|
99
|
+
break;
|
100
|
+
case "enable_all_mcp_server_tools":
|
101
|
+
broadcastMsg = await this.onEnableAllMcpServerTools(msg.server_name);
|
102
|
+
break;
|
103
|
+
case "disable_all_mcp_server_tools":
|
104
|
+
broadcastMsg = await this.onDisableAllMcpServerTools(msg.server_name);
|
105
|
+
break;
|
106
|
+
case "set_system_prompt":
|
107
|
+
broadcastMsg = await this.onSetSystemPrompt(msg.system_prompt);
|
108
|
+
break;
|
109
|
+
case "set_model":
|
110
|
+
broadcastMsg = await this.onSetModel(msg.model);
|
111
|
+
break;
|
112
|
+
default:
|
113
|
+
throw `unknown message: ${JSON.stringify(queuedMessage)}`;
|
114
|
+
}
|
115
|
+
if (broadcastMsg) {
|
116
|
+
if (broadcastMsg instanceof Array) {
|
117
|
+
broadcastMsg.map((msg) => this.broadcast(msg));
|
118
|
+
}
|
119
|
+
else {
|
120
|
+
this.broadcast(broadcastMsg);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
catch (err) {
|
125
|
+
if (typeof err === "string") {
|
126
|
+
this.sendTo(queuedMessage.from, { type: "error", message: err });
|
127
|
+
}
|
128
|
+
else {
|
129
|
+
throw err;
|
130
|
+
}
|
66
131
|
}
|
67
132
|
}
|
68
133
|
async onAgentMessage(msg, end) {
|
@@ -75,20 +140,114 @@ class OpenSession {
|
|
75
140
|
end,
|
76
141
|
});
|
77
142
|
}
|
78
|
-
async onChatMessage(
|
79
|
-
|
80
|
-
|
143
|
+
async onChatMessage(message, userToken) {
|
144
|
+
// We manually broadcast the user's message here and start the agent
|
145
|
+
// conversation, and then wait to get back all data from the agent before
|
146
|
+
// processing further messages from clients.
|
81
147
|
const msgId = (0, uuid_1.v4)();
|
82
148
|
this.broadcast({
|
83
149
|
type: "user_msg",
|
84
150
|
message_id: msgId,
|
85
|
-
message
|
151
|
+
message,
|
86
152
|
from: userToken,
|
87
153
|
});
|
88
|
-
// Messages will be handled by the Agent.onMessage callback. We await the
|
89
|
-
// response here before processing further messages.
|
90
154
|
this.curAgentMsgId = `${msgId}-resp`;
|
91
|
-
await this.agent.
|
155
|
+
await this.agent.userMessageEx(message, undefined, userToken);
|
156
|
+
}
|
157
|
+
async onAddMcpServer(serverName, enableAll) {
|
158
|
+
logger.info(`[onAddMcpServer]: Adding server ${serverName} (enable_all: ${enableAll})`);
|
159
|
+
const mcpServerManager = this.agent.getMcpServerManager();
|
160
|
+
if (mcpServerManager.hasMcpServer(serverName)) {
|
161
|
+
throw `${serverName} already added`;
|
162
|
+
}
|
163
|
+
if (!this.skillManager.hasServer(serverName)) {
|
164
|
+
throw `no such server: ${serverName}`;
|
165
|
+
}
|
166
|
+
await this.skillManager.addMcpServer(serverName, enableAll);
|
167
|
+
mcpServerManager.enableAllTools(serverName);
|
168
|
+
// Save changes to the AgentProfile
|
169
|
+
await this.updateAgentProfile();
|
170
|
+
// Broadcast the message to all participants.
|
171
|
+
const server = mcpServerManager.getMcpServer(serverName);
|
172
|
+
const tools = server.getTools();
|
173
|
+
const enabled_tools = Object.keys(server.getEnabledTools());
|
174
|
+
return {
|
175
|
+
type: "mcp_server_added",
|
176
|
+
server_name: serverName,
|
177
|
+
tools,
|
178
|
+
enabled_tools,
|
179
|
+
};
|
180
|
+
}
|
181
|
+
async onRemoveMcpServer(server_name) {
|
182
|
+
logger.info(`[onRemoveMcpServer]: Removing server ${server_name}`);
|
183
|
+
const mcpServerManager = this.agent.getMcpServerManager();
|
184
|
+
if (!mcpServerManager.hasMcpServer(server_name)) {
|
185
|
+
throw `${server_name} not enabled`;
|
186
|
+
}
|
187
|
+
await mcpServerManager.removeMcpServer(server_name);
|
188
|
+
await this.updateAgentProfile();
|
189
|
+
return {
|
190
|
+
type: "mcp_server_removed",
|
191
|
+
server_name,
|
192
|
+
};
|
193
|
+
}
|
194
|
+
async onEnableMcpServerTool(server_name, tool) {
|
195
|
+
const msm = this.agent.getMcpServerManager();
|
196
|
+
this.ensureMcpServerAndTool(msm, server_name, tool);
|
197
|
+
msm.enableTool(server_name, tool);
|
198
|
+
await this.updateAgentProfile();
|
199
|
+
return { type: "mcp_server_tool_enabled", server_name, tool };
|
200
|
+
}
|
201
|
+
async onDisableMcpServerTool(server_name, tool) {
|
202
|
+
const msm = this.agent.getMcpServerManager();
|
203
|
+
this.ensureMcpServerAndTool(msm, server_name, tool);
|
204
|
+
msm.disableTool(server_name, tool);
|
205
|
+
await this.updateAgentProfile();
|
206
|
+
return { type: "mcp_server_tool_disabled", server_name, tool };
|
207
|
+
}
|
208
|
+
async onEnableAllMcpServerTools(server_name) {
|
209
|
+
// We reimplement the logic to enable any disabled tools so we can
|
210
|
+
// construct messages along the way.
|
211
|
+
const msm = this.agent.getMcpServerManager();
|
212
|
+
const server = this.ensureMcpServer(msm, server_name);
|
213
|
+
const enabledTools = server.getEnabledTools();
|
214
|
+
const msgs = [];
|
215
|
+
for (const tool of server.getTools()) {
|
216
|
+
if (!enabledTools[tool.name]) {
|
217
|
+
msm.enableTool(server_name, tool.name);
|
218
|
+
msgs.push({
|
219
|
+
type: "mcp_server_tool_enabled",
|
220
|
+
server_name,
|
221
|
+
tool: tool.name,
|
222
|
+
});
|
223
|
+
}
|
224
|
+
}
|
225
|
+
await this.updateAgentProfile();
|
226
|
+
return msgs;
|
227
|
+
}
|
228
|
+
async onDisableAllMcpServerTools(server_name) {
|
229
|
+
// We reimplement the logic to disable all enabled tools so we can
|
230
|
+
// construct messages along the way.
|
231
|
+
const msm = this.agent.getMcpServerManager();
|
232
|
+
const server = this.ensureMcpServer(msm, server_name);
|
233
|
+
const enabledTools = server.getEnabledTools();
|
234
|
+
const msgs = [];
|
235
|
+
for (const tool in enabledTools) {
|
236
|
+
msm.disableTool(server_name, tool);
|
237
|
+
msgs.push({ type: "mcp_server_tool_disabled", server_name, tool });
|
238
|
+
}
|
239
|
+
await this.updateAgentProfile();
|
240
|
+
return msgs;
|
241
|
+
}
|
242
|
+
async onSetSystemPrompt(system_prompt) {
|
243
|
+
this.agent.setSystemPrompt(system_prompt);
|
244
|
+
await this.updateAgentProfile();
|
245
|
+
return { type: "system_prompt_updated", system_prompt };
|
246
|
+
}
|
247
|
+
async onSetModel(model) {
|
248
|
+
this.agent.setModel(model);
|
249
|
+
await this.updateAgentProfile();
|
250
|
+
return { type: "model_updated", model };
|
92
251
|
}
|
93
252
|
broadcast(msg) {
|
94
253
|
logger.info(`[broadcast]: broadcast msg: ${JSON.stringify(msg)}`);
|
@@ -97,6 +256,34 @@ class OpenSession {
|
|
97
256
|
ws.send(msgString);
|
98
257
|
}
|
99
258
|
}
|
259
|
+
sendTo(userName, msg) {
|
260
|
+
const ws = this.connections[userName];
|
261
|
+
const msgString = JSON.stringify(msg);
|
262
|
+
logger.info(`[sendTo]: (${userName}) msg: ${msgString}`);
|
263
|
+
(0, assert_1.strict)(ws);
|
264
|
+
ws.send(msgString);
|
265
|
+
}
|
266
|
+
ensureMcpServer(msm, serverName) {
|
267
|
+
const server = msm.getMcpServer(serverName);
|
268
|
+
if (!server) {
|
269
|
+
throw `${serverName} not added`;
|
270
|
+
}
|
271
|
+
return server;
|
272
|
+
}
|
273
|
+
ensureMcpServerAndTool(msm, serverName, toolName) {
|
274
|
+
const server = this.ensureMcpServer(msm, serverName);
|
275
|
+
const tool = server.getTool(toolName);
|
276
|
+
if (!tool) {
|
277
|
+
throw `Tool ${toolName} on ${serverName} not found`;
|
278
|
+
}
|
279
|
+
return tool;
|
280
|
+
}
|
281
|
+
async updateAgentProfile() {
|
282
|
+
const profile = this.agent.getAgentProfile();
|
283
|
+
logger.debug(`[updateAgentProfile]: uuid: ${this.agentProfileUUID} profile: ` +
|
284
|
+
JSON.stringify(profile));
|
285
|
+
return this.db.updateAgentProfile(this.agentProfileUUID, profile);
|
286
|
+
}
|
100
287
|
}
|
101
288
|
exports.OpenSession = OpenSession;
|
102
289
|
/**
|
@@ -113,7 +300,7 @@ class ConversationManager {
|
|
113
300
|
}
|
114
301
|
async join(sessionId, llmApiKey, xmcpApiKey, userData, ws) {
|
115
302
|
await this.openSessionsLock.lockAndProcess(() => {
|
116
|
-
return this.getOrCreateAndSubscribe(sessionId, llmApiKey, xmcpApiKey, userData.nickname || userData.
|
303
|
+
return this.getOrCreateAndSubscribe(sessionId, llmApiKey, xmcpApiKey, userData.nickname || userData.uuid, ws);
|
117
304
|
});
|
118
305
|
}
|
119
306
|
/**
|
@@ -138,20 +325,46 @@ class ConversationManager {
|
|
138
325
|
throw `no such session ${sessionId}`;
|
139
326
|
}
|
140
327
|
const conversation = sessionData.conversation;
|
141
|
-
const
|
328
|
+
const agentProfileUUID = sessionData.agent_profile_uuid;
|
329
|
+
const agentProfile = await this.db.getAgentProfileById(agentProfileUUID);
|
142
330
|
if (!agentProfile) {
|
143
|
-
throw `no such agent profile ${
|
331
|
+
throw `no such agent profile ${agentProfileUUID}`;
|
144
332
|
}
|
333
|
+
// TODO: store some owner data on the OpenSession object itself?
|
334
|
+
const owner = await this.db.getUserFromUuid(sessionData.user_uuid);
|
335
|
+
(0, assert_1.strict)(owner, `no owner for session ${JSON.stringify(sessionData)}`);
|
336
|
+
const ownerUserName = owner.nickname;
|
337
|
+
if (!ownerUserName) {
|
338
|
+
throw (`user ${sessionData.user_uuid} has no user name - ` +
|
339
|
+
"cannot create chat session");
|
340
|
+
}
|
341
|
+
// Access to the OpenSession (once it is iniailized
|
342
|
+
const context = {};
|
145
343
|
const platform = {
|
146
|
-
openUrl: (
|
147
|
-
|
344
|
+
openUrl: (url, _authResultP, display_name) => {
|
345
|
+
// These requests are always passed to the original owner, since it is
|
346
|
+
// his settings that will be used for all mcp servers.
|
347
|
+
if (context.openSession) {
|
348
|
+
const conn = openSession.connections[ownerUserName];
|
349
|
+
if (conn) {
|
350
|
+
openSession.sendTo(ownerUserName, {
|
351
|
+
type: "open_url",
|
352
|
+
url,
|
353
|
+
display_name,
|
354
|
+
});
|
355
|
+
}
|
356
|
+
else {
|
357
|
+
throw `user ${ownerUserName} must authenticate`;
|
358
|
+
}
|
359
|
+
}
|
360
|
+
else {
|
361
|
+
throw `no open session ${sessionData.uuid}`;
|
362
|
+
}
|
148
363
|
},
|
149
364
|
load: (_filename) => {
|
150
|
-
throw "unimpl";
|
365
|
+
throw "unimpl platform.load";
|
151
366
|
},
|
152
367
|
};
|
153
|
-
// Forward agent messages to the OpenSession (once it is iniailized
|
154
|
-
const context = {};
|
155
368
|
const onMessage = async (msg, end) => {
|
156
369
|
logger.debug(`[onMessage] msg: ${msg}, end: ${end}`);
|
157
370
|
(0, assert_1.strict)(context.openSession);
|
@@ -159,11 +372,17 @@ class ConversationManager {
|
|
159
372
|
};
|
160
373
|
const onToolCall = async (toolCall) => {
|
161
374
|
logger.debug(`[onToolCall] : ${JSON.stringify(toolCall)}`);
|
375
|
+
(0, assert_1.strict)(context.openSession);
|
376
|
+
context.openSession.broadcast({
|
377
|
+
type: "agent_tool_call",
|
378
|
+
message_id: toolCall.id,
|
379
|
+
message: toolCall,
|
380
|
+
});
|
162
381
|
return true;
|
163
382
|
};
|
164
383
|
const xmcpConfig = sdk_1.Configuration.new(xmcpApiKey, this.xmcpUrl, false);
|
165
384
|
const [agent, smsm] = await (0, agentUtils_1.createAgentWithSkills)(this.llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, xmcpConfig, undefined, conversation, true);
|
166
|
-
openSession = new OpenSession(agent, smsm, onEmpty);
|
385
|
+
openSession = new OpenSession(this.db, agent, sessionId, agentProfileUUID, smsm, onEmpty);
|
167
386
|
context.openSession = openSession;
|
168
387
|
this.openSessions[sessionId] = openSession;
|
169
388
|
openSession.join(userName, ws);
|