@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.
Files changed (48) hide show
  1. package/dist/agent/src/agent/agent.js +8 -5
  2. package/dist/agent/src/agent/agentUtils.js +9 -12
  3. package/dist/agent/src/chat/client/chatClient.js +88 -240
  4. package/dist/agent/src/chat/client/constants.js +1 -2
  5. package/dist/agent/src/chat/client/sessionClient.js +4 -13
  6. package/dist/agent/src/chat/client/sessionFiles.js +3 -3
  7. package/dist/agent/src/chat/protocol/messages.js +0 -1
  8. package/dist/agent/src/chat/server/chatContextManager.js +5 -9
  9. package/dist/agent/src/chat/server/connectionManager.test.js +1 -0
  10. package/dist/agent/src/chat/server/conversation.js +9 -4
  11. package/dist/agent/src/chat/server/openSession.js +241 -238
  12. package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
  13. package/dist/agent/src/chat/server/sessionRegistry.js +17 -12
  14. package/dist/agent/src/chat/utils/approvalManager.js +82 -64
  15. package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
  16. package/dist/agent/src/test/agent.test.js +90 -53
  17. package/dist/agent/src/test/approvalManager.test.js +79 -35
  18. package/dist/agent/src/test/chatContextManager.test.js +12 -17
  19. package/dist/agent/src/test/responseAwaiter.test.js +74 -0
  20. package/dist/agent/src/tool/agentChat.js +1 -1
  21. package/dist/agent/src/tool/chatMain.js +2 -2
  22. package/package.json +1 -1
  23. package/scripts/setup_chat +2 -2
  24. package/scripts/test_chat +61 -60
  25. package/src/agent/agent.ts +9 -5
  26. package/src/agent/agentUtils.ts +14 -27
  27. package/src/chat/client/chatClient.ts +167 -296
  28. package/src/chat/client/constants.ts +0 -2
  29. package/src/chat/client/sessionClient.ts +15 -19
  30. package/src/chat/client/sessionFiles.ts +9 -12
  31. package/src/chat/data/dataModels.ts +1 -0
  32. package/src/chat/protocol/messages.ts +9 -12
  33. package/src/chat/server/chatContextManager.ts +7 -12
  34. package/src/chat/server/connectionManager.test.ts +1 -0
  35. package/src/chat/server/conversation.ts +19 -11
  36. package/src/chat/server/openSession.ts +383 -340
  37. package/src/chat/server/openSessionMessageSender.ts +4 -0
  38. package/src/chat/server/sessionRegistry.ts +33 -12
  39. package/src/chat/utils/approvalManager.ts +153 -81
  40. package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
  41. package/src/test/agent.test.ts +130 -62
  42. package/src/test/approvalManager.test.ts +108 -40
  43. package/src/test/chatContextManager.test.ts +19 -20
  44. package/src/test/responseAwaiter.test.ts +103 -0
  45. package/src/tool/agentChat.ts +2 -2
  46. package/src/tool/chatMain.ts +2 -2
  47. package/dist/agent/src/test/responseHandler.test.js +0 -61
  48. package/src/test/responseHandler.test.ts +0 -78
@@ -18,18 +18,16 @@ import {
18
18
  ClientSessionMessage,
19
19
  ClientSessionMessageData,
20
20
  ServerSessionScopedMessage,
21
- ServerSessionInfo,
22
21
  ClientSetWorkspace,
23
22
  ClientToServer,
24
23
  isServerSessionFileMessage,
25
24
  } from "../protocol/messages";
26
25
  import { SessionParticipantMap } from "../data/dataModels";
27
- import { createSessionParticipantMap } from "../data/database";
28
26
  import { ISessionMessageSender } from "./interfaces";
29
27
  import { IMessageSender } from "./connection";
30
28
  import { SessionFiles } from "./sessionFiles";
31
29
  import { SessionFileDescriptor } from "../data/dbSessionFileModels";
32
- import { ResponseHandler } from "./responseHandler";
30
+ import { ResponseAwaiter } from "../utils/responseAwaiter";
33
31
 
34
32
  const logger = getLogger();
35
33
 
@@ -205,16 +203,18 @@ class RemoteSudoMcpServerManager implements ISkillManager {
205
203
  async shutdown(): Promise<void> {}
206
204
  }
207
205
 
