@xalia/agent 0.6.0 → 0.6.2

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 (112) hide show
  1. package/dist/agent/src/agent/agent.js +103 -54
  2. package/dist/agent/src/agent/agentUtils.js +22 -21
  3. package/dist/agent/src/agent/compressingContextManager.js +3 -2
  4. package/dist/agent/src/agent/dummyLLM.js +1 -3
  5. package/dist/agent/src/agent/imageGenLLM.js +67 -0
  6. package/dist/agent/src/agent/imageGenerator.js +43 -0
  7. package/dist/agent/src/agent/llm.js +27 -0
  8. package/dist/agent/src/agent/mcpServerManager.js +18 -6
  9. package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
  10. package/dist/agent/src/agent/openAILLM.js +3 -3
  11. package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
  12. package/dist/agent/src/chat/client/chatClient.js +84 -13
  13. package/dist/agent/src/chat/client/sessionClient.js +47 -6
  14. package/dist/agent/src/chat/client/sessionFiles.js +102 -0
  15. package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
  16. package/dist/agent/src/chat/data/database.js +83 -70
  17. package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
  18. package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
  19. package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
  20. package/dist/agent/src/chat/data/mimeTypes.js +44 -0
  21. package/dist/agent/src/chat/protocol/messages.js +21 -0
  22. package/dist/agent/src/chat/server/chatContextManager.js +14 -7
  23. package/dist/agent/src/chat/server/connectionManager.js +14 -36
  24. package/dist/agent/src/chat/server/connectionManager.test.js +2 -16
  25. package/dist/agent/src/chat/server/conversation.js +69 -45
  26. package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
  27. package/dist/agent/src/chat/server/openSession.js +205 -43
  28. package/dist/agent/src/chat/server/server.js +5 -8
  29. package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
  30. package/dist/agent/src/chat/server/sessionRegistry.js +199 -32
  31. package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
  32. package/dist/agent/src/chat/server/tools.js +27 -6
  33. package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
  34. package/dist/agent/src/test/agent.test.js +15 -11
  35. package/dist/agent/src/test/chatContextManager.test.js +4 -0
  36. package/dist/agent/src/test/clientServerConnection.test.js +2 -2
  37. package/dist/agent/src/test/db.test.js +33 -70
  38. package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
  39. package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
  40. package/dist/agent/src/test/dbTestTools.js +6 -5
  41. package/dist/agent/src/test/imageLoad.test.js +1 -1
  42. package/dist/agent/src/test/mcpServerManager.test.js +1 -1
  43. package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
  44. package/dist/agent/src/test/testTools.js +12 -0
  45. package/dist/agent/src/tool/agentChat.js +25 -6
  46. package/dist/agent/src/tool/agentMain.js +1 -1
  47. package/dist/agent/src/tool/chatMain.js +113 -4
  48. package/dist/agent/src/tool/commandPrompt.js +7 -3
  49. package/dist/agent/src/tool/files.js +23 -15
  50. package/dist/agent/src/tool/options.js +2 -2
  51. package/package.json +1 -1
  52. package/scripts/test_chat +124 -66
  53. package/src/agent/agent.ts +145 -38
  54. package/src/agent/agentUtils.ts +27 -21
  55. package/src/agent/compressingContextManager.ts +5 -4
  56. package/src/agent/context.ts +1 -1
  57. package/src/agent/dummyLLM.ts +1 -3
  58. package/src/agent/iAgentEventHandler.ts +15 -2
  59. package/src/agent/imageGenLLM.ts +99 -0
  60. package/src/agent/imageGenerator.ts +60 -0
  61. package/src/agent/llm.ts +128 -4
  62. package/src/agent/mcpServerManager.ts +26 -7
  63. package/src/agent/nullAgentEventHandler.ts +6 -0
  64. package/src/agent/openAILLM.ts +3 -8
  65. package/src/agent/openAILLMStreaming.ts +60 -14
  66. package/src/chat/client/chatClient.ts +119 -14
  67. package/src/chat/client/sessionClient.ts +75 -9
  68. package/src/chat/client/sessionFiles.ts +145 -0
  69. package/src/chat/data/apiKeyManager.ts +55 -7
  70. package/src/chat/data/dataModels.ts +16 -7
  71. package/src/chat/data/database.ts +107 -92
  72. package/src/chat/data/dbSessionFileModels.ts +91 -0
  73. package/src/chat/data/dbSessionFiles.ts +99 -0
  74. package/src/chat/data/dbSessionMessages.ts +68 -0
  75. package/src/chat/data/mimeTypes.ts +58 -0
  76. package/src/chat/protocol/messages.ts +127 -13
  77. package/src/chat/server/chatContextManager.ts +36 -13
  78. package/src/chat/server/connectionManager.test.ts +1 -22
  79. package/src/chat/server/connectionManager.ts +18 -53
  80. package/src/chat/server/conversation.ts +96 -57
  81. package/src/chat/server/imageGeneratorTools.ts +138 -0
  82. package/src/chat/server/openSession.ts +287 -49
  83. package/src/chat/server/server.ts +5 -11
  84. package/src/chat/server/sessionFileManager.ts +223 -63
  85. package/src/chat/server/sessionRegistry.ts +285 -41
  86. package/src/chat/server/test-utils/mockFactories.ts +13 -13
  87. package/src/chat/server/tools.ts +43 -8
  88. package/src/chat/utils/agentSessionMap.ts +2 -2
  89. package/src/chat/utils/multiAsyncQueue.ts +11 -1
  90. package/src/test/agent.test.ts +23 -14
  91. package/src/test/chatContextManager.test.ts +7 -2
  92. package/src/test/clientServerConnection.test.ts +3 -3
  93. package/src/test/compressingContextManager.test.ts +1 -1
  94. package/src/test/context.test.ts +2 -1
  95. package/src/test/conversation.test.ts +1 -1
  96. package/src/test/db.test.ts +41 -83
  97. package/src/test/dbSessionFiles.test.ts +258 -0
  98. package/src/test/dbSessionMessages.test.ts +85 -0
  99. package/src/test/dbTestTools.ts +9 -5
  100. package/src/test/imageLoad.test.ts +2 -2
  101. package/src/test/mcpServerManager.test.ts +3 -1
  102. package/src/test/multiAsyncQueue.test.ts +58 -0
  103. package/src/test/testTools.ts +15 -1
  104. package/src/tool/agentChat.ts +35 -7
  105. package/src/tool/agentMain.ts +7 -7
  106. package/src/tool/chatMain.ts +126 -5
  107. package/src/tool/commandPrompt.ts +10 -5
  108. package/src/tool/files.ts +30 -13
  109. package/src/tool/options.ts +1 -1
  110. package/test_data/dummyllm_script_image_gen.json +19 -0
  111. package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
  112. package/test_data/image_gen_test_profile.json +5 -0
