agent-world 0.11.1 → 0.12.0

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 (267) hide show
  1. package/README.md +17 -7
  2. package/dist/cli/commands.d.ts +109 -0
  3. package/dist/cli/commands.js +2024 -0
  4. package/dist/cli/display.d.ts +124 -0
  5. package/dist/cli/display.js +381 -0
  6. package/dist/cli/hitl.d.ts +33 -0
  7. package/dist/cli/hitl.js +81 -0
  8. package/dist/cli/index.d.ts +2 -0
  9. package/dist/cli/stream.d.ts +41 -0
  10. package/dist/cli/stream.js +222 -0
  11. package/dist/core/activity-tracker.d.ts +16 -0
  12. package/dist/core/activity-tracker.d.ts.map +1 -0
  13. package/dist/core/activity-tracker.js +91 -0
  14. package/dist/core/activity-tracker.js.map +1 -0
  15. package/dist/core/ai-commands.d.ts +16 -0
  16. package/dist/core/ai-commands.d.ts.map +1 -0
  17. package/dist/core/ai-commands.js +24 -0
  18. package/dist/core/ai-commands.js.map +1 -0
  19. package/dist/core/ai-sdk-patch.d.ts +24 -0
  20. package/dist/core/ai-sdk-patch.d.ts.map +1 -0
  21. package/dist/core/ai-sdk-patch.js +169 -0
  22. package/dist/core/ai-sdk-patch.js.map +1 -0
  23. package/dist/core/anthropic-direct.d.ts +52 -0
  24. package/dist/core/anthropic-direct.d.ts.map +1 -0
  25. package/dist/core/anthropic-direct.js +301 -0
  26. package/dist/core/anthropic-direct.js.map +1 -0
  27. package/dist/core/approval-cache.d.ts +104 -0
  28. package/dist/core/approval-cache.d.ts.map +1 -0
  29. package/dist/core/approval-cache.js +150 -0
  30. package/dist/core/approval-cache.js.map +1 -0
  31. package/dist/core/chat-constants.d.ts +20 -0
  32. package/dist/core/chat-constants.d.ts.map +1 -0
  33. package/dist/core/chat-constants.js +22 -0
  34. package/dist/core/chat-constants.js.map +1 -0
  35. package/dist/core/create-agent-tool.d.ts +66 -0
  36. package/dist/core/create-agent-tool.d.ts.map +1 -0
  37. package/dist/core/create-agent-tool.js +212 -0
  38. package/dist/core/create-agent-tool.js.map +1 -0
  39. package/dist/core/events/approval-checker.d.ts +61 -0
  40. package/dist/core/events/approval-checker.d.ts.map +1 -0
  41. package/dist/core/events/approval-checker.js +226 -0
  42. package/dist/core/events/approval-checker.js.map +1 -0
  43. package/dist/core/events/index.d.ts +25 -0
  44. package/dist/core/events/index.d.ts.map +1 -0
  45. package/dist/core/events/index.js +30 -0
  46. package/dist/core/events/index.js.map +1 -0
  47. package/dist/core/events/memory-manager.d.ts +73 -0
  48. package/dist/core/events/memory-manager.d.ts.map +1 -0
  49. package/dist/core/events/memory-manager.js +1218 -0
  50. package/dist/core/events/memory-manager.js.map +1 -0
  51. package/dist/core/events/mention-logic.d.ts +39 -0
  52. package/dist/core/events/mention-logic.d.ts.map +1 -0
  53. package/dist/core/events/mention-logic.js +163 -0
  54. package/dist/core/events/mention-logic.js.map +1 -0
  55. package/dist/core/events/orchestrator.d.ts +69 -0
  56. package/dist/core/events/orchestrator.d.ts.map +1 -0
  57. package/dist/core/events/orchestrator.js +883 -0
  58. package/dist/core/events/orchestrator.js.map +1 -0
  59. package/dist/core/events/persistence.d.ts +41 -0
  60. package/dist/core/events/persistence.d.ts.map +1 -0
  61. package/dist/core/events/persistence.js +296 -0
  62. package/dist/core/events/persistence.js.map +1 -0
  63. package/dist/core/events/publishers.d.ts +81 -0
  64. package/dist/core/events/publishers.d.ts.map +1 -0
  65. package/dist/core/events/publishers.js +272 -0
  66. package/dist/core/events/publishers.js.map +1 -0
  67. package/dist/core/events/subscribers.d.ts +45 -0
  68. package/dist/core/events/subscribers.d.ts.map +1 -0
  69. package/dist/core/events/subscribers.js +288 -0
  70. package/dist/core/events/subscribers.js.map +1 -0
  71. package/dist/core/events/tool-bridge-logging.d.ts +28 -0
  72. package/dist/core/events/tool-bridge-logging.d.ts.map +1 -0
  73. package/dist/core/events/tool-bridge-logging.js +94 -0
  74. package/dist/core/events/tool-bridge-logging.js.map +1 -0
  75. package/dist/core/events-metadata.d.ts +72 -0
  76. package/dist/core/events-metadata.d.ts.map +1 -0
  77. package/dist/core/events-metadata.js +167 -0
  78. package/dist/core/events-metadata.js.map +1 -0
  79. package/dist/core/events.d.ts +186 -0
  80. package/dist/core/events.d.ts.map +1 -0
  81. package/dist/core/events.js +1248 -0
  82. package/dist/core/events.js.map +1 -0
  83. package/dist/core/export.d.ts +106 -0
  84. package/dist/core/export.d.ts.map +1 -0
  85. package/dist/core/export.js +705 -0
  86. package/dist/core/export.js.map +1 -0
  87. package/dist/core/file-tools.d.ts +114 -0
  88. package/dist/core/file-tools.d.ts.map +1 -0
  89. package/dist/core/file-tools.js +370 -0
  90. package/dist/core/file-tools.js.map +1 -0
  91. package/dist/core/google-direct.d.ts +58 -0
  92. package/dist/core/google-direct.d.ts.map +1 -0
  93. package/dist/core/google-direct.js +298 -0
  94. package/dist/core/google-direct.js.map +1 -0
  95. package/dist/core/hitl.d.ts +54 -0
  96. package/dist/core/hitl.d.ts.map +1 -0
  97. package/dist/core/hitl.js +153 -0
  98. package/dist/core/hitl.js.map +1 -0
  99. package/dist/core/index.d.ts +59 -0
  100. package/dist/core/index.d.ts.map +1 -0
  101. package/dist/core/index.js +70 -0
  102. package/dist/core/index.js.map +1 -0
  103. package/dist/core/llm-config.d.ts +128 -0
  104. package/dist/core/llm-config.d.ts.map +1 -0
  105. package/dist/core/llm-config.js +164 -0
  106. package/dist/core/llm-config.js.map +1 -0
  107. package/dist/core/llm-manager.d.ts +163 -0
  108. package/dist/core/llm-manager.d.ts.map +1 -0
  109. package/dist/core/llm-manager.js +669 -0
  110. package/dist/core/llm-manager.js.map +1 -0
  111. package/dist/core/load-skill-tool.d.ts +55 -0
  112. package/dist/core/load-skill-tool.d.ts.map +1 -0
  113. package/dist/core/load-skill-tool.js +468 -0
  114. package/dist/core/load-skill-tool.js.map +1 -0
  115. package/dist/core/logger.d.ts +88 -0
  116. package/dist/core/logger.d.ts.map +1 -0
  117. package/dist/core/logger.js +358 -0
  118. package/dist/core/logger.js.map +1 -0
  119. package/dist/core/managers.d.ts +131 -0
  120. package/dist/core/managers.d.ts.map +1 -0
  121. package/dist/core/managers.js +1223 -0
  122. package/dist/core/managers.js.map +1 -0
  123. package/dist/core/mcp-server-registry.d.ts +304 -0
  124. package/dist/core/mcp-server-registry.d.ts.map +1 -0
  125. package/dist/core/mcp-server-registry.js +1769 -0
  126. package/dist/core/mcp-server-registry.js.map +1 -0
  127. package/dist/core/mcp-tools.d.ts +56 -0
  128. package/dist/core/mcp-tools.d.ts.map +1 -0
  129. package/dist/core/mcp-tools.js +186 -0
  130. package/dist/core/mcp-tools.js.map +1 -0
  131. package/dist/core/message-prep.d.ts +81 -0
  132. package/dist/core/message-prep.d.ts.map +1 -0
  133. package/dist/core/message-prep.js +223 -0
  134. package/dist/core/message-prep.js.map +1 -0
  135. package/dist/core/message-processing-control.d.ts +54 -0
  136. package/dist/core/message-processing-control.d.ts.map +1 -0
  137. package/dist/core/message-processing-control.js +139 -0
  138. package/dist/core/message-processing-control.js.map +1 -0
  139. package/dist/core/openai-direct.d.ts +80 -0
  140. package/dist/core/openai-direct.d.ts.map +1 -0
  141. package/dist/core/openai-direct.js +374 -0
  142. package/dist/core/openai-direct.js.map +1 -0
  143. package/dist/core/shell-cmd-tool.d.ts +235 -0
  144. package/dist/core/shell-cmd-tool.d.ts.map +1 -0
  145. package/dist/core/shell-cmd-tool.js +1157 -0
  146. package/dist/core/shell-cmd-tool.js.map +1 -0
  147. package/dist/core/shell-process-registry.d.ts +88 -0
  148. package/dist/core/shell-process-registry.d.ts.map +1 -0
  149. package/dist/core/shell-process-registry.js +309 -0
  150. package/dist/core/shell-process-registry.js.map +1 -0
  151. package/dist/core/skill-registry.d.ts +75 -0
  152. package/dist/core/skill-registry.d.ts.map +1 -0
  153. package/dist/core/skill-registry.js +369 -0
  154. package/dist/core/skill-registry.js.map +1 -0
  155. package/dist/core/skill-script-runner.d.ts +89 -0
  156. package/dist/core/skill-script-runner.d.ts.map +1 -0
  157. package/dist/core/skill-script-runner.js +274 -0
  158. package/dist/core/skill-script-runner.js.map +1 -0
  159. package/dist/core/skill-selector.d.ts +65 -0
  160. package/dist/core/skill-selector.d.ts.map +1 -0
  161. package/dist/core/skill-selector.js +190 -0
  162. package/dist/core/skill-selector.js.map +1 -0
  163. package/dist/core/skill-settings.d.ts +20 -0
  164. package/dist/core/skill-settings.d.ts.map +1 -0
  165. package/dist/core/skill-settings.js +40 -0
  166. package/dist/core/skill-settings.js.map +1 -0
  167. package/dist/core/storage/agent-storage.d.ts +134 -0
  168. package/dist/core/storage/agent-storage.d.ts.map +1 -0
  169. package/dist/core/storage/agent-storage.js +498 -0
  170. package/dist/core/storage/agent-storage.js.map +1 -0
  171. package/dist/core/storage/eventStorage/fileEventStorage.d.ts +100 -0
  172. package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -0
  173. package/dist/core/storage/eventStorage/fileEventStorage.js +494 -0
  174. package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -0
  175. package/dist/core/storage/eventStorage/index.d.ts +31 -0
  176. package/dist/core/storage/eventStorage/index.d.ts.map +1 -0
  177. package/dist/core/storage/eventStorage/index.js +31 -0
  178. package/dist/core/storage/eventStorage/index.js.map +1 -0
  179. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +87 -0
  180. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -0
  181. package/dist/core/storage/eventStorage/memoryEventStorage.js +244 -0
  182. package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -0
  183. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +45 -0
  184. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -0
  185. package/dist/core/storage/eventStorage/sqliteEventStorage.js +301 -0
  186. package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -0
  187. package/dist/core/storage/eventStorage/types.d.ts +142 -0
  188. package/dist/core/storage/eventStorage/types.d.ts.map +1 -0
  189. package/dist/core/storage/eventStorage/types.js +43 -0
  190. package/dist/core/storage/eventStorage/types.js.map +1 -0
  191. package/dist/core/storage/eventStorage/validation.d.ts +30 -0
  192. package/dist/core/storage/eventStorage/validation.d.ts.map +1 -0
  193. package/dist/core/storage/eventStorage/validation.js +68 -0
  194. package/dist/core/storage/eventStorage/validation.js.map +1 -0
  195. package/dist/core/storage/legacy-migrations.d.ts +45 -0
  196. package/dist/core/storage/legacy-migrations.d.ts.map +1 -0
  197. package/dist/core/storage/legacy-migrations.js +295 -0
  198. package/dist/core/storage/legacy-migrations.js.map +1 -0
  199. package/dist/core/storage/memory-storage.d.ts +105 -0
  200. package/dist/core/storage/memory-storage.d.ts.map +1 -0
  201. package/dist/core/storage/memory-storage.js +415 -0
  202. package/dist/core/storage/memory-storage.js.map +1 -0
  203. package/dist/core/storage/migration-runner.d.ts +96 -0
  204. package/dist/core/storage/migration-runner.d.ts.map +1 -0
  205. package/dist/core/storage/migration-runner.js +306 -0
  206. package/dist/core/storage/migration-runner.js.map +1 -0
  207. package/dist/core/storage/queue-storage.d.ts +147 -0
  208. package/dist/core/storage/queue-storage.d.ts.map +1 -0
  209. package/dist/core/storage/queue-storage.js +290 -0
  210. package/dist/core/storage/queue-storage.js.map +1 -0
  211. package/dist/core/storage/skill-storage.d.ts +136 -0
  212. package/dist/core/storage/skill-storage.d.ts.map +1 -0
  213. package/dist/core/storage/skill-storage.js +474 -0
  214. package/dist/core/storage/skill-storage.js.map +1 -0
  215. package/dist/core/storage/sqlite-schema.d.ts +95 -0
  216. package/dist/core/storage/sqlite-schema.d.ts.map +1 -0
  217. package/dist/core/storage/sqlite-schema.js +156 -0
  218. package/dist/core/storage/sqlite-schema.js.map +1 -0
  219. package/dist/core/storage/sqlite-storage.d.ts +146 -0
  220. package/dist/core/storage/sqlite-storage.d.ts.map +1 -0
  221. package/dist/core/storage/sqlite-storage.js +709 -0
  222. package/dist/core/storage/sqlite-storage.js.map +1 -0
  223. package/dist/core/storage/storage-factory.d.ts +61 -0
  224. package/dist/core/storage/storage-factory.d.ts.map +1 -0
  225. package/dist/core/storage/storage-factory.js +794 -0
  226. package/dist/core/storage/storage-factory.js.map +1 -0
  227. package/dist/core/storage/validation.d.ts +36 -0
  228. package/dist/core/storage/validation.d.ts.map +1 -0
  229. package/dist/core/storage/validation.js +79 -0
  230. package/dist/core/storage/validation.js.map +1 -0
  231. package/dist/core/storage/world-storage.d.ts +114 -0
  232. package/dist/core/storage/world-storage.d.ts.map +1 -0
  233. package/dist/core/storage/world-storage.js +378 -0
  234. package/dist/core/storage/world-storage.js.map +1 -0
  235. package/dist/core/subscription.d.ts +43 -0
  236. package/dist/core/subscription.d.ts.map +1 -0
  237. package/dist/core/subscription.js +227 -0
  238. package/dist/core/subscription.js.map +1 -0
  239. package/dist/core/tool-utils.d.ts +80 -0
  240. package/dist/core/tool-utils.d.ts.map +1 -0
  241. package/dist/core/tool-utils.js +273 -0
  242. package/dist/core/tool-utils.js.map +1 -0
  243. package/dist/core/types.d.ts +595 -0
  244. package/dist/core/types.d.ts.map +1 -0
  245. package/dist/core/types.js +158 -0
  246. package/dist/core/types.js.map +1 -0
  247. package/dist/core/utils.d.ts +138 -0
  248. package/dist/core/utils.d.ts.map +1 -0
  249. package/dist/core/utils.js +478 -0
  250. package/dist/core/utils.js.map +1 -0
  251. package/dist/core/world-class.d.ts +43 -0
  252. package/dist/core/world-class.d.ts.map +1 -0
  253. package/dist/core/world-class.js +90 -0
  254. package/dist/core/world-class.js.map +1 -0
  255. package/dist/index.d.ts +18 -0
  256. package/dist/public/assets/agent-sprites-DJFgj-zP.png +0 -0
  257. package/dist/public/assets/border-KHK37r8y.svg +83 -0
  258. package/dist/public/assets/index-C9kPXL6G.css +1 -0
  259. package/dist/public/assets/index-DOQEHGWt.js +96 -0
  260. package/dist/public/index.html +21 -0
  261. package/dist/server/api.d.ts +2 -0
  262. package/dist/server/api.js +1124 -0
  263. package/dist/server/index.d.ts +29 -0
  264. package/dist/server/sse-handler.d.ts +62 -0
  265. package/dist/server/sse-handler.js +234 -0
  266. package/package.json +15 -3
  267. package/scripts/launch-electron.js +0 -58