206
+ type ServerResponseMessages = Extract<
207
+ ServerSessionScopedMessage,
208
+ { client_message_id?: string }
209
+ >;
210
+
208
211
  export class SessionClient implements ISessionMessageSender, IConversation {
209
212
  private readonly sessionUUID: string;
210
213
  private readonly savedAgentProfile: SavedAgentProfile;
211
214
  private readonly sender: IMessageSender<ClientToServer>;
212
215
  private readonly smsm: RemoteSudoMcpServerManager;
213
216
  private readonly sessionFiles: SessionFiles;
214
- private readonly responseHandler: ResponseHandler<
215
- ClientSessionMessage,
216
- ServerSessionScopedMessage
217
- >;
217
+ private readonly responseHandler: ResponseAwaiter<ServerResponseMessages>;
218
218
  private participants: SessionParticipantMap;
219
219
  private systemPrompt: string;
220
220
  private model: string;
@@ -234,7 +234,10 @@ export class SessionClient implements ISessionMessageSender, IConversation {
234
234
  this.sender = sender;
235
235
  this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
236
236
  this.sessionFiles = new SessionFiles(sessionUUID, fileList, sender);
237
- this.responseHandler = new ResponseHandler("session_error");
237
+ this.responseHandler = ResponseAwaiter.init(
238
+ "session_error",
239
+ (msg: ServerResponseMessages) => msg.client_message_id
240
+ );
238
241
  this.participants = participants;
239
242
  this.systemPrompt = "";
240
243
  this.model = "";
@@ -381,12 +384,6 @@ export class SessionClient implements ISessionMessageSender, IConversation {
381
384
  this.sender.send(enrichedMessage);
382
385
  }
383
386
 
384
- public updateSessionInfo(sessionInfo: ServerSessionInfo): void {
385
- // TODO: Determine the correct approach. Cache the workspace?
386
- const infoStr = JSON.stringify(sessionInfo.workspace);
387
- logger.debug(`[SessionClient] ignoring session info: ${infoStr}`);
388
- }
389
-
390
387
  public setWorkspace(
391
388
  message: string | undefined,
392
389
  imageB64: string | undefined
@@ -428,7 +425,9 @@ export class SessionClient implements ISessionMessageSender, IConversation {
428
425
  };
429
426
 
430
427
  this.sender.send(msg);
431
- const response = await this.responseHandler.waitForResponse(msg);
428
+ const response = await this.responseHandler.waitForResponse(
429
+ msg.client_message_id
430
+ );
432
431
  if (response.type === "session_shared") {
433
432
  return response.access_token;
434
433
  }
@@ -470,10 +469,7 @@ export class SessionClient implements ISessionMessageSender, IConversation {
470
469
  this.model = message.model;
471
470
  break;
472
471
  case "session_info":
473
- // Update participants if we receive session_info again
474
- if ("participants" in message) {
475
- this.participants = createSessionParticipantMap(message.participants);
476
- }
472
+ // This is handled in the layer above, in the chatClient
477
473
  break;
478
474
  case "user_added":
479
475
  this.participants.set(message.user_uuid, {
@@ -8,16 +8,11 @@ import {
8
8
  ServerSessionFileContent,
9
9
  ServerSessionFileMessage,
10
10
  } from "../protocol/messages";
11
- import { ResponseHandler } from "./responseHandler";
12
11
  import { IMessageSender } from "./connection";
12
+ import { ResponseAwaiter } from "../utils/responseAwaiter";
13
13
 
14
14
  const logger = getLogger();
15
15
 
16
- type ClientRequestContent = Extract<
17
- ClientToServer,
18
- { type: "session_file_get_content" }
19
- >;
20
-
21
16
  /// Object for the UI to use to interact with the FileManager. If the UI
22
17
  /// receives ServerSessionFileChanged or ServerSessionFileDeleted then it
23
18
  /// should call `listFiles()` to get the new list of files, and optionally
@@ -25,10 +20,7 @@ type ClientRequestContent = Extract<
25
20
  export class SessionFiles {
26
21
  readonly sessionUUID: string;
27
22
  readonly descriptors: Map<string, SessionFileDescriptor>;
28
- readonly responseHandler: ResponseHandler<
29
- ClientRequestContent,
30
- ServerSessionFileContent
31
- >;
23
+ readonly responseHandler: ResponseAwaiter<ServerSessionFileContent>;
32
24
  readonly sender: IMessageSender<ClientToServer>;
33
25
 
34
26
  constructor(
@@ -38,7 +30,10 @@ export class SessionFiles {
38
30
  ) {
39
31
  this.sessionUUID = sessionUUID;
40
32
  this.descriptors = new Map(initialFileList.map((d) => [d.name, d]));
41
- this.responseHandler = new ResponseHandler(undefined);
33
+ this.responseHandler = ResponseAwaiter.init(
34
+ undefined,
35
+ (msg) => msg.client_message_id
36
+ );
42
37
  this.sender = sender;
43
38
  }
44
39
 
@@ -57,7 +52,9 @@ export class SessionFiles {
57
52
  client_message_id: uuidv4(),
58
53
  };
59
54
  this.sender.send(msg);
60
- const response = await this.responseHandler.waitForResponse(msg);
55
+ const response = await this.responseHandler.waitForResponse(
56
+ msg.client_message_id
57
+ );
61
58
  if (response.name !== name) {
62
59
  throw new Error(
63
60
  `invalid name for file ${name}: ${JSON.stringify(response)}`
@@ -52,6 +52,7 @@ export type SessionCreateData = {
52
52
  user_uuid: string;
53
53
 
54
54
  team_uuid?: string | undefined;
55
+ participants?: Array<TeamParticipant>;
55
56
  access_token?: string;
56
57
  agent_paused: boolean;
57
58
  };
@@ -129,6 +129,7 @@ export type ClientControlMessage =
129
129
 
130
130
  export type ClientUserMessage = {
131
131
  type: "msg";
132
+ race_mode?: boolean;
132
133
  } & UserMessageData;
133
134
 
134
135
  export type ClientSetWorkspace = {
@@ -224,6 +225,12 @@ export type ClientShareSession = {
224
225
  type: "share_session";
225
226
  };
226
227
 
228
+ export type ClientRaceModeResult = {
229
+ type: "race_mode_result";
230
+ message_id: string;
231
+ result: string;
232
+ } & UserMessageData;
233
+
227
234
  export type ClientSessionMessageData =
228
235
  | ClientUserMessage
229
236
  | ClientSetWorkspace
@@ -341,6 +348,7 @@ export type ServerHistory = {
341
348
  export type ServerUserMessage = {
342
349
  type: "user_msg";
343
350
  user_uuid: string;
351
+ user_nickname: string;
344
352
  message_idx: number;
345
353
  } & UserMessageData &
346
354
  ServerSessionMessage;
@@ -355,6 +363,7 @@ export type ServerAgentMessageChunk = {
355
363
  type: "agent_msg_chunk";
356
364
  message: string;
357
365
  message_idx: number;
366
+ alternative?: string;
358
367
  end: boolean;
359
368
  } & ServerSessionMessage;
360
369
 
@@ -378,10 +387,6 @@ export type ServerUserLeft = {
378
387
  user_uuid: string;
379
388
  } & ServerSessionMessage;
380
389
 
381
- export type ServerSessionUpdate = {
382
- type: "session_update";
383
- title?: string;
384
- } & ServerSessionMessage;
385
390
 
386
391
  // Note: ChatCompletionMessageToolCall contains the tool call id so we don't
387
392
  // need an extra id field.
@@ -432,7 +437,6 @@ export type ServerUserTyping = {
432
437
  export type ServerToClientNotifications =
433
438
  | ServerUserJoined
434
439
  | ServerUserLeft
435
- | ServerSessionUpdate
436
440
  | ServerToolAutoApprovalSet
437
441
  | ServerToolCall
438
442
  | ServerToolCallApprovalResult
@@ -471,12 +475,6 @@ export type ServerSessionFileMessage =
471
475
  //
472
476
  // state updates
473
477
  //
474
-
475
- /**
476
- * A new team has been created.
477
- * The members array contains the members of the team.
478
- * The members array contains undefined for failed lookups.
479
- */
480
478
  export type ServerMcpServerAdded = {
481
479
  type: "mcp_server_added";
482
480
  server_name: string;
@@ -731,7 +729,6 @@ export function isServerSessionScopedMessage(
731
729
  case "agent_reasoning_chunk":
732
730
  case "user_joined":
733
731
  case "user_left":
734
- case "session_update":
735
732
  case "tool_auto_approval_set":
736
733
  case "tool_call":
737
734
  case "tool_call_approval_result":
@@ -19,7 +19,6 @@ import {
19
19
  ClientUserMessage,
20
20
  ServerAgentMessage,
21
21
  ServerAgentMessageChunk,
22
- ServerSessionError,
23
22
  ServerToolCallResult,
24
23
  ServerUserMessage,
25
24
  } from "../protocol/messages";
@@ -213,7 +212,8 @@ export class ChatContextManager
213
212
 
214
213
  processUserMessage(
215
214
  msg: ClientUserMessage,
216
- from: string
215
+ from_uuid: string,
216
+ from_nickname: string
217
217
  ): ServerUserMessage | undefined {
218
218
  // TODO: maintain a queue internally instead of relying on the caller to
219
219
  // pass in our generated messages back into `startAgentResponse`.
@@ -229,7 +229,8 @@ export class ChatContextManager
229
229
  session_id: this.sessionUUID,
230
230
  message_idx,
231
231
  message: msg.message,
232
- user_uuid: from,
232
+ user_uuid: from_uuid,
233
+ user_nickname: from_nickname,
233
234
  };
234
235
  if (msg.imageB64) {
235
236
  userMessage.imageB64 = msg.imageB64;
@@ -288,7 +289,7 @@ export class ChatContextManager
288
289
  const userMsg = createUserMessage(
289
290
  msg.message,
290
291
  msg.imageB64,
291
- msg.user_uuid
292
+ msg.user_nickname
292
293
  );
293
294
  if (userMsg) {
294
295
  llmUserMessages.push(userMsg);
@@ -366,7 +367,7 @@ export class ChatContextManager
366
367
  if (JSON.stringify(sMsg.content) !== JSON.stringify(lMsg)) {
367
368
  messageListError(
368
369
  `newSessionMessages[${String(i)}].content !== ` +
369
- `newLLMMessages[${String(i)}].content`
370
+ `newLLMMessages[${String(i)}]`
370
371
  );
371
372
  }
372
373
  if (sMsg.message_idx !== pMsg.message_idx) {
@@ -399,7 +400,7 @@ export class ChatContextManager
399
400
  * This function checks that nothing has been entered into the LLM context,
400
401
  * and drops any new user messages or responses before the error.
401
402
  */
402
- revertAgentResponse(errMsg: string): ServerSessionError {
403
+ revertAgentResponse(errMsg: string): void {
403
404
  logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
404
405
 
405
406
  assert(typeof this.startingLLMContextLength !== "undefined");
@@ -425,12 +426,6 @@ export class ChatContextManager
425
426
  this.startingLLMContextLength = undefined;
426
427
  this.pendingMessages = undefined;
427
428
  this.curAgentMsgIdx = undefined;
428
-
429
- return {
430
- type: "session_error",
431
- session_id: this.sessionUUID,
432
- message: errMsg,
433
- };
434
429
  }
435
430
 
436
431
  processAgentMessage(msg: string, end: boolean): ServerAgentMessageChunk {
@@ -117,6 +117,7 @@ describe("ConnectionManager", () => {
117
117
  d: {
118
118
  type: "user_msg",
119
119
  user_uuid: "user_1",
120
+ user_nickname: "User 1",
120
121
  session_id: "session_1",
121
122
  message: "message text",
122
123
  message_idx: 12,
@@ -104,19 +104,24 @@ export function sessionMessagesToLLMConversation(
104
104
  */
105
105
  export function userMessageToConversationMessage(
106
106
  userMessage: ChatCompletionUserMessageParam,
107
+ user_uuid: string,
107
108
  message_idx: number,
108
- defaultUserUuid: string,
109
109
  session_id: string
110
110
  ): ServerUserMessage {
111
111
  // The name on the message should be the uuid
112
112
 
113
113
  const userMsgData = llmUserMessageToUserMessageData(userMessage);
114
- const user_uuid = userMessage.name || defaultUserUuid;
114
+ const user_nickname = userMessage.name;
115
+ assert(
116
+ user_nickname,
117
+ `ChatCompletionUserMessageParam without user ${JSON.stringify(userMessage)}`
118
+ );
115
119
  const msg: ServerUserMessage = {
116
120
  type: "user_msg",
117
121
  message: userMsgData.message,
118
122
  message_idx,
119
123
  user_uuid,
124
+ user_nickname,
120
125
  session_id,
121
126
  };
122
127
  if (userMsgData.imageB64) {
@@ -183,14 +188,17 @@ export function sessionMessagesToConversationMessages(
183
188
  );
184
189
  break;
185
190
  case "user":
186
- msgs.push(
187
- userMessageToConversationMessage(
188
- ccmp,
189
- message_idx,
190
- defaultUserUuid,
191
- session_id
192
- )
193
- );
191
+ {
192
+ const user_uuid = sm.sender_uuid || defaultUserUuid;
193
+ msgs.push(
194
+ userMessageToConversationMessage(
195
+ ccmp,
196
+ user_uuid,
197
+ message_idx,
198
+ session_id
199
+ )
200
+ );
201
+ }
194
202
  break;
195
203
  case "tool":
196
204
  msgs.push(
@@ -267,7 +275,7 @@ export function chatUserMessageToSessionMessage(
267
275
  const userMsg = createUserMessage(
268
276
  chatMessage.message,
269
277
  chatMessage.imageB64,
270
- chatMessage.user_uuid
278
+ chatMessage.user_nickname
271
279
  );
272
280
  assert(userMsg);
273
281
  return {