@@ -1,8 +1,16 @@
1
- import { getLogger } from "@xalia/xmcp/sdk";
2
- import { ILLM } from "./llm";
3
1
  import { OpenAI } from "openai";
4
2
  import { strict as assert } from "assert";
5
- import { DEFAULT_LLM_MODEL, XALIA_APP_HEADER } from "./agentUtils";
3
+
4
+ import { getLogger } from "@xalia/xmcp/sdk";
5
+
6
+ import {
7
+ ILLM,
8
+ ChatCompletionChunkChoiceDeltaWithReasoning,
9
+ ChatCompletionMessageWithReasoning,
10
+ Reasoning,
11
+ choiceDeltaExtractReasoning,
12
+ XALIA_APP_HEADER,
13
+ } from "./llm";
6
14
 
7
15
  const logger = getLogger();
8
16
 
@@ -186,9 +194,20 @@ function initializeCompletionMessage(
186
194
  };
187
195
  }
188
196
 
197
+ function updateReasoning(
198
+ message: ChatCompletionMessageWithReasoning,
199
+ reasoning: string
200
+ ) {
201
+ if (!message.reasoning) {
202
+ message.reasoning = reasoning;
203
+ } else {
204
+ message.reasoning += reasoning;
205
+ }
206
+ }
207
+
189
208
  function updateCompletionMessage(
190
- message: OpenAI.Chat.Completions.ChatCompletionMessage,
191
- delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta
209
+ message: ChatCompletionMessageWithReasoning,
210
+ delta: ChatCompletionChunkChoiceDeltaWithReasoning
192
211
  ) {
193
212
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
194
213
  assert(message.role === "assistant");
@@ -200,12 +219,20 @@ function updateCompletionMessage(
200
219
  typeof message.tool_calls === "undefined"
201
220
  );
202
221
 
203
- // export interface ChatCompletionChunk.Choice.Delta {
222
+ // export interface ChatCompletionChunkChoiceDeltaWithReasoning {
204
223
  // content?: string | null;
205
224
  // function_call?: Delta.FunctionCall;
206
225
  // refusal?: string | null;
207
226
  // role?: 'developer' | 'system' | 'user' | 'assistant' | 'tool';
208
227
  // tool_calls?: Array<Delta.ToolCall>;
228
+ //
229
+ // reasoning?: string;
230
+ // reasoning_details?: {
231
+ // type: "reasoning.text",
232
+ // text?: string,
233
+ // format?: string,
234
+ // index?:0
235
+ // }[]
209
236
  // }
210
237
  //
211
238
  // ->
@@ -218,6 +245,8 @@ function updateCompletionMessage(
218
245
  // audio?: ChatCompletionAudio | null;
219
246
  // function_call?: ChatCompletionMessage.FunctionCall | null;
220
247
  // tool_calls?: Array<ChatCompletionMessageToolCall>;
248
+ //
249
+ // reasoning?: string;
221
250
  // }
222
251
 
223
252
  if (delta.content) {
@@ -242,6 +271,11 @@ function updateCompletionMessage(
242
271
  message.tool_calls = updateToolCalls(message.tool_calls, t);
243
272
  }
244
273
  }
274
+
275
+ const reasoning = choiceDeltaExtractReasoning(delta);
276
+ if (reasoning) {
277
+ updateReasoning(message, reasoning);
278
+ }
245
279
  }
246
280
 
247
281
  function initializeCompletionChoice(
@@ -451,18 +485,14 @@ export class OpenAILLMStreaming implements ILLM {
451
485
  private readonly openai: OpenAI;
452
486
  private model: string;
453
487
 
454
- constructor(
455
- apiKey: string,
456
- apiUrl: string | undefined,
457
- model: string | undefined
458
- ) {
488
+ constructor(apiKey: string, apiUrl: string | undefined, model: string) {
459
489
  this.openai = new OpenAI({
460
490
  apiKey,
461
491
  baseURL: apiUrl,
462
492
  dangerouslyAllowBrowser: true,
463
493
  defaultHeaders: XALIA_APP_HEADER,
464
494
  });
465
- this.model = model || DEFAULT_LLM_MODEL;
495
+ this.model = model;
466
496
  }
467
497
 
468
498
  public setModel(model: string) {
@@ -480,8 +510,13 @@ export class OpenAILLMStreaming implements ILLM {
480
510
  public async getConversationResponse(
481
511
  messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
482
512
  tools?: OpenAI.Chat.Completions.ChatCompletionTool[],
483
- onMessage?: (msg: string, end: boolean) => Promise<void>
513
+ onMessage?: (msg: string, end: boolean) => Promise<void>,
514
+ onReasoning?: (reasoning: string) => Promise<void>
484
515
  ): Promise<OpenAI.Chat.Completions.ChatCompletion> {
516
+ const reasoning: Reasoning = {
517
+ effort: "medium",
518
+ enabled: true,
519
+ };
485
520
  const chunks = await this.openai.chat.completions.create({
486
521
  model: this.model,
487
522
  messages,
@@ -490,7 +525,8 @@ export class OpenAILLMStreaming implements ILLM {
490
525
  stream_options: {
491
526
  include_usage: true,
492
527
  },
493
- });
528
+ extra_body: { reasoning },
529
+ } as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);
494
530
 
495
531
  // Check the type casting above
496
532
  if (!(chunks as unknown as { iterator: unknown }).iterator) {
@@ -498,6 +534,7 @@ export class OpenAILLMStreaming implements ILLM {
498
534
  }
499
535
 
500
536
  let aggregatedMessage: OpenAI.Chat.Completions.ChatCompletion | undefined;
537
+
501
538
  for await (const chunk of chunks) {
502
539
  logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
503
540
 
@@ -526,6 +563,15 @@ export class OpenAILLMStreaming implements ILLM {
526
563
  await onMessage(delta.content, false);
527
564
  }
528
565
  }
566
+
567
+ if (onReasoning) {
568
+ const delta = chunk.choices[0]
569
+ ?.delta as ChatCompletionChunkChoiceDeltaWithReasoning;
570
+ const reasoning = choiceDeltaExtractReasoning(delta);
571
+ if (reasoning) {
572
+ await onReasoning(reasoning);
573
+ }
574
+ }
529
575
  }
530
576
 
531
577
  if (onMessage) {
@@ -6,10 +6,10 @@ import { Connection } from "./connection";
6
6
  import { SessionClient } from "./sessionClient";
7
7
  import { IChatClientEventHandler } from "./interfaces";
8
8
  import {
9
- SessionData,
10
9
  TeamInfo,
11
10
  TeamParticipant,
12
11
  AgentSessionData,
12
+ SessionDescriptor,
13
13
  } from "../data/dataModels";
14
14
  import { createSessionParticipantMap } from "../data/database";
15
15
  import {
@@ -21,6 +21,7 @@ import {
21
21
  ServerControlMessage,
22
22
  ServerControlSessionDeleted,
23
23
  ServerControlTeamCreated,
24
+ ServerControlTeamMembersUpdated,
24
25
  ServerToClient,
25
26
  ClientToServer,
26
27
  ServerControlAgentProfileCreated,
@@ -59,6 +60,8 @@ export class ChatClient implements ITeamManager {
59
60
  private userAgentSessionMap: Map<string, AgentSessionData> = new Map(),
60
61
  // team_uuid -> team info
61
62
  private teams: Map<string, ClientTeamInfo> = new Map(),
63
+ // Whether this client is in guest mode
64
+ private isGuestMode: boolean = false,
62
65
  private closed: boolean = false,
63
66
  private currentSessionId: string | undefined = undefined,
64
67
  // note: currentTeamId will not be reset when swtiching to a user session.
@@ -81,6 +84,9 @@ export class ChatClient implements ITeamManager {
81
84
  token: string,
82
85
  eventHandler: IChatClientEventHandler
83
86
  ): Promise<ChatClient> {
87
+ // Determine if this is a guest token (format: guest_<hash>_<session-id>)
88
+ const isGuestMode = token.startsWith('guest_');
89
+
84
90
  const connection = new Connection<ClientToServer, ServerToClient>({
85
91
  url,
86
92
  token,
@@ -92,7 +98,7 @@ export class ChatClient implements ITeamManager {
92
98
  // Register session_info handler for initialization
93
99
  connection.on("control_session_list", (msg) => {
94
100
  // get user sessions, user agents, and team sessions
95
- const userSessions = new Map<string, SessionData>();
101
+ const userSessions = new Map<string, SessionDescriptor>();
96
102
  msg.user_sessions.forEach((session) => {
97
103
  userSessions.set(session.session_uuid, session);
98
104
  });
@@ -127,7 +133,8 @@ export class ChatClient implements ITeamManager {
127
133
  connection,
128
134
  eventHandler,
129
135
  userAgentSessionMap,
130
- teams
136
+ teams,
137
+ isGuestMode
131
138
  );
132
139
  resolveClient(client);
133
140
  } else {
@@ -476,6 +483,27 @@ export class ChatClient implements ITeamManager {
476
483
  );
477
484
  }
478
485
 
486
+ /**
487
+ * Delete an agent profile by sending control_agent_profile_delete message
488
+ * @param agentProfileUuid - The UUID of the agent profile to delete
489
+ * @returns void
490
+ */
491
+ deleteAgentProfile(agentProfileUuid: string): void {
492
+ if (this.closed) {
493
+ throw new Error("ChatClient is closed");
494
+ }
495
+
496
+ this.connection.send({
497
+ type: "control_agent_profile_delete",
498
+ agent_profile_uuid: agentProfileUuid,
499
+ });
500
+
501
+ logger.debug(
502
+ `[ChatClient] Sent control_agent_profile_delete for profile ` +
503
+ agentProfileUuid
504
+ );
505
+ }
506
+
479
507
  /**
480
508
  * Create a new team with initial members
481
509
  * @param teamName
@@ -644,6 +672,9 @@ export class ChatClient implements ITeamManager {
644
672
  case "control_team_created":
645
673
  this.handleTeamCreatedMessage(msg);
646
674
  break;
675
+ case "control_team_members_updated":
676
+ this.handleTeamMembersUpdated(msg);
677
+ break;
647
678
  default: {
648
679
  const _exhaustive: never = msg;
649
680
  throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
@@ -676,8 +707,36 @@ export class ChatClient implements ITeamManager {
676
707
  void this.eventHandler.onMessage(msg, this);
677
708
  }
678
709
 
679
- private handleAgentProfileDeleted(_msg: ServerControlAgentProfileDeleted) {
680
- throw new Error("handleAgentProfileDeleted not implemented");
710
+ private handleAgentProfileDeleted(msg: ServerControlAgentProfileDeleted) {
711
+ logger.debug(
712
+ `[ChatClient.handleAgentProfileDeleted] msg: ${JSON.stringify(msg)}`
713
+ );
714
+
715
+ const profileUuid = msg.profile_uuid;
716
+ let found = false;
717
+
718
+ // Try to remove from user agent session map
719
+ if (this.userAgentSessionMap.has(profileUuid)) {
720
+ this.userAgentSessionMap.delete(profileUuid);
721
+ found = true;
722
+ } else {
723
+ // Try to remove from team agent session maps
724
+ for (const [_teamId, team] of this.teams.entries()) {
725
+ if (team.agentSessionMap.has(profileUuid)) {
726
+ team.agentSessionMap.delete(profileUuid);
727
+ found = true;
728
+ break;
729
+ }
730
+ }
731
+ }
732
+
733
+ if (!found) {
734
+ logger.warn(
735
+ `[ChatClient] Agent profile ${profileUuid} not found in any session map`
736
+ );
737
+ }
738
+
739
+ void this.eventHandler.onMessage(msg, this);
681
740
  }
682
741
 
683
742
  private handleSessionMessage(msg: ServerSessionScopedMessage): void {
@@ -725,6 +784,28 @@ export class ChatClient implements ITeamManager {
725
784
  return;
726
785
  }
727
786
 
787
+ private handleTeamMembersUpdated(msg: ServerControlTeamMembersUpdated): void {
788
+ const team = this.teams.get(msg.team_uuid);
789
+ if (!team) {
790
+ logger.warn(
791
+ `[ChatClient] Received team_members_updated for unknown team ` +
792
+ msg.team_uuid
793
+ );
794
+ return;
795
+ }
796
+
797
+ // Update the participants map
798
+ team.participants = new Map(
799
+ msg.members.map((participant) => [participant.user_uuid, participant])
800
+ );
801
+
802
+ logger.info(
803
+ `[ChatClient] Updated team members for team ${msg.team_uuid}, ` +
804
+ `now has ${String(msg.members.length)} members`
805
+ );
806
+ void this.eventHandler.onMessage(msg, this);
807
+ }
808
+
728
809
  /**
729
810
  * Handle session_info message which can be for:
730
811
  * 1. A pending session join/create request
@@ -751,7 +832,8 @@ export class ChatClient implements ITeamManager {
751
832
  msg.saved_agent_profile,
752
833
  this.connection,
753
834
  msg.mcp_server_briefs,
754
- createSessionParticipantMap(msg.participants)
835
+ createSessionParticipantMap(msg.participants),
836
+ msg.agent_paused
755
837
  );
756
838
 
757
839
  // we need to pass the session id if this is a new session
@@ -797,12 +879,23 @@ export class ChatClient implements ITeamManager {
797
879
  */
798
880
  private updateAgentSessionMap(sessionInfo: ServerSessionInfo): void {
799
881
  const sessionId = sessionInfo.session_id;
882
+
883
+ // Skip agent session map updates for guest sessions since they don't
884
+ // need session organization
885
+ if (this.isGuestMode) {
886
+ logger.info(
887
+ `[ChatClient] Skipping agent session map update for ` +
888
+ `guest session ${sessionId}`
889
+ );
890
+ return;
891
+ }
892
+
800
893
  if (sessionInfo.team_uuid) {
801
894
  const teamInfo = this.teams.get(sessionInfo.team_uuid);
802
895
  if (!teamInfo) {
803
896
  throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
804
897
  }
805
- //teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
898
+
806
899
  const agentSessionMap = teamInfo.agentSessionMap;
807
900
  if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
808
901
  throw new Error(
@@ -822,9 +915,9 @@ export class ChatClient implements ITeamManager {
822
915
  }
823
916
 
824
917
  /**
825
- * Update the session info for an existing session. This passes the session
826
- * info to the session client. An error is thrown if the session client is not
827
- * found.
918
+ * Update the SessionDescriptor info for an existing session, given a full
919
+ * ServerSessionInfo. This also passes the session info to the session
920
+ * client. An error is thrown if the session client is not found.
828
921
  */
829
922
  private updateSessionInfo(msg: ServerSessionInfo): void {
830
923
  const sessionId = msg.session_id;
@@ -838,6 +931,16 @@ export class ChatClient implements ITeamManager {
838
931
  }
839
932
 
840
933
  private addSessionToAgentSessionMap(msg: ServerSessionInfo): void {
934
+ // Skip agent session map updates for guest sessions since they don't
935
+ // need session organization
936
+ if (this.isGuestMode) {
937
+ logger.info(
938
+ `[ChatClient] Skipping agent session map add for ` +
939
+ `guest session ${msg.session_id}`
940
+ );
941
+ return;
942
+ }
943
+
841
944
  // get the correct agent session map
842
945
  const agentSessionMap = msg.team_uuid
843
946
  ? this.teams.get(msg.team_uuid)?.agentSessionMap
@@ -854,20 +957,22 @@ export class ChatClient implements ITeamManager {
854
957
  ` not found in agent session map`
855
958
  );
856
959
  }
857
- agentSession.sessions.push(sessionInfoToSessionData(msg));
960
+ agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
858
961
  agentSession.updated_at = new Date(msg.updated_at).getTime();
859
962
  }
860
963
  }
861
964
 
862
- function sessionInfoToSessionData(msg: ServerSessionInfo): SessionData {
965
+ function sessionInfoToSessionDescriptor(
966
+ msg: ServerSessionInfo
967
+ ): SessionDescriptor {
863
968
  return {
864
969
  session_uuid: msg.session_id,
865
970
  title: msg.title,
866
971
  team_uuid: msg.team_uuid,
867
972
  agent_profile_uuid: msg.saved_agent_profile.uuid,
868
- workspace: msg.workspace,
869
973
  updated_at: msg.updated_at,
870
974
  user_uuid: msg.owner_uuid,
975
+ agent_paused: msg.agent_paused,
871
976
  };
872
977
  }
873
978
 
@@ -891,7 +996,7 @@ function doUpdateAgentSessionMap(
891
996
  agent.sessions.map((session) => {
892
997
  if (session.session_uuid === sessionId) {
893
998
  updated = true;
894
- return sessionInfoToSessionData(sessionInfo);
999
+ return sessionInfoToSessionDescriptor(sessionInfo);
895
1000
  } else {
896
1001
  return session;
897
1002
  }
@@ -11,7 +11,8 @@ import {
11
11
 
12
12
  import { ISkillManager } from "../../agent/sudoMcpServerManager";
13
13
  import { McpServerInfo, McpServerInfoRW } from "../../agent/mcpServerManager";
14
- import { ChatCompletionMessageParam, IConversation } from "../../agent/agent";
14
+ import { IConversation } from "../../agent/agent";
15
+ import { ChatCompletionMessageParam } from "../../agent/llm";
15
16
 
16
17
  import {
17
18
  ClientSessionMessage,
@@ -20,11 +21,15 @@ import {
20
21
  ServerSessionInfo,
21
22
  ClientSetWorkspace,
22
23
  ClientToServer,
24
+ isServerSessionFileMessage,
23
25
  } from "../protocol/messages";
24
26
  import { SessionParticipantMap } from "../data/dataModels";
25
27
  import { createSessionParticipantMap } from "../data/database";
26
28
  import { ISessionMessageSender } from "./interfaces";
27
29
  import { IMessageSender } from "./connection";
30
+ import { SessionFiles } from "./sessionFiles";
31
+ import { SessionFileDescriptor } from "../data/dbSessionFileModels";
32
+ import { ResponseHandler } from "./responseHandler";
28
33
 
29
34
  const logger = getLogger();
30
35
 
@@ -201,28 +206,51 @@ class RemoteSudoMcpServerManager implements ISkillManager {
201
206
  }
202
207
 
203
208
  export class SessionClient implements ISessionMessageSender, IConversation {
204
- private smsm: RemoteSudoMcpServerManager;
209
+ private readonly sessionUUID: string;
210
+ private readonly savedAgentProfile: SavedAgentProfile;
211
+ private readonly sender: IMessageSender<ClientToServer>;
212
+ private readonly smsm: RemoteSudoMcpServerManager;
213
+ private readonly sessionFiles: SessionFiles;
214
+ private readonly responseHandler: ResponseHandler<
215
+ ClientSessionMessage,
216
+ ServerSessionScopedMessage
217
+ >;
218
+ private participants: SessionParticipantMap;
205
219
  private systemPrompt: string;
206
220
  private model: string;
221
+ private agentPaused: boolean;
207
222
 
208
223
  public constructor(
209
- private sessionUUID: string,
210
- private savedAgentProfile: SavedAgentProfile,
211
- private sender: IMessageSender<ClientToServer>,
224
+ sessionUUID: string,
225
+ savedAgentProfile: SavedAgentProfile,
226
+ sender: IMessageSender<ClientToServer>,
212
227
  serverBriefs: McpServerBrief[],
213
- private participants: SessionParticipantMap
228
+ participants: SessionParticipantMap,
229
+ agentPaused: boolean
214
230
  ) {
231
+ const fileList: SessionFileDescriptor[] = []; // Pass in?
232
+ this.sessionUUID = sessionUUID;
233
+ this.savedAgentProfile = savedAgentProfile;
215
234
  this.sender = sender;
216
235
  this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
236
+ this.sessionFiles = new SessionFiles(sessionUUID, fileList, sender);
237
+ this.responseHandler = new ResponseHandler("session_error");
238
+ this.participants = participants;
217
239
  this.systemPrompt = "";
218
240
  this.model = "";
219
- this.participants = participants;
241
+ this.agentPaused = agentPaused;
220
242
  }
221
243
 
222
244
  public getSessionUUID(): string {
223
245
  return this.sessionUUID;
224
246
  }
225
247
 
248
+ /// This object can be queried to upload or download files, or get the
249
+ /// latest list. See `SessionFiles` for full usage.
250
+ public getSessionFiles(): SessionFiles {
251
+ return this.sessionFiles;
252
+ }
253
+
226
254
  public getSudoMcpServerManager(): ISkillManager {
227
255
  return this.smsm;
228
256
  }
@@ -262,6 +290,17 @@ export class SessionClient implements ISessionMessageSender, IConversation {
262
290
  this.sendSessionMessage({ type: "set_model", model });
263
291
  }
264
292
 
293
+ getAgentPaused(): boolean {
294
+ return this.agentPaused;
295
+ }
296
+
297
+ setAgentPaused(paused: boolean) {
298
+ this.sendSessionMessage({
299
+ type: "set_agent_paused",
300
+ paused,
301
+ });
302
+ }
303
+
265
304
  userMessage(msg?: string, imageB64?: string): void {
266
305
  assert(msg || imageB64, "Either message or image must be provided");
267
306
 
@@ -343,8 +382,7 @@ export class SessionClient implements ISessionMessageSender, IConversation {
343
382
  }
344
383
 
345
384
  public updateSessionInfo(sessionInfo: ServerSessionInfo): void {
346
- // TODO: Implement this
347
- // throw new Error("[SessionClient.updateSessionInfo] not implemented");
385
+ // TODO: Determine the correct approach. Cache the workspace?
348
386
  const infoStr = JSON.stringify(sessionInfo.workspace);
349
387
  logger.debug(`[SessionClient] ignoring session info: ${infoStr}`);
350
388
  }
@@ -382,7 +420,28 @@ export class SessionClient implements ISessionMessageSender, IConversation {
382
420
  });
383
421
  }
384
422
 
423
+ public async shareSession(): Promise<string> {
424
+ const msg: ClientToServer = {
425
+ type: "share_session",
426
+ client_message_id: uuidv4(),
427
+ session_id: this.sessionUUID,
428
+ };
429
+
430
+ this.sender.send(msg);
431
+ const response = await this.responseHandler.waitForResponse(msg);
432
+ if (response.type === "session_shared") {
433
+ return response.access_token;
434
+ }
435
+
436
+ throw new Error(`unexpected response to "share"session": ${response.type}`);
437
+ }
438
+
385
439
  handleMessage(message: ServerSessionScopedMessage): void {
440
+ if (isServerSessionFileMessage(message)) {
441
+ this.sessionFiles.onMessage(message);
442
+ return;
443
+ }
444
+
386
445
  switch (message.type) {
387
446
  //
388
447
  // State updates
@@ -427,6 +486,13 @@ export class SessionClient implements ISessionMessageSender, IConversation {
427
486
  case "user_removed":
428
487
  this.participants.delete(message.user_uuid);
429
488
  break;
489
+ case "session_shared":
490
+ this.responseHandler.onMessage(message);
491
+ break;
492
+
493
+ case "agent_paused":
494
+ this.agentPaused = message.paused;
495
+ break;
430
496
 
431
497
  //
432
498
  // Ignore other messages - the owner (the UI layer) can handle them at