@xalia/agent 0.6.9 → 0.6.11

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 (199) hide show
  1. package/README.md +11 -0
  2. package/dist/agent/src/agent/agent.js +77 -18
  3. package/dist/agent/src/agent/agentUtils.js +3 -2
  4. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  5. package/dist/agent/src/agent/dummyLLM.js +25 -22
  6. package/dist/agent/src/agent/imageGenLLM.js +22 -19
  7. package/dist/agent/src/agent/llm.js +1 -1
  8. package/dist/agent/src/agent/openAILLM.js +15 -12
  9. package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
  10. package/dist/agent/src/agent/repeatLLM.js +16 -7
  11. package/dist/agent/src/agent/tokenCounter.js +390 -0
  12. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  13. package/dist/agent/src/agent/toolSettings.js +17 -0
  14. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  15. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  16. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  17. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  18. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  19. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  20. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  21. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  22. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  23. package/dist/agent/src/agent/tools/index.js +64 -0
  24. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  25. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  26. package/dist/agent/src/agent/tools/utils.js +61 -0
  27. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  28. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  29. package/dist/agent/src/chat/client/chatClient.js +28 -0
  30. package/dist/agent/src/chat/client/index.js +4 -1
  31. package/dist/agent/src/chat/client/sessionClient.js +28 -2
  32. package/dist/agent/src/chat/constants.js +8 -0
  33. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  34. package/dist/agent/src/chat/protocol/messages.js +5 -0
  35. package/dist/agent/src/chat/server/chatContextManager.js +45 -25
  36. package/dist/agent/src/chat/server/conversation.js +3 -0
  37. package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
  38. package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
  39. package/dist/agent/src/chat/server/openSession.js +218 -55
  40. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  41. package/dist/agent/src/chat/server/server.js +5 -1
  42. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  43. package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
  44. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  45. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  46. package/dist/agent/src/chat/server/tools.js +63 -287
  47. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  48. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  49. package/dist/agent/src/test/agent.test.js +16 -17
  50. package/dist/agent/src/test/chatContextManager.test.js +15 -3
  51. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  52. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  53. package/dist/agent/src/test/testTools.js +6 -1
  54. package/dist/agent/src/test/tools.test.js +27 -9
  55. package/dist/agent/src/tool/agentChat.js +5 -2
  56. package/dist/agent/src/tool/chatMain.js +34 -7
  57. package/dist/agent/src/tool/commandPrompt.js +2 -2
  58. package/dist/agent/src/tool/files.js +7 -8
  59. package/package.json +8 -2
  60. package/.env.development +0 -1
  61. package/.prettierrc.json +0 -11
  62. package/dist/agent/src/agent/tools.js +0 -44
  63. package/eslint.config.mjs +0 -38
  64. package/scripts/chat_server +0 -8
  65. package/scripts/git_message +0 -31
  66. package/scripts/git_wip +0 -21
  67. package/scripts/pr_message +0 -18
  68. package/scripts/pr_review +0 -16
  69. package/scripts/setup_chat +0 -90
  70. package/scripts/shutdown_chat_server +0 -42
  71. package/scripts/start_chat_server +0 -24
  72. package/scripts/sudomcp_import +0 -23
  73. package/scripts/test_chat +0 -308
  74. package/src/agent/agent.ts +0 -624
  75. package/src/agent/agentUtils.ts +0 -285
  76. package/src/agent/compressingContextManager.ts +0 -129
  77. package/src/agent/context.ts +0 -265
  78. package/src/agent/contextWithWorkspace.ts +0 -162
  79. package/src/agent/dummyLLM.ts +0 -126
  80. package/src/agent/iAgentEventHandler.ts +0 -64
  81. package/src/agent/imageGenLLM.ts +0 -97
  82. package/src/agent/imageGenerator.ts +0 -45
  83. package/src/agent/iplatform.ts +0 -18
  84. package/src/agent/llm.ts +0 -74
  85. package/src/agent/mcpServerManager.ts +0 -541
  86. package/src/agent/nullAgentEventHandler.ts +0 -26
  87. package/src/agent/nullPlatform.ts +0 -13
  88. package/src/agent/openAI.ts +0 -123
  89. package/src/agent/openAILLM.ts +0 -95
  90. package/src/agent/openAILLMStreaming.ts +0 -609
  91. package/src/agent/promptProvider.ts +0 -87
  92. package/src/agent/repeatLLM.ts +0 -50
  93. package/src/agent/sudoMcpServerManager.ts +0 -361
  94. package/src/agent/tokenAuth.ts +0 -50
  95. package/src/agent/tools.ts +0 -57
  96. package/src/chat/client/chatClient.ts +0 -922
  97. package/src/chat/client/connection.test.ts +0 -241
  98. package/src/chat/client/connection.ts +0 -286
  99. package/src/chat/client/constants.ts +0 -1
  100. package/src/chat/client/index.ts +0 -18
  101. package/src/chat/client/interfaces.ts +0 -34
  102. package/src/chat/client/sessionClient.ts +0 -537
  103. package/src/chat/client/sessionFiles.ts +0 -142
  104. package/src/chat/client/teamManager.ts +0 -29
  105. package/src/chat/data/apiKeyManager.ts +0 -76
  106. package/src/chat/data/dataModels.ts +0 -101
  107. package/src/chat/data/database.ts +0 -997
  108. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  109. package/src/chat/data/dbSessionFileModels.ts +0 -113
  110. package/src/chat/data/dbSessionFiles.ts +0 -99
  111. package/src/chat/data/dbSessionMessages.ts +0 -102
  112. package/src/chat/data/mimeTypes.ts +0 -58
  113. package/src/chat/protocol/connectionMessages.ts +0 -49
  114. package/src/chat/protocol/constants.ts +0 -55
  115. package/src/chat/protocol/errors.ts +0 -16
  116. package/src/chat/protocol/messages.ts +0 -846
  117. package/src/chat/server/README.md +0 -127
  118. package/src/chat/server/chatContextManager.ts +0 -639
  119. package/src/chat/server/connectionManager.test.ts +0 -246
  120. package/src/chat/server/connectionManager.ts +0 -506
  121. package/src/chat/server/conversation.ts +0 -316
  122. package/src/chat/server/errorUtils.ts +0 -28
  123. package/src/chat/server/imageGeneratorTools.ts +0 -160
  124. package/src/chat/server/openAIRouterLLM.ts +0 -171
  125. package/src/chat/server/openSession.ts +0 -1689
  126. package/src/chat/server/openSessionMessageSender.ts +0 -4
  127. package/src/chat/server/server.ts +0 -175
  128. package/src/chat/server/sessionFileManager.ts +0 -422
  129. package/src/chat/server/sessionRegistry.test.ts +0 -137
  130. package/src/chat/server/sessionRegistry.ts +0 -1425
  131. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  132. package/src/chat/server/tools.ts +0 -397
  133. package/src/chat/utils/agentSessionMap.ts +0 -76
  134. package/src/chat/utils/approvalManager.ts +0 -183
  135. package/src/chat/utils/asyncLock.ts +0 -43
  136. package/src/chat/utils/asyncQueue.ts +0 -62
  137. package/src/chat/utils/htmlToText.ts +0 -61
  138. package/src/chat/utils/multiAsyncQueue.ts +0 -62
  139. package/src/chat/utils/responseAwaiter.ts +0 -181
  140. package/src/chat/utils/search.ts +0 -139
  141. package/src/chat/utils/userResolver.ts +0 -48
  142. package/src/chat/utils/websocket.ts +0 -16
  143. package/src/index.ts +0 -0
  144. package/src/test/agent.test.ts +0 -590
  145. package/src/test/approvalManager.test.ts +0 -141
  146. package/src/test/chatContextManager.test.ts +0 -527
  147. package/src/test/clientServerConnection.test.ts +0 -205
  148. package/src/test/compressingContextManager.test.ts +0 -77
  149. package/src/test/context.test.ts +0 -150
  150. package/src/test/contextTestTools.ts +0 -95
  151. package/src/test/conversation.test.ts +0 -109
  152. package/src/test/db.test.ts +0 -363
  153. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  154. package/src/test/dbSessionFiles.test.ts +0 -258
  155. package/src/test/dbSessionMessages.test.ts +0 -85
  156. package/src/test/dbTestTools.ts +0 -157
  157. package/src/test/imageLoad.test.ts +0 -15
  158. package/src/test/mcpServerManager.test.ts +0 -114
  159. package/src/test/multiAsyncQueue.test.ts +0 -183
  160. package/src/test/openaiStreaming.test.ts +0 -177
  161. package/src/test/prompt.test.ts +0 -27
  162. package/src/test/promptProvider.test.ts +0 -33
  163. package/src/test/responseAwaiter.test.ts +0 -103
  164. package/src/test/sudoMcpServerManager.test.ts +0 -63
  165. package/src/test/testTools.ts +0 -171
  166. package/src/test/tools.test.ts +0 -39
  167. package/src/tool/agentChat.ts +0 -194
  168. package/src/tool/agentMain.ts +0 -180
  169. package/src/tool/chatMain.ts +0 -594
  170. package/src/tool/commandPrompt.ts +0 -264
  171. package/src/tool/files.ts +0 -84
  172. package/src/tool/main.ts +0 -25
  173. package/src/tool/nodePlatform.ts +0 -73
  174. package/src/tool/options.ts +0 -144
  175. package/src/tool/prompt.ts +0 -101
  176. package/test_data/background_test_profile.json +0 -6
  177. package/test_data/background_test_script.json +0 -11
  178. package/test_data/dummyllm_script_crash.json +0 -32
  179. package/test_data/dummyllm_script_image_gen.json +0 -19
  180. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  181. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  182. package/test_data/dummyllm_script_render_tool.json +0 -29
  183. package/test_data/dummyllm_script_simplecalc.json +0 -28
  184. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  185. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  186. package/test_data/frog.png +0 -0
  187. package/test_data/frog.png.b64 +0 -1
  188. package/test_data/git_message_profile.json +0 -4
  189. package/test_data/git_wip_system.txt +0 -5
  190. package/test_data/image_gen_test_profile.json +0 -5
  191. package/test_data/pr_message_profile.json +0 -4
  192. package/test_data/pr_review_profile.json +0 -4
  193. package/test_data/prompt_simplecalc.txt +0 -1
  194. package/test_data/simplecalc_profile.json +0 -4
  195. package/test_data/sudomcp_import_profile.json +0 -4
  196. package/test_data/test_script_profile.json +0 -8
  197. package/tsconfig.json +0 -13
  198. package/vitest.config.ts +0 -39
  199. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
