@xalia/agent 0.5.4 → 0.5.6

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.
Files changed (43) hide show
  1. package/dist/agent/src/agent/agent.js +16 -9
  2. package/dist/agent/src/agent/agentUtils.js +24 -4
  3. package/dist/agent/src/agent/mcpServerManager.js +19 -9
  4. package/dist/agent/src/agent/openAILLM.js +3 -1
  5. package/dist/agent/src/agent/openAILLMStreaming.js +24 -25
  6. package/dist/agent/src/agent/repeatLLM.js +43 -0
  7. package/dist/agent/src/agent/sudoMcpServerManager.js +12 -6
  8. package/dist/agent/src/chat/client.js +259 -36
  9. package/dist/agent/src/chat/conversationManager.js +243 -24
  10. package/dist/agent/src/chat/db.js +24 -1
  11. package/dist/agent/src/chat/frontendClient.js +74 -0
  12. package/dist/agent/src/chat/server.js +3 -3
  13. package/dist/agent/src/test/db.test.js +25 -2
  14. package/dist/agent/src/test/openaiStreaming.test.js +133 -0
  15. package/dist/agent/src/test/prompt.test.js +2 -2
  16. package/dist/agent/src/test/sudoMcpServerManager.test.js +1 -1
  17. package/dist/agent/src/tool/agentChat.js +7 -197
  18. package/dist/agent/src/tool/chatMain.js +18 -23
  19. package/dist/agent/src/tool/commandPrompt.js +248 -0
  20. package/dist/agent/src/tool/prompt.js +27 -31
  21. package/package.json +1 -1
  22. package/scripts/test_chat +17 -1
  23. package/src/agent/agent.ts +34 -11
  24. package/src/agent/agentUtils.ts +52 -3
  25. package/src/agent/mcpServerManager.ts +43 -13
  26. package/src/agent/openAILLM.ts +3 -1
  27. package/src/agent/openAILLMStreaming.ts +28 -27
  28. package/src/agent/repeatLLM.ts +51 -0
  29. package/src/agent/sudoMcpServerManager.ts +41 -12
  30. package/src/chat/client.ts +353 -40
  31. package/src/chat/conversationManager.ts +345 -33
  32. package/src/chat/db.ts +28 -2
  33. package/src/chat/frontendClient.ts +123 -0
  34. package/src/chat/messages.ts +146 -2
  35. package/src/chat/server.ts +3 -3
  36. package/src/test/db.test.ts +35 -2
  37. package/src/test/openaiStreaming.test.ts +142 -0
  38. package/src/test/prompt.test.ts +1 -1
  39. package/src/test/sudoMcpServerManager.test.ts +1 -1
  40. package/src/tool/agentChat.ts +13 -211
  41. package/src/tool/chatMain.ts +28 -43
  42. package/src/tool/commandPrompt.ts +252 -0
  43. 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
- dotenv.config();
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((r, e) => {
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
- const client = new ChatClient(ws, onMessageCB, onConnectionClosedCB);
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
- const msg = JSON.parse(msgData);
67
- client.onMessageCB(msg);
224
+ onMsg(JSON.parse(msgData));
68
225
  }
69
226
  catch (e) {
70
- client.close();
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.closed = true;
80
- onConnectionClosedCB();
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.closed = true;
86
- onConnectionClosedCB();
245
+ if (client) {
246
+ client.closed = true;
247
+ }
248
+ await onConnectionClosedCB();
87
249
  };
88
250
  });
89
251
  }
90
- // public static async initWithSession(
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
- // public agent: Agent,
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
- const msg = queuedMessage.msg;
61
- switch (msg.type) {
62
- case "msg":
63
- return this.onChatMessage(queuedMessage);
64
- default:
65
- throw `unknown message: ${JSON.stringify(queuedMessage)}`;
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(queuedMessage) {
79
- const msg = queuedMessage.msg;
80
- const userToken = queuedMessage.from;
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: msg.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.userMessage(msg.message, undefined, userToken);
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.user_uuid, ws);
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 agentProfile = await this.db.getAgentProfileById(sessionData.agent_profile_uuid);
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 ${sessionData.agent_profile_uuid}`;
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: (_url, _authResultP, _displayName) => {
147
- throw "unimpl";
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);