@xalia/agent 0.6.2 → 0.6.4
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 +8 -5
- package/dist/agent/src/agent/agentUtils.js +9 -12
- package/dist/agent/src/chat/client/chatClient.js +88 -240
- package/dist/agent/src/chat/client/constants.js +1 -2
- package/dist/agent/src/chat/client/sessionClient.js +4 -13
- package/dist/agent/src/chat/client/sessionFiles.js +3 -3
- package/dist/agent/src/chat/protocol/messages.js +0 -1
- package/dist/agent/src/chat/server/chatContextManager.js +5 -9
- package/dist/agent/src/chat/server/connectionManager.test.js +1 -0
- package/dist/agent/src/chat/server/conversation.js +9 -4
- package/dist/agent/src/chat/server/openSession.js +241 -238
- package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +17 -12
- package/dist/agent/src/chat/utils/approvalManager.js +82 -64
- package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
- package/dist/agent/src/test/agent.test.js +90 -53
- package/dist/agent/src/test/approvalManager.test.js +79 -35
- package/dist/agent/src/test/chatContextManager.test.js +12 -17
- package/dist/agent/src/test/responseAwaiter.test.js +74 -0
- package/dist/agent/src/tool/agentChat.js +1 -1
- package/dist/agent/src/tool/chatMain.js +2 -2
- package/package.json +1 -1
- package/scripts/setup_chat +2 -2
- package/scripts/test_chat +61 -60
- package/src/agent/agent.ts +9 -5
- package/src/agent/agentUtils.ts +14 -27
- package/src/chat/client/chatClient.ts +167 -296
- package/src/chat/client/constants.ts +0 -2
- package/src/chat/client/sessionClient.ts +15 -19
- package/src/chat/client/sessionFiles.ts +9 -12
- package/src/chat/data/dataModels.ts +1 -0
- package/src/chat/protocol/messages.ts +9 -12
- package/src/chat/server/chatContextManager.ts +7 -12
- package/src/chat/server/connectionManager.test.ts +1 -0
- package/src/chat/server/conversation.ts +19 -11
- package/src/chat/server/openSession.ts +383 -340
- package/src/chat/server/openSessionMessageSender.ts +4 -0
- package/src/chat/server/sessionRegistry.ts +33 -12
- package/src/chat/utils/approvalManager.ts +153 -81
- package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
- package/src/test/agent.test.ts +130 -62
- package/src/test/approvalManager.test.ts +108 -40
- package/src/test/chatContextManager.test.ts +19 -20
- package/src/test/responseAwaiter.test.ts +103 -0
- package/src/tool/agentChat.ts +2 -2
- package/src/tool/chatMain.ts +2 -2
- package/dist/agent/src/test/responseHandler.test.js +0 -61
- package/src/test/responseHandler.test.ts +0 -78
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OpenSession = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
|
|
3
|
+
exports.OpenSession = exports.ChatSessionAgentEventHandler = exports.ChatSessionMessageSender = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
|
|
4
4
|
const assert_1 = require("assert");
|
|
5
5
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
6
6
|
const agent_1 = require("../../agent/agent");
|
|
@@ -43,6 +43,140 @@ class DBCheckpointWriter {
|
|
|
43
43
|
return this.db.sessionCheckpointSet(this.sessionId, checkpoint);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
class ChatSessionMessageSender {
|
|
47
|
+
constructor(connectionManager, sessionParticipants) {
|
|
48
|
+
this.connectionManager = connectionManager;
|
|
49
|
+
this.sessionParticipants = sessionParticipants;
|
|
50
|
+
}
|
|
51
|
+
broadcast(msg) {
|
|
52
|
+
const users = new Set(this.sessionParticipants.keys());
|
|
53
|
+
this.connectionManager.sendToUsers(users, msg);
|
|
54
|
+
}
|
|
55
|
+
sendTo(userUUID, msg) {
|
|
56
|
+
this.connectionManager.sendToUsers(new Set([userUUID]), msg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.ChatSessionMessageSender = ChatSessionMessageSender;
|
|
60
|
+
class ChatSessionPlatform {
|
|
61
|
+
constructor(sender, sessionUUID, ownerUUID) {
|
|
62
|
+
this.sender = sender;
|
|
63
|
+
this.sessionUUID = sessionUUID;
|
|
64
|
+
this.ownerUUID = ownerUUID;
|
|
65
|
+
}
|
|
66
|
+
// IPlatform.openUrl
|
|
67
|
+
openUrl(url, authResultP, display_name) {
|
|
68
|
+
// These requests are always passed to the original owner, since it is
|
|
69
|
+
// their settings that will be used for all MCP servers.
|
|
70
|
+
this.sender.broadcast({
|
|
71
|
+
type: "authentication_started",
|
|
72
|
+
session_id: this.sessionUUID,
|
|
73
|
+
url,
|
|
74
|
+
});
|
|
75
|
+
this.sender.sendTo(this.ownerUUID, {
|
|
76
|
+
type: "authenticate",
|
|
77
|
+
session_id: this.sessionUUID,
|
|
78
|
+
url,
|
|
79
|
+
display_name,
|
|
80
|
+
});
|
|
81
|
+
// TODO: auth timeout
|
|
82
|
+
// Don't stall this function waiting for authentication
|
|
83
|
+
void authResultP.then((result) => {
|
|
84
|
+
this.sender.broadcast({
|
|
85
|
+
type: "authentication_finished",
|
|
86
|
+
session_id: this.sessionUUID,
|
|
87
|
+
url,
|
|
88
|
+
result,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// IPlatform.load
|
|
93
|
+
load(filename) {
|
|
94
|
+
if (process.env.DEVELOPMENT === "1") {
|
|
95
|
+
return nodePlatform_1.NODE_PLATFORM.load(filename);
|
|
96
|
+
}
|
|
97
|
+
throw new errors_1.ChatErrorMessage("Platform.load not implemented");
|
|
98
|
+
}
|
|
99
|
+
// IPlatform.renderHTML
|
|
100
|
+
renderHTML(html) {
|
|
101
|
+
return new Promise((r) => {
|
|
102
|
+
this.sender.broadcast({
|
|
103
|
+
type: "render_html",
|
|
104
|
+
html,
|
|
105
|
+
session_id: this.sessionUUID,
|
|
106
|
+
});
|
|
107
|
+
r();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
class ChatSessionAgentEventHandler {
|
|
112
|
+
constructor(sessionUUID, sender, approvalManager, contextManager) {
|
|
113
|
+
this.sessionUUID = sessionUUID;
|
|
114
|
+
this.sender = sender;
|
|
115
|
+
this.approvalManager = approvalManager;
|
|
116
|
+
this.contextManager = contextManager;
|
|
117
|
+
}
|
|
118
|
+
onCompletion(result) {
|
|
119
|
+
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
120
|
+
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
121
|
+
this.contextManager.processAgentResponse(result);
|
|
122
|
+
}
|
|
123
|
+
onImage(image) {
|
|
124
|
+
logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
|
|
125
|
+
throw new Error("[OpenSession.onImage] unimplemented");
|
|
126
|
+
}
|
|
127
|
+
onToolCallResult(result) {
|
|
128
|
+
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
129
|
+
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
130
|
+
this.sender.broadcast(toolCallMessage);
|
|
131
|
+
}
|
|
132
|
+
async onToolCall(toolCall, agentTool) {
|
|
133
|
+
if (agentTool) {
|
|
134
|
+
// "Agent" tools are considered internal to the agent, and are always
|
|
135
|
+
// permitted. Inform all clients and immediately approve.
|
|
136
|
+
this.sender.broadcast({
|
|
137
|
+
type: "tool_call",
|
|
138
|
+
tool_call: toolCall,
|
|
139
|
+
session_id: this.sessionUUID,
|
|
140
|
+
});
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
144
|
+
const [serverName, tool] = toolCall.function.name.split("__");
|
|
145
|
+
const { approved, requested } = await this.approvalManager.getApproval(serverName, tool, toolCall);
|
|
146
|
+
// For now, the frontend uses the tool_call data in the
|
|
147
|
+
// "approve_tool_call" request to display the tool call data. If approval
|
|
148
|
+
// was requested in this way, don't send the "tool_call" message as well.
|
|
149
|
+
if (approved && !requested) {
|
|
150
|
+
this.sender.broadcast({
|
|
151
|
+
type: "tool_call",
|
|
152
|
+
tool_call: toolCall,
|
|
153
|
+
session_id: this.sessionUUID,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return approved;
|
|
157
|
+
}
|
|
158
|
+
onAgentMessage(msg, end) {
|
|
159
|
+
logger.debug(`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`);
|
|
160
|
+
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
161
|
+
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
162
|
+
this.sender.broadcast(agentMsgChunk);
|
|
163
|
+
return Promise.resolve();
|
|
164
|
+
}
|
|
165
|
+
onReasoning(reasoning) {
|
|
166
|
+
return new Promise((r) => {
|
|
167
|
+
logger.debug(`[OpenSession.onReasoning]${reasoning}`);
|
|
168
|
+
if (reasoning.length > 0) {
|
|
169
|
+
this.sender.broadcast({
|
|
170
|
+
type: "agent_reasoning_chunk",
|
|
171
|
+
reasoning,
|
|
172
|
+
session_id: this.sessionUUID,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
r();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.ChatSessionAgentEventHandler = ChatSessionAgentEventHandler;
|
|
46
180
|
/**
|
|
47
181
|
* Describes a Session (conversation) with connected participants.
|
|
48
182
|
*
|
|
@@ -54,7 +188,7 @@ class DBCheckpointWriter {
|
|
|
54
188
|
* tool approvals and other interactions).
|
|
55
189
|
*/
|
|
56
190
|
class OpenSession {
|
|
57
|
-
constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager,
|
|
191
|
+
constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, sender, approvalManager, fileManager) {
|
|
58
192
|
this.db = db;
|
|
59
193
|
this.agent = agent;
|
|
60
194
|
this.sessionUUID = sessionData.session_uuid;
|
|
@@ -65,7 +199,7 @@ class OpenSession {
|
|
|
65
199
|
this.sessionParticipants = sessionParticipants;
|
|
66
200
|
this.agentProfilePreferences = agentProfilePreferences;
|
|
67
201
|
this.skillManager = skillManager;
|
|
68
|
-
this.
|
|
202
|
+
this.sender = sender;
|
|
69
203
|
this.messageQueue = new asyncQueue_1.AsyncQueue((m) => this.processMessage(m));
|
|
70
204
|
this.userMessageQueue = new multiAsyncQueue_1.MultiAsyncQueue((m) => this.processUserMessages(m));
|
|
71
205
|
this.contextManager = contextManager;
|
|
@@ -81,23 +215,12 @@ class OpenSession {
|
|
|
81
215
|
static async init(db, isPersisted, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager) {
|
|
82
216
|
const sessionId = sessionData.session_uuid;
|
|
83
217
|
const fileManager = await sessionFileManager_1.ChatSessionFileManager.init(db, sessionId);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, {}, // Placeholder - will be replaced after agent creation
|
|
91
|
-
contextManager, connectionManager, new approvalManager_1.ApprovalManager(), fileManager);
|
|
92
|
-
// Initialize an empty agent (to ensure there are no callbacks before
|
|
93
|
-
// the OpenSession and client are fully set up).
|
|
94
|
-
const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
95
|
-
const [agent, skillManager] = await (0, agentUtils_1.createAgentWithoutSkills)(llmUrl, savedAgentProfile.profile, DEFAULT_CHAT_LLM_MODEL, openSession, openSession, contextManager, ownerApiKey, xmcpConfig, undefined, true);
|
|
96
|
-
await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, openSession, fileManager, llmUrl, ownerApiKey);
|
|
97
|
-
// Update OpenSession with real agent and skillManager
|
|
98
|
-
openSession.agent = agent;
|
|
99
|
-
openSession.skillManager = skillManager;
|
|
100
|
-
await openSession.restoreMcpSettings(savedAgentProfile.profile.mcp_settings);
|
|
218
|
+
const sender = new ChatSessionMessageSender(connectionManager, sessionParticipants);
|
|
219
|
+
const platform = new ChatSessionPlatform(sender, sessionId, ownerData.uuid);
|
|
220
|
+
const toolApprovalManager = new approvalManager_1.ToolApprovalManager(sessionData.session_uuid, savedAgentProfile.uuid, savedAgentProfile.preferences, sender, new approvalManager_1.DbAgentPreferencesWriter(db));
|
|
221
|
+
const { agent, skillManager, contextManager } = await createContextAndAgent(sessionId, savedAgentProfile.profile.system_prompt, savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL, sessionMessages, sessionData.workspace, sessionCheckpoint, ownerData, ownerApiKey, llmUrl, xmcpUrl, fileManager, sender, platform, toolApprovalManager);
|
|
222
|
+
const openSession = new OpenSession(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, skillManager, contextManager, sender, toolApprovalManager, fileManager);
|
|
223
|
+
// Note, MCP servers have not been enabled yet
|
|
101
224
|
return openSession;
|
|
102
225
|
}
|
|
103
226
|
static async initWithEmptySession(db, sessionData, llmUrl, xmcpUrl, connectionManager) {
|
|
@@ -108,12 +231,19 @@ class OpenSession {
|
|
|
108
231
|
throw new errors_1.ChatErrorMessage("failed finding owners default api key");
|
|
109
232
|
}
|
|
110
233
|
const sessionParticipants = new Map();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
234
|
+
if (sessionData.participants && sessionData.participants.length > 0) {
|
|
235
|
+
sessionData.participants.forEach((p) => {
|
|
236
|
+
sessionParticipants.set(p.user_uuid, p);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
sessionParticipants.set(sessionData.user_uuid, {
|
|
241
|
+
user_uuid: sessionData.user_uuid,
|
|
242
|
+
nickname: ownerData.nickname || "",
|
|
243
|
+
email: ownerData.email,
|
|
244
|
+
role: "owner",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
117
247
|
return OpenSession.init(db, false /* isPersisted */, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager);
|
|
118
248
|
}
|
|
119
249
|
static async initWithExistingSession(db, sessionId, llmUrl, xmcpUrl, connectionManager) {
|
|
@@ -141,14 +271,22 @@ class OpenSession {
|
|
|
141
271
|
getParticipants() {
|
|
142
272
|
return this.sessionParticipants;
|
|
143
273
|
}
|
|
144
|
-
sendSessionData(connectionId, clientMessageId) {
|
|
274
|
+
async sendSessionData(connectionId, clientMessageId, restoreMcpState) {
|
|
145
275
|
logger.info(`[SessionRegistry] sending session data for session ${this.sessionUUID}`);
|
|
146
276
|
const sessionInfo = this.serverSessionInfo(clientMessageId);
|
|
147
|
-
this.connectionManager
|
|
277
|
+
const connMgr = this.sender.connectionManager;
|
|
278
|
+
connMgr.sendToConnection(connectionId, sessionInfo);
|
|
279
|
+
// This could be cleaner. If the session has just been created, we must
|
|
280
|
+
// restore the mcp servers. However, we cannot do that until the client
|
|
281
|
+
// has initialized itself (in case we need auth messages), hence it must
|
|
282
|
+
// happen at this stage, BEFORE we call sendMcpSettings below.
|
|
283
|
+
if (restoreMcpState) {
|
|
284
|
+
await this.restoreMcpSettings(this.savedAgentProfile.profile.mcp_settings);
|
|
285
|
+
}
|
|
148
286
|
// send session file info
|
|
149
287
|
const fileDescriptors = this.sessionFileManager.listFiles();
|
|
150
288
|
fileDescriptors.forEach((descriptor) => {
|
|
151
|
-
|
|
289
|
+
connMgr.sendToConnection(connectionId, {
|
|
152
290
|
type: "session_file_changed",
|
|
153
291
|
session_id: this.sessionUUID,
|
|
154
292
|
descriptor,
|
|
@@ -158,18 +296,18 @@ class OpenSession {
|
|
|
158
296
|
// send conversation history
|
|
159
297
|
const conversationMessages = this.contextManager.getConversationMessages();
|
|
160
298
|
conversationMessages.forEach((message) => {
|
|
161
|
-
|
|
299
|
+
connMgr.sendToConnection(connectionId, message);
|
|
162
300
|
});
|
|
163
301
|
// add MCP settings
|
|
164
302
|
this.sendMcpSettings(connectionId);
|
|
165
303
|
// add system prompt and model
|
|
166
304
|
const agentProfile = this.agent.getAgentProfile();
|
|
167
|
-
|
|
305
|
+
connMgr.sendToConnection(connectionId, {
|
|
168
306
|
type: "system_prompt_updated",
|
|
169
307
|
system_prompt: agentProfile.system_prompt,
|
|
170
308
|
session_id: this.sessionUUID,
|
|
171
309
|
});
|
|
172
|
-
|
|
310
|
+
connMgr.sendToConnection(connectionId, {
|
|
173
311
|
type: "model_updated",
|
|
174
312
|
model: agentProfile.model || "",
|
|
175
313
|
session_id: this.sessionUUID,
|
|
@@ -212,7 +350,7 @@ class OpenSession {
|
|
|
212
350
|
}
|
|
213
351
|
}
|
|
214
352
|
catch (e) {
|
|
215
|
-
this.broadcast({
|
|
353
|
+
this.sender.broadcast({
|
|
216
354
|
type: "session_error",
|
|
217
355
|
message: `Error adding MCP server ${server_name}: ${String(e)}`,
|
|
218
356
|
session_id: this.sessionUUID,
|
|
@@ -230,10 +368,10 @@ class OpenSession {
|
|
|
230
368
|
handleError(err, from) {
|
|
231
369
|
const sendError = (msg) => {
|
|
232
370
|
if (from) {
|
|
233
|
-
this.sendTo(from, msg);
|
|
371
|
+
this.sender.sendTo(from, msg);
|
|
234
372
|
}
|
|
235
373
|
else {
|
|
236
|
-
this.broadcast(msg);
|
|
374
|
+
this.sender.broadcast(msg);
|
|
237
375
|
}
|
|
238
376
|
};
|
|
239
377
|
if (err instanceof errors_1.ChatFatalError) {
|
|
@@ -262,150 +400,9 @@ class OpenSession {
|
|
|
262
400
|
}
|
|
263
401
|
return true;
|
|
264
402
|
}
|
|
265
|
-
broadcast(msg) {
|
|
266
|
-
const users = new Set(this.sessionParticipants.keys());
|
|
267
|
-
this.connectionManager.sendToUsers(users, msg);
|
|
268
|
-
}
|
|
269
|
-
sendTo(user_uuid, msg) {
|
|
270
|
-
this.connectionManager.sendToUsers(new Set([user_uuid]), msg);
|
|
271
|
-
}
|
|
272
|
-
// IPlatform.openUrl
|
|
273
|
-
openUrl(url, authResultP, display_name) {
|
|
274
|
-
// These requests are always passed to the original owner, since it is
|
|
275
|
-
// their settings that will be used for all MCP servers.
|
|
276
|
-
this.broadcast({
|
|
277
|
-
type: "authentication_started",
|
|
278
|
-
session_id: this.sessionUUID,
|
|
279
|
-
url,
|
|
280
|
-
});
|
|
281
|
-
this.sendTo(this.userUUID, {
|
|
282
|
-
type: "authenticate",
|
|
283
|
-
session_id: this.sessionUUID,
|
|
284
|
-
url,
|
|
285
|
-
display_name,
|
|
286
|
-
});
|
|
287
|
-
// TODO: auth timeout
|
|
288
|
-
// Don't stall this function waiting for authentication
|
|
289
|
-
void authResultP.then((result) => {
|
|
290
|
-
this.sendTo(this.userUUID, {
|
|
291
|
-
type: "authentication_finished",
|
|
292
|
-
session_id: this.sessionUUID,
|
|
293
|
-
url,
|
|
294
|
-
result,
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
// IPlatform.load
|
|
299
|
-
load(filename) {
|
|
300
|
-
if (process.env.DEVELOPMENT === "1") {
|
|
301
|
-
return nodePlatform_1.NODE_PLATFORM.load(filename);
|
|
302
|
-
}
|
|
303
|
-
throw new errors_1.ChatErrorMessage("Platform.load not implemented");
|
|
304
|
-
}
|
|
305
|
-
// IPlatform.renderHTML
|
|
306
|
-
renderHTML(html) {
|
|
307
|
-
return new Promise((r) => {
|
|
308
|
-
this.broadcast({
|
|
309
|
-
type: "render_html",
|
|
310
|
-
html,
|
|
311
|
-
session_id: this.sessionUUID,
|
|
312
|
-
});
|
|
313
|
-
r();
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
// IAgentEventHandler.onCompletion
|
|
317
|
-
onCompletion(result) {
|
|
318
|
-
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
319
|
-
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
320
|
-
this.contextManager.processAgentResponse(result);
|
|
321
|
-
}
|
|
322
|
-
// IAgentEventHandler.onImage
|
|
323
|
-
onImage(image) {
|
|
324
|
-
logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
|
|
325
|
-
throw new Error("[OpenSession.onImage] unimplemented");
|
|
326
|
-
}
|
|
327
|
-
// IAgentEventHandler.onToolCallResult
|
|
328
|
-
onToolCallResult(result) {
|
|
329
|
-
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
330
|
-
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
331
|
-
this.broadcast(toolCallMessage);
|
|
332
|
-
}
|
|
333
|
-
// IAgentEventHandler.onToolCall
|
|
334
|
-
async onToolCall(toolCall, agentTool) {
|
|
335
|
-
if (agentTool) {
|
|
336
|
-
// "Agent" tools are considered internal to the agent, and are always
|
|
337
|
-
// permitted. Inform all clients and immediately approve.
|
|
338
|
-
this.broadcast({
|
|
339
|
-
type: "tool_call",
|
|
340
|
-
tool_call: toolCall,
|
|
341
|
-
session_id: this.sessionUUID,
|
|
342
|
-
});
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
346
|
-
const [serverName, tool] = toolCall.function.name.split("__");
|
|
347
|
-
const autoApproved = (0, sdk_1.prefsGetAutoApprove)(this.agentProfilePreferences, serverName, tool);
|
|
348
|
-
if (!autoApproved) {
|
|
349
|
-
const { id, resultP } = this.approvalManager.startApproval(toolCall.function.name);
|
|
350
|
-
this.broadcast({
|
|
351
|
-
type: "approve_tool_call",
|
|
352
|
-
id,
|
|
353
|
-
tool_call: toolCall,
|
|
354
|
-
session_id: this.sessionUUID,
|
|
355
|
-
});
|
|
356
|
-
try {
|
|
357
|
-
logger.debug(`[OpenSession.onToolCall] awaiting approval ${id}`);
|
|
358
|
-
const { approved, auto_approve } = await resultP;
|
|
359
|
-
logger.debug(`[OpenSession.onToolCall] approval ${id}: ${String(approved)}`);
|
|
360
|
-
if (auto_approve) {
|
|
361
|
-
logger.debug("[OpenSession.onToolCall] auto_approve set. updated preferences");
|
|
362
|
-
const autoApprovalMsg = await this.onSetAutoApproval(serverName, tool, true);
|
|
363
|
-
if (autoApprovalMsg) {
|
|
364
|
-
this.broadcast(autoApprovalMsg);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return approved;
|
|
368
|
-
}
|
|
369
|
-
catch (e) {
|
|
370
|
-
logger.debug(`[OpenSession.onToolCall] error waiting for approval ${id}: ` +
|
|
371
|
-
String(e));
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
this.broadcast({
|
|
377
|
-
type: "tool_call",
|
|
378
|
-
tool_call: toolCall,
|
|
379
|
-
session_id: this.sessionUUID,
|
|
380
|
-
});
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
// IAgentEventHandler.onAgentMessage
|
|
385
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
386
|
-
async onAgentMessage(msg, end) {
|
|
387
|
-
logger.debug(`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`);
|
|
388
|
-
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
389
|
-
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
390
|
-
this.broadcast(agentMsgChunk);
|
|
391
|
-
}
|
|
392
|
-
// IAgentEventHandler.onReasoning
|
|
393
|
-
onReasoning(reasoning) {
|
|
394
|
-
return new Promise((r) => {
|
|
395
|
-
logger.debug(`[OpenSession.onReasoning]${reasoning}`);
|
|
396
|
-
if (reasoning.length > 0) {
|
|
397
|
-
this.broadcast({
|
|
398
|
-
type: "agent_reasoning_chunk",
|
|
399
|
-
reasoning,
|
|
400
|
-
session_id: this.sessionUUID,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
r();
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
403
|
// ISessionFileManagerEventHandler.onFileDeleted
|
|
407
404
|
onFileDeleted(name) {
|
|
408
|
-
this.broadcast({
|
|
405
|
+
this.sender.broadcast({
|
|
409
406
|
type: "session_file_deleted",
|
|
410
407
|
session_id: this.sessionUUID,
|
|
411
408
|
name,
|
|
@@ -413,7 +410,7 @@ class OpenSession {
|
|
|
413
410
|
}
|
|
414
411
|
// ISessionFileManagerEventHandler.onFileChanged
|
|
415
412
|
onFileChanged(entry, new_file) {
|
|
416
|
-
this.broadcast({
|
|
413
|
+
this.sender.broadcast({
|
|
417
414
|
type: "session_file_changed",
|
|
418
415
|
session_id: this.sessionUUID,
|
|
419
416
|
descriptor: {
|
|
@@ -435,7 +432,7 @@ class OpenSession {
|
|
|
435
432
|
// Validate user is in session
|
|
436
433
|
if (!this.sessionParticipants.get(from)) {
|
|
437
434
|
logger.warn(`User ${from} not in session ${this.sessionUUID} - ignoring message`);
|
|
438
|
-
this.sendTo(from, {
|
|
435
|
+
this.sender.sendTo(from, {
|
|
439
436
|
type: "session_error",
|
|
440
437
|
message: "You are not a participant in this session",
|
|
441
438
|
session_id: this.sessionUUID,
|
|
@@ -445,7 +442,7 @@ class OpenSession {
|
|
|
445
442
|
}
|
|
446
443
|
// Enqueue message for processing
|
|
447
444
|
if (!this.messageQueue.tryEnqueue({ msg: message, from })) {
|
|
448
|
-
this.sendTo(from, this.addSessionContext({
|
|
445
|
+
this.sender.sendTo(from, this.addSessionContext({
|
|
449
446
|
type: "session_error",
|
|
450
447
|
message: "message queue full. try again later",
|
|
451
448
|
client_message_id: message.client_message_id,
|
|
@@ -462,7 +459,7 @@ class OpenSession {
|
|
|
462
459
|
const mcpServer = this.skillManager.getMcpServer(server_name);
|
|
463
460
|
const tools = mcpServer.getTools();
|
|
464
461
|
const enabled_tools = Array.from(mcpServer.getEnabledTools().keys());
|
|
465
|
-
this.connectionManager.sendToConnection(connectionId, {
|
|
462
|
+
this.sender.connectionManager.sendToConnection(connectionId, {
|
|
466
463
|
type: "mcp_server_added",
|
|
467
464
|
server_name,
|
|
468
465
|
tools,
|
|
@@ -485,7 +482,7 @@ class OpenSession {
|
|
|
485
482
|
let broadcastMsg = undefined;
|
|
486
483
|
switch (msg.type) {
|
|
487
484
|
case "msg":
|
|
488
|
-
broadcastMsg = this.handleUserMessage(msg, queuedMessage.from);
|
|
485
|
+
broadcastMsg = await this.handleUserMessage(msg, queuedMessage.from);
|
|
489
486
|
break;
|
|
490
487
|
case "add_mcp_server":
|
|
491
488
|
broadcastMsg = await this.handleAddMcpServer(msg.server_name, msg.enable_all);
|
|
@@ -506,14 +503,7 @@ class OpenSession {
|
|
|
506
503
|
broadcastMsg = await this.handleDisableAllMcpServerTools(msg.server_name);
|
|
507
504
|
break;
|
|
508
505
|
case "tool_call_approval_result":
|
|
509
|
-
|
|
510
|
-
broadcastMsg = {
|
|
511
|
-
type: "tool_call_approval_result",
|
|
512
|
-
id: msg.id,
|
|
513
|
-
result: msg.result,
|
|
514
|
-
session_id: this.sessionUUID,
|
|
515
|
-
};
|
|
516
|
-
}
|
|
506
|
+
this.approvalManager.onApprovalResult(msg);
|
|
517
507
|
break;
|
|
518
508
|
case "session_file_get_content":
|
|
519
509
|
void this.handleSessionFileGetContent(msg, queuedMessage.from);
|
|
@@ -525,7 +515,7 @@ class OpenSession {
|
|
|
525
515
|
await this.handleSessionFilePutContent(msg);
|
|
526
516
|
break;
|
|
527
517
|
case "set_auto_approval":
|
|
528
|
-
broadcastMsg = await this.
|
|
518
|
+
broadcastMsg = await this.approvalManager.setAutoApprove(msg.server_name, msg.tool, msg.auto_approve);
|
|
529
519
|
break;
|
|
530
520
|
case "set_system_prompt":
|
|
531
521
|
broadcastMsg = await this.handleSetSystemPrompt(msg.system_prompt);
|
|
@@ -553,11 +543,11 @@ class OpenSession {
|
|
|
553
543
|
if (broadcastMsg) {
|
|
554
544
|
if (broadcastMsg instanceof Array) {
|
|
555
545
|
broadcastMsg.map((msg) => {
|
|
556
|
-
this.broadcast(msg);
|
|
546
|
+
this.sender.broadcast(msg);
|
|
557
547
|
});
|
|
558
548
|
}
|
|
559
549
|
else {
|
|
560
|
-
this.broadcast(broadcastMsg);
|
|
550
|
+
this.sender.broadcast(broadcastMsg);
|
|
561
551
|
}
|
|
562
552
|
}
|
|
563
553
|
}
|
|
@@ -573,7 +563,7 @@ class OpenSession {
|
|
|
573
563
|
await this.db.sessionUpdateAccessToken(this.sessionUUID, accessToken);
|
|
574
564
|
this.accessToken = accessToken;
|
|
575
565
|
}
|
|
576
|
-
this.sendTo(from, {
|
|
566
|
+
this.sender.sendTo(from, {
|
|
577
567
|
type: "session_shared",
|
|
578
568
|
access_token: this.accessToken,
|
|
579
569
|
client_message_id: msg.client_message_id,
|
|
@@ -604,13 +594,9 @@ class OpenSession {
|
|
|
604
594
|
this.contextManager.addMessages(llmUserMessages);
|
|
605
595
|
return this.contextManager.endAgentResponse();
|
|
606
596
|
}
|
|
607
|
-
/**
|
|
608
|
-
* `processUserMessage` logic when agent is active. Start the Agent loop,
|
|
609
|
-
* adding all agent messages to the context. Extract the new DB messages.
|
|
610
|
-
*/
|
|
611
597
|
async processUserMessagesActive(msgs) {
|
|
612
598
|
const { llmUserMessages, agentFirstChunk } = this.contextManager.startAgentResponse(msgs);
|
|
613
|
-
this.broadcast(agentFirstChunk);
|
|
599
|
+
this.sender.broadcast(agentFirstChunk);
|
|
614
600
|
try {
|
|
615
601
|
await this.agent.userMessagesRaw(llmUserMessages);
|
|
616
602
|
}
|
|
@@ -618,20 +604,18 @@ class OpenSession {
|
|
|
618
604
|
logger.warn(`[OpenSession.processUserMessages] agent error: ${String(e)}`);
|
|
619
605
|
// Errors during agent replies must be turned into messages.
|
|
620
606
|
const errMsg = `error from LLM: ${String(e)}`;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
this.broadcast(err);
|
|
624
|
-
// return await this.processuserMessagesActive(msgs);
|
|
607
|
+
this.contextManager.revertAgentResponse(errMsg);
|
|
608
|
+
throw new Error(errMsg);
|
|
625
609
|
}
|
|
626
610
|
return this.contextManager.endAgentResponse();
|
|
627
611
|
}
|
|
628
612
|
async processUserMessages(msgs) {
|
|
629
|
-
const newSessionMessages = this.agentPaused
|
|
630
|
-
? this.processUserMessagePaused(msgs)
|
|
631
|
-
: await this.processUserMessagesActive(msgs);
|
|
632
|
-
logger.debug("[processUserMessages] newSessionMessages: " +
|
|
633
|
-
JSON.stringify(newSessionMessages));
|
|
634
613
|
try {
|
|
614
|
+
const newSessionMessages = this.agentPaused
|
|
615
|
+
? this.processUserMessagePaused(msgs)
|
|
616
|
+
: await this.processUserMessagesActive(msgs);
|
|
617
|
+
logger.debug("[processUserMessages] newSessionMessages: " +
|
|
618
|
+
JSON.stringify(newSessionMessages));
|
|
635
619
|
// Append to in-memory conversation and write to the DB
|
|
636
620
|
const dbsm = this.db.createTypedClient(dbSessionMessages_1.DbSessionMessages);
|
|
637
621
|
await dbsm.append(this.sessionUUID, newSessionMessages);
|
|
@@ -642,28 +626,32 @@ class OpenSession {
|
|
|
642
626
|
}
|
|
643
627
|
}
|
|
644
628
|
}
|
|
645
|
-
handleUserMessage(msg, from) {
|
|
629
|
+
async handleUserMessage(msg, from) {
|
|
646
630
|
// Return a ServerUserMessage for broadcast. The actual message is places
|
|
647
631
|
// on a queue to be dealt with in another loop. This allows Agent
|
|
648
632
|
// processing of user messages to depend on other messages.
|
|
649
633
|
(0, assert_1.strict)(msg);
|
|
650
634
|
(0, assert_1.strict)(from);
|
|
651
635
|
// Assign the user message_idx and attempt to enqueue.
|
|
652
|
-
const
|
|
636
|
+
const user = this.sessionParticipants.get(from);
|
|
637
|
+
if (!user) {
|
|
638
|
+
throw new Error(`unrecognized user ${from}`);
|
|
639
|
+
}
|
|
640
|
+
const userMessage = this.contextManager.processUserMessage(msg, from, user.nickname);
|
|
653
641
|
if (!userMessage) {
|
|
654
642
|
return;
|
|
655
643
|
}
|
|
656
644
|
// Special case for the first message of the session
|
|
657
645
|
if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE) {
|
|
658
646
|
// No need to wait for this to complete before broadcasting.
|
|
659
|
-
|
|
647
|
+
await this.onFirstMessage(userMessage);
|
|
660
648
|
}
|
|
661
649
|
if (!this.userMessageQueue.tryEnqueue(userMessage)) {
|
|
662
650
|
// We failed to enqueue - revert the `getNextMessageIdx`
|
|
663
651
|
// NOTE: Nothing should await between `getNextMessageIdx` and
|
|
664
652
|
// `freeMessageIdx` here.
|
|
665
653
|
this.contextManager.unprocessUserMessage(userMessage);
|
|
666
|
-
this.sendTo(from, {
|
|
654
|
+
this.sender.sendTo(from, {
|
|
667
655
|
type: "session_error",
|
|
668
656
|
message: "failed to queue message. try again later.",
|
|
669
657
|
session_id: this.sessionUUID,
|
|
@@ -703,13 +691,28 @@ class OpenSession {
|
|
|
703
691
|
await this.db.sessionUpdateTitle(this.sessionUUID, this.sessionTitle);
|
|
704
692
|
}
|
|
705
693
|
(0, assert_1.strict)(this.isPersisted);
|
|
706
|
-
//
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
694
|
+
// Send session created notification
|
|
695
|
+
const sessionInfo = this.serverSessionInfo("");
|
|
696
|
+
if (this.teamUUID) {
|
|
697
|
+
// Team session: notify all members about the new session
|
|
698
|
+
try {
|
|
699
|
+
const teamMembers = await this.db.teamGetMembers(this.teamUUID);
|
|
700
|
+
const teamMemberIds = new Set(teamMembers.map((m) => m.user_uuid));
|
|
701
|
+
this.sender.connectionManager.sendToUsers(teamMemberIds, sessionInfo);
|
|
702
|
+
logger.info(`[OpenSession] notified ${String(teamMemberIds.size)} team members` +
|
|
703
|
+
` about new session ${this.sessionUUID} in team ${this.teamUUID}`);
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
logger.error("[OpenSession] Error notifying team members about session" +
|
|
707
|
+
`${this.sessionUUID}: ${String(error)}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// If this is a user session, notify the session owner
|
|
712
|
+
this.sender.connectionManager.sendToUsers(new Set([this.userUUID]), sessionInfo);
|
|
713
|
+
logger.info(`[OpenSession] notified session owner ${this.userUUID} about ` +
|
|
714
|
+
`new session ${this.sessionUUID}`);
|
|
715
|
+
}
|
|
713
716
|
}
|
|
714
717
|
async handleAddMcpServer(serverName, enableAll) {
|
|
715
718
|
logger.info(`[onAddMcpServer]: Adding server ${serverName} ` +
|
|
@@ -862,7 +865,7 @@ class OpenSession {
|
|
|
862
865
|
name: msg.name,
|
|
863
866
|
data_url,
|
|
864
867
|
};
|
|
865
|
-
this.sendTo(from, contentMsg);
|
|
868
|
+
this.sender.sendTo(from, contentMsg);
|
|
866
869
|
}
|
|
867
870
|
async handleSessionFileDelete(msg) {
|
|
868
871
|
await this.sessionFileManager.deleteFile(msg.name);
|
|
@@ -882,18 +885,6 @@ class OpenSession {
|
|
|
882
885
|
// `ISessionFileManagerEventHandler.onFileChanged` if the deletion
|
|
883
886
|
// succeeds. We broadcast in that callback.
|
|
884
887
|
}
|
|
885
|
-
async onSetAutoApproval(serverName, tool, autoApprove) {
|
|
886
|
-
if ((0, sdk_1.prefsSetAutoApprove)(this.agentProfilePreferences, serverName, tool, autoApprove)) {
|
|
887
|
-
await this.db.updateAgentProfilePreferences(this.agentProfileUUID, this.agentProfilePreferences);
|
|
888
|
-
return {
|
|
889
|
-
type: "tool_auto_approval_set",
|
|
890
|
-
server_name: serverName,
|
|
891
|
-
tool,
|
|
892
|
-
auto_approve: autoApprove,
|
|
893
|
-
session_id: this.sessionUUID,
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
888
|
ensureMcpServer(serverName) {
|
|
898
889
|
return this.skillManager.getMcpServer(serverName);
|
|
899
890
|
}
|
|
@@ -916,18 +907,18 @@ class OpenSession {
|
|
|
916
907
|
* This only updates the local participant map - actual membership
|
|
917
908
|
* tracking is handled by SessionRegistry.
|
|
918
909
|
*/
|
|
919
|
-
addParticipant(userId,
|
|
920
|
-
this.sessionParticipants.set(userId,
|
|
910
|
+
addParticipant(userId, participant) {
|
|
911
|
+
this.sessionParticipants.set(userId, participant);
|
|
921
912
|
// Broadcast result to all session participants
|
|
922
913
|
const broadcastMessage = {
|
|
923
914
|
type: "user_added",
|
|
924
915
|
user_uuid: userId,
|
|
925
916
|
role: "participant",
|
|
926
|
-
nickname:
|
|
927
|
-
email:
|
|
917
|
+
nickname: participant.nickname,
|
|
918
|
+
email: participant.email,
|
|
928
919
|
session_id: this.sessionUUID,
|
|
929
920
|
};
|
|
930
|
-
this.broadcast(broadcastMessage);
|
|
921
|
+
this.sender.broadcast(broadcastMessage);
|
|
931
922
|
}
|
|
932
923
|
/**
|
|
933
924
|
* Remove participant from session (called by SessionRegistry).
|
|
@@ -942,7 +933,7 @@ class OpenSession {
|
|
|
942
933
|
user_uuid: userId,
|
|
943
934
|
session_id: this.sessionUUID,
|
|
944
935
|
};
|
|
945
|
-
this.broadcast(broadcastMessage);
|
|
936
|
+
this.sender.broadcast(broadcastMessage);
|
|
946
937
|
}
|
|
947
938
|
getSessionParticipants() {
|
|
948
939
|
return Array.from(this.sessionParticipants.entries()).map(([userId, user]) => ({
|
|
@@ -1029,3 +1020,15 @@ async function loadSessionData(db, sessionId) {
|
|
|
1029
1020
|
sessionParticipants: (0, database_1.createSessionParticipantMap)(sessionParticipants),
|
|
1030
1021
|
};
|
|
1031
1022
|
}
|
|
1023
|
+
async function createContextAndAgent(sessionUUID, systemPrompt, model, sessionMessages, workspace, sessionCheckpoint, ownerData, ownerApiKey, llmUrl, xmcpUrl, fileManager, sender, platform, approvalManager) {
|
|
1024
|
+
const contextManager = new chatContextManager_1.ChatContextManager(systemPrompt, sessionMessages, sessionUUID, ownerData.uuid, sessionCheckpoint, llmUrl, model, ownerApiKey, undefined, // TODO
|
|
1025
|
+
fileManager);
|
|
1026
|
+
if (workspace) {
|
|
1027
|
+
contextManager.setWorkspace((0, agent_1.createUserMessage)(workspace.message, workspace.imageB64));
|
|
1028
|
+
}
|
|
1029
|
+
const eventHandler = new ChatSessionAgentEventHandler(sessionUUID, sender, approvalManager, contextManager);
|
|
1030
|
+
const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
1031
|
+
const [agent, skillManager] = await (0, agentUtils_1.createAgentWithoutSkills)(llmUrl, model, eventHandler, platform, contextManager, ownerApiKey, xmcpConfig, undefined, true);
|
|
1032
|
+
await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, platform, fileManager, llmUrl, ownerApiKey);
|
|
1033
|
+
return { agent, skillManager, contextManager };
|
|
1034
|
+
}
|