@@ -14,7 +14,6 @@ const DEFAULT_PROVIDER_URLS = {
14
14
  openrouter: "https://openrouter.ai/api/v1",
15
15
  openai: "https://api.openai.com/v1",
16
16
  anthropic: "https://api.anthropic.com/v1",
17
- together: "https://api.together.xyz/v1",
18
17
  };
19
18
  const DEFAULT_MODEL_MAP = {
20
19
  "gpt-4o-mini": "openai",
@@ -28,8 +27,6 @@ const DEFAULT_MODEL_MAP = {
28
27
  "anthropic/claude-sonnet-4": "openrouter",
29
28
  "anthropic/claude-sonnet-4.5": "openrouter",
30
29
  "claude-3-7-sonnet-20250219": "anthropic",
31
- "arcee-ai/AFM-4.5B-Preview": "together",
32
- "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": "together",
33
30
  };
34
31
  let cached_maps = undefined;
35
32
  // TODO: Move this to the CLI command args?
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpenSession = exports.ChatSessionAgentEventHandler = exports.ChatSessionMessageSender = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
3
+ exports.OpenSession = exports.ChatSessionAgentEventHandler = exports.ChatSessionMessageSender = exports.RACE_MODE_TIMEOUT_MS = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
4
4
  const assert_1 = require("assert");
5
+ const uuid_1 = require("uuid");
5
6
  const sdk_1 = require("@xalia/xmcp/sdk");
6
7
  const agent_1 = require("../../agent/agent");
7
8
  const sudoMcpServerManager_1 = require("../../agent/sudoMcpServerManager");
8
9
  const agentUtils_1 = require("../../agent/agentUtils");
10
+ const titleGenerator_1 = require("./titleGenerator");
9
11
  const asyncQueue_1 = require("../utils/asyncQueue");
10
12
  const multiAsyncQueue_1 = require("../utils/multiAsyncQueue");
11
13
  const database_1 = require("../data/database");
@@ -15,12 +17,16 @@ const tools_1 = require("./tools");
15
17
  const chatContextManager_1 = require("./chatContextManager");
16
18
  const conversation_1 = require("./conversation");
17
19
  const sessionFileManager_1 = require("./sessionFileManager");
20
+ const fileManager_1 = require("../../agent/tools/fileManager");
21
+ const pdfToText_1 = require("../../agent/tools/contentExtractors/pdfToText");
22
+ const documentSummarizer_1 = require("../../agent/documentSummarizer");
18
23
  const errorUtils_1 = require("./errorUtils");
19
24
  const nodePlatform_1 = require("../../tool/nodePlatform");
20
25
  const dbMcpServerConfigs_1 = require("../data/dbMcpServerConfigs");
21
26
  const apiKeyManager_1 = require("../data/apiKeyManager");
22
27
  const dbSessionMessages_1 = require("../data/dbSessionMessages");
23
28
  const openAIRouterLLM_1 = require("./openAIRouterLLM");
29
+ const responseAwaiter_1 = require("../utils/responseAwaiter");
24
30
  /**
25
31
  * The model to use when the AgentProfile does not specify one.
26
32
  */
@@ -30,6 +36,7 @@ const DEFAULT_CHAT_LLM_MODEL = process.env["DEFAULT_LLM_MODEL"] || "anthropic/cl
30
36
  */
31
37
  exports.DEFAULT_NUM_MESSGAES = 500;
32
38
  exports.GUEST_TOKEN_PREFIX = "guest";
39
+ exports.RACE_MODE_TIMEOUT_MS = 120000;
33
40
  const logger = (0, sdk_1.getLogger)();
34
41
  /**
35
42
  * Implementation of ICheckpointWriter that writes into the DB.
@@ -111,23 +118,24 @@ class ChatSessionPlatform {
111
118
  }
112
119
  }
113
120
  class ChatSessionAgentEventHandler {
114
- constructor(sessionUUID, sender, approvalManager, contextTx) {
121
+ constructor(sessionUUID, sender, approvalManager, contextTx, alt) {
115
122
  this.sessionUUID = sessionUUID;
116
123
  this.sender = sender;
117
124
  this.approvalManager = approvalManager;
118
125
  this.contextTx = contextTx;
126
+ this.alt = alt;
119
127
  }
120
128
  onCompletion(result) {
121
- logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
129
+ logger.debug(`[OpenSession.onCompletion] ${this.alt || ""} : ${JSON.stringify(result)}`);
122
130
  // Nothing to broadcast. Caller will receive this via onAgentMessage.
123
131
  this.contextTx.processAgentResponse(result);
124
132
  }
125
133
  onImage(image) {
126
- logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
134
+ logger.debug(`[OpenSession.onImage] ${this.alt || ""} : ${image.image_url.url}`);
127
135
  throw new Error("[OpenSession.onImage] unimplemented");
128
136
  }
129
137
  onToolCallResult(result) {
130
- logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
138
+ logger.debug(`[onToolCallResult] ${this.alt || ""} : ${JSON.stringify(result)}`);
131
139
  const toolCallMessage = this.contextTx.processToolCallResult(result);
132
140
  this.sender.broadcast(toolCallMessage);
133
141
  }
@@ -139,12 +147,13 @@ class ChatSessionAgentEventHandler {
139
147
  type: "tool_call",
140
148
  tool_call: toolCall,
141
149
  session_id: this.sessionUUID,
150
+ ...(this.alt ? { alt: this.alt } : {}),
142
151
  });
143
152
  return true;
144
153
  }
145
154
  // TODO: Need a proper mapping to/from MCP calls to tool names
146
155
  const [serverName, tool] = toolCall.function.name.split("__");
147
- const { approved, requested } = await this.approvalManager.getApproval(serverName, tool, toolCall);
156
+ const { approved, requested } = await this.approvalManager.getApproval(serverName, tool, toolCall, this.alt);
148
157
  // For now, the frontend uses the tool_call data in the
149
158
  // "approve_tool_call" request to display the tool call data. If approval
150
159
  // was requested in this way, don't send the "tool_call" message as well.
@@ -153,25 +162,31 @@ class ChatSessionAgentEventHandler {
153
162
  type: "tool_call",
154
163
  tool_call: toolCall,
155
164
  session_id: this.sessionUUID,
165
+ ...(this.alt ? { alt: this.alt } : {}),
156
166
  });
157
167
  }
158
168
  return approved;
159
169
  }
160
170
  onAgentMessage(msg, end) {
161
- logger.debug(`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`);
171
+ logger.debug(`[OpenSession.onAgentMessage] ${this.alt || ""} msg: ${msg}, ` +
172
+ `end: ${String(end)}`);
162
173
  // Inform the contextManager and broadcast the ServerAgentMessageChunk
163
174
  const agentMsgChunk = this.contextTx.processAgentMessageChunk(msg, end);
175
+ if (this.alt) {
176
+ agentMsgChunk.alt = this.alt;
177
+ }
164
178
  this.sender.broadcast(agentMsgChunk);
165
179
  return Promise.resolve();
166
180
  }
167
181
  onReasoning(reasoning) {
168
182
  return new Promise((r) => {
169
- logger.debug(`[OpenSession.onReasoning]${reasoning}`);
183
+ logger.debug(`[OpenSession.onReasoning] ${this.alt || ""} ${reasoning}`);
170
184
  if (reasoning.length > 0) {
171
185
  this.sender.broadcast({
172
186
  type: "agent_reasoning_chunk",
173
187
  reasoning,
174
188
  session_id: this.sessionUUID,
189
+ ...(this.alt ? { alt: this.alt } : {}),
175
190
  });
176
191
  }
177
192
  r();
@@ -190,7 +205,7 @@ exports.ChatSessionAgentEventHandler = ChatSessionAgentEventHandler;
190
205
  * tool approvals and other interactions).
191
206
  */
192
207
  class OpenSession {
193
- constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, sender, approvalManager, fileManager) {
208
+ constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, sender, approvalManager, fileManager, platform) {
194
209
  this.db = db;
195
210
  this.agent = agent;
196
211
  this.sessionUUID = sessionData.session_uuid;
@@ -208,10 +223,13 @@ class OpenSession {
208
223
  this.approvalManager = approvalManager;
209
224
  this.savedAgentProfile = savedAgentProfile;
210
225
  this.sessionFileManager = fileManager;
226
+ this.platform = platform;
227
+ this.raceModeAwaiter = responseAwaiter_1.ResponseAwaiter.init(undefined, (m) => m.message_id, exports.RACE_MODE_TIMEOUT_MS);
211
228
  this.isPersisted = isPersisted;
212
229
  this.sessionTitle = sessionData.title;
213
230
  this.sessionUpdatedAt = sessionData.updated_at;
214
231
  this.agentPaused = sessionData.agent_paused;
232
+ this.titleGenerator = (0, titleGenerator_1.createTitleGenerator)();
215
233
  fileManager.addEventHandler(this);
216
234
  this.updateParticipantsPrompt();
217
235
  }
@@ -222,8 +240,8 @@ class OpenSession {
222
240
  const platform = new ChatSessionPlatform(sender, sessionId, ownerData.uuid);
223
241
  const toolApprovalManager = new approvalManager_1.ToolApprovalManager(sessionData.session_uuid, savedAgentProfile.uuid, savedAgentProfile.preferences, sender, new approvalManager_1.DbAgentPreferencesWriter(db));
224
242
  const checkpointWriter = new DBCheckpointWriter(db, sessionId);
225
- const { agent, skillManager, contextManager } = await createContextAndAgent(sessionId, savedAgentProfile.profile.system_prompt, savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL, sessionMessages, sessionData.workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter);
226
- const openSession = new OpenSession(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, skillManager, contextManager, sender, toolApprovalManager, fileManager);
243
+ const { agent, skillManager, contextManager } = await createContextAndAgent(sessionId, savedAgentProfile.profile.system_prompt, savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL, sessionMessages, sessionData.workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter, sender);
244
+ const openSession = new OpenSession(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, skillManager, contextManager, sender, toolApprovalManager, fileManager, platform);
227
245
  // Note, MCP servers have not been enabled yet
228
246
  return openSession;
229
247
  }
@@ -298,7 +316,9 @@ class OpenSession {
298
316
  });
299
317
  });
