@xalia/agent 0.6.2 → 0.6.3
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
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
Configuration,
|
|
8
8
|
getLogger,
|
|
9
9
|
McpServerSettings,
|
|
10
|
-
prefsGetAutoApprove,
|
|
11
|
-
prefsSetAutoApprove,
|
|
12
10
|
SavedAgentProfile,
|
|
13
11
|
} from "@xalia/xmcp/sdk";
|
|
14
12
|
|
|
@@ -34,13 +32,11 @@ import type {
|
|
|
34
32
|
ServerModelUpdated,
|
|
35
33
|
ServerUserMessage,
|
|
36
34
|
ClientUserMessage,
|
|
37
|
-
ServerToolAutoApprovalSet,
|
|
38
35
|
ClientSessionMessage,
|
|
39
36
|
ServerSessionError,
|
|
40
37
|
ServerUserAdded,
|
|
41
38
|
ServerUserRemoved,
|
|
42
39
|
ServerSessionInfo,
|
|
43
|
-
ServerSessionUpdate,
|
|
44
40
|
ClientSetWorkspace,
|
|
45
41
|
ClientToServer,
|
|
46
42
|
ServerSessionFileContent,
|
|
@@ -66,7 +62,10 @@ import {
|
|
|
66
62
|
createSessionParticipantMap,
|
|
67
63
|
UserData,
|
|
68
64
|
} from "../data/database";
|
|
69
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
DbAgentPreferencesWriter,
|
|
67
|
+
ToolApprovalManager,
|
|
68
|
+
} from "../utils/approvalManager";
|
|
70
69
|
import { ChatErrorMessage, ChatFatalError } from "../protocol/errors";
|
|
71
70
|
import { addDefaultChatTools } from "./tools";
|
|
72
71
|
import { ChatContextManager, ICheckpointWriter } from "./chatContextManager";
|
|
@@ -86,6 +85,7 @@ import { DbMcpServerConfigs } from "../data/dbMcpServerConfigs";
|
|
|
86
85
|
import { SessionFileEntry } from "../data/dbSessionFileModels";
|
|
87
86
|
import { ApiKeyManager } from "../data/apiKeyManager";
|
|
88
87
|
import { DbSessionMessages } from "../data/dbSessionMessages";
|
|
88
|
+
import { ISessionMessageSender } from "./openSessionMessageSender";
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* The model to use when the AgentProfile does not specify one.
|
|
@@ -127,6 +127,170 @@ class DBCheckpointWriter implements ICheckpointWriter {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export class ChatSessionMessageSender
|
|
131
|
+
implements ISessionMessageSender<ServerToClient>
|
|
132
|
+
{
|
|
133
|
+
constructor(
|
|
134
|
+
public readonly connectionManager: IUserConnectionManager<ServerToClient>,
|
|
135
|
+
public readonly sessionParticipants: SessionParticipantMap
|
|
136
|
+
) {}
|
|
137
|
+
|
|
138
|
+
broadcast(msg: ServerToClient): void {
|
|
139
|
+
const users: Set<string> = new Set(this.sessionParticipants.keys());
|
|
140
|
+
this.connectionManager.sendToUsers(users, msg);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
sendTo(userUUID: string, msg: ServerToClient): void {
|
|
144
|
+
this.connectionManager.sendToUsers(new Set([userUUID]), msg);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class ChatSessionPlatform {
|
|
149
|
+
constructor(
|
|
150
|
+
private sender: ISessionMessageSender<ServerToClient>,
|
|
151
|
+
private sessionUUID: string,
|
|
152
|
+
private ownerUUID: string
|
|
153
|
+
) {}
|
|
154
|
+
|
|
155
|
+
// IPlatform.openUrl
|
|
156
|
+
openUrl(url: string, authResultP: Promise<boolean>, display_name: string) {
|
|
157
|
+
// These requests are always passed to the original owner, since it is
|
|
158
|
+
// their settings that will be used for all MCP servers.
|
|
159
|
+
this.sender.broadcast({
|
|
160
|
+
type: "authentication_started",
|
|
161
|
+
session_id: this.sessionUUID,
|
|
162
|
+
url,
|
|
163
|
+
});
|
|
164
|
+
this.sender.sendTo(this.ownerUUID, {
|
|
165
|
+
type: "authenticate",
|
|
166
|
+
session_id: this.sessionUUID,
|
|
167
|
+
url,
|
|
168
|
+
display_name,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// TODO: auth timeout
|
|
172
|
+
// Don't stall this function waiting for authentication
|
|
173
|
+
void authResultP.then((result) => {
|
|
174
|
+
this.sender.broadcast({
|
|
175
|
+
type: "authentication_finished",
|
|
176
|
+
session_id: this.sessionUUID,
|
|
177
|
+
url,
|
|
178
|
+
result,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// IPlatform.load
|
|
184
|
+
load(filename: string): Promise<string> {
|
|
185
|
+
if (process.env.DEVELOPMENT === "1") {
|
|
186
|
+
return NODE_PLATFORM.load(filename);
|
|
187
|
+
}
|
|
188
|
+
throw new ChatErrorMessage("Platform.load not implemented");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// IPlatform.renderHTML
|
|
192
|
+
renderHTML(html: string): Promise<void> {
|
|
193
|
+
return new Promise<void>((r) => {
|
|
194
|
+
this.sender.broadcast({
|
|
195
|
+
type: "render_html",
|
|
196
|
+
html,
|
|
197
|
+
session_id: this.sessionUUID,
|
|
198
|
+
});
|
|
199
|
+
r();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
205
|
+
constructor(
|
|
206
|
+
private readonly sessionUUID: string,
|
|
207
|
+
private readonly sender: ISessionMessageSender<ServerToClient>,
|
|
208
|
+
private readonly approvalManager: ToolApprovalManager,
|
|
209
|
+
private readonly contextManager: ChatContextManager
|
|
210
|
+
) {}
|
|
211
|
+
|
|
212
|
+
onCompletion(result: ChatCompletionAssistantMessageParam): void {
|
|
213
|
+
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
214
|
+
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
215
|
+
this.contextManager.processAgentResponse(result);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
onImage(image: OpenAI.Chat.Completions.ChatCompletionContentPartImage): void {
|
|
219
|
+
logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
|
|
220
|
+
throw new Error("[OpenSession.onImage] unimplemented");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
onToolCallResult(result: ChatCompletionToolMessageParam): void {
|
|
224
|
+
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
225
|
+
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
226
|
+
this.sender.broadcast(toolCallMessage);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async onToolCall(
|
|
230
|
+
toolCall: ChatCompletionMessageToolCall,
|
|
231
|
+
agentTool: boolean
|
|
232
|
+
): Promise<boolean> {
|
|
233
|
+
if (agentTool) {
|
|
234
|
+
// "Agent" tools are considered internal to the agent, and are always
|
|
235
|
+
// permitted. Inform all clients and immediately approve.
|
|
236
|
+
this.sender.broadcast({
|
|
237
|
+
type: "tool_call",
|
|
238
|
+
tool_call: toolCall,
|
|
239
|
+
session_id: this.sessionUUID,
|
|
240
|
+
});
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
245
|
+
|
|
246
|
+
const [serverName, tool] = toolCall.function.name.split("__");
|
|
247
|
+
const { approved, requested } = await this.approvalManager.getApproval(
|
|
248
|
+
serverName,
|
|
249
|
+
tool,
|
|
250
|
+
toolCall
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// For now, the frontend uses the tool_call data in the
|
|
254
|
+
// "approve_tool_call" request to display the tool call data. If approval
|
|
255
|
+
// was requested in this way, don't send the "tool_call" message as well.
|
|
256
|
+
|
|
257
|
+
if (approved && !requested) {
|
|
258
|
+
this.sender.broadcast({
|
|
259
|
+
type: "tool_call",
|
|
260
|
+
tool_call: toolCall,
|
|
261
|
+
session_id: this.sessionUUID,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return approved;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onAgentMessage(msg: string, end: boolean): Promise<void> {
|
|
269
|
+
logger.debug(
|
|
270
|
+
`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
274
|
+
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
275
|
+
this.sender.broadcast(agentMsgChunk);
|
|
276
|
+
return Promise.resolve();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
onReasoning(reasoning: string): Promise<void> {
|
|
280
|
+
return new Promise<void>((r) => {
|
|
281
|
+
logger.debug(`[OpenSession.onReasoning]${reasoning}`);
|
|
282
|
+
if (reasoning.length > 0) {
|
|
283
|
+
this.sender.broadcast({
|
|
284
|
+
type: "agent_reasoning_chunk",
|
|
285
|
+
reasoning,
|
|
286
|
+
session_id: this.sessionUUID,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
r();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
130
294
|
/**
|
|
131
295
|
* Describes a Session (conversation) with connected participants.
|
|
132
296
|
*
|
|
@@ -137,9 +301,7 @@ class DBCheckpointWriter implements ICheckpointWriter {
|
|
|
137
301
|
* be seen until user messages had been fully processed, which could block
|
|
138
302
|
* tool approvals and other interactions).
|
|
139
303
|
*/
|
|
140
|
-
export class OpenSession
|
|
141
|
-
implements IAgentEventHandler, ISessionFileManagerEventHandler, IPlatform
|
|
142
|
-
{
|
|
304
|
+
export class OpenSession implements ISessionFileManagerEventHandler {
|
|
143
305
|
private readonly db: Database;
|
|
144
306
|
private /* readonly */ agent: Agent;
|
|
145
307
|
private readonly sessionUUID: string;
|
|
@@ -149,11 +311,11 @@ export class OpenSession
|
|
|
149
311
|
private readonly sessionParticipants: SessionParticipantMap;
|
|
150
312
|
private readonly agentProfilePreferences: AgentPreferences;
|
|
151
313
|
private /* readonly */ skillManager: SkillManager;
|
|
152
|
-
private readonly
|
|
314
|
+
private readonly sender: ChatSessionMessageSender;
|
|
153
315
|
private readonly messageQueue: AsyncQueue<QueuedClientMessage>;
|
|
154
316
|
private readonly userMessageQueue: MultiAsyncQueue<ServerUserMessage>;
|
|
155
317
|
private readonly contextManager: ChatContextManager;
|
|
156
|
-
private readonly approvalManager:
|
|
318
|
+
private readonly approvalManager: ToolApprovalManager;
|
|
157
319
|
private readonly savedAgentProfile: SavedAgentProfile;
|
|
158
320
|
private readonly sessionFileManager: ISessionFileManager;
|
|
159
321
|
private isPersisted: boolean;
|
|
@@ -172,8 +334,8 @@ export class OpenSession
|
|
|
172
334
|
agentProfilePreferences: AgentPreferences,
|
|
173
335
|
skillManager: SkillManager,
|
|
174
336
|
contextManager: ChatContextManager,
|
|
175
|
-
|
|
176
|
-
approvalManager:
|
|
337
|
+
sender: ChatSessionMessageSender,
|
|
338
|
+
approvalManager: ToolApprovalManager,
|
|
177
339
|
fileManager: ISessionFileManager
|
|
178
340
|
) {
|
|
179
341
|
this.db = db;
|
|
@@ -186,7 +348,7 @@ export class OpenSession
|
|
|
186
348
|
this.sessionParticipants = sessionParticipants;
|
|
187
349
|
this.agentProfilePreferences = agentProfilePreferences;
|
|
188
350
|
this.skillManager = skillManager;
|
|
189
|
-
this.
|
|
351
|
+
this.sender = sender;
|
|
190
352
|
this.messageQueue = new AsyncQueue<QueuedClientMessage>((m) =>
|
|
191
353
|
this.processMessage(m)
|
|
192
354
|
);
|
|
@@ -223,70 +385,52 @@ export class OpenSession
|
|
|
223
385
|
const sessionId = sessionData.session_uuid;
|
|
224
386
|
|
|
225
387
|
const fileManager = await ChatSessionFileManager.init(db, sessionId);
|
|
226
|
-
const
|
|
388
|
+
const sender = new ChatSessionMessageSender(
|
|
389
|
+
connectionManager,
|
|
390
|
+
sessionParticipants
|
|
391
|
+
);
|
|
392
|
+
const platform = new ChatSessionPlatform(sender, sessionId, ownerData.uuid);
|
|
393
|
+
const toolApprovalManager = new ToolApprovalManager(
|
|
394
|
+
sessionData.session_uuid,
|
|
395
|
+
savedAgentProfile.uuid,
|
|
396
|
+
savedAgentProfile.preferences,
|
|
397
|
+
sender,
|
|
398
|
+
new DbAgentPreferencesWriter(db)
|
|
399
|
+
);
|
|
400
|
+
const { agent, skillManager, contextManager } = await createContextAndAgent(
|
|
401
|
+
sessionId,
|
|
227
402
|
savedAgentProfile.profile.system_prompt,
|
|
403
|
+
savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL,
|
|
228
404
|
sessionMessages,
|
|
229
|
-
|
|
230
|
-
ownerData.uuid,
|
|
405
|
+
sessionData.workspace,
|
|
231
406
|
sessionCheckpoint,
|
|
232
|
-
|
|
233
|
-
savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL,
|
|
407
|
+
ownerData,
|
|
234
408
|
ownerApiKey,
|
|
235
|
-
|
|
236
|
-
|
|
409
|
+
llmUrl,
|
|
410
|
+
xmcpUrl,
|
|
411
|
+
fileManager,
|
|
412
|
+
sender,
|
|
413
|
+
platform,
|
|
414
|
+
toolApprovalManager
|
|
237
415
|
);
|
|
238
|
-
if (sessionData.workspace) {
|
|
239
|
-
const ws = sessionData.workspace;
|
|
240
|
-
contextManager.setWorkspace(createUserMessage(ws.message, ws.imageB64));
|
|
241
|
-
}
|
|
242
416
|
|
|
243
417
|
const openSession = new OpenSession(
|
|
244
418
|
db,
|
|
245
|
-
|
|
419
|
+
agent,
|
|
246
420
|
sessionData,
|
|
247
421
|
savedAgentProfile,
|
|
248
422
|
isPersisted,
|
|
249
423
|
sessionParticipants,
|
|
250
424
|
savedAgentProfile.preferences,
|
|
251
|
-
|
|
425
|
+
skillManager,
|
|
252
426
|
contextManager,
|
|
253
|
-
|
|
254
|
-
|
|
427
|
+
sender,
|
|
428
|
+
toolApprovalManager,
|
|
255
429
|
fileManager
|
|
256
430
|
);
|
|
257
431
|
|
|
258
|
-
//
|
|
259
|
-
// the OpenSession and client are fully set up).
|
|
260
|
-
|
|
261
|
-
const xmcpConfig = Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
262
|
-
const [agent, skillManager] = await createAgentWithoutSkills(
|
|
263
|
-
llmUrl,
|
|
264
|
-
savedAgentProfile.profile,
|
|
265
|
-
DEFAULT_CHAT_LLM_MODEL,
|
|
266
|
-
openSession,
|
|
267
|
-
openSession,
|
|
268
|
-
contextManager,
|
|
269
|
-
ownerApiKey,
|
|
270
|
-
xmcpConfig,
|
|
271
|
-
undefined,
|
|
272
|
-
true
|
|
273
|
-
);
|
|
274
|
-
await addDefaultChatTools(
|
|
275
|
-
agent,
|
|
276
|
-
ownerData.timezone,
|
|
277
|
-
openSession,
|
|
278
|
-
fileManager,
|
|
279
|
-
llmUrl,
|
|
280
|
-
ownerApiKey
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
// Update OpenSession with real agent and skillManager
|
|
284
|
-
openSession.agent = agent;
|
|
285
|
-
openSession.skillManager = skillManager;
|
|
432
|
+
// Note, MCP servers have not been enabled yet
|
|
286
433
|
|
|
287
|
-
await openSession.restoreMcpSettings(
|
|
288
|
-
savedAgentProfile.profile.mcp_settings
|
|
289
|
-
);
|
|
290
434
|
return openSession;
|
|
291
435
|
}
|
|
292
436
|
|
|
@@ -311,12 +455,18 @@ export class OpenSession
|
|
|
311
455
|
}
|
|
312
456
|
|
|
313
457
|
const sessionParticipants = new Map<string, TeamParticipant>();
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
458
|
+
if (sessionData.participants && sessionData.participants.length > 0) {
|
|
459
|
+
sessionData.participants.forEach((p) => {
|
|
460
|
+
sessionParticipants.set(p.user_uuid, p);
|
|
461
|
+
});
|
|
462
|
+
} else {
|
|
463
|
+
sessionParticipants.set(sessionData.user_uuid, {
|
|
464
|
+
user_uuid: sessionData.user_uuid,
|
|
465
|
+
nickname: ownerData.nickname || "",
|
|
466
|
+
email: ownerData.email,
|
|
467
|
+
role: "owner",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
320
470
|
|
|
321
471
|
return OpenSession.init(
|
|
322
472
|
db,
|
|
@@ -392,18 +542,34 @@ export class OpenSession
|
|
|
392
542
|
return this.sessionParticipants;
|
|
393
543
|
}
|
|
394
544
|
|
|
395
|
-
sendSessionData(
|
|
545
|
+
async sendSessionData(
|
|
546
|
+
connectionId: string,
|
|
547
|
+
clientMessageId: string,
|
|
548
|
+
restoreMcpState: boolean
|
|
549
|
+
): Promise<void> {
|
|
396
550
|
logger.info(
|
|
397
551
|
`[SessionRegistry] sending session data for session ${this.sessionUUID}`
|
|
398
552
|
);
|
|
399
553
|
|
|
400
554
|
const sessionInfo = this.serverSessionInfo(clientMessageId);
|
|
401
|
-
this.connectionManager
|
|
555
|
+
const connMgr = this.sender.connectionManager;
|
|
556
|
+
connMgr.sendToConnection(connectionId, sessionInfo);
|
|
557
|
+
|
|
558
|
+
// This could be cleaner. If the session has just been created, we must
|
|
559
|
+
// restore the mcp servers. However, we cannot do that until the client
|
|
560
|
+
// has initialized itself (in case we need auth messages), hence it must
|
|
561
|
+
// happen at this stage, BEFORE we call sendMcpSettings below.
|
|
562
|
+
|
|
563
|
+
if (restoreMcpState) {
|
|
564
|
+
await this.restoreMcpSettings(
|
|
565
|
+
this.savedAgentProfile.profile.mcp_settings
|
|
566
|
+
);
|
|
567
|
+
}
|
|
402
568
|
|
|
403
569
|
// send session file info
|
|
404
570
|
const fileDescriptors = this.sessionFileManager.listFiles();
|
|
405
571
|
fileDescriptors.forEach((descriptor) => {
|
|
406
|
-
|
|
572
|
+
connMgr.sendToConnection(connectionId, {
|
|
407
573
|
type: "session_file_changed",
|
|
408
574
|
session_id: this.sessionUUID,
|
|
409
575
|
descriptor,
|
|
@@ -414,7 +580,7 @@ export class OpenSession
|
|
|
414
580
|
// send conversation history
|
|
415
581
|
const conversationMessages = this.contextManager.getConversationMessages();
|
|
416
582
|
conversationMessages.forEach((message) => {
|
|
417
|
-
|
|
583
|
+
connMgr.sendToConnection(connectionId, message);
|
|
418
584
|
});
|
|
419
585
|
|
|
420
586
|
// add MCP settings
|
|
@@ -422,12 +588,12 @@ export class OpenSession
|
|
|
422
588
|
|
|
423
589
|
// add system prompt and model
|
|
424
590
|
const agentProfile = this.agent.getAgentProfile();
|
|
425
|
-
|
|
591
|
+
connMgr.sendToConnection(connectionId, {
|
|
426
592
|
type: "system_prompt_updated",
|
|
427
593
|
system_prompt: agentProfile.system_prompt,
|
|
428
594
|
session_id: this.sessionUUID,
|
|
429
595
|
});
|
|
430
|
-
|
|
596
|
+
connMgr.sendToConnection(connectionId, {
|
|
431
597
|
type: "model_updated",
|
|
432
598
|
model: agentProfile.model || "",
|
|
433
599
|
session_id: this.sessionUUID,
|
|
@@ -478,7 +644,7 @@ export class OpenSession
|
|
|
478
644
|
skillManager.enableTool(server_name, enabled_tool);
|
|
479
645
|
}
|
|
480
646
|
} catch (e) {
|
|
481
|
-
this.broadcast({
|
|
647
|
+
this.sender.broadcast({
|
|
482
648
|
type: "session_error",
|
|
483
649
|
message: `Error adding MCP server ${server_name}: ${String(e)}`,
|
|
484
650
|
session_id: this.sessionUUID,
|
|
@@ -497,9 +663,9 @@ export class OpenSession
|
|
|
497
663
|
private handleError(err: unknown, from?: string): boolean {
|
|
498
664
|
const sendError = (msg: ServerSessionError) => {
|
|
499
665
|
if (from) {
|
|
500
|
-
this.sendTo(from, msg);
|
|
666
|
+
this.sender.sendTo(from, msg);
|
|
501
667
|
} else {
|
|
502
|
-
this.broadcast(msg);
|
|
668
|
+
this.sender.broadcast(msg);
|
|
503
669
|
}
|
|
504
670
|
};
|
|
505
671
|
|
|
@@ -529,186 +695,9 @@ export class OpenSession
|
|
|
529
695
|
return true;
|
|
530
696
|
}
|
|
531
697
|
|
|
532
|
-
private broadcast(msg: ServerToClient): void {
|
|
533
|
-
const users: Set<string> = new Set(this.sessionParticipants.keys());
|
|
534
|
-
this.connectionManager.sendToUsers(users, msg);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
sendTo(user_uuid: string, msg: ServerToClient): void {
|
|
538
|
-
this.connectionManager.sendToUsers(new Set([user_uuid]), msg);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// IPlatform.openUrl
|
|
542
|
-
openUrl(url: string, authResultP: Promise<boolean>, display_name: string) {
|
|
543
|
-
// These requests are always passed to the original owner, since it is
|
|
544
|
-
// their settings that will be used for all MCP servers.
|
|
545
|
-
this.broadcast({
|
|
546
|
-
type: "authentication_started",
|
|
547
|
-
session_id: this.sessionUUID,
|
|
548
|
-
url,
|
|
549
|
-
});
|
|
550
|
-
this.sendTo(this.userUUID, {
|
|
551
|
-
type: "authenticate",
|
|
552
|
-
session_id: this.sessionUUID,
|
|
553
|
-
url,
|
|
554
|
-
display_name,
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// TODO: auth timeout
|
|
558
|
-
// Don't stall this function waiting for authentication
|
|
559
|
-
void authResultP.then((result) => {
|
|
560
|
-
this.sendTo(this.userUUID, {
|
|
561
|
-
type: "authentication_finished",
|
|
562
|
-
session_id: this.sessionUUID,
|
|
563
|
-
url,
|
|
564
|
-
result,
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// IPlatform.load
|
|
570
|
-
load(filename: string): Promise<string> {
|
|
571
|
-
if (process.env.DEVELOPMENT === "1") {
|
|
572
|
-
return NODE_PLATFORM.load(filename);
|
|
573
|
-
}
|
|
574
|
-
throw new ChatErrorMessage("Platform.load not implemented");
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// IPlatform.renderHTML
|
|
578
|
-
renderHTML(html: string): Promise<void> {
|
|
579
|
-
return new Promise<void>((r) => {
|
|
580
|
-
this.broadcast({
|
|
581
|
-
type: "render_html",
|
|
582
|
-
html,
|
|
583
|
-
session_id: this.sessionUUID,
|
|
584
|
-
});
|
|
585
|
-
r();
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// IAgentEventHandler.onCompletion
|
|
590
|
-
onCompletion(result: ChatCompletionAssistantMessageParam): void {
|
|
591
|
-
logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
|
|
592
|
-
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
593
|
-
this.contextManager.processAgentResponse(result);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// IAgentEventHandler.onImage
|
|
597
|
-
onImage(image: OpenAI.Chat.Completions.ChatCompletionContentPartImage): void {
|
|
598
|
-
logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
|
|
599
|
-
throw new Error("[OpenSession.onImage] unimplemented");
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// IAgentEventHandler.onToolCallResult
|
|
603
|
-
onToolCallResult(result: ChatCompletionToolMessageParam): void {
|
|
604
|
-
logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
|
|
605
|
-
const toolCallMessage = this.contextManager.processToolCallResult(result);
|
|
606
|
-
this.broadcast(toolCallMessage);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// IAgentEventHandler.onToolCall
|
|
610
|
-
async onToolCall(
|
|
611
|
-
toolCall: ChatCompletionMessageToolCall,
|
|
612
|
-
agentTool: boolean
|
|
613
|
-
): Promise<boolean> {
|
|
614
|
-
if (agentTool) {
|
|
615
|
-
// "Agent" tools are considered internal to the agent, and are always
|
|
616
|
-
// permitted. Inform all clients and immediately approve.
|
|
617
|
-
this.broadcast({
|
|
618
|
-
type: "tool_call",
|
|
619
|
-
tool_call: toolCall,
|
|
620
|
-
session_id: this.sessionUUID,
|
|
621
|
-
});
|
|
622
|
-
return true;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// TODO: Need a proper mapping to/from MCP calls to tool names
|
|
626
|
-
|
|
627
|
-
const [serverName, tool] = toolCall.function.name.split("__");
|
|
628
|
-
const autoApproved = prefsGetAutoApprove(
|
|
629
|
-
this.agentProfilePreferences,
|
|
630
|
-
serverName,
|
|
631
|
-
tool
|
|
632
|
-
);
|
|
633
|
-
if (!autoApproved) {
|
|
634
|
-
const { id, resultP } = this.approvalManager.startApproval(
|
|
635
|
-
toolCall.function.name
|
|
636
|
-
);
|
|
637
|
-
this.broadcast({
|
|
638
|
-
type: "approve_tool_call",
|
|
639
|
-
id,
|
|
640
|
-
tool_call: toolCall,
|
|
641
|
-
session_id: this.sessionUUID,
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
try {
|
|
645
|
-
logger.debug(`[OpenSession.onToolCall] awaiting approval ${id}`);
|
|
646
|
-
const { approved, auto_approve } = await resultP;
|
|
647
|
-
logger.debug(
|
|
648
|
-
`[OpenSession.onToolCall] approval ${id}: ${String(approved)}`
|
|
649
|
-
);
|
|
650
|
-
if (auto_approve) {
|
|
651
|
-
logger.debug(
|
|
652
|
-
"[OpenSession.onToolCall] auto_approve set. updated preferences"
|
|
653
|
-
);
|
|
654
|
-
const autoApprovalMsg = await this.onSetAutoApproval(
|
|
655
|
-
serverName,
|
|
656
|
-
tool,
|
|
657
|
-
true
|
|
658
|
-
);
|
|
659
|
-
if (autoApprovalMsg) {
|
|
660
|
-
this.broadcast(autoApprovalMsg);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
return approved;
|
|
665
|
-
} catch (e) {
|
|
666
|
-
logger.debug(
|
|
667
|
-
`[OpenSession.onToolCall] error waiting for approval ${id}: ` +
|
|
668
|
-
String(e)
|
|
669
|
-
);
|
|
670
|
-
return false;
|
|
671
|
-
}
|
|
672
|
-
} else {
|
|
673
|
-
this.broadcast({
|
|
674
|
-
type: "tool_call",
|
|
675
|
-
tool_call: toolCall,
|
|
676
|
-
session_id: this.sessionUUID,
|
|
677
|
-
});
|
|
678
|
-
return true;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// IAgentEventHandler.onAgentMessage
|
|
683
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
684
|
-
async onAgentMessage(msg: string, end: boolean): Promise<void> {
|
|
685
|
-
logger.debug(
|
|
686
|
-
`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
690
|
-
const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
|
|
691
|
-
this.broadcast(agentMsgChunk);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// IAgentEventHandler.onReasoning
|
|
695
|
-
onReasoning(reasoning: string): Promise<void> {
|
|
696
|
-
return new Promise<void>((r) => {
|
|
697
|
-
logger.debug(`[OpenSession.onReasoning]${reasoning}`);
|
|
698
|
-
if (reasoning.length > 0) {
|
|
699
|
-
this.broadcast({
|
|
700
|
-
type: "agent_reasoning_chunk",
|
|
701
|
-
reasoning,
|
|
702
|
-
session_id: this.sessionUUID,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
r();
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
|
|
709
698
|
// ISessionFileManagerEventHandler.onFileDeleted
|
|
710
699
|
onFileDeleted(name: string): void {
|
|
711
|
-
this.broadcast({
|
|
700
|
+
this.sender.broadcast({
|
|
712
701
|
type: "session_file_deleted",
|
|
713
702
|
session_id: this.sessionUUID,
|
|
714
703
|
name,
|
|
@@ -717,7 +706,7 @@ export class OpenSession
|
|
|
717
706
|
|
|
718
707
|
// ISessionFileManagerEventHandler.onFileChanged
|
|
719
708
|
onFileChanged(entry: SessionFileEntry, new_file: boolean): void {
|
|
720
|
-
this.broadcast({
|
|
709
|
+
this.sender.broadcast({
|
|
721
710
|
type: "session_file_changed",
|
|
722
711
|
session_id: this.sessionUUID,
|
|
723
712
|
descriptor: {
|
|
@@ -745,7 +734,7 @@ export class OpenSession
|
|
|
745
734
|
logger.warn(
|
|
746
735
|
`User ${from} not in session ${this.sessionUUID} - ignoring message`
|
|
747
736
|
);
|
|
748
|
-
this.sendTo(from, {
|
|
737
|
+
this.sender.sendTo(from, {
|
|
749
738
|
type: "session_error",
|
|
750
739
|
message: "You are not a participant in this session",
|
|
751
740
|
session_id: this.sessionUUID,
|
|
@@ -756,7 +745,7 @@ export class OpenSession
|
|
|
756
745
|
|
|
757
746
|
// Enqueue message for processing
|
|
758
747
|
if (!this.messageQueue.tryEnqueue({ msg: message, from })) {
|
|
759
|
-
this.sendTo(
|
|
748
|
+
this.sender.sendTo(
|
|
760
749
|
from,
|
|
761
750
|
this.addSessionContext({
|
|
762
751
|
type: "session_error",
|
|
@@ -777,7 +766,7 @@ export class OpenSession
|
|
|
777
766
|
const mcpServer = this.skillManager.getMcpServer(server_name);
|
|
778
767
|
const tools = mcpServer.getTools();
|
|
779
768
|
const enabled_tools = Array.from(mcpServer.getEnabledTools().keys());
|
|
780
|
-
this.connectionManager.sendToConnection(connectionId, {
|
|
769
|
+
this.sender.connectionManager.sendToConnection(connectionId, {
|
|
781
770
|
type: "mcp_server_added",
|
|
782
771
|
server_name,
|
|
783
772
|
tools,
|
|
@@ -808,7 +797,7 @@ export class OpenSession
|
|
|
808
797
|
undefined;
|
|
809
798
|
switch (msg.type) {
|
|
810
799
|
case "msg":
|
|
811
|
-
broadcastMsg = this.handleUserMessage(msg, queuedMessage.from);
|
|
800
|
+
broadcastMsg = await this.handleUserMessage(msg, queuedMessage.from);
|
|
812
801
|
break;
|
|
813
802
|
case "add_mcp_server":
|
|
814
803
|
broadcastMsg = await this.handleAddMcpServer(
|
|
@@ -842,20 +831,7 @@ export class OpenSession
|
|
|
842
831
|
);
|
|
843
832
|
break;
|
|
844
833
|
case "tool_call_approval_result":
|
|
845
|
-
|
|
846
|
-
this.approvalManager.approvalResult(
|
|
847
|
-
msg.id,
|
|
848
|
-
msg.result,
|
|
849
|
-
msg.auto_approve
|
|
850
|
-
)
|
|
851
|
-
) {
|
|
852
|
-
broadcastMsg = {
|
|
853
|
-
type: "tool_call_approval_result",
|
|
854
|
-
id: msg.id,
|
|
855
|
-
result: msg.result,
|
|
856
|
-
session_id: this.sessionUUID,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
834
|
+
this.approvalManager.onApprovalResult(msg);
|
|
859
835
|
break;
|
|
860
836
|
case "session_file_get_content":
|
|
861
837
|
void this.handleSessionFileGetContent(msg, queuedMessage.from);
|
|
@@ -867,7 +843,7 @@ export class OpenSession
|
|
|
867
843
|
await this.handleSessionFilePutContent(msg);
|
|
868
844
|
break;
|
|
869
845
|
case "set_auto_approval":
|
|
870
|
-
broadcastMsg = await this.
|
|
846
|
+
broadcastMsg = await this.approvalManager.setAutoApprove(
|
|
871
847
|
msg.server_name,
|
|
872
848
|
msg.tool,
|
|
873
849
|
msg.auto_approve
|
|
@@ -900,10 +876,10 @@ export class OpenSession
|
|
|
900
876
|
if (broadcastMsg) {
|
|
901
877
|
if (broadcastMsg instanceof Array) {
|
|
902
878
|
broadcastMsg.map((msg) => {
|
|
903
|
-
this.broadcast(msg);
|
|
879
|
+
this.sender.broadcast(msg);
|
|
904
880
|
});
|
|
905
881
|
} else {
|
|
906
|
-
this.broadcast(broadcastMsg);
|
|
882
|
+
this.sender.broadcast(broadcastMsg);
|
|
907
883
|
}
|
|
908
884
|
}
|
|
909
885
|
} catch (err: unknown) {
|
|
@@ -926,7 +902,7 @@ export class OpenSession
|
|
|
926
902
|
this.accessToken = accessToken;
|
|
927
903
|
}
|
|
928
904
|
|
|
929
|
-
this.sendTo(from, {
|
|
905
|
+
this.sender.sendTo(from, {
|
|
930
906
|
type: "session_shared",
|
|
931
907
|
access_token: this.accessToken,
|
|
932
908
|
client_message_id: msg.client_message_id,
|
|
@@ -970,16 +946,12 @@ export class OpenSession
|
|
|
970
946
|
return this.contextManager.endAgentResponse();
|
|
971
947
|
}
|
|
972
948
|
|
|
973
|
-
/**
|
|
974
|
-
* `processUserMessage` logic when agent is active. Start the Agent loop,
|
|
975
|
-
* adding all agent messages to the context. Extract the new DB messages.
|
|
976
|
-
*/
|
|
977
949
|
private async processUserMessagesActive(
|
|
978
950
|
msgs: ServerUserMessage[]
|
|
979
951
|
): Promise<SessionMessage[]> {
|
|
980
952
|
const { llmUserMessages, agentFirstChunk } =
|
|
981
953
|
this.contextManager.startAgentResponse(msgs);
|
|
982
|
-
this.broadcast(agentFirstChunk);
|
|
954
|
+
this.sender.broadcast(agentFirstChunk);
|
|
983
955
|
try {
|
|
984
956
|
await this.agent.userMessagesRaw(llmUserMessages);
|
|
985
957
|
} catch (e) {
|
|
@@ -990,26 +962,23 @@ export class OpenSession
|
|
|
990
962
|
// Errors during agent replies must be turned into messages.
|
|
991
963
|
|
|
992
964
|
const errMsg = `error from LLM: ${String(e)}`;
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
this.broadcast(err);
|
|
996
|
-
|
|
997
|
-
// return await this.processuserMessagesActive(msgs);
|
|
965
|
+
this.contextManager.revertAgentResponse(errMsg);
|
|
966
|
+
throw new Error(errMsg);
|
|
998
967
|
}
|
|
999
968
|
return this.contextManager.endAgentResponse();
|
|
1000
969
|
}
|
|
1001
970
|
|
|
1002
971
|
private async processUserMessages(msgs: ServerUserMessage[]): Promise<void> {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
972
|
+
try {
|
|
973
|
+
const newSessionMessages = this.agentPaused
|
|
974
|
+
? this.processUserMessagePaused(msgs)
|
|
975
|
+
: await this.processUserMessagesActive(msgs);
|
|
1006
976
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
977
|
+
logger.debug(
|
|
978
|
+
"[processUserMessages] newSessionMessages: " +
|
|
979
|
+
JSON.stringify(newSessionMessages)
|
|
980
|
+
);
|
|
1011
981
|
|
|
1012
|
-
try {
|
|
1013
982
|
// Append to in-memory conversation and write to the DB
|
|
1014
983
|
const dbsm = this.db.createTypedClient(DbSessionMessages);
|
|
1015
984
|
await dbsm.append(this.sessionUUID, newSessionMessages);
|
|
@@ -1020,10 +989,10 @@ export class OpenSession
|
|
|
1020
989
|
}
|
|
1021
990
|
}
|
|
1022
991
|
|
|
1023
|
-
private handleUserMessage(
|
|
992
|
+
private async handleUserMessage(
|
|
1024
993
|
msg: ClientUserMessage,
|
|
1025
994
|
from: string
|
|
1026
|
-
): ServerUserMessage | undefined {
|
|
995
|
+
): Promise<ServerUserMessage | undefined> {
|
|
1027
996
|
// Return a ServerUserMessage for broadcast. The actual message is places
|
|
1028
997
|
// on a queue to be dealt with in another loop. This allows Agent
|
|
1029
998
|
// processing of user messages to depend on other messages.
|
|
@@ -1033,7 +1002,15 @@ export class OpenSession
|
|
|
1033
1002
|
|
|
1034
1003
|
// Assign the user message_idx and attempt to enqueue.
|
|
1035
1004
|
|
|
1036
|
-
const
|
|
1005
|
+
const user = this.sessionParticipants.get(from);
|
|
1006
|
+
if (!user) {
|
|
1007
|
+
throw new Error(`unrecognized user ${from}`);
|
|
1008
|
+
}
|
|
1009
|
+
const userMessage = this.contextManager.processUserMessage(
|
|
1010
|
+
msg,
|
|
1011
|
+
from,
|
|
1012
|
+
user.nickname
|
|
1013
|
+
);
|
|
1037
1014
|
if (!userMessage) {
|
|
1038
1015
|
return;
|
|
1039
1016
|
}
|
|
@@ -1041,7 +1018,7 @@ export class OpenSession
|
|
|
1041
1018
|
|
|
1042
1019
|
if (userMessage.message_idx === MESSAGE_INDEX_START_VALUE) {
|
|
1043
1020
|
// No need to wait for this to complete before broadcasting.
|
|
1044
|
-
|
|
1021
|
+
await this.onFirstMessage(userMessage);
|
|
1045
1022
|
}
|
|
1046
1023
|
|
|
1047
1024
|
if (!this.userMessageQueue.tryEnqueue(userMessage)) {
|
|
@@ -1052,7 +1029,7 @@ export class OpenSession
|
|
|
1052
1029
|
|
|
1053
1030
|
this.contextManager.unprocessUserMessage(userMessage);
|
|
1054
1031
|
|
|
1055
|
-
this.sendTo(from, {
|
|
1032
|
+
this.sender.sendTo(from, {
|
|
1056
1033
|
type: "session_error",
|
|
1057
1034
|
message: "failed to queue message. try again later.",
|
|
1058
1035
|
session_id: this.sessionUUID,
|
|
@@ -1103,14 +1080,39 @@ export class OpenSession
|
|
|
1103
1080
|
}
|
|
1104
1081
|
assert(this.isPersisted);
|
|
1105
1082
|
|
|
1106
|
-
//
|
|
1083
|
+
// Send session created notification
|
|
1084
|
+
const sessionInfo = this.serverSessionInfo("");
|
|
1085
|
+
|
|
1086
|
+
if (this.teamUUID) {
|
|
1087
|
+
// Team session: notify all members about the new session
|
|
1088
|
+
try {
|
|
1089
|
+
const teamMembers = await this.db.teamGetMembers(this.teamUUID);
|
|
1090
|
+
const teamMemberIds = new Set(teamMembers.map((m) => m.user_uuid));
|
|
1107
1091
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1092
|
+
this.sender.connectionManager.sendToUsers(teamMemberIds, sessionInfo);
|
|
1093
|
+
|
|
1094
|
+
logger.info(
|
|
1095
|
+
`[OpenSession] notified ${String(teamMemberIds.size)} team members` +
|
|
1096
|
+
` about new session ${this.sessionUUID} in team ${this.teamUUID}`
|
|
1097
|
+
);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
logger.error(
|
|
1100
|
+
"[OpenSession] Error notifying team members about session" +
|
|
1101
|
+
`${this.sessionUUID}: ${String(error)}`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
// If this is a user session, notify the session owner
|
|
1106
|
+
this.sender.connectionManager.sendToUsers(
|
|
1107
|
+
new Set([this.userUUID]),
|
|
1108
|
+
sessionInfo
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
logger.info(
|
|
1112
|
+
`[OpenSession] notified session owner ${this.userUUID} about ` +
|
|
1113
|
+
`new session ${this.sessionUUID}`
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1114
1116
|
}
|
|
1115
1117
|
|
|
1116
1118
|
private async handleAddMcpServer(
|
|
@@ -1324,7 +1326,7 @@ export class OpenSession
|
|
|
1324
1326
|
name: msg.name,
|
|
1325
1327
|
data_url,
|
|
1326
1328
|
};
|
|
1327
|
-
this.sendTo(from, contentMsg);
|
|
1329
|
+
this.sender.sendTo(from, contentMsg);
|
|
1328
1330
|
}
|
|
1329
1331
|
|
|
1330
1332
|
private async handleSessionFileDelete(
|
|
@@ -1356,34 +1358,6 @@ export class OpenSession
|
|
|
1356
1358
|
// succeeds. We broadcast in that callback.
|
|
1357
1359
|
}
|
|
1358
1360
|
|
|
1359
|
-
private async onSetAutoApproval(
|
|
1360
|
-
serverName: string,
|
|
1361
|
-
tool: string,
|
|
1362
|
-
autoApprove: boolean
|
|
1363
|
-
): Promise<ServerToolAutoApprovalSet | undefined> {
|
|
1364
|
-
if (
|
|
1365
|
-
prefsSetAutoApprove(
|
|
1366
|
-
this.agentProfilePreferences,
|
|
1367
|
-
serverName,
|
|
1368
|
-
tool,
|
|
1369
|
-
autoApprove
|
|
1370
|
-
)
|
|
1371
|
-
) {
|
|
1372
|
-
await this.db.updateAgentProfilePreferences(
|
|
1373
|
-
this.agentProfileUUID,
|
|
1374
|
-
this.agentProfilePreferences
|
|
1375
|
-
);
|
|
1376
|
-
|
|
1377
|
-
return {
|
|
1378
|
-
type: "tool_auto_approval_set",
|
|
1379
|
-
server_name: serverName,
|
|
1380
|
-
tool,
|
|
1381
|
-
auto_approve: autoApprove,
|
|
1382
|
-
session_id: this.sessionUUID,
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
1361
|
private ensureMcpServer(serverName: string): McpServerInfo {
|
|
1388
1362
|
return this.skillManager.getMcpServer(serverName);
|
|
1389
1363
|
}
|
|
@@ -1411,19 +1385,19 @@ export class OpenSession
|
|
|
1411
1385
|
* This only updates the local participant map - actual membership
|
|
1412
1386
|
* tracking is handled by SessionRegistry.
|
|
1413
1387
|
*/
|
|
1414
|
-
addParticipant(userId: string,
|
|
1415
|
-
this.sessionParticipants.set(userId,
|
|
1388
|
+
addParticipant(userId: string, participant: TeamParticipant): void {
|
|
1389
|
+
this.sessionParticipants.set(userId, participant);
|
|
1416
1390
|
// Broadcast result to all session participants
|
|
1417
1391
|
const broadcastMessage: ServerUserAdded = {
|
|
1418
1392
|
type: "user_added",
|
|
1419
1393
|
user_uuid: userId,
|
|
1420
1394
|
role: "participant",
|
|
1421
|
-
nickname:
|
|
1422
|
-
email:
|
|
1395
|
+
nickname: participant.nickname,
|
|
1396
|
+
email: participant.email,
|
|
1423
1397
|
session_id: this.sessionUUID,
|
|
1424
1398
|
};
|
|
1425
1399
|
|
|
1426
|
-
this.broadcast(broadcastMessage);
|
|
1400
|
+
this.sender.broadcast(broadcastMessage);
|
|
1427
1401
|
}
|
|
1428
1402
|
|
|
1429
1403
|
/**
|
|
@@ -1440,7 +1414,7 @@ export class OpenSession
|
|
|
1440
1414
|
session_id: this.sessionUUID,
|
|
1441
1415
|
};
|
|
1442
1416
|
|
|
1443
|
-
this.broadcast(broadcastMessage);
|
|
1417
|
+
this.sender.broadcast(broadcastMessage);
|
|
1444
1418
|
}
|
|
1445
1419
|
|
|
1446
1420
|
private getSessionParticipants(): TeamParticipant[] {
|
|
@@ -1568,3 +1542,72 @@ async function loadSessionData(
|
|
|
1568
1542
|
sessionParticipants: createSessionParticipantMap(sessionParticipants),
|
|
1569
1543
|
};
|
|
1570
1544
|
}
|
|
1545
|
+
|
|
1546
|
+
async function createContextAndAgent(
|
|
1547
|
+
sessionUUID: string,
|
|
1548
|
+
systemPrompt: string,
|
|
1549
|
+
model: string,
|
|
1550
|
+
sessionMessages: SessionMessage[],
|
|
1551
|
+
workspace: UserMessageData | undefined,
|
|
1552
|
+
sessionCheckpoint: SessionCheckpoint | undefined,
|
|
1553
|
+
ownerData: UserData,
|
|
1554
|
+
ownerApiKey: string,
|
|
1555
|
+
llmUrl: string,
|
|
1556
|
+
xmcpUrl: string,
|
|
1557
|
+
fileManager: ChatSessionFileManager,
|
|
1558
|
+
sender: ISessionMessageSender<ServerToClient>,
|
|
1559
|
+
platform: IPlatform,
|
|
1560
|
+
approvalManager: ToolApprovalManager
|
|
1561
|
+
): Promise<{
|
|
1562
|
+
agent: Agent;
|
|
1563
|
+
skillManager: SkillManager;
|
|
1564
|
+
contextManager: ChatContextManager;
|
|
1565
|
+
}> {
|
|
1566
|
+
const contextManager = new ChatContextManager(
|
|
1567
|
+
systemPrompt,
|
|
1568
|
+
sessionMessages,
|
|
1569
|
+
sessionUUID,
|
|
1570
|
+
ownerData.uuid,
|
|
1571
|
+
sessionCheckpoint,
|
|
1572
|
+
llmUrl,
|
|
1573
|
+
model,
|
|
1574
|
+
ownerApiKey,
|
|
1575
|
+
undefined as unknown as DBCheckpointWriter, // TODO
|
|
1576
|
+
fileManager
|
|
1577
|
+
);
|
|
1578
|
+
if (workspace) {
|
|
1579
|
+
contextManager.setWorkspace(
|
|
1580
|
+
createUserMessage(workspace.message, workspace.imageB64)
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const eventHandler = new ChatSessionAgentEventHandler(
|
|
1585
|
+
sessionUUID,
|
|
1586
|
+
sender,
|
|
1587
|
+
approvalManager,
|
|
1588
|
+
contextManager
|
|
1589
|
+
);
|
|
1590
|
+
|
|
1591
|
+
const xmcpConfig = Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
1592
|
+
const [agent, skillManager] = await createAgentWithoutSkills(
|
|
1593
|
+
llmUrl,
|
|
1594
|
+
model,
|
|
1595
|
+
eventHandler,
|
|
1596
|
+
platform,
|
|
1597
|
+
contextManager,
|
|
1598
|
+
ownerApiKey,
|
|
1599
|
+
xmcpConfig,
|
|
1600
|
+
undefined,
|
|
1601
|
+
true
|
|
1602
|
+
);
|
|
1603
|
+
await addDefaultChatTools(
|
|
1604
|
+
agent,
|
|
1605
|
+
ownerData.timezone,
|
|
1606
|
+
platform,
|
|
1607
|
+
fileManager,
|
|
1608
|
+
llmUrl,
|
|
1609
|
+
ownerApiKey
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
return { agent, skillManager, contextManager };
|
|
1613
|
+
}
|