agent-world 0.11.0 → 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 +55 -4
  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 +21 -5
  267. package/scripts/launch-electron.js +0 -58
@@ -0,0 +1,1248 @@
1
+ /**
2
+ * Unified Events Module - World and Agent Event Functions
3
+ *
4
+ * Purpose: Event-driven message publishing, agent response processing, and memory persistence
5
+ *
6
+ * Logging: Enable with LOG_EVENTS=debug or specific categories:
7
+ * - LOG_EVENTS_PUBLISH, LOG_EVENTS_AGENT, LOG_EVENTS_RESPONSE, LOG_EVENTS_MEMORY
8
+ * - LOG_EVENTS_AUTOMENTION, LOG_EVENTS_TURNLIMIT, LOG_EVENTS_CHATTITLE
9
+ *
10
+ * Core Features:
11
+ * - Event publishing/subscription via World.eventEmitter with type safety
12
+ * - Agent message filtering with mention detection and turn limits
13
+ * - Auto-mention logic with loop prevention and world tags (<world>STOP|TO:a,b</world>)
14
+ * - Message threading with replyToMessageId preservation
15
+ * - Event persistence with automatic chatId defaulting to world.currentChatId
16
+ * - Tool approval system with session/one-time approval tracking
17
+ * - Chat title generation on world idle events
18
+ *
19
+ * Recent Changes (2025-11):
20
+ * - Fixed tool event metadata validation errors: Added required ownerAgentId and
21
+ * triggeredByMessageId fields to toolHandler, added validation guards to reject
22
+ * tool events missing messageId or agentName
23
+ * - Removed unnecessary 'info' event emission from tool-utils.ts to prevent validation errors
24
+ * - Fixed approval response broadcast bug: Removed HUMAN check from shouldAutoMention to ensure
25
+ * agent responses to HUMAN approval messages include proper targeting mentions (@HUMAN),
26
+ * preventing unintended broadcast to all agents
27
+ * - Consolidated redundant logging and streamlined approval checking
28
+ * - Added tool_calls/tool_call_id persistence for approval messages
29
+ * - Pre-generate message IDs for agent responses
30
+ * - Fixed activity tracking to prevent premature idle signals
31
+ */
32
+ import { SenderType, EventType } from './types.js';
33
+ import { generateId } from './utils.js';
34
+ import { generateAgentResponse } from './llm-manager.js';
35
+ import { beginWorldActivity } from './activity-tracker.js';
36
+ import { createStorageWithWrappers } from './storage/storage-factory.js';
37
+ import { getWorldTurnLimit, extractMentions, extractParagraphBeginningMentions, determineSenderType, prepareMessagesForLLM } from './utils.js';
38
+ import { parseMessageContent } from './message-prep.js';
39
+ import { createCategoryLogger } from './logger.js';
40
+ import { calculateOwnerAgentIds, calculateRecipientAgentId, calculateIsMemoryOnly, calculateIsCrossAgentMessage, calculateMessageDirection } from './events-metadata.js';
41
+ // Function-specific loggers for granular debugging control
42
+ const loggerPublish = createCategoryLogger('events.publish');
43
+ const loggerAgent = createCategoryLogger('events.agent');
44
+ const loggerResponse = createCategoryLogger('events.response');
45
+ const loggerMemory = createCategoryLogger('events.memory');
46
+ const loggerAutoMention = createCategoryLogger('events.automention');
47
+ const loggerTurnLimit = createCategoryLogger('events.turnlimit');
48
+ const loggerChatTitle = createCategoryLogger('events.chattitle');
49
+ // Global streaming control
50
+ let globalStreamingEnabled = true;
51
+ export function enableStreaming() { globalStreamingEnabled = true; }
52
+ export function disableStreaming() { globalStreamingEnabled = false; }
53
+ /**
54
+ * Publish CRUD event for agent, chat, or world configuration changes
55
+ * Allows subscribed clients to receive real-time updates for all CRUD operations
56
+ */
57
+ export function publishCRUDEvent(world, operation, entityType, entityId, entityData) {
58
+ const event = {
59
+ operation,
60
+ entityType,
61
+ entityId,
62
+ entityData: operation === 'delete' ? null : entityData,
63
+ timestamp: new Date(),
64
+ chatId: world.currentChatId ?? null
65
+ };
66
+ world.eventEmitter.emit(EventType.CRUD, event);
67
+ loggerPublish.debug('CRUD event published', {
68
+ worldId: world.id,
69
+ operation,
70
+ entityType,
71
+ entityId
72
+ });
73
+ }
74
+ // Storage wrapper instance - initialized lazily
75
+ let storageWrappers = null;
76
+ async function getStorageWrappers() {
77
+ if (!storageWrappers) {
78
+ storageWrappers = await createStorageWithWrappers();
79
+ }
80
+ return storageWrappers;
81
+ }
82
+ /**
83
+ * Setup automatic event persistence listeners on World event emitter.
84
+ * Should be called once during World initialization.
85
+ *
86
+ * Events are persisted synchronously/awaitable for reliability.
87
+ * Failures are logged but don't block event emission.
88
+ * Returns a cleanup function to remove listeners.
89
+ *
90
+ * Environment variables:
91
+ * - DISABLE_EVENT_PERSISTENCE=true: Skip all persistence
92
+ */
93
+ export function setupEventPersistence(world) {
94
+ if (process.env.DISABLE_EVENT_PERSISTENCE === 'true') {
95
+ loggerPublish.debug('Event persistence disabled by environment', { worldId: world.id });
96
+ return () => { }; // Return no-op cleanup
97
+ }
98
+ if (!world.eventStorage) {
99
+ loggerPublish.debug('Event storage not configured - events will not be persisted', { worldId: world.id });
100
+ return () => { }; // Return no-op cleanup
101
+ }
102
+ const storage = world.eventStorage;
103
+ // Helper to handle async persistence
104
+ const persistEvent = async (eventData) => {
105
+ try {
106
+ await storage.saveEvent(eventData);
107
+ }
108
+ catch (error) {
109
+ loggerPublish.error('Failed to persist event', {
110
+ worldId: world.id,
111
+ eventId: eventData.id,
112
+ eventType: eventData.type,
113
+ error: error instanceof Error ? error.message : error
114
+ });
115
+ }
116
+ };
117
+ // Message event persistence
118
+ const messageHandler = (event) => {
119
+ // Calculate enhanced metadata using helper functions
120
+ const ownerAgentIds = calculateOwnerAgentIds(world, event);
121
+ const recipientAgentId = calculateRecipientAgentId(world, event);
122
+ const messageDirection = calculateMessageDirection(world, event);
123
+ const isMemoryOnly = calculateIsMemoryOnly(world, event);
124
+ const isCrossAgentMessage = calculateIsCrossAgentMessage(world, event);
125
+ const isHumanMessage = event.sender === 'human' || event.sender === 'user';
126
+ // Calculate thread metadata (requires loading messages for accurate depth calculation)
127
+ // For now, use simplified version - can enhance later with full message history
128
+ const threadMetadata = event.replyToMessageId
129
+ ? { threadRootId: event.replyToMessageId, threadDepth: 1, isReply: true }
130
+ : { threadRootId: null, threadDepth: 0, isReply: false };
131
+ // Get tool call information if present
132
+ const hasToolCalls = !!(event.tool_calls?.length);
133
+ const toolCallCount = event.tool_calls?.length || 0;
134
+ const eventData = {
135
+ id: event.messageId,
136
+ worldId: world.id,
137
+ chatId: event.chatId || null,
138
+ type: 'message',
139
+ payload: {
140
+ content: event.content,
141
+ sender: event.sender,
142
+ replyToMessageId: event.replyToMessageId,
143
+ // Preserve OpenAI protocol fields for tool calls and approvals
144
+ role: event.role,
145
+ tool_calls: event.tool_calls,
146
+ tool_call_id: event.tool_call_id
147
+ },
148
+ meta: {
149
+ // Core fields
150
+ sender: event.sender,
151
+ chatId: event.chatId || null,
152
+ // Agent Context
153
+ ownerAgentIds,
154
+ recipientAgentId,
155
+ originalSender: null, // Will be set for cross-agent forwarding in future
156
+ deliveredToAgents: ownerAgentIds, // Same as owner for now
157
+ // Message Classification
158
+ messageDirection,
159
+ isMemoryOnly,
160
+ isCrossAgentMessage,
161
+ isHumanMessage,
162
+ // Threading
163
+ threadRootId: threadMetadata.threadRootId,
164
+ threadDepth: threadMetadata.threadDepth,
165
+ isReply: threadMetadata.isReply,
166
+ hasReplies: false, // Will be updated async in future
167
+ // Tool Approval
168
+ requiresApproval: event.requiresApproval || false,
169
+ approvalScope: null, // Set when approval is granted
170
+ approvedAt: null,
171
+ approvedBy: null,
172
+ deniedAt: null,
173
+ denialReason: null,
174
+ // Performance (for agent messages with LLM usage)
175
+ llmTokensInput: event.usage?.inputTokens || null,
176
+ llmTokensOutput: event.usage?.outputTokens || null,
177
+ llmLatency: null, // Can be calculated from SSE start/end events
178
+ llmProvider: null, // Not available in message event
179
+ llmModel: null,
180
+ // UI State
181
+ hasToolCalls,
182
+ toolCallCount
183
+ },
184
+ createdAt: event.timestamp
185
+ };
186
+ return persistEvent(eventData);
187
+ };
188
+ // SSE event handler - persist only start and end events
189
+ const sseHandler = (event) => {
190
+ // Only persist start and end events, not chunk events
191
+ if (event.type !== 'start' && event.type !== 'end') {
192
+ return;
193
+ }
194
+ // Make ID unique by combining messageId with event type
195
+ const eventData = {
196
+ id: `${event.messageId}-sse-${event.type}`,
197
+ worldId: world.id,
198
+ chatId: world.currentChatId || null, // Default to current chat
199
+ type: 'sse',
200
+ payload: {
201
+ agentName: event.agentName,
202
+ type: event.type,
203
+ content: event.content,
204
+ error: event.error,
205
+ usage: event.usage,
206
+ logEvent: event.logEvent
207
+ },
208
+ meta: {
209
+ agentName: event.agentName,
210
+ sseType: event.type
211
+ },
212
+ createdAt: new Date()
213
+ };
214
+ return persistEvent(eventData);
215
+ };
216
+ // Tool event persistence (world channel)
217
+ // Handles WorldToolEvent (tool execution) and WorldActivityEventPayload (activity tracking)
218
+ const toolHandler = (event) => {
219
+ // Check event type category
220
+ const isActivityEvent = event.type && ['response-start', 'response-end', 'idle'].includes(event.type);
221
+ const isToolEvent = event.type && ['tool-start', 'tool-result', 'tool-error', 'tool-progress'].includes(event.type);
222
+ // Validate required fields for tool events only
223
+ if (isToolEvent) {
224
+ if (!event.messageId) {
225
+ loggerPublish.error('Tool event missing required messageId', {
226
+ worldId: world.id,
227
+ eventType: event.type,
228
+ agentName: event.agentName
229
+ });
230
+ return; // Skip persistence for invalid events
231
+ }
232
+ if (!event.agentName) {
233
+ loggerPublish.error('Tool event missing required agentName', {
234
+ worldId: world.id,
235
+ eventType: event.type,
236
+ messageId: event.messageId
237
+ });
238
+ return; // Skip persistence for invalid events
239
+ }
240
+ }
241
+ // Generate unique ID for tool events by combining messageId with tool type
242
+ // This prevents duplicate ID conflicts when multiple tool events (tool-start, tool-result, tool-error)
243
+ // share the same messageId
244
+ const eventId = isActivityEvent
245
+ ? event.messageId // Activity events already have unique messageIds
246
+ : `${event.messageId}-tool-${event.type}`; // Tool events need type suffix for uniqueness
247
+ const eventData = {
248
+ id: eventId,
249
+ worldId: world.id,
250
+ chatId: world.currentChatId || null, // Default to current chat
251
+ type: isActivityEvent ? 'world' : 'tool',
252
+ payload: isActivityEvent ? {
253
+ activityType: event.type,
254
+ pendingOperations: event.pendingOperations,
255
+ activityId: event.activityId,
256
+ source: event.source,
257
+ activeSources: event.activeSources,
258
+ timestamp: event.timestamp
259
+ } : {
260
+ agentName: event.agentName,
261
+ type: event.type,
262
+ toolExecution: event.toolExecution
263
+ },
264
+ meta: isActivityEvent ? {
265
+ activityType: event.type,
266
+ source: event.source
267
+ } : {
268
+ agentName: event.agentName,
269
+ toolType: event.type,
270
+ // Required metadata for tool events
271
+ ownerAgentId: event.agentName,
272
+ triggeredByMessageId: event.messageId,
273
+ executionDuration: event.toolExecution?.duration ?? 0,
274
+ resultSize: event.toolExecution?.resultSize ?? 0,
275
+ wasApproved: false // Default, should be updated if approval tracking is needed
276
+ },
277
+ createdAt: isActivityEvent ? new Date(event.timestamp) : new Date()
278
+ };
279
+ return persistEvent(eventData);
280
+ };
281
+ // System event persistence
282
+ const systemHandler = (event) => {
283
+ const eventData = {
284
+ id: event.messageId,
285
+ worldId: world.id,
286
+ chatId: event.chatId !== undefined ? event.chatId : (world.currentChatId || null), // Default to current chat
287
+ type: 'system',
288
+ payload: event.content,
289
+ meta: {},
290
+ createdAt: event.timestamp
291
+ };
292
+ return persistEvent(eventData);
293
+ };
294
+ // CRUD event persistence
295
+ const crudHandler = (event) => {
296
+ const eventData = {
297
+ id: `crud-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
298
+ worldId: world.id,
299
+ chatId: event.chatId || null,
300
+ type: 'crud',
301
+ payload: {
302
+ operation: event.operation,
303
+ entityType: event.entityType,
304
+ entityId: event.entityId,
305
+ entityData: event.entityData,
306
+ timestamp: event.timestamp
307
+ },
308
+ meta: {
309
+ operation: event.operation,
310
+ entityType: event.entityType,
311
+ entityId: event.entityId
312
+ },
313
+ createdAt: event.timestamp
314
+ };
315
+ return persistEvent(eventData);
316
+ };
317
+ // Attach listeners
318
+ world.eventEmitter.on('message', messageHandler);
319
+ world.eventEmitter.on('sse', sseHandler);
320
+ world.eventEmitter.on('world', toolHandler);
321
+ world.eventEmitter.on('system', systemHandler);
322
+ world.eventEmitter.on(EventType.CRUD, crudHandler);
323
+ loggerPublish.debug('Event persistence setup complete', {
324
+ worldId: world.id
325
+ });
326
+ // Return cleanup function
327
+ return () => {
328
+ world.eventEmitter.off('message', messageHandler);
329
+ world.eventEmitter.off('sse', sseHandler);
330
+ world.eventEmitter.off('world', toolHandler);
331
+ world.eventEmitter.off('system', systemHandler);
332
+ world.eventEmitter.off(EventType.CRUD, crudHandler);
333
+ loggerPublish.debug('Event persistence listeners cleaned up', { worldId: world.id });
334
+ };
335
+ }
336
+ /**
337
+ * Publish event to a specific channel using World.eventEmitter
338
+ */
339
+ export function publishEvent(world, type, content) {
340
+ const event = {
341
+ content,
342
+ timestamp: new Date(),
343
+ messageId: generateId(),
344
+ chatId: world.currentChatId || null
345
+ };
346
+ world.eventEmitter.emit(type, event);
347
+ }
348
+ /**
349
+ * Message publishing using World.eventEmitter with chat session management
350
+ * Parses enhanced string protocol and automatically prepends @mention if agentId detected
351
+ * Returns the messageEvent so callers can access the generated messageId
352
+ *
353
+ * @param chatId - Optional chat ID. If not provided, uses world.currentChatId
354
+ * @param replyToMessageId - Optional parent message ID for threading
355
+ */
356
+ export function publishMessage(world, content, sender, chatId, replyToMessageId) {
357
+ const messageId = generateId();
358
+ const targetChatId = chatId !== undefined ? chatId : world.currentChatId;
359
+ // Parse enhanced string protocol to extract targetAgentId
360
+ const { targetAgentId } = parseMessageContent(content, 'user');
361
+ // Prepend @mention if agentId is present in enhanced protocol
362
+ let finalContent = content;
363
+ if (targetAgentId) {
364
+ finalContent = `@${targetAgentId}, ${content}`;
365
+ loggerMemory.debug('[publishMessage] Prepended @mention from enhanced protocol', {
366
+ agentId: targetAgentId,
367
+ messageId
368
+ });
369
+ }
370
+ const messageEvent = {
371
+ content: finalContent,
372
+ sender,
373
+ timestamp: new Date(),
374
+ messageId,
375
+ chatId: targetChatId,
376
+ replyToMessageId
377
+ };
378
+ loggerMemory.debug('[publishMessage] Generated messageId', {
379
+ messageId,
380
+ sender,
381
+ worldId: world.id,
382
+ chatId: targetChatId,
383
+ hasAgentId: !!targetAgentId,
384
+ contentPreview: finalContent.substring(0, 50)
385
+ });
386
+ world.eventEmitter.emit('message', messageEvent);
387
+ return messageEvent;
388
+ }
389
+ /**
390
+ * Message publishing with pre-generated messageId
391
+ * Used when messageId needs to be known before publishing (e.g., for agent responses)
392
+ *
393
+ * @param chatId - Optional chat ID. If not provided, uses world.currentChatId
394
+ * @param replyToMessageId - Optional parent message ID for threading
395
+ */
396
+ export function publishMessageWithId(world, content, sender, messageId, chatId, replyToMessageId) {
397
+ const targetChatId = chatId !== undefined ? chatId : world.currentChatId;
398
+ const messageEvent = {
399
+ content,
400
+ sender,
401
+ timestamp: new Date(),
402
+ messageId,
403
+ chatId: targetChatId,
404
+ replyToMessageId
405
+ };
406
+ world.eventEmitter.emit('message', messageEvent);
407
+ return messageEvent;
408
+ }
409
+ export function subscribeToMessages(world, handler) {
410
+ world.eventEmitter.on('message', handler);
411
+ return () => world.eventEmitter.off('message', handler);
412
+ }
413
+ /**
414
+ * SSE events using World.eventEmitter (for LLM streaming)
415
+ */
416
+ export function publishSSE(world, data) {
417
+ const sseEvent = {
418
+ agentName: data.agentName,
419
+ type: data.type,
420
+ content: data.content,
421
+ error: data.error,
422
+ messageId: data.messageId || generateId(),
423
+ usage: data.usage,
424
+ logEvent: data.logEvent
425
+ };
426
+ world.eventEmitter.emit('sse', sseEvent);
427
+ }
428
+ /**
429
+ * Tool events using World.eventEmitter (for agent behavioral events)
430
+ */
431
+ export function publishToolEvent(world, data) {
432
+ const toolEvent = {
433
+ agentName: data.agentName,
434
+ type: data.type,
435
+ messageId: data.messageId || generateId(),
436
+ toolExecution: data.toolExecution
437
+ };
438
+ world.eventEmitter.emit('world', toolEvent);
439
+ }
440
+ /**
441
+ * Publish approval request event
442
+ * Used when a tool requires approval before execution
443
+ * Note: This function is legacy - approval requests now use direct message events
444
+ * with OpenAI tool call protocol (see tool-utils.ts)
445
+ */
446
+ export function publishApprovalRequest(world, approvalRequest, agentId, messageId) {
447
+ const approvalEvent = {
448
+ type: 'approval_request',
449
+ agentId,
450
+ messageId,
451
+ approvalRequest,
452
+ timestamp: new Date().toISOString()
453
+ };
454
+ // Emit as approval event for legacy compatibility
455
+ world.eventEmitter.emit('approval', approvalEvent);
456
+ // Note: SSE events are for streaming only, not for tool messages
457
+ // Approval requests should use message events with OpenAI tool call format
458
+ }
459
+ /**
460
+ * SSE subscription using World.eventEmitter
461
+ */
462
+ export function subscribeToSSE(world, handler) {
463
+ world.eventEmitter.on('sse', handler);
464
+ return () => world.eventEmitter.off('sse', handler);
465
+ }
466
+ // Check if response has any mention at paragraph beginning (prevents auto-mention loops)
467
+ export function hasAnyMentionAtBeginning(response) {
468
+ if (!response?.trim())
469
+ return false;
470
+ const result = extractParagraphBeginningMentions(response).length > 0;
471
+ loggerAutoMention.debug('Checking for mentions at beginning', { response: response.substring(0, 100), hasMentions: result });
472
+ return result;
473
+ }
474
+ // Remove all mentions from paragraph beginnings (including commas and spaces)
475
+ export function removeMentionsFromParagraphBeginnings(text, specificMention) {
476
+ if (!text?.trim())
477
+ return text;
478
+ const lines = text.split('\n');
479
+ const processedLines = lines.map(line => {
480
+ const trimmed = line.trimStart();
481
+ let cleaned = trimmed;
482
+ if (specificMention) {
483
+ // For specific mentions, escape special regex characters and handle consecutive mentions
484
+ const escapedMention = specificMention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
485
+ // Pattern to match @mention followed by optional comma/space combinations
486
+ const mentionPattern = new RegExp(`^@${escapedMention}(?:[,\\s]+|$)`, 'gi');
487
+ // Keep removing mentions from the beginning until no more are found
488
+ while (mentionPattern.test(cleaned)) {
489
+ cleaned = cleaned.replace(mentionPattern, '');
490
+ mentionPattern.lastIndex = 0; // Reset regex for next iteration
491
+ }
492
+ }
493
+ else {
494
+ // For any mentions
495
+ const mentionPattern = /^@\w+(?:[-_]\w+)*(?:[,\s]+|$)/;
496
+ // Keep removing mentions from the beginning until no more are found
497
+ while (mentionPattern.test(cleaned)) {
498
+ cleaned = cleaned.replace(mentionPattern, '');
499
+ }
500
+ }
501
+ const leadingWhitespace = line.match(/^(\s*)/)?.[1] || '';
502
+ return leadingWhitespace + cleaned;
503
+ });
504
+ return processedLines.join('\n');
505
+ }
506
+ // Add auto-mention at beginning if no existing mentions (prevents loops)
507
+ // Supports world tags: <world>STOP|DONE|PASS</world> and <world>TO: a,b,c</world>
508
+ export function addAutoMention(response, sender) {
509
+ if (!response?.trim() || !sender) {
510
+ return response;
511
+ }
512
+ loggerAutoMention.debug('Processing auto-mention', { sender, responseStart: response.substring(0, 100) });
513
+ // Consolidated regex patterns for world tags (case insensitive)
514
+ const worldTagPattern = /<world>(STOP|DONE|PASS|TO:\s*([^<]*))<\/world>/gi;
515
+ let match;
516
+ let processedResponse = response;
517
+ while ((match = worldTagPattern.exec(response)) !== null) {
518
+ const [fullMatch, action, toRecipients] = match;
519
+ loggerAutoMention.debug('Found world tag', { action, toRecipients, fullMatch });
520
+ // Remove the world tag from response
521
+ processedResponse = processedResponse.replace(fullMatch, '');
522
+ const upperAction = action.toUpperCase();
523
+ if (upperAction === 'STOP' || upperAction === 'DONE' || upperAction === 'PASS') {
524
+ // Stop tags prevent auto-mention and remove ALL mentions at beginning of paragraphs
525
+ loggerAutoMention.debug('Processing STOP/DONE/PASS tag - removing mentions');
526
+ const cleanResponse = processedResponse.trim();
527
+ return removeMentionsFromParagraphBeginnings(cleanResponse).trim();
528
+ }
529
+ else if (upperAction.startsWith('TO:')) {
530
+ // TO tag with recipients - also remove existing mentions
531
+ const recipients = toRecipients?.split(',').map(name => name.trim()).filter(name => name) || [];
532
+ loggerAutoMention.debug('Processing TO tag', { recipients });
533
+ // Remove existing mentions from the response
534
+ const cleanResponse = removeMentionsFromParagraphBeginnings(processedResponse.trim()).trim();
535
+ if (recipients.length > 0) {
536
+ const mentions = recipients.map(recipient => `@${recipient}`).join('\n');
537
+ const result = `${mentions}\n\n${cleanResponse}`;
538
+ loggerAutoMention.debug('Added TO tag mentions', { mentions, result: result.substring(0, 100) });
539
+ return result;
540
+ }
541
+ else {
542
+ // Empty TO tag - fall back to normal auto-mention behavior
543
+ loggerAutoMention.debug('Empty TO tag - falling back to normal auto-mention');
544
+ if (hasAnyMentionAtBeginning(cleanResponse)) {
545
+ return cleanResponse;
546
+ }
547
+ return `@${sender} ${cleanResponse}`;
548
+ }
549
+ }
550
+ } // Existing logic: add auto-mention if no existing mentions at beginning
551
+ if (hasAnyMentionAtBeginning(processedResponse)) {
552
+ loggerAutoMention.debug('Response already has mentions at beginning - no auto-mention needed');
553
+ return processedResponse;
554
+ }
555
+ const result = `@${sender} ${processedResponse.trim()}`;
556
+ loggerAutoMention.debug('Added auto-mention', { sender, result: result.substring(0, 100) });
557
+ return result;
558
+ }
559
+ // Get valid mentions excluding self-mentions (case-insensitive)
560
+ export function getValidMentions(response, agentId) {
561
+ if (!response?.trim() || !agentId)
562
+ return [];
563
+ return extractParagraphBeginningMentions(response)
564
+ .filter(mention => mention.toLowerCase() !== agentId.toLowerCase());
565
+ }
566
+ // Determine if agent should auto-mention sender (no valid mentions in response)
567
+ // Auto-mention is used to target responses and prevent unintended broadcasting
568
+ export function shouldAutoMention(response, sender, agentId) {
569
+ if (!response?.trim() || !sender || !agentId)
570
+ return false;
571
+ if (determineSenderType(sender) === SenderType.HUMAN)
572
+ return false;
573
+ if (sender.toLowerCase() === agentId.toLowerCase())
574
+ return false;
575
+ // Check if response already has valid mentions (excluding self)
576
+ return getValidMentions(response, agentId).length === 0;
577
+ }
578
+ // Remove consecutive self-mentions from response beginning (case-insensitive)
579
+ export function removeSelfMentions(response, agentId) {
580
+ if (!response || !agentId)
581
+ return response;
582
+ const trimmedResponse = response.trim();
583
+ if (!trimmedResponse)
584
+ return response;
585
+ loggerAutoMention.debug('Removing self-mentions', { agentId, responseStart: response.substring(0, 100) });
586
+ // Use the helper function to remove self-mentions
587
+ const result = removeMentionsFromParagraphBeginnings(trimmedResponse, agentId);
588
+ loggerAutoMention.debug('Self-mention removal result', {
589
+ agentId,
590
+ before: trimmedResponse.substring(0, 100),
591
+ after: result.substring(0, 100),
592
+ changed: trimmedResponse !== result
593
+ });
594
+ // Preserve original leading whitespace
595
+ const originalMatch = response.match(/^(\s*)/);
596
+ const originalLeadingWhitespace = originalMatch ? originalMatch[1] : '';
597
+ return originalLeadingWhitespace + result;
598
+ } /**
599
+ * Agent subscription with automatic message processing
600
+ */
601
+ export function subscribeAgentToMessages(world, agent) {
602
+ const handler = async (messageEvent) => {
603
+ loggerAgent.debug('Agent received message', {
604
+ agentId: agent.id,
605
+ sender: messageEvent.sender,
606
+ messageId: messageEvent.messageId
607
+ });
608
+ if (!messageEvent.messageId) {
609
+ loggerAgent.error('Received message WITHOUT messageId', {
610
+ agentId: agent.id,
611
+ sender: messageEvent.sender,
612
+ worldId: world.id
613
+ });
614
+ }
615
+ // Check if this is an assistant message with tool_calls (approval request)
616
+ // These need to be saved to agent memory even though they're from the agent
617
+ const messageData = messageEvent;
618
+ if (messageData.role === 'assistant' && messageData.tool_calls && messageEvent.sender === agent.id) {
619
+ loggerMemory.debug('Saving approval request to agent memory', {
620
+ agentId: agent.id,
621
+ messageId: messageEvent.messageId,
622
+ toolCalls: messageData.tool_calls.length
623
+ });
624
+ const approvalMessage = {
625
+ role: 'assistant',
626
+ content: messageEvent.content || '',
627
+ sender: agent.id,
628
+ createdAt: messageEvent.timestamp,
629
+ chatId: world.currentChatId || null,
630
+ messageId: messageEvent.messageId,
631
+ replyToMessageId: messageData.replyToMessageId,
632
+ tool_calls: messageData.tool_calls,
633
+ agentId: agent.id
634
+ };
635
+ agent.memory.push(approvalMessage);
636
+ // Auto-save agent memory
637
+ try {
638
+ const storage = await getStorageWrappers();
639
+ await storage.saveAgent(world.id, agent);
640
+ loggerMemory.debug('Approval request saved to agent memory', {
641
+ agentId: agent.id,
642
+ messageId: messageEvent.messageId
643
+ });
644
+ }
645
+ catch (error) {
646
+ loggerMemory.error('Failed to save approval request to memory', {
647
+ agentId: agent.id,
648
+ error: error instanceof Error ? error.message : error
649
+ });
650
+ }
651
+ return; // Don't process this message further
652
+ }
653
+ // Check if this is a tool result message (approval response)
654
+ // These need to be saved to agent memory for persistence
655
+ if (messageData.role === 'tool' && messageData.tool_call_id) {
656
+ loggerMemory.debug('Saving approval response to agent memory', {
657
+ agentId: agent.id,
658
+ messageId: messageEvent.messageId,
659
+ toolCallId: messageData.tool_call_id
660
+ });
661
+ const approvalResponse = {
662
+ role: 'tool',
663
+ content: messageEvent.content || '',
664
+ sender: messageEvent.sender || 'system',
665
+ createdAt: messageEvent.timestamp,
666
+ chatId: world.currentChatId || null,
667
+ messageId: messageEvent.messageId,
668
+ tool_call_id: messageData.tool_call_id,
669
+ agentId: agent.id
670
+ };
671
+ agent.memory.push(approvalResponse);
672
+ // Auto-save agent memory
673
+ try {
674
+ const storage = await getStorageWrappers();
675
+ await storage.saveAgent(world.id, agent);
676
+ loggerMemory.debug('Approval response saved to agent memory', {
677
+ agentId: agent.id,
678
+ messageId: messageEvent.messageId
679
+ });
680
+ }
681
+ catch (error) {
682
+ loggerMemory.error('Failed to save approval response to memory', {
683
+ agentId: agent.id,
684
+ error: error instanceof Error ? error.message : error
685
+ });
686
+ }
687
+ return; // Don't process this message further
688
+ }
689
+ // Skip messages from this agent itself
690
+ if (messageEvent.sender === agent.id) {
691
+ loggerAgent.debug('Skipping own message in handler', { agentId: agent.id, sender: messageEvent.sender });
692
+ return;
693
+ }
694
+ // Reset LLM call count if needed (for human/system messages)
695
+ await resetLLMCallCountIfNeeded(world, agent, messageEvent);
696
+ // Process message if agent should respond
697
+ loggerResponse.debug('Checking if agent should respond', { agentId: agent.id, sender: messageEvent.sender });
698
+ const shouldRespond = await shouldAgentRespond(world, agent, messageEvent);
699
+ if (shouldRespond) {
700
+ // Save incoming messages to agent memory only when they plan to respond
701
+ await saveIncomingMessageToMemory(world, agent, messageEvent);
702
+ loggerAgent.debug('Agent will respond - processing message', { agentId: agent.id, sender: messageEvent.sender });
703
+ await processAgentMessage(world, agent, messageEvent);
704
+ }
705
+ else {
706
+ loggerAgent.debug('Agent will NOT respond - skipping memory save and SSE publishing', {
707
+ agentId: agent.id,
708
+ sender: messageEvent.sender
709
+ });
710
+ }
711
+ };
712
+ return subscribeToMessages(world, handler);
713
+ }
714
+ /**
715
+ * Save incoming message to agent memory with auto-save
716
+ */
717
+ export async function saveIncomingMessageToMemory(world, agent, messageEvent) {
718
+ try {
719
+ if (messageEvent.sender?.toLowerCase() === agent.id.toLowerCase())
720
+ return;
721
+ if (!messageEvent.messageId) {
722
+ loggerMemory.error('Message missing messageId', {
723
+ agentId: agent.id,
724
+ sender: messageEvent.sender,
725
+ worldId: world.id
726
+ });
727
+ }
728
+ if (!world.currentChatId) {
729
+ loggerMemory.warn('Saving message without chatId', {
730
+ agentId: agent.id,
731
+ messageId: messageEvent.messageId
732
+ });
733
+ }
734
+ // Parse message content to detect enhanced format (e.g., tool results)
735
+ const { message: parsedMessage } = parseMessageContent(messageEvent.content, 'user');
736
+ const userMessage = {
737
+ ...parsedMessage,
738
+ sender: messageEvent.sender,
739
+ createdAt: messageEvent.timestamp,
740
+ chatId: world.currentChatId || null,
741
+ messageId: messageEvent.messageId,
742
+ replyToMessageId: messageEvent.replyToMessageId,
743
+ agentId: agent.id
744
+ };
745
+ agent.memory.push(userMessage);
746
+ try {
747
+ const storage = await getStorageWrappers();
748
+ await storage.saveAgent(world.id, agent);
749
+ loggerMemory.debug('Agent saved successfully', {
750
+ agentId: agent.id,
751
+ messageId: messageEvent.messageId
752
+ });
753
+ }
754
+ catch (error) {
755
+ loggerMemory.error('Failed to auto-save memory', { agentId: agent.id, error: error instanceof Error ? error.message : error });
756
+ }
757
+ }
758
+ catch (error) {
759
+ loggerMemory.error('Could not save incoming message to memory', { agentId: agent.id, error: error instanceof Error ? error.message : error });
760
+ }
761
+ }
762
+ /**
763
+ * Agent message processing with LLM response generation and auto-mention logic
764
+ */
765
+ export async function processAgentMessage(world, agent, messageEvent) {
766
+ const completeActivity = beginWorldActivity(world, `agent:${agent.id}`);
767
+ try {
768
+ // Load conversation history from storage for current chat (last 10 messages)
769
+ // NOTE: Don't save incoming message yet to avoid duplication in prepareMessagesForLLM
770
+ let conversationHistory = [];
771
+ try {
772
+ const storage = await getStorageWrappers();
773
+ const allMessages = await storage.getMemory(world.id, world.currentChatId);
774
+ conversationHistory = allMessages.slice(-10); // Get last 10 messages for current chat
775
+ }
776
+ catch (error) {
777
+ loggerMemory.error('Could not load conversation history from storage', { agentId: agent.id, chatId: world.currentChatId, error: error instanceof Error ? error.message : error });
778
+ }
779
+ // Prepare messages for LLM with history + current message
780
+ const messageData = {
781
+ id: messageEvent.messageId || generateId(),
782
+ name: 'message',
783
+ sender: messageEvent.sender,
784
+ content: messageEvent.content,
785
+ payload: {}
786
+ };
787
+ const messages = prepareMessagesForLLM(agent, messageData, conversationHistory);
788
+ // Note: Incoming message already saved in subscribeAgentToMessages handler
789
+ // Increment LLM call count and save agent state
790
+ agent.llmCallCount++;
791
+ agent.lastLLMCall = new Date();
792
+ try {
793
+ const storage = await getStorageWrappers();
794
+ await storage.saveAgent(world.id, agent);
795
+ }
796
+ catch (error) {
797
+ loggerAgent.error('Failed to auto-save agent after LLM call increment', { agentId: agent.id, error: error instanceof Error ? error.message : error });
798
+ }
799
+ // Generate LLM response (streaming or non-streaming)
800
+ let response;
801
+ let messageId;
802
+ if (globalStreamingEnabled) {
803
+ const { streamAgentResponse } = await import('./llm-manager.js');
804
+ const result = await streamAgentResponse(world, agent, messages, publishSSE);
805
+ response = result.response;
806
+ messageId = result.messageId; // Use the same messageId from streaming
807
+ }
808
+ else {
809
+ const { generateAgentResponse } = await import('./llm-manager.js');
810
+ response = await generateAgentResponse(world, agent, messages);
811
+ messageId = generateId(); // Generate new ID for non-streaming
812
+ }
813
+ if (!response) {
814
+ // Empty response could mean approval request was sent or actual error
815
+ // For approval requests, this is normal behavior - just return silently
816
+ loggerAgent.debug('LLM response is empty - could be approval request or error', { agentId: agent.id });
817
+ return;
818
+ }
819
+ // Process auto-mention logic: remove self-mentions, then add auto-mention if needed
820
+ let finalResponse = removeSelfMentions(response, agent.id);
821
+ if (shouldAutoMention(finalResponse, messageEvent.sender, agent.id)) {
822
+ finalResponse = addAutoMention(finalResponse, messageEvent.sender);
823
+ }
824
+ if (!messageEvent.messageId) {
825
+ loggerMemory.error('messageEvent.messageId required for threading', {
826
+ agentId: agent.id,
827
+ sender: messageEvent.sender
828
+ });
829
+ }
830
+ // Save final response to memory with pre-generated ID and parent link
831
+ const assistantMessage = {
832
+ role: 'assistant',
833
+ content: finalResponse,
834
+ createdAt: new Date(),
835
+ chatId: world.currentChatId || null,
836
+ messageId: messageId,
837
+ replyToMessageId: messageEvent.messageId, // Link to message we're replying to
838
+ sender: agent.id, // Add sender field for consistency
839
+ agentId: agent.id
840
+ };
841
+ // Validate threading before saving
842
+ try {
843
+ const { validateMessageThreading } = await import('./types.js');
844
+ const validationContext = [...agent.memory, assistantMessage];
845
+ validateMessageThreading(assistantMessage, validationContext);
846
+ }
847
+ catch (error) {
848
+ loggerMemory.error('Threading validation failed', {
849
+ agentId: agent.id,
850
+ messageId: assistantMessage.messageId,
851
+ error: error instanceof Error ? error.message : error
852
+ });
853
+ // Clear threading for critical errors (self-reference, circular, depth exceeded)
854
+ if (error instanceof Error &&
855
+ (error.message.includes('cannot reply to itself') ||
856
+ error.message.includes('Circular reference detected') ||
857
+ error.message.includes('Thread depth exceeds maximum'))) {
858
+ loggerMemory.warn('Clearing threading due to critical error', {
859
+ agentId: agent.id,
860
+ error: error.message
861
+ });
862
+ assistantMessage.replyToMessageId = undefined;
863
+ }
864
+ }
865
+ agent.memory.push(assistantMessage);
866
+ // Publish final response with pre-generated messageId and threading info
867
+ if (finalResponse && typeof finalResponse === 'string') {
868
+ publishMessageWithId(world, finalResponse, agent.id, messageId, world.currentChatId, messageEvent.messageId);
869
+ }
870
+ // Auto-save memory after adding response (now with correct messageId)
871
+ try {
872
+ const storage = await getStorageWrappers();
873
+ await storage.saveAgent(world.id, agent);
874
+ }
875
+ catch (error) {
876
+ loggerMemory.error('Failed to auto-save memory after response', { agentId: agent.id, error: error instanceof Error ? error.message : error });
877
+ }
878
+ }
879
+ catch (error) {
880
+ loggerAgent.error('Agent failed to process message', { agentId: agent.id, error: error instanceof Error ? error.message : error });
881
+ publishEvent(world, 'system', { message: `[Error] ${error.message}`, type: 'error' });
882
+ }
883
+ finally {
884
+ completeActivity();
885
+ }
886
+ }
887
+ /**
888
+ * Reset LLM call count for human/world messages with persistence
889
+ */
890
+ export async function resetLLMCallCountIfNeeded(world, agent, messageEvent) {
891
+ const senderType = determineSenderType(messageEvent.sender);
892
+ if ((senderType === SenderType.HUMAN || senderType === SenderType.WORLD) && agent.llmCallCount > 0) {
893
+ loggerTurnLimit.debug('Resetting LLM call count', { agentId: agent.id, oldCount: agent.llmCallCount });
894
+ agent.llmCallCount = 0;
895
+ try {
896
+ const storage = await getStorageWrappers();
897
+ await storage.saveAgent(world.id, agent);
898
+ }
899
+ catch (error) {
900
+ loggerTurnLimit.warn('Failed to auto-save agent after turn limit reset', { agentId: agent.id, error: error instanceof Error ? error.message : error });
901
+ }
902
+ }
903
+ }
904
+ /**
905
+ * Enhanced message filtering logic with turn limits and mention detection
906
+ */
907
+ export async function shouldAgentRespond(world, agent, messageEvent) {
908
+ // Never respond to own messages
909
+ if (messageEvent.sender?.toLowerCase() === agent.id.toLowerCase()) {
910
+ loggerResponse.debug('Skipping own message', { agentId: agent.id, sender: messageEvent.sender });
911
+ return false;
912
+ }
913
+ const content = messageEvent.content || '';
914
+ // Never respond to turn limit messages (prevents endless loops)
915
+ if (content.includes('Turn limit reached')) {
916
+ loggerTurnLimit.debug('Skipping turn limit message', { agentId: agent.id });
917
+ return false;
918
+ }
919
+ // Check turn limit based on LLM call count
920
+ const worldTurnLimit = getWorldTurnLimit(world);
921
+ loggerTurnLimit.debug('Checking turn limit', { agentId: agent.id, llmCallCount: agent.llmCallCount, worldTurnLimit });
922
+ if (agent.llmCallCount >= worldTurnLimit) {
923
+ loggerTurnLimit.debug('Turn limit reached, sending turn limit message', { agentId: agent.id, llmCallCount: agent.llmCallCount, worldTurnLimit });
924
+ const turnLimitMessage = `@human Turn limit reached (${worldTurnLimit} LLM calls). Please take control of the conversation.`;
925
+ publishMessage(world, turnLimitMessage, agent.id);
926
+ return false;
927
+ }
928
+ // Determine sender type for message handling logic
929
+ const senderType = determineSenderType(messageEvent.sender);
930
+ loggerResponse.debug('Determined sender type', { agentId: agent.id, sender: messageEvent.sender, senderType });
931
+ // Never respond to system messages
932
+ if (messageEvent.sender === 'system') {
933
+ loggerResponse.debug('Skipping system message', { agentId: agent.id });
934
+ return false;
935
+ }
936
+ // Always respond to world messages
937
+ if (messageEvent.sender === 'world') {
938
+ loggerResponse.debug('Responding to world message', { agentId: agent.id });
939
+ return true;
940
+ }
941
+ const anyMentions = extractMentions(messageEvent.content);
942
+ const mentions = extractParagraphBeginningMentions(messageEvent.content);
943
+ loggerResponse.debug('Extracted mentions', { mentions, anyMentions });
944
+ // For HUMAN messages
945
+ if (senderType === SenderType.HUMAN) {
946
+ if (mentions.length === 0) {
947
+ if (anyMentions.length > 0) {
948
+ loggerResponse.debug('Mentions exist but not at paragraph beginning', { agentId: agent.id });
949
+ return false;
950
+ }
951
+ loggerResponse.debug('No mentions - public message', { agentId: agent.id });
952
+ return true;
953
+ }
954
+ const shouldRespond = mentions.includes(agent.id.toLowerCase());
955
+ loggerResponse.debug('HUMAN message mention check', { agentId: agent.id, shouldRespond });
956
+ return shouldRespond;
957
+ }
958
+ // For agent messages, only respond if this agent has a paragraph-beginning mention
959
+ const shouldRespond = mentions.includes(agent.id.toLowerCase());
960
+ loggerResponse.debug('AGENT message mention check', { agentId: agent.id, shouldRespond });
961
+ return shouldRespond;
962
+ }
963
+ /**
964
+ * Subscribe world to messages with cleanup function
965
+ */
966
+ export function subscribeWorldToMessages(world) {
967
+ return subscribeToMessages(world, async (_event) => {
968
+ // No-op - title updates handled by setupWorldActivityListener on idle
969
+ });
970
+ }
971
+ /**
972
+ * Setup world activity listener for chat title updates
973
+ * Triggers title generation when world becomes idle (pendingOperations === 0)
974
+ */
975
+ export function setupWorldActivityListener(world) {
976
+ const handler = async (event) => {
977
+ // Only update title when world becomes idle (all agents done)
978
+ if (event.type === 'idle' && event.pendingOperations === 0) {
979
+ try {
980
+ if (!world.currentChatId)
981
+ return;
982
+ const chat = world.chats.get(world.currentChatId);
983
+ if (!chat)
984
+ return;
985
+ // Only update if still default title
986
+ if (chat.name === 'New Chat') {
987
+ const title = await generateChatTitleFromMessages(world, '');
988
+ if (title) {
989
+ chat.name = title;
990
+ const storage = await getStorageWrappers();
991
+ await storage.updateChatData(world.id, world.currentChatId, { name: title });
992
+ publishEvent(world, 'system', `chat-title-updated`);
993
+ }
994
+ }
995
+ }
996
+ catch (err) {
997
+ loggerChatTitle.warn('Activity-based title update failed', { error: err instanceof Error ? err.message : err });
998
+ }
999
+ }
1000
+ };
1001
+ world.eventEmitter.on('world', handler);
1002
+ return () => world.eventEmitter.off('world', handler);
1003
+ }
1004
+ /**
1005
+ * Generate chat title from message content with LLM support and fallback
1006
+ */
1007
+ async function generateChatTitleFromMessages(world, content) {
1008
+ loggerChatTitle.debug('Generating chat title', { worldId: world.id, contentStart: content.substring(0, 50) });
1009
+ let title = '';
1010
+ let messages = [];
1011
+ const maxLength = 100; // Max title length
1012
+ try {
1013
+ const firstAgent = Array.from(world.agents.values())[0];
1014
+ const storage = await getStorageWrappers();
1015
+ // Load messages for current chat only, not all messages
1016
+ messages = await storage.getMemory(world.id, world.currentChatId);
1017
+ if (content)
1018
+ messages.push({ role: 'user', content });
1019
+ loggerChatTitle.debug('Calling LLM for title generation', {
1020
+ messageCount: messages.length,
1021
+ provider: world.chatLLMProvider || firstAgent?.provider,
1022
+ model: world.chatLLMModel || firstAgent?.model
1023
+ });
1024
+ const tempAgent = {
1025
+ provider: world.chatLLMProvider || firstAgent?.provider || 'openai',
1026
+ model: world.chatLLMModel || firstAgent?.model || 'gpt-4',
1027
+ systemPrompt: 'You are a helpful assistant that turns conversations into concise titles.',
1028
+ maxTokens: 20,
1029
+ };
1030
+ const userPrompt = {
1031
+ role: 'user',
1032
+ content: `Below is a conversation between a user and an assistant. Generate a short, punchy title (3–6 words) that captures its main topic.
1033
+
1034
+ ${messages.filter(msg => msg.role !== 'tool').map(msg => `-${msg.role}: ${msg.content}`).join('\n')}
1035
+ `
1036
+ };
1037
+ title = await generateAgentResponse(world, tempAgent, [userPrompt], undefined, true); // skipTools = true for title generation
1038
+ loggerChatTitle.debug('LLM generated title', { rawTitle: title });
1039
+ }
1040
+ catch (error) {
1041
+ loggerChatTitle.warn('Failed to generate LLM title, using fallback', {
1042
+ error: error instanceof Error ? error.message : error
1043
+ });
1044
+ }
1045
+ if (!title) {
1046
+ // Fallback: use content if provided, otherwise extract from first user message
1047
+ title = content.trim();
1048
+ if (!title && messages?.length > 0) {
1049
+ const firstUserMsg = messages.find((msg) => msg.role === 'user');
1050
+ title = firstUserMsg?.content?.substring(0, 50) || 'Chat';
1051
+ }
1052
+ if (!title)
1053
+ title = 'Chat';
1054
+ }
1055
+ title = title.trim().replace(/^["']|["']$/g, ''); // Remove quotes
1056
+ title = title.replace(/[\n\r\*]+/g, ' '); // Replace newlines with spaces
1057
+ title = title.replace(/\s+/g, ' '); // Normalize whitespace
1058
+ // Truncate if too long
1059
+ if (title.length > maxLength) {
1060
+ title = title.substring(0, maxLength - 3) + '...';
1061
+ }
1062
+ loggerChatTitle.debug('Final processed title', { title, originalLength: title.length });
1063
+ return title;
1064
+ }
1065
+ /**
1066
+ * Check if a specific tool requires approval based on message history
1067
+ * Simplified: Only checks for session-wide approval, not one-time or denials
1068
+ *
1069
+ * Logic:
1070
+ * 1. Search for session approval → Execute immediately
1071
+ * 2. No session approval → Request approval
1072
+ *
1073
+ * @param context - Execution context with workingDirectory (CRITICAL FIX: AR Issue #1)
1074
+ */
1075
+ export async function checkToolApproval(world, toolName, toolArgs, message, messages, context) {
1076
+ try {
1077
+ // Check for session-wide approval ONLY (matches name + directory + params)
1078
+ const workingDirectory = context?.workingDirectory || process.cwd();
1079
+ const sessionApproval = findSessionApproval(messages, toolName, toolArgs, workingDirectory);
1080
+ if (sessionApproval) {
1081
+ return {
1082
+ needsApproval: false,
1083
+ canExecute: true
1084
+ };
1085
+ }
1086
+ // No session approval found - need to request approval
1087
+ return {
1088
+ needsApproval: true,
1089
+ canExecute: false,
1090
+ approvalRequest: {
1091
+ toolName,
1092
+ toolArgs,
1093
+ message,
1094
+ workingDirectory, // Include for session approval matching
1095
+ requestId: `approval-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1096
+ options: ['deny', 'approve_once', 'approve_session']
1097
+ }
1098
+ };
1099
+ }
1100
+ catch (error) {
1101
+ loggerAgent.error('Error checking tool approval', {
1102
+ toolName,
1103
+ error: error instanceof Error ? error.message : error
1104
+ });
1105
+ return {
1106
+ needsApproval: true,
1107
+ canExecute: false,
1108
+ approvalRequest: {
1109
+ toolName,
1110
+ toolArgs,
1111
+ message,
1112
+ workingDirectory: context?.workingDirectory || process.cwd(), // Include even in error case
1113
+ requestId: `approval-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1114
+ options: ['deny', 'approve_once', 'approve_session']
1115
+ }
1116
+ };
1117
+ }
1118
+ }
1119
+ /**
1120
+ * Find session-wide approval for a tool in message history
1121
+ * Supports both enhanced string protocol (JSON) and legacy text parsing
1122
+ *
1123
+ * Session approval matches on:
1124
+ * - Tool name (required)
1125
+ * - Working directory (if provided)
1126
+ * - Parameters (exact match)
1127
+ *
1128
+ * Enhanced protocol format:
1129
+ * {
1130
+ * role: 'tool',
1131
+ * tool_call_id: 'approval_...',
1132
+ * content: '{"__type":"tool_result","content":"{\"decision\":\"approve\",\"scope\":\"session\",\"toolName\":\"...\",\"toolArgs\":{...},\"workingDirectory\":\"...\"}"}'
1133
+ * }
1134
+ */
1135
+ export function findSessionApproval(messages, toolName, toolArgs, workingDirectory) {
1136
+ for (let i = messages.length - 1; i >= 0; i--) {
1137
+ const msg = messages[i];
1138
+ // Primary: Enhanced string protocol (JSON tool result)
1139
+ if (msg.role === 'tool' && msg.tool_call_id && msg.content) {
1140
+ try {
1141
+ const parsed = JSON.parse(msg.content);
1142
+ if (parsed.__type === 'tool_result' && parsed.content) {
1143
+ const result = JSON.parse(parsed.content);
1144
+ if (result.decision === 'approve' &&
1145
+ result.scope === 'session' &&
1146
+ result.toolName?.toLowerCase() === toolName.toLowerCase()) {
1147
+ // Match working directory if provided in approval
1148
+ if (result.workingDirectory && workingDirectory) {
1149
+ if (result.workingDirectory !== workingDirectory) {
1150
+ continue; // Directory mismatch, keep searching
1151
+ }
1152
+ }
1153
+ // Match parameters (exact deep equality)
1154
+ if (result.toolArgs && toolArgs) {
1155
+ const argsMatch = JSON.stringify(result.toolArgs) === JSON.stringify(toolArgs);
1156
+ if (!argsMatch) {
1157
+ continue; // Parameters mismatch, keep searching
1158
+ }
1159
+ }
1160
+ return { decision: 'approve', scope: 'session', toolName };
1161
+ }
1162
+ }
1163
+ }
1164
+ catch (e) {
1165
+ // Not JSON or malformed, continue to fallback
1166
+ }
1167
+ }
1168
+ // Fallback: Legacy text parsing (backwards compatibility)
1169
+ if (msg.content && typeof msg.content === 'string') {
1170
+ const content = msg.content.toLowerCase();
1171
+ if ((content.includes('approve') && content.includes(toolName.toLowerCase()) && content.includes('session')) ||
1172
+ (content.includes(`approve_session`) && content.includes(toolName.toLowerCase()))) {
1173
+ // ⚠️ Security warning: Legacy approvals don't check parameters
1174
+ loggerMemory.warn('Using legacy text-based approval (no parameter/directory check)', {
1175
+ toolName,
1176
+ security: 'UNSCOPED - all parameters and directories allowed for this tool'
1177
+ });
1178
+ return { decision: 'approve', scope: 'session', toolName };
1179
+ }
1180
+ }
1181
+ }
1182
+ return undefined;
1183
+ }
1184
+ /**
1185
+ * @deprecated This function is no longer used in approval checking logic.
1186
+ * One-time approvals are consumed after tool execution, checking for them is redundant.
1187
+ * Kept for backwards compatibility only.
1188
+ */
1189
+ export function findRecentApproval(messages, toolName) {
1190
+ loggerMemory.warn('DEPRECATED: findRecentApproval() uses text parsing. Migrate to enhanced string protocol with __type: "tool_result"', {
1191
+ toolName,
1192
+ hint: 'Send JSON.stringify({__type:"tool_result",tool_call_id:"...",content:"..."})'
1193
+ });
1194
+ const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
1195
+ let approvalIndex = -1;
1196
+ for (let i = messages.length - 1; i >= 0; i--) {
1197
+ const msg = messages[i];
1198
+ if (msg.createdAt && msg.createdAt < fiveMinutesAgo)
1199
+ break;
1200
+ if (msg.content && typeof msg.content === 'string') {
1201
+ const content = msg.content.toLowerCase();
1202
+ if ((content.includes('approve') && content.includes(toolName.toLowerCase()) &&
1203
+ (content.includes('once') || (!content.includes('session')))) ||
1204
+ (content.includes(`approve_once`) && content.includes(toolName.toLowerCase()))) {
1205
+ approvalIndex = i;
1206
+ break;
1207
+ }
1208
+ }
1209
+ }
1210
+ if (approvalIndex === -1)
1211
+ return undefined;
1212
+ // Check if approval has been consumed by subsequent tool execution
1213
+ for (let i = approvalIndex + 1; i < messages.length; i++) {
1214
+ const msg = messages[i];
1215
+ if (msg.content && typeof msg.content === 'string') {
1216
+ const content = msg.content.toLowerCase();
1217
+ if ((content.includes('tool') && content.includes(toolName.toLowerCase()) &&
1218
+ (content.includes('executed') || content.includes('completed') || content.includes('finished'))) ||
1219
+ (content.includes(toolName.toLowerCase()) && content.includes('successfully'))) {
1220
+ return undefined;
1221
+ }
1222
+ }
1223
+ }
1224
+ return { decision: 'approve', scope: 'once', toolName };
1225
+ }
1226
+ /**
1227
+ * @deprecated This function is no longer used in approval checking logic.
1228
+ * Users should be allowed to change their mind about denials.
1229
+ * Kept for backwards compatibility only.
1230
+ */
1231
+ export function findRecentDenial(messages, toolName) {
1232
+ const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
1233
+ // Look for recent denial
1234
+ for (let i = messages.length - 1; i >= 0; i--) {
1235
+ const msg = messages[i];
1236
+ if (msg.createdAt && msg.createdAt < fiveMinutesAgo) {
1237
+ break; // Stop if we've gone back more than 5 minutes
1238
+ }
1239
+ if (msg.content && typeof msg.content === 'string') {
1240
+ const content = msg.content.toLowerCase();
1241
+ if (content.includes('deny') && content.includes(toolName.toLowerCase())) {
1242
+ return { decision: 'deny', toolName };
1243
+ }
1244
+ }
1245
+ }
1246
+ return undefined;
1247
+ }
1248
+ //# sourceMappingURL=events.js.map