300
318
  // send conversation history
301
- const conversationMessages = this.contextManager.getConversationMessages();
319
+ const conversationMessages = this.contextManager
320
+ .getConversationMessages()
321
+ .concat(this.userMessageQueue.getEntries());
302
322
  conversationMessages.forEach((message) => {
303
323
  connMgr.sendToConnection(connectionId, message);
304
324
  });
@@ -381,30 +401,25 @@ class OpenSession {
381
401
  this.sender.broadcast(msg);
382
402
  }
383
403
  };
404
+ let errorMessage;
384
405
  if (err instanceof errors_1.ChatFatalError) {
385
406
  // ChatFatalError in session context should send error to specific user
386
407
  // or broadcast if no specific user context
387
- sendError({
388
- type: "session_error",
389
- message: err.message,
390
- session_id: this.sessionUUID,
391
- });
408
+ errorMessage = err.message;
392
409
  }
393
410
  else if (err instanceof errors_1.ChatErrorMessage) {
394
- sendError({
395
- type: "session_error",
396
- message: err.message,
397
- session_id: this.sessionUUID,
398
- });
411
+ errorMessage = err.message;
399
412
  }
400
413
  else {
401
- const errString = (0, errorUtils_1.getErrorString)(err);
402
- sendError({
403
- type: "session_error",
404
- message: errString,
405
- session_id: this.sessionUUID,
406
- });
414
+ errorMessage = (0, errorUtils_1.getErrorString)(err);
407
415
  }