@@ -0,0 +1,883 @@
1
+ /**
2
+ * Orchestrator Module
3
+ *
4
+ * Coordinates agent message processing, response generation, and turn management.
5
+ * Provides high-level orchestration functions for agent behavior and LLM interaction.
6
+ *
7
+ * Features:
8
+ * - Process agent messages with LLM response generation
9
+ * - Determine if agent should respond based on mentions and turn limits
10
+ * - Reset LLM call count for new conversation turns
11
+ * - Turn limit enforcement with automatic handoff to human
12
+ * - Enhanced tool call message formatting with parameters display
13
+ * - SSE tool call data for web clients (streaming mode)
14
+ * - Robust JSON parsing with detailed error logging for malformed tool arguments
15
+ *
16
+ * Implementation:
17
+ * - Tool calls follow standard tool execution and LLM continuation flow
18
+ * - Tool call messages show up to 3 parameters with truncation for readability
19
+ * * Single tool: "Calling tool: shell_cmd (command: "ls", directory: "./")"
20
+ * * Multiple tools: "Calling 2 tools: shell_cmd, read_file"
21
+ * - In streaming mode, formatted tool call content with tool_calls data is sent via SSE
22
+ * * Ensures web/Electron clients display complete tool call info with parameters
23
+ * * Prevents incomplete display (e.g., "Calling tool: shell_cmd" without params)
24
+ * - JSON parse errors include detailed logging (preview, length, error position)
25
+ * * Helps diagnose LLM-generated malformed JSON in tool arguments
26
+ * - JSON sanitization attempts to fix common LLM JSON issues before parsing
27
+ * * Handles unterminated strings, trailing commas, truncation, unmatched braces
28
+ * * Tries progressive fixes: trailing commas → close strings → truncate to valid
29
+ *
30
+ * Dependencies (Layer 5):
31
+ * - types.ts (Layer 1)
32
+ * - mention-logic.ts (Layer 2)
33
+ * - publishers.ts (Layer 3)
34
+ * - memory-manager.ts (Layer 4)
35
+ * - utils.ts, logger.ts
36
+ * - llm-manager.ts (runtime)
37
+ * - storage (runtime)
38
+ *
39
+ * Changes:
40
+ * - 2026-02-16: Added `LOG_LLM_TOOL_BRIDGE` gate for LLM↔tool console bridge logs.
41
+ * - 2026-02-16: Added explicit console debug logs for LLM↔tool request/result/error handoff payloads.
42
+ * - 2026-02-14: Shell tool trusted cwd fallback now uses core default working directory (user home) when world `working_directory` is missing.
43
+ * - 2026-02-13: Fixed shell_cmd mismatch handling by validating path targets in command parameters (e.g. `~/`) against world working_directory before execution.
44
+ * - 2026-02-13: Added hard-stop guard for shell_cmd directory mismatches (LLM-requested `directory` must match world `working_directory`).
45
+ * - 2026-02-13: Enriched displayed `shell_cmd` tool-call arguments with trusted world cwd so UI tool-call messages show the actual execution directory.
46
+ * - 2026-02-13: Forced shell tool cwd to trusted world `working_directory`; mismatched LLM `directory` requests now stop execution with explicit error.
47
+ * - 2026-02-13: Added chat-scoped `tool-start/tool-result/tool-error` event publishing so renderer session state stays accurate during tool execution.
48
+ * - 2026-02-13: Added session processing-handle guards so stop requests abort active tool/continuation flow without spawning new LLM work.
49
+ * - 2026-02-13: Propagated explicit `chatId` and stop abort-signal context through LLM/tool execution paths.
50
+ * - 2026-02-11: Enhanced JSON parse error logging with rawArgs preview and suffix
51
+ * - 2026-02-11: Fixed tool call display in Electron/web - send formatted content with tool_calls via SSE
52
+ * - 2026-02-11: Fixed OpenAI tool-call protocol integrity.
53
+ * - Persist only the first executable tool_call when agent execution is single-call.
54
+ * - Route JSON parse/tool lookup failures through tool-error persistence so each persisted tool_call gets a matching tool message.
55
+ * - 2026-02-10: Upgrade generic LLM tool-call text (e.g., "Calling tool: shell_cmd") to include parsed parameters
56
+ * - 2026-02-10: Made tool-call argument parsing more robust for both JSON strings and object-like payloads
57
+ * - 2026-02-08: Enhanced tool call message formatting to include parameters
58
+ * - 2025-11-09: Extracted from events.ts for modular architecture
59
+ */
60
+ import { SenderType } from '../types.js';
61
+ import { generateId, determineSenderType, prepareMessagesForLLM, getWorldTurnLimit, extractMentions, extractParagraphBeginningMentions, getDefaultWorkingDirectory, getEnvValueFromText } from '../utils.js';
62
+ import { createCategoryLogger } from '../logger.js';
63
+ import { beginWorldActivity } from '../activity-tracker.js';
64
+ import { createStorageWithWrappers } from '../storage/storage-factory.js';
65
+ import { publishMessage, publishSSE, publishEvent, publishToolEvent, isStreamingEnabled } from './publishers.js';
66
+ import { handleTextResponse } from './memory-manager.js';
67
+ import { validateShellDirectoryRequest, validateShellCommandScope } from '../shell-cmd-tool.js';
68
+ import { beginChatMessageProcessing, isMessageProcessingCanceledError, throwIfMessageProcessingStopped } from '../message-processing-control.js';
69
+ import { logToolBridge, getToolResultPreview } from './tool-bridge-logging.js';
70
+ const loggerAgent = createCategoryLogger('agent');
71
+ const loggerResponse = createCategoryLogger('response');
72
+ const loggerTurnLimit = createCategoryLogger('turnlimit');
73
+ // Storage wrapper instance - initialized lazily
74
+ let storageWrappers = null;
75
+ async function getStorageWrappers() {
76
+ if (!storageWrappers) {
77
+ storageWrappers = await createStorageWithWrappers();
78
+ }
79
+ return storageWrappers;
80
+ }
81
+ /**
82
+ * Sanitize and fix common JSON issues from LLM-generated tool arguments
83
+ * Handles: unterminated strings, unescaped quotes, trailing commas, truncation
84
+ */
85
+ function sanitizeAndParseJSON(jsonString) {
86
+ if (!jsonString || jsonString.trim() === '') {
87
+ return {};
88
+ }
89
+ // Try parsing as-is first
90
+ try {
91
+ return JSON.parse(jsonString);
92
+ }
93
+ catch (firstError) {
94
+ loggerAgent.debug('Initial JSON parse failed, attempting sanitization', {
95
+ error: firstError instanceof Error ? firstError.message : String(firstError)
96
+ });
97
+ }
98
+ let sanitized = jsonString;
99
+ // Fix 1: Remove trailing commas (common LLM mistake)
100
+ sanitized = sanitized.replace(/,(\s*[}\]])/g, '$1');
101
+ // Fix 2: Handle unterminated strings at end (truncation)
102
+ // If the error is "Unterminated string", try to close it
103
+ const unterminatedMatch = sanitized.match(/"[^"]*$/);
104
+ if (unterminatedMatch) {
105
+ loggerAgent.debug('Detected unterminated string at end, attempting to close');
106
+ sanitized = sanitized + '"';
107
+ // Check if we need to close open braces/brackets
108
+ const openBraces = (sanitized.match(/{/g) || []).length;
109
+ const closeBraces = (sanitized.match(/}/g) || []).length;
110
+ const openBrackets = (sanitized.match(/\[/g) || []).length;
111
+ const closeBrackets = (sanitized.match(/\]/g) || []).length;
112
+ // Close any unclosed arrays
113
+ for (let i = 0; i < openBrackets - closeBrackets; i++) {
114
+ sanitized += ']';
115
+ }
116
+ // Close any unclosed objects
117
+ for (let i = 0; i < openBraces - closeBraces; i++) {
118
+ sanitized += '}';
119
+ }
120
+ }
121
+ // Try parsing sanitized version
122
+ try {
123
+ return JSON.parse(sanitized);
124
+ }
125
+ catch (secondError) {
126
+ loggerAgent.debug('Sanitization failed, trying more aggressive fixes');
127
+ }
128
+ // Fix 3: Try to extract valid JSON from the beginning if there's garbage at the end
129
+ // Find the last valid closing brace/bracket
130
+ let lastValidIndex = -1;
131
+ let depth = 0;
132
+ let inString = false;
133
+ let escaped = false;
134
+ for (let i = 0; i < sanitized.length; i++) {
135
+ const char = sanitized[i];
136
+ if (escaped) {
137
+ escaped = false;
138
+ continue;
139
+ }
140
+ if (char === '\\') {
141
+ escaped = true;
142
+ continue;
143
+ }
144
+ if (char === '"' && !escaped) {
145
+ inString = !inString;
146
+ continue;
147
+ }
148
+ if (!inString) {
149
+ if (char === '{' || char === '[') {
150
+ depth++;
151
+ }
152
+ else if (char === '}' || char === ']') {
153
+ depth--;
154
+ if (depth === 0) {
155
+ lastValidIndex = i;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ if (lastValidIndex > 0) {
161
+ const truncated = sanitized.substring(0, lastValidIndex + 1);
162
+ try {
163
+ loggerAgent.debug('Attempting to parse truncated JSON', {
164
+ originalLength: sanitized.length,
165
+ truncatedLength: truncated.length
166
+ });
167
+ return JSON.parse(truncated);
168
+ }
169
+ catch (truncError) {
170
+ loggerAgent.debug('Truncated parse also failed');
171
+ }
172
+ }
173
+ // If all else fails, throw the original error with the sanitized string
174
+ throw new Error(`Unable to parse or sanitize JSON. Original length: ${jsonString.length}, Sanitized length: ${sanitized.length}`);
175
+ }
176
+ /**
177
+ * Format tool calls with their parameters for display
178
+ * @param toolCalls - Array of tool calls from LLM response
179
+ * @returns Formatted message string showing tool names and parameters
180
+ */
181
+ function parseToolCallArgs(rawArguments) {
182
+ if (rawArguments == null)
183
+ return {};
184
+ if (typeof rawArguments === 'object' && !Array.isArray(rawArguments)) {
185
+ return rawArguments;
186
+ }
187
+ if (typeof rawArguments !== 'string')
188
+ return null;
189
+ const trimmed = rawArguments.trim();
190
+ if (!trimmed)
191
+ return {};
192
+ const parsed = JSON.parse(trimmed);
193
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
194
+ return parsed;
195
+ }
196
+ return null;
197
+ }
198
+ function shouldUpgradeToolCallMessage(content, toolCalls) {
199
+ if (!content.trim())
200
+ return true;
201
+ if (!toolCalls || toolCalls.length === 0)
202
+ return false;
203
+ const normalizedContent = content.trim().toLowerCase().replace(/\s+/g, ' ');
204
+ const genericCallingToolPattern = /^calling tool(?::|\s)/i;
205
+ if (genericCallingToolPattern.test(content) && !content.includes('(')) {
206
+ return true;
207
+ }
208
+ const firstToolName = String(toolCalls[0]?.function?.name || '').trim().toLowerCase();
209
+ if (!firstToolName)
210
+ return false;
211
+ return normalizedContent === `calling tool: ${firstToolName}` ||
212
+ normalizedContent === `calling tool ${firstToolName}` ||
213
+ normalizedContent === `calling tool: ${firstToolName}.` ||
214
+ normalizedContent === `calling tool ${firstToolName}.`;
215
+ }
216
+ function formatToolCallsMessage(toolCalls) {
217
+ const toolCount = toolCalls.length;
218
+ if (toolCount === 1) {
219
+ const tc = toolCalls[0];
220
+ const toolName = tc.function.name;
221
+ try {
222
+ const args = parseToolCallArgs(tc.function.arguments);
223
+ if (!args) {
224
+ return `Calling tool: ${toolName}`;
225
+ }
226
+ const paramParts = [];
227
+ // Format parameters - show up to 3 key parameters
228
+ const keys = Object.keys(args).slice(0, 3);
229
+ for (const key of keys) {
230
+ let value = args[key];
231
+ // Truncate long values
232
+ if (typeof value === 'string' && value.length > 50) {
233
+ value = value.substring(0, 47) + '...';
234
+ }
235
+ else if (value !== null && typeof value === 'object') {
236
+ const serialized = JSON.stringify(value);
237
+ value = serialized ?? String(value);
238
+ if (typeof value === 'string' && value.length > 50) {
239
+ value = value.substring(0, 47) + '...';
240
+ }
241
+ }
242
+ paramParts.push(`${key}: ${JSON.stringify(value)}`);
243
+ }
244
+ if (Object.keys(args).length > 3) {
245
+ paramParts.push('...');
246
+ }
247
+ return paramParts.length > 0
248
+ ? `Calling tool: ${toolName} (${paramParts.join(', ')})`
249
+ : `Calling tool: ${toolName}`;
250
+ }
251
+ catch {
252
+ // If arguments can't be parsed, just show the tool name
253
+ return `Calling tool: ${toolName}`;
254
+ }
255
+ }
256
+ else {
257
+ // Multiple tools - just list the names
258
+ const toolNames = toolCalls.map(tc => tc.function.name).join(', ');
259
+ return `Calling ${toolCount} tools: ${toolNames}`;
260
+ }
261
+ }
262
+ function withTrustedShellDirectory(toolCalls, trustedWorkingDirectory) {
263
+ return toolCalls.map((toolCall) => {
264
+ if (toolCall.function?.name !== 'shell_cmd') {
265
+ return toolCall;
266
+ }
267
+ try {
268
+ const args = parseToolCallArgs(toolCall.function.arguments);
269
+ if (!args) {
270
+ return toolCall;
271
+ }
272
+ const orderedArgs = {};
273
+ if (Object.prototype.hasOwnProperty.call(args, 'command')) {
274
+ orderedArgs.command = args.command;
275
+ }
276
+ if (Object.prototype.hasOwnProperty.call(args, 'parameters')) {
277
+ orderedArgs.parameters = args.parameters;
278
+ }
279
+ const requestedDirectory = typeof args.directory === 'string' ? args.directory.trim() : '';
280
+ if (requestedDirectory) {
281
+ // Preserve what the model requested; mismatch handling happens at execution guard.
282
+ orderedArgs.directory = args.directory;
283
+ }
284
+ orderedArgs.workingDirectory = trustedWorkingDirectory;
285
+ for (const [key, value] of Object.entries(args)) {
286
+ if (key === 'command' || key === 'parameters' || key === 'directory' || key === 'workingDirectory')
287
+ continue;
288
+ orderedArgs[key] = value;
289
+ }
290
+ return {
291
+ ...toolCall,
292
+ function: {
293
+ ...toolCall.function,
294
+ arguments: JSON.stringify(orderedArgs)
295
+ }
296
+ };
297
+ }
298
+ catch {
299
+ return toolCall;
300
+ }
301
+ });
302
+ }
303
+ /**
304
+ * Agent message processing with LLM response generation and auto-mention logic
305
+ */
306
+ export async function processAgentMessage(world, agent, messageEvent) {
307
+ const completeActivity = beginWorldActivity(world, `agent:${agent.id}`);
308
+ let processingHandle = null;
309
+ try {
310
+ // Derive target chatId from the originating message event for concurrency-safe processing
311
+ // This ensures processing stays bound to the originating session even if world.currentChatId changes
312
+ const targetChatId = messageEvent.chatId ?? world.currentChatId ?? null;
313
+ if (targetChatId) {
314
+ processingHandle = beginChatMessageProcessing(world.id, targetChatId);
315
+ }
316
+ throwIfMessageProcessingStopped(processingHandle?.signal);
317
+ // Prepare messages for LLM - loads fresh data from storage
318
+ // The user message is already saved in subscribeAgentToMessages, so it's in storage
319
+ const filteredMessages = await prepareMessagesForLLM(world.id, agent, targetChatId);
320
+ throwIfMessageProcessingStopped(processingHandle?.signal);
321
+ // Log prepared messages for debugging
322
+ loggerAgent.debug('Prepared messages for LLM', {
323
+ agentId: agent.id,
324
+ chatId: targetChatId,
325
+ totalMessages: filteredMessages.length,
326
+ systemMessages: filteredMessages.filter(m => m.role === 'system').length,
327
+ userMessages: filteredMessages.filter(m => m.role === 'user').length,
328
+ assistantMessages: filteredMessages.filter(m => m.role === 'assistant').length,
329
+ toolMessages: filteredMessages.filter(m => m.role === 'tool').length
330
+ });
331
+ // Increment LLM call count and save agent state
332
+ agent.llmCallCount++;
333
+ agent.lastLLMCall = new Date();
334
+ try {
335
+ const storage = await getStorageWrappers();
336
+ await storage.saveAgent(world.id, agent);
337
+ }
338
+ catch (error) {
339
+ loggerAgent.error('Failed to auto-save agent after LLM call increment', { agentId: agent.id, error: error instanceof Error ? error.message : error });
340
+ }
341
+ // Generate LLM response (streaming or non-streaming) - now returns LLMResponse
342
+ let llmResponse;
343
+ let messageId;
344
+ // Create a wrapped publishSSE that captures the targetChatId for concurrency-safe event routing
345
+ // This ensures SSE events stay bound to the originating session even during concurrent processing
346
+ const publishSSEWithChatId = (w, data) => {
347
+ publishSSE(w, { ...data, chatId: targetChatId });
348
+ };
349
+ if (isStreamingEnabled()) {
350
+ const { streamAgentResponse } = await import('../llm-manager.js');
351
+ const result = await streamAgentResponse(world, agent, filteredMessages, publishSSEWithChatId, targetChatId ?? null, processingHandle?.signal);
352
+ llmResponse = result.response;
353
+ messageId = result.messageId;
354
+ }
355
+ else {
356
+ const { generateAgentResponse } = await import('../llm-manager.js');
357
+ const result = await generateAgentResponse(world, agent, filteredMessages, undefined, false, targetChatId ?? null, processingHandle?.signal);
358
+ llmResponse = result.response;
359
+ messageId = result.messageId;
360
+ }
361
+ throwIfMessageProcessingStopped(processingHandle?.signal);
362
+ loggerAgent.debug('LLM response received', {
363
+ agentId: agent.id,
364
+ responseType: llmResponse.type,
365
+ hasContent: !!llmResponse.content,
366
+ hasToolCalls: llmResponse.type === 'tool_calls',
367
+ toolCallCount: llmResponse.tool_calls?.length || 0
368
+ });
369
+ // Handle text responses
370
+ if (llmResponse.type === 'text') {
371
+ const responseText = llmResponse.content || '';
372
+ if (!responseText) {
373
+ loggerAgent.debug('LLM text response is empty', { agentId: agent.id });
374
+ return;
375
+ }
376
+ // Process text response (existing logic below)
377
+ // Pass targetChatId explicitly for concurrency-safe processing
378
+ throwIfMessageProcessingStopped(processingHandle?.signal);
379
+ await handleTextResponse(world, agent, responseText, messageId, messageEvent, targetChatId);
380
+ return;
381
+ }
382
+ // Handle tool calls - Execute tools through unified execution path
383
+ // This works for both streaming and non-streaming modes
384
+ if (llmResponse.type === 'tool_calls') {
385
+ const returnedToolCalls = llmResponse.tool_calls || [];
386
+ const executableToolCalls = returnedToolCalls.slice(0, 1);
387
+ const trustedWorkingDirectory = String(getEnvValueFromText(world.variables, 'working_directory') || getDefaultWorkingDirectory()).trim() || getDefaultWorkingDirectory();
388
+ const displayToolCalls = withTrustedShellDirectory(executableToolCalls, trustedWorkingDirectory);
389
+ if (returnedToolCalls.length > executableToolCalls.length) {
390
+ loggerAgent.warn('LLM returned multiple tool calls; processing first call only', {
391
+ agentId: agent.id,
392
+ returnedToolCallCount: returnedToolCalls.length,
393
+ processedToolCallIds: executableToolCalls.map(tc => tc.id),
394
+ droppedToolCallIds: returnedToolCalls.slice(1).map(tc => tc.id)
395
+ });
396
+ }
397
+ loggerAgent.debug('LLM returned tool calls', {
398
+ agentId: agent.id,
399
+ toolCallCount: executableToolCalls.length,
400
+ toolNames: executableToolCalls.map(tc => tc.function.name)
401
+ });
402
+ // Save assistant message with tool_calls to agent memory FIRST
403
+ // This ensures the tool call is in memory before execution
404
+ // Format meaningful content for tool calls if LLM didn't provide text
405
+ let messageContent = llmResponse.content || '';
406
+ if (displayToolCalls.length > 0 &&
407
+ shouldUpgradeToolCallMessage(messageContent, displayToolCalls)) {
408
+ messageContent = formatToolCallsMessage(displayToolCalls);
409
+ }
410
+ // For streaming mode, send the formatted tool call message via SSE
411
+ // This ensures web clients receive the complete tool call info with parameters
412
+ // Use publishSSEWithChatId to ensure concurrency-safe event routing
413
+ if (isStreamingEnabled()) {
414
+ publishSSEWithChatId(world, {
415
+ agentName: agent.id,
416
+ type: 'chunk',
417
+ content: messageContent,
418
+ messageId,
419
+ tool_calls: displayToolCalls
420
+ });
421
+ }
422
+ const assistantMessage = {
423
+ role: 'assistant',
424
+ content: messageContent,
425
+ sender: agent.id,
426
+ createdAt: new Date(),
427
+ chatId: targetChatId,
428
+ messageId,
429
+ replyToMessageId: messageEvent.messageId,
430
+ tool_calls: displayToolCalls,
431
+ agentId: agent.id,
432
+ // Mark tool calls as incomplete (waiting for execution)
433
+ toolCallStatus: displayToolCalls.reduce((acc, tc) => {
434
+ acc[tc.id] = { complete: false, result: null };
435
+ return acc;
436
+ }, {})
437
+ };
438
+ agent.memory.push(assistantMessage);
439
+ // Auto-save agent memory
440
+ try {
441
+ const storage = await getStorageWrappers();
442
+ await storage.saveAgent(world.id, agent);
443
+ loggerAgent.debug('Assistant message with tool_calls saved to memory', {
444
+ agentId: agent.id,
445
+ messageId,
446
+ toolCallCount: executableToolCalls.length,
447
+ toolCallIds: executableToolCalls.map(tc => tc.id)
448
+ });
449
+ }
450
+ catch (error) {
451
+ loggerAgent.error('Failed to save assistant message with tool_calls', {
452
+ agentId: agent.id,
453
+ error: error instanceof Error ? error.message : error
454
+ });
455
+ }
456
+ // Publish original tool call message event (for display/logging)
457
+ const toolCallEvent = {
458
+ content: assistantMessage.content || '',
459
+ sender: agent.id,
460
+ timestamp: assistantMessage.createdAt || new Date(),
461
+ messageId: assistantMessage.messageId,
462
+ chatId: assistantMessage.chatId,
463
+ replyToMessageId: assistantMessage.replyToMessageId
464
+ };
465
+ toolCallEvent.role = 'assistant';
466
+ toolCallEvent.tool_calls = assistantMessage.tool_calls;
467
+ toolCallEvent.toolCallStatus = assistantMessage.toolCallStatus;
468
+ world.eventEmitter.emit('message', toolCallEvent);
469
+ // Execute first tool call (only handle one at a time for now)
470
+ // This is the UNIFIED tool execution path for both streaming and non-streaming
471
+ const toolCall = executableToolCalls[0];
472
+ if (toolCall) {
473
+ throwIfMessageProcessingStopped(processingHandle?.signal);
474
+ loggerAgent.debug('Executing tool call', {
475
+ agentId: agent.id,
476
+ toolCallId: toolCall.id,
477
+ toolName: toolCall.function.name
478
+ });
479
+ // Get MCP tools
480
+ const { getMCPToolsForWorld } = await import('../mcp-server-registry.js');
481
+ const mcpTools = await getMCPToolsForWorld(world.id);
482
+ try {
483
+ const toolDef = mcpTools[toolCall.function.name];
484
+ if (!toolDef) {
485
+ throw new Error(`Tool not found: ${toolCall.function.name}`);
486
+ }
487
+ let toolArgs;
488
+ const rawArgs = toolCall.function.arguments;
489
+ if (typeof rawArgs === 'string') {
490
+ try {
491
+ // Use sanitization function to handle common LLM JSON issues
492
+ toolArgs = sanitizeAndParseJSON(rawArgs);
493
+ // Log if sanitization was needed (successful parse after initial failure)
494
+ try {
495
+ JSON.parse(rawArgs);
496
+ }
497
+ catch {
498
+ loggerAgent.warn('Tool arguments required JSON sanitization', {
499
+ agentId: agent.id,
500
+ toolCallId: toolCall.id,
501
+ toolName: toolCall.function.name,
502
+ rawArgsLength: rawArgs.length
503
+ });
504
+ }
505
+ }
506
+ catch (parseError) {
507
+ // Enhanced error logging for JSON parse failures
508
+ loggerAgent.error('Failed to parse tool call arguments as JSON (even after sanitization)', {
509
+ agentId: agent.id,
510
+ toolCallId: toolCall.id,
511
+ toolName: toolCall.function.name,
512
+ error: parseError instanceof Error ? parseError.message : String(parseError),
513
+ rawArgsLength: rawArgs.length,
514
+ rawArgsPreview: rawArgs.substring(0, 500), // First 500 chars
515
+ rawArgsSuffix: rawArgs.length > 500 ? rawArgs.substring(rawArgs.length - 200) : '', // Last 200 chars
516
+ });
517
+ throw new Error(`Invalid JSON in tool arguments: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
518
+ }
519
+ }
520
+ else if (rawArgs && typeof rawArgs === 'object') {
521
+ toolArgs = rawArgs;
522
+ }
523
+ else {
524
+ toolArgs = {};
525
+ }
526
+ publishToolEvent(world, {
527
+ agentName: agent.id,
528
+ type: 'tool-start',
529
+ messageId: toolCall.id,
530
+ chatId: targetChatId,
531
+ toolExecution: {
532
+ toolName: toolCall.function.name,
533
+ toolCallId: toolCall.id,
534
+ input: toolArgs,
535
+ metadata: {
536
+ isStreaming: isStreamingEnabled()
537
+ }
538
+ }
539
+ });
540
+ logToolBridge('LLM -> TOOL', {
541
+ worldId: world.id,
542
+ agentId: agent.id,
543
+ chatId: targetChatId,
544
+ toolCallId: toolCall.id,
545
+ toolName: toolCall.function.name,
546
+ args: toolArgs,
547
+ });
548
+ if (toolCall.function.name === 'shell_cmd') {
549
+ const directoryValidation = validateShellDirectoryRequest(toolArgs.directory, trustedWorkingDirectory);
550
+ if (!directoryValidation.valid) {
551
+ throw new Error(directoryValidation.error);
552
+ }
553
+ const scopeValidation = validateShellCommandScope(toolArgs.command, toolArgs.parameters, trustedWorkingDirectory);
554
+ if (!scopeValidation.valid) {
555
+ throw new Error(scopeValidation.error);
556
+ }
557
+ }
558
+ // Execute tool with context
559
+ const toolContext = {
560
+ world,
561
+ messages: agent.memory,
562
+ toolCallId: toolCall.id,
563
+ chatId: targetChatId,
564
+ abortSignal: processingHandle?.signal,
565
+ workingDirectory: trustedWorkingDirectory
566
+ };
567
+ const toolResult = await toolDef.execute(toolArgs, undefined, undefined, toolContext);
568
+ if (processingHandle?.isStopped()) {
569
+ const toolCallMsg = agent.memory.find(m => m.role === 'assistant' && m.tool_calls?.some((tc) => tc.id === toolCall.id));
570
+ if (toolCallMsg && toolCallMsg.toolCallStatus) {
571
+ toolCallMsg.toolCallStatus[toolCall.id] = { complete: true, result: 'canceled' };
572
+ }
573
+ try {
574
+ const storage = await getStorageWrappers();
575
+ await storage.saveAgent(world.id, agent);
576
+ }
577
+ catch (error) {
578
+ loggerAgent.error('Failed to save canceled tool state', {
579
+ agentId: agent.id,
580
+ error: error instanceof Error ? error.message : error
581
+ });
582
+ }
583
+ loggerAgent.info('Tool execution canceled by stop request before continuation', {
584
+ agentId: agent.id,
585
+ toolCallId: toolCall.id,
586
+ targetChatId
587
+ });
588
+ publishToolEvent(world, {
589
+ agentName: agent.id,
590
+ type: 'tool-error',
591
+ messageId: toolCall.id,
592
+ chatId: targetChatId,
593
+ toolExecution: {
594
+ toolName: toolCall.function.name,
595
+ toolCallId: toolCall.id,
596
+ input: toolArgs,
597
+ error: 'Tool execution canceled by user'
598
+ }
599
+ });
600
+ return;
601
+ }
602
+ // Tool executed successfully - save result and continue LLM loop
603
+ loggerAgent.debug('Tool executed successfully', {
604
+ agentId: agent.id,
605
+ toolCallId: toolCall.id,
606
+ resultLength: typeof toolResult === 'string' ? toolResult.length : 0
607
+ });
608
+ // Publish tool-execution event
609
+ publishEvent(world, 'tool-execution', {
610
+ agentId: agent.id,
611
+ toolName: toolCall.function.name,
612
+ toolCallId: toolCall.id,
613
+ chatId: targetChatId,
614
+ ...(toolArgs.command && { command: toolArgs.command }),
615
+ ...(toolArgs.parameters && { parameters: toolArgs.parameters }),
616
+ ...(toolCall.function.name === 'shell_cmd' && { directory: trustedWorkingDirectory }),
617
+ ...(toolCall.function.name !== 'shell_cmd' && toolArgs.directory && { directory: toolArgs.directory })
618
+ });
619
+ const serializedToolResult = typeof toolResult === 'string'
620
+ ? toolResult
621
+ : JSON.stringify(toolResult) ?? String(toolResult);
622
+ const toolResultPreview = serializedToolResult.slice(0, 4000);
623
+ publishToolEvent(world, {
624
+ agentName: agent.id,
625
+ type: 'tool-result',
626
+ messageId: toolCall.id,
627
+ chatId: targetChatId,
628
+ toolExecution: {
629
+ toolName: toolCall.function.name,
630
+ toolCallId: toolCall.id,
631
+ input: toolArgs,
632
+ result: toolResultPreview,
633
+ resultType: typeof toolResult === 'string'
634
+ ? 'string'
635
+ : Array.isArray(toolResult)
636
+ ? 'array'
637
+ : toolResult === null
638
+ ? 'null'
639
+ : 'object',
640
+ resultSize: toolResultPreview.length
641
+ }
642
+ });
643
+ logToolBridge('TOOL -> LLM', {
644
+ worldId: world.id,
645
+ agentId: agent.id,
646
+ chatId: targetChatId,
647
+ toolCallId: toolCall.id,
648
+ toolName: toolCall.function.name,
649
+ resultPreview: getToolResultPreview(toolResult),
650
+ });
651
+ // Save tool result to agent memory
652
+ const toolResultMessage = {
653
+ role: 'tool',
654
+ content: serializedToolResult,
655
+ tool_call_id: toolCall.id,
656
+ sender: agent.id,
657
+ createdAt: new Date(),
658
+ chatId: targetChatId,
659
+ messageId: generateId(),
660
+ replyToMessageId: messageId,
661
+ agentId: agent.id
662
+ };
663
+ agent.memory.push(toolResultMessage);
664
+ // Update tool call status to complete
665
+ const toolCallMsg = agent.memory.find(m => m.role === 'assistant' && m.tool_calls?.some((tc) => tc.id === toolCall.id));
666
+ if (toolCallMsg && toolCallMsg.toolCallStatus) {
667
+ toolCallMsg.toolCallStatus[toolCall.id] = { complete: true, result: toolResult };
668
+ }
669
+ // Save agent with tool result
670
+ try {
671
+ const storage = await getStorageWrappers();
672
+ await storage.saveAgent(world.id, agent);
673
+ loggerAgent.debug('Tool result saved to memory', {
674
+ agentId: agent.id,
675
+ toolCallId: toolCall.id,
676
+ messageId: toolResultMessage.messageId
677
+ });
678
+ }
679
+ catch (error) {
680
+ loggerAgent.error('Failed to save tool result', {
681
+ agentId: agent.id,
682
+ error: error instanceof Error ? error.message : error
683
+ });
684
+ }
685
+ // Continue LLM loop with tool result
686
+ // The tool result is now in memory, so the next LLM call will see it
687
+ loggerAgent.debug('Continuing LLM loop with tool result', {
688
+ agentId: agent.id,
689
+ toolCallId: toolCall.id,
690
+ targetChatId
691
+ });
692
+ // Continue the LLM execution loop with the tool result
693
+ // Pass explicit chatId for concurrency-safe continuation
694
+ throwIfMessageProcessingStopped(processingHandle?.signal);
695
+ const { continueLLMAfterToolExecution } = await import('./memory-manager.js');
696
+ await continueLLMAfterToolExecution(world, agent, targetChatId, {
697
+ abortSignal: processingHandle?.signal
698
+ });
699
+ }
700
+ catch (error) {
701
+ if (isMessageProcessingCanceledError(error) || processingHandle?.isStopped()) {
702
+ loggerAgent.info('Tool execution canceled', {
703
+ agentId: agent.id,
704
+ toolCallId: toolCall.id,
705
+ error: error instanceof Error ? error.message : String(error)
706
+ });
707
+ const toolCallMsg = agent.memory.find(m => m.role === 'assistant' && m.tool_calls?.some((tc) => tc.id === toolCall.id));
708
+ if (toolCallMsg && toolCallMsg.toolCallStatus) {
709
+ toolCallMsg.toolCallStatus[toolCall.id] = { complete: true, result: 'canceled' };
710
+ }
711
+ try {
712
+ const storage = await getStorageWrappers();
713
+ await storage.saveAgent(world.id, agent);
714
+ }
715
+ catch (saveError) {
716
+ loggerAgent.error('Failed to save canceled tool state', {
717
+ agentId: agent.id,
718
+ error: saveError instanceof Error ? saveError.message : saveError
719
+ });
720
+ }
721
+ publishToolEvent(world, {
722
+ agentName: agent.id,
723
+ type: 'tool-error',
724
+ messageId: toolCall.id,
725
+ chatId: targetChatId,
726
+ toolExecution: {
727
+ toolName: toolCall.function.name,
728
+ toolCallId: toolCall.id,
729
+ error: 'Tool execution canceled by user'
730
+ }
731
+ });
732
+ return;
733
+ }
734
+ loggerAgent.error('Tool execution error', {
735
+ agentId: agent.id,
736
+ toolCallId: toolCall.id,
737
+ error: error instanceof Error ? error.message : error
738
+ });
739
+ publishToolEvent(world, {
740
+ agentName: agent.id,
741
+ type: 'tool-error',
742
+ messageId: toolCall.id,
743
+ chatId: targetChatId,
744
+ toolExecution: {
745
+ toolName: toolCall.function.name,
746
+ toolCallId: toolCall.id,
747
+ error: error instanceof Error ? error.message : String(error)
748
+ }
749
+ });
750
+ logToolBridge('TOOL ERROR -> LLM', {
751
+ worldId: world.id,
752
+ agentId: agent.id,
753
+ chatId: targetChatId,
754
+ toolCallId: toolCall.id,
755
+ toolName: toolCall.function.name,
756
+ error: error instanceof Error ? error.message : String(error),
757
+ });
758
+ // Save error as tool result
759
+ const errorMessage = {
760
+ role: 'tool',
761
+ content: `Error executing tool: ${error instanceof Error ? error.message : String(error)}`,
762
+ tool_call_id: toolCall.id,
763
+ sender: agent.id,
764
+ createdAt: new Date(),
765
+ chatId: targetChatId,
766
+ messageId: generateId(),
767
+ replyToMessageId: messageId,
768
+ agentId: agent.id
769
+ };
770
+ agent.memory.push(errorMessage);
771
+ const toolCallMsg = agent.memory.find(m => m.role === 'assistant' && m.tool_calls?.some((tc) => tc.id === toolCall.id));
772
+ if (toolCallMsg && toolCallMsg.toolCallStatus) {
773
+ toolCallMsg.toolCallStatus[toolCall.id] = {
774
+ complete: true,
775
+ result: errorMessage.content
776
+ };
777
+ }
778
+ try {
779
+ const storage = await getStorageWrappers();
780
+ await storage.saveAgent(world.id, agent);
781
+ }
782
+ catch (saveError) {
783
+ loggerAgent.error('Failed to save error message', {
784
+ agentId: agent.id,
785
+ error: saveError instanceof Error ? saveError.message : saveError
786
+ });
787
+ }
788
+ loggerAgent.debug('Continuing LLM loop with tool error result', {
789
+ agentId: agent.id,
790
+ toolCallId: toolCall.id,
791
+ targetChatId
792
+ });
793
+ throwIfMessageProcessingStopped(processingHandle?.signal);
794
+ const { continueLLMAfterToolExecution } = await import('./memory-manager.js');
795
+ await continueLLMAfterToolExecution(world, agent, targetChatId, {
796
+ abortSignal: processingHandle?.signal
797
+ });
798
+ }
799
+ }
800
+ return;
801
+ }
802
+ }
803
+ catch (error) {
804
+ if (isMessageProcessingCanceledError(error) || processingHandle?.isStopped()) {
805
+ loggerAgent.info('Agent message processing canceled', {
806
+ agentId: agent.id,
807
+ chatId: messageEvent.chatId ?? world.currentChatId ?? null,
808
+ error: error instanceof Error ? error.message : String(error)
809
+ });
810
+ return;
811
+ }
812
+ loggerAgent.error('Error processing agent message', {
813
+ agentId: agent.id,
814
+ error: error instanceof Error ? error.message : String(error),
815
+ stack: error instanceof Error ? error.stack : undefined
816
+ });
817
+ throw error;
818
+ }
819
+ finally {
820
+ processingHandle?.complete();
821
+ completeActivity();
822
+ }
823
+ }
824
+ /**
825
+ * Enhanced message filtering logic with turn limits and mention detection
826
+ */
827
+ export async function shouldAgentRespond(world, agent, messageEvent) {
828
+ // Never respond to own messages
829
+ if (messageEvent.sender?.toLowerCase() === agent.id.toLowerCase()) {
830
+ loggerResponse.debug('Skipping own message', { agentId: agent.id, sender: messageEvent.sender });
831
+ return false;
832
+ }
833
+ const content = messageEvent.content || '';
834
+ // Never respond to turn limit messages (prevents endless loops)
835
+ if (content.includes('Turn limit reached')) {
836
+ loggerTurnLimit.debug('Skipping turn limit message', { agentId: agent.id });
837
+ return false;
838
+ }
839
+ // Check turn limit based on LLM call count
840
+ const worldTurnLimit = getWorldTurnLimit(world);
841
+ loggerTurnLimit.debug('Checking turn limit', { agentId: agent.id, llmCallCount: agent.llmCallCount, worldTurnLimit });
842
+ if (agent.llmCallCount >= worldTurnLimit) {
843
+ loggerTurnLimit.debug('Turn limit reached, sending turn limit message', { agentId: agent.id, llmCallCount: agent.llmCallCount, worldTurnLimit });
844
+ const turnLimitMessage = `@human Turn limit reached (${worldTurnLimit} LLM calls). Please take control of the conversation.`;
845
+ publishMessage(world, turnLimitMessage, agent.id);
846
+ return false;
847
+ }
848
+ // Determine sender type for message handling logic
849
+ const senderType = determineSenderType(messageEvent.sender);
850
+ loggerResponse.debug('Determined sender type', { agentId: agent.id, sender: messageEvent.sender, senderType });
851
+ // Never respond to system messages
852
+ if (messageEvent.sender === 'system') {
853
+ loggerResponse.debug('Skipping system message', { agentId: agent.id });
854
+ return false;
855
+ }
856
+ // Always respond to world messages
857
+ if (messageEvent.sender === 'world') {
858
+ loggerResponse.debug('Responding to world message', { agentId: agent.id });
859
+ return true;
860
+ }
861
+ const anyMentions = extractMentions(messageEvent.content);
862
+ const mentions = extractParagraphBeginningMentions(messageEvent.content);
863
+ loggerResponse.debug('Extracted mentions', { mentions, anyMentions });
864
+ // For HUMAN messages
865
+ if (senderType === SenderType.HUMAN) {
866
+ if (mentions.length === 0) {
867
+ if (anyMentions.length > 0) {
868
+ loggerResponse.debug('Mentions exist but not at paragraph beginning', { agentId: agent.id });
869
+ return false;
870
+ }
871
+ loggerResponse.debug('No mentions - public message', { agentId: agent.id });
872
+ return true;
873
+ }
874
+ const shouldRespond = mentions.includes(agent.id.toLowerCase());
875
+ loggerResponse.debug('HUMAN message mention check', { agentId: agent.id, shouldRespond });
876
+ return shouldRespond;
877
+ }
878
+ // For agent messages, only respond if this agent has a paragraph-beginning mention
879
+ const shouldRespond = mentions.includes(agent.id.toLowerCase());
880
+ loggerResponse.debug('AGENT message mention check', { agentId: agent.id, shouldRespond });
881
+ return shouldRespond;
882
+ }
883
+ //# sourceMappingURL=orchestrator.js.map