416
+ logger.error(`[OpenSession.handleError] sessionUUID=${this.sessionUUID} ` +
417
+ `from=${from ?? "broadcast"}: ${errorMessage}`);
418
+ sendError({
419
+ type: "session_error",
420
+ message: errorMessage,
421
+ session_id: this.sessionUUID,
422
+ });
408
423
  return true;
409
424
  }
410
425
  // ISessionFileManagerEventHandler.onFileDeleted
@@ -415,6 +430,15 @@ class OpenSession {
415
430
  name,
416
431
  });
417
432
  }
433
+ broadcastContextUsage() {
434
+ const { used, max } = this.contextManager.getContextUsage();
435
+ this.sender.broadcast({
436
+ type: "context_usage",
437
+ session_id: this.sessionUUID,
438
+ used_tokens: used,
439
+ max_tokens: max,
440
+ });
441
+ }
418
442
  // ISessionFileManagerEventHandler.onFileChanged
419
443
  onFileChanged(entry, new_file) {
420
444
  this.sender.broadcast({
@@ -493,6 +517,9 @@ class OpenSession {
493
517
  case "msg":
494
518
  broadcastMsg = await this.handleUserMessage(msg, queuedMessage.from);
495
519
  break;
520
+ case "stop":
521
+ this.agent.stop();
522
+ break;
496
523
  case "add_mcp_server":
497
524
  broadcastMsg = await this.handleAddMcpServer(msg.server_name, msg.enable_all);
498
525
  break;
@@ -547,6 +574,9 @@ class OpenSession {
547
574
  case "get_mcp_resource":
548
575
  void this.handleGetMcpResource(msg, queuedMessage.from);
549
576
  break;
577
+ case "race_mode_result":
578
+ this.raceModeAwaiter.onMessage(msg);
579
+ break;
550
580
  default: {
551
581
  const exhaustive = msg; // Error => non-exhaustive switch-case.
552
582
  return exhaustive;
@@ -560,6 +590,10 @@ class OpenSession {
560
590
  }
561
591
  else {
562
592
  this.sender.broadcast(broadcastMsg);
593
+ // Broadcast context usage after user message
594
+ if (broadcastMsg.type === "user_msg") {
595
+ this.broadcastContextUsage();
596
+ }
563
597
  }
564
598
  }
565
599
  }
@@ -618,12 +652,11 @@ class OpenSession {
618
652
  // Just send the user LLM user messages direct to the ContextManager, so
619
653
  // they are available to the LLM once it is restarted.
620
654
  const { contextTx } = await this.contextManager.startAgentResponse(msgs);
621
- return this.contextManager.endAgentResponse(contextTx);
655
+ const result = this.contextManager.endAgentResponse(contextTx);
656
+ this.broadcastContextUsage();
657
+ return result;
622
658
  }
623
659
  async processUserMessagesActive(msgs) {
624
- // TODO: create the contextTx and store all new messages on this. Event
625
- // handler should accept the contextTx and forward messages to it, as well
626
- // as sending the updates.
627
660
  // All accumulated messages (DB, Protocol and LLM) should be on the
628
661
  // specialized contextTx.
629
662
  const { contextTx, agentFirstChunk } = await this.contextManager.startAgentResponse(msgs);
@@ -641,13 +674,77 @@ class OpenSession {
641
674
  // DB. Should we keep them?
642
675
  throw new Error(errMsg);
643
676
  }
644
- return this.contextManager.endAgentResponse(contextTx);
677
+ const result = this.contextManager.endAgentResponse(contextTx);
678
+ this.broadcastContextUsage();
679
+ return result;
680
+ }
681
+ /**
682
+ * `processUserMessage` logic for race-mode.
683
+ */
684
+ async processUserMessagesRaceMode(msgs) {
685
+ // Create a second agent, same skillManager
686
+ const modelB = msgs[0].race_mode;
687
+ (0, assert_1.strict)(typeof modelB === "string");
688
+ const agentB = await (async () => {
689
+ try {
690
+ const llmB = await createLLM(modelB, this.platform);
691
+ return new agent_1.AgentEx(this.skillManager, llmB);
692
+ }
693
+ catch (e) {
694
+ logger.warn("[OpenSession.processUserMessages] error creating race agent: " +
695
+ String(e));
696
+ throw new Error(`error creating race: ${String(e)}`);
697
+ }
698
+ })();
699
+ const run = async (alt, msgs) => {
700
+ const { contextTx: contextTx, agentFirstChunk: agentFirstChunk } = await this.contextManager.startAgentResponse(msgs.slice());
701
+ this.sender.broadcast(agentFirstChunk);
702
+ const eventHandler = new ChatSessionAgentEventHandler(this.sessionUUID, this.sender, this.approvalManager, contextTx, alt);
703
+ try {
704
+ const agent = alt === "B" ? agentB : this.agent;
705
+ await agent.userMessagesRaw(contextTx, eventHandler);
706
+ }
707
+ catch (e) {
708
+ logger.warn(`[OpenSession.processUserMessages] agent ${alt} error: ${String(e)}`);
709
+ const errMsg = `error from LLM: ${String(e)}`;
710
+ contextTx.revertAgentResponse(errMsg);
711
+ throw new Error(errMsg);
712
+ }
713
+ return contextTx;
714
+ };
715
+ const [txA, txB] = await Promise.all([
716
+ run("A", msgs.slice()),
717
+ run("B", msgs),
718
+ ]);
719
+ // Ask which fork to commit
720
+ const answer = await (async () => {
721
+ const message_id = (0, uuid_1.v4)();
722
+ const answerP = this.raceModeAwaiter.waitForResponse(message_id);
723
+ const getResultMsg = {
724
+ type: "race_mode_get_result",
725
+ message_id,
726
+ alts: ["A", "B"],
727
+ session_id: this.sessionUUID,
728
+ };
729
+ this.sender.broadcast(getResultMsg);
730
+ return answerP;
731
+ })();
732
+ if (answer.result === "A") {
733
+ return this.contextManager.endAgentResponse(txA);
734
+ }
735
+ else {
736
+ return this.contextManager.endAgentResponse(txB);
737
+ }
738
+ // UI is responsible for switching model if the user wants to.
645
739
  }
646
740
  async processUserMessages(msgs) {
741
+ logger.debug(`[processUserMessages] msgs: ${JSON.stringify(msgs)}`);
647
742
  try {
648
743
  const newSessionMessages = this.agentPaused
649
744
  ? await this.processUserMessagePaused(msgs)
650
- : await this.processUserMessagesActive(msgs);
745
+ : await (msgs[0].race_mode
746
+ ? this.processUserMessagesRaceMode(msgs)
747
+ : this.processUserMessagesActive(msgs));
651
748
  logger.debug("[processUserMessages] newSessionMessages: " +
652
749
  JSON.stringify(newSessionMessages));
653
750
  // Append to in-memory conversation and write to the DB
@@ -655,6 +752,8 @@ class OpenSession {
655
752
  await dbsm.append(this.sessionUUID, newSessionMessages);
656
753
  }
657
754
  catch (e) {
755
+ logger.error(`[processUserMessages] ERROR session=${this.sessionUUID}: ` +
756
+ String(e));
658
757
  if (!this.handleError(e)) {
659
758
  throw e;
660
759
  }
@@ -664,9 +763,16 @@ class OpenSession {
664
763
  // Return a ServerUserMessage for broadcast. The actual message is places
665
764
  // on a queue to be dealt with in another loop. This allows Agent
666
765
  // processing of user messages to depend on other messages.
667
- (0, assert_1.strict)(msg);
668
- (0, assert_1.strict)(from);
669
- // Assign the user message_idx and attempt to enqueue.
766
+ (0, assert_1.strict)(msg, "undefined user message");
767
+ (0, assert_1.strict)(from, "undefined user message sender");
768
+ logger.info(`[handleUserMessage] msg.attachedFiles: ${JSON.stringify(msg.attachedFiles
769
+ ? msg.attachedFiles.map((f) => ({
770
+ name: f.name,
771
+ data_url_length: f.data_url.length,
772
+ }))
773
+ : "none")}`);
774
+ // Assign the user message_idx first so we can check if this is
775
+ // the first message
670
776
  const user = this.sessionParticipants.get(from);
671
777
  if (!user) {
672
778
  throw new Error(`unrecognized user ${from}`);
@@ -675,6 +781,62 @@ class OpenSession {
675
781
  if (!userMessage) {
676
782
  return;
677
783
  }
784
+ // Handle attached files by adding them to session files
785
+ if (msg.attachedFiles && msg.attachedFiles.length > 0) {
786
+ const fileCount = String(msg.attachedFiles.length);
787
+ logger.info(`[handleUserMessage] Processing ${fileCount} attached files`);
788
+ // If this is the first message, set the session title before
789
+ // creating the session
790
+ if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE &&
791
+ !this.isPersisted) {
792
+ this.sessionTitle = userMessage.message?.slice(0, 128) || "New Chat";
793
+ }
794
+ // If the session hasn't been persisted, it must be written to
795
+ // the DB first
796
+ if (!this.isPersisted) {
797
+ await this.createSessionInDB();
798
+ }
799
+ // Add each attached file to session files
800
+ for (const file of msg.attachedFiles) {
801
+ logger.info(`[handleUserMessage] Adding file ${file.name} to session files`);
802
+ // Extract content and generate summary for PDFs
803
+ let parsed_content;
804
+ let summary;
805
+ const mimeType = (0, fileManager_1.getMimeTypeFromDataUrl)(file.data_url);
806
+ if (mimeType === "application/pdf") {
807
+ try {
808
+ // Extract base64 data and convert to ArrayBuffer
809
+ const base64Data = file.data_url.split(",")[1];
810
+ const binaryString = atob(base64Data);
811
+ const bytes = new Uint8Array(binaryString.length);
812
+ for (let i = 0; i < binaryString.length; i++) {
813
+ bytes[i] = binaryString.charCodeAt(i);
814
+ }
815
+ const arrayBuffer = bytes.buffer;
816
+ // Extract text from PDF
817
+ const extractedText = await (0, pdfToText_1.pdfToText)(arrayBuffer);
818
+ parsed_content = {
819
+ version: 1,
820
+ text: extractedText,
821
+ };
822
+ // Generate summary from extracted text
823
+ if (extractedText.trim().length > 0) {
824
+ summary = await (0, documentSummarizer_1.summarizeDocument)(extractedText);
825
+ }
826
+ logger.info(`[handleUserMessage] Extracted ${String(extractedText.length)}` +
827
+ ` chars from PDF ${file.name}`);
828
+ }
829
+ catch (err) {
830
+ logger.warn(`[handleUserMessage] Failed to extract PDF content: ` +
831
+ String(err));
832
+ }
833
+ }
834
+ await this.sessionFileManager.putFileContent(file.name, summary, file.data_url, parsed_content);
835
+ }
836
+ // Remove attachedFiles from the message so they don't get
837
+ // included in the LLM context
838
+ msg = { ...msg, attachedFiles: undefined };
839
+ }
678
840
  // Special case for the first message of the session
679
841
  if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE) {
680
842
  // No need to wait for this to complete before broadcasting.
@@ -714,8 +876,8 @@ class OpenSession {
714
876
  this.isPersisted = true;
715
877
  }
716
878
  async onFirstMessage(userMsg) {
717
- // Update title on the class before writing to the DB
718
- this.sessionTitle = userMsg.message?.slice(0, 128) || "New Chat";
879
+ // Generate title using LLM with automatic fallback
880
+ this.sessionTitle = await this.titleGenerator.generateTitle(userMsg.message || "");
719
881
  // The session may already have been saved (e.g. if the workspace is
720
882
  // updated before any messages are sent).
721
883
  if (!this.isPersisted) {
@@ -1080,27 +1242,28 @@ async function loadSessionData(db, sessionId) {
1080
1242
  sessionParticipants: (0, database_1.createSessionParticipantMap)(sessionParticipants),
1081
1243
  };
1082
1244
  }
1083
- async function createContextAndAgent(sessionUUID, systemPrompt, model, sessionMessages, workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter) {
1084
- // Fn to create the llm. One invocation for the compression context, one
1085
- // for the Agent.
1086
- const createLLM = async () => {
1087
- let llm = await (0, agentUtils_1.createSpecializedLLM)(model, platform);
1088
- if (!llm) {
1089
- llm = new openAIRouterLLM_1.OpenAIRouterLLM(model);
1090
- }
1091
- (0, assert_1.strict)(llm);
1092
- return llm;
1093
- };
1094
- const contextManager = new chatContextManager_1.ChatContextManager(systemPrompt, sessionMessages, sessionUUID, ownerData.uuid, sessionCheckpoint, checkpointWriter, fileManager, await createLLM());
1095
- if (workspace) {
1096
- contextManager.setWorkspace((0, agent_1.createUserMessage)(workspace.message, workspace.imageB64));
1245
+ async function createLLM(model, platform) {
1246
+ let llm = await (0, agentUtils_1.createSpecializedLLM)(model, platform);
1247
+ if (!llm) {
1248
+ llm = new openAIRouterLLM_1.OpenAIRouterLLM(model);
1097
1249
  }
1250
+ (0, assert_1.strict)(llm);
1251
+ return llm;
1252
+ }
1253
+ async function createContextAndAgent(sessionUUID, systemPrompt, model, sessionMessages, workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter, messageSender) {
1254
+ // Create SkillManager
1098
1255
  const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
1099
1256
  const skillManager = await sudoMcpServerManager_1.SkillManager.initialize((url, authResultP, displayName) => {
1100
1257
  platform.openUrl(url, authResultP, displayName);
1101
1258
  }, xmcpConfig.backend_url, xmcpConfig.api_key, undefined /* authorizedUrl */);
1102
- const llm = await createLLM();
1259
+ // Fn to create the llm. One invocation for the compression context, one
1260
+ // for the Agent.
1261
+ const llm = await createLLM(model, platform);
1103
1262
  const agent = new agent_1.AgentEx(skillManager, llm);
1104
- await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, platform, fileManager);
1263
+ await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, platform, fileManager, messageSender);
1264
+ const contextManager = new chatContextManager_1.ChatContextManager(systemPrompt, sessionMessages, sessionUUID, ownerData.uuid, sessionCheckpoint, checkpointWriter, fileManager, await createLLM(model, platform));
1265
+ if (workspace) {
1266
+ contextManager.setWorkspace((0, agent_1.createUserMessage)(workspace.message, workspace.imageB64));
1267
+ }
1105
1268
  return { agent, skillManager, contextManager };
1106
1269
  }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LLMPromptRefiner = void 0;
4
+ exports.getPromptRefiner = getPromptRefiner;
5
+ const openAIRouterLLM_1 = require("./openAIRouterLLM");
6
+ const sdk_1 = require("@xalia/xmcp/sdk");
7
+ const logger = (0, sdk_1.getLogger)();
8
+ const REFINER_MODEL = "google/gemini-2.5-flash";
9
+ const REFINER_MAX_TOKENS = 2000;
10
+ const REFINER_TEMPERATURE = 0.3;
11
+ const REFINER_TIMEOUT_MS = 30000;
12
+ // prettier-ignore
13
+ const REFINEMENT_SYSTEM_PROMPT = `You are an expert at writing system prompts for AI assistants. Your task is \
14
+ to refine and improve the given system prompt to make it more effective, \
15
+ clear, and well-structured for an LLM to follow.
16
+
17
+ Guidelines for refinement:
18
+ 1. Make instructions clear, specific, and actionable
19
+ 2. Use structured formatting (sections, bullet points) when appropriate
20
+ 3. Remove ambiguity and redundancy
21
+ 4. Add relevant context if missing
22
+ 5. Ensure the tone and style are consistent
23
+ 6. Preserve the original intent and personality
24
+ 7. Keep the prompt concise but comprehensive
25
+
26
+ Return ONLY the refined system prompt, without any explanations or \
27
+ meta-commentary.`;
28
+ class LLMPromptRefiner {
29
+ constructor(model = REFINER_MODEL) {
30
+ this.model = model;
31
+ }
32
+ async refinePrompt(prompt) {
33
+ if (!prompt || prompt.trim().length === 0) {
34
+ return prompt;
35
+ }
36
+ try {
37
+ const refined = await this.refineWithTimeout(prompt);
38
+ return refined.trim();
39
+ }
40
+ catch (error) {
41
+ const errorMsg = error instanceof Error ? error.message : String(error);
42
+ logger.warn(`[PromptRefiner] LLM refinement failed: ${errorMsg}, returning original`);
43
+ throw error;
44
+ }
45
+ }
46
+ async refineWithTimeout(prompt) {
47
+ const timeoutPromise = new Promise((_, reject) => {
48
+ setTimeout(() => {
49
+ reject(new Error("Prompt refinement timeout"));
50
+ }, REFINER_TIMEOUT_MS);
51
+ });
52
+ const refinePromise = this.callLLM(prompt);
53
+ return Promise.race([refinePromise, timeoutPromise]);
54
+ }
55
+ async callLLM(prompt) {
56
+ const client = (0, openAIRouterLLM_1.getOpenAIClient)(this.model);
57
+ const response = await client.chat.completions.create({
58
+ model: this.model,
59
+ messages: [
60
+ {
61
+ role: "system",
62
+ content: REFINEMENT_SYSTEM_PROMPT,
63
+ },
64
+ {
65
+ role: "user",
66
+ content: `Please refine this system prompt:\n\n${prompt}`,
67
+ },
68
+ ],
69
+ max_tokens: REFINER_MAX_TOKENS,
70
+ temperature: REFINER_TEMPERATURE,
71
+ });
72
+ const refined = response.choices[0]?.message?.content?.trim();
73
+ if (!refined) {
74
+ throw new Error("Empty response from LLM");
75
+ }
76
+ return refined;
77
+ }
78
+ }
79
+ exports.LLMPromptRefiner = LLMPromptRefiner;
80
+ let promptRefinerInstance;
81
+ function getPromptRefiner(model) {
82
+ if (!promptRefinerInstance) {
83
+ promptRefinerInstance = new LLMPromptRefiner(model);
84
+ }
85
+ return promptRefinerInstance;
86
+ }
@@ -47,8 +47,12 @@ const database_1 = require("../data/database");
47
47
  const errors_1 = require("../protocol/errors");
48
48
  const sessionRegistry_1 = require("./sessionRegistry");
49
49
  const DEVELOPMENT = process.env.DEVELOPMENT === "1";
50
+ const TEST = process.env.TEST === "1";
50
51
  dotenv.config();
51
- if (DEVELOPMENT) {
52
+ if (TEST) {
53
+ dotenv.config({ path: ".env.test" });
54
+ }
55
+ else if (DEVELOPMENT) {
52
56
  dotenv.config({ path: ".env.development" });
53
57
  }
54
58
  const logger = (0, sdk_1.getLogger)();