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,705 @@
1
+ /**
2
+ * World Markdown Export Functionality
3
+ *
4
+ * Provides comprehensive world export functionality to Markdown format including:
5
+ * - World configuration and metadata
6
+ * - Agent configurations and system prompts (memory excluded)
7
+ * - Chat sessions with complete message histories
8
+ * - World events in chronological order with CLI-style formatting
9
+ * - Improved message labeling for better readability
10
+ * - In-memory message detection (messages received without reply)
11
+ * - Timestamp formatting and content preservation
12
+ * - Message deduplication using messageId (consistent with frontend)
13
+ *
14
+ * Features:
15
+ * - Complete world export with only the current chat
16
+ * - Agent configuration details (without memory)
17
+ * - Current chat message history with improved formatting:
18
+ * - Human messages: "From: HUMAN / To: agent1, agent2"
19
+ * - Agent incoming: "Agent: agentName (incoming from sender)"
20
+ * - Agent reply: "Agent: agentName (reply to targetName)"
21
+ * - In-memory detection: "[in-memory, no reply]" for received messages without response
22
+ * - Chat events section for current chat only with chronological order:
23
+ * - Message events: ● [message] sender: full content (no truncation)
24
+ * - SSE events: ● [sse] agent: type content (shows full streaming chunks)
25
+ * - World events: ● [world] agent: activity pending=N (activity tracking)
26
+ * - Tool events: ● [tool] agent type (tool execution)
27
+ * - System events: ● [system] full content (no truncation)
28
+ * - Timestamp shown as HH:MM:SS for readability
29
+ * - Structured markdown with clear sections and navigation
30
+ * - Uses getMemory() for efficient message retrieval
31
+ * - O(n) messageId-based deduplication (replaces O(n²) content-based approach)
32
+ * - Tool call detection and summarization
33
+ * - Enhanced tool call request display with tool name and argument details
34
+ * - Enhanced tool result display with tool name and argument details
35
+ *
36
+ * Message Format Examples:
37
+ * ```
38
+ * From: HUMAN
39
+ * To: a1
40
+ * Time: 2025-10-25T21:24:51.218Z
41
+ * hi
42
+ *
43
+ * Agent: o1 (incoming from HUMAN)
44
+ * Time: 2025-10-25T21:24:57.105Z
45
+ * [Tool: shell_cmd (command: ls -la, cwd: /home/user)]
46
+ *
47
+ * Agent: o1 (reply to human)
48
+ * Time: 2025-10-25T21:24:57.105Z
49
+ * [2 tool calls:
50
+ * 1. read_file (filePath: /path/to/file.txt, limit: 100)
51
+ * 2. grep_search (query: pattern, isRegexp: true, ...)]
52
+ *
53
+ * Agent: a1 (tool result)
54
+ * Time: 2025-10-25T21:24:58.395Z
55
+ * [Tool: run_command (command: ls -la, cwd: /home/user)]
56
+ *
57
+ * Agent: a1 (incoming from o1) [in-memory, no reply]
58
+ * Time: 2025-10-25T21:24:58.395Z
59
+ * Hi — how can I help you today?
60
+ * ```
61
+ *
62
+ * Event Format Examples:
63
+ * ```
64
+ * 1. `10:00:00` ● [message] human: Test message content
65
+ * 2. `10:01:00` ● [sse] Test Agent: start
66
+ * 3. `10:01:01` ● [sse] Test Agent: chunk Hello world
67
+ * 4. `10:01:02` ● [sse] Test Agent: end
68
+ * 5. `10:02:00` ● [world] a1: response-start pending=1
69
+ * 6. `10:03:00` ● [world] a1: response-end pending=0
70
+ * 7. `10:04:00` ● [tool] Test Agent tool-start
71
+ * 8. `10:05:00` ● [system] chat-title-updated
72
+ * ```
73
+ *
74
+ * Implementation:
75
+ * - Uses managers module for data access
76
+ * - Formats dates consistently as ISO strings
77
+ * - Preserves message content with proper escaping
78
+ * - Organizes export by logical sections (world → agents → current chat → events)
79
+ * - Simplified chat loading using world.chats.get() and getMemory()
80
+ * - Maps agentId to agent names for clear identification
81
+ * - Deduplicates user messages by messageId using Map for O(1) lookup
82
+ * - Detects in-memory messages by checking for subsequent assistant replies
83
+ * - Events displayed in chronological order for current chat only
84
+ * - Limits event display to 100 events with overflow indication
85
+ * - Event format: time ● [type] agent: event-name content
86
+ *
87
+ * Deduplication Strategy:
88
+ * - Only user messages with messageId are deduplicated
89
+ * - Uses exact messageId matching (no fuzzy content comparison)
90
+ * - Agent messages remain separate (one per agent)
91
+ * - Tracks which agents received each message via agentIds/agentNames arrays
92
+ * - Consistent with frontend deduplication logic for predictable behavior
93
+ *
94
+ * Changes:
95
+ * - 2025-11-11: Removed all content truncation - show full message and event content without cutting off
96
+ * - 2025-11-01: Fixed export to show events for current chat only (not all chats)
97
+ * - 2025-11-01: Reformatted events to show: time ● [type] agent: event-name content
98
+ * - 2025-11-01: SSE chunk events now properly display content in export
99
+ * - 2025-11-01: Fixed null chatId bug - all events now default to world.currentChatId during persistence
100
+ */
101
+ // Core module imports
102
+ import { createCategoryLogger } from './logger.js';
103
+ import { getWorld, listAgents, getAgent, getMemory } from './managers.js';
104
+ import { createStorageWithWrappers } from './storage/storage-factory.js';
105
+ // Initialize logger and storage
106
+ const logger = createCategoryLogger('core.export');
107
+ let storageWrappers = null;
108
+ async function initializeModules() {
109
+ // Skip storage initialization in test environment to prevent SQLite errors
110
+ // Tests use mocked storage from vitest-setup.ts
111
+ if (process.env.NODE_ENV === 'test') {
112
+ return;
113
+ }
114
+ storageWrappers = await createStorageWithWrappers();
115
+ }
116
+ const moduleInitialization = initializeModules();
117
+ async function buildAgentMap(worldId, agentSummaries) {
118
+ const summaries = agentSummaries ?? await listAgents(worldId);
119
+ const agentsMap = new Map();
120
+ for (const agentInfo of summaries) {
121
+ const fullAgent = await getAgent(worldId, agentInfo.id);
122
+ if (fullAgent) {
123
+ agentsMap.set(fullAgent.id, fullAgent);
124
+ }
125
+ }
126
+ return agentsMap;
127
+ }
128
+ function formatSenderLabel(message, agentsMap) {
129
+ const raw = message.sender;
130
+ const agent = message.agentId ? agentsMap.get(message.agentId) : null;
131
+ const agentName = agent ? agent.name : message.agentId;
132
+ if (message.role === 'user' || message.role === 'assistant') {
133
+ if (raw) {
134
+ const senderLabel = raw.toLowerCase() === 'human' ? 'HUMAN' : raw;
135
+ return agentName ? `${senderLabel} → ${agentName}` : senderLabel;
136
+ }
137
+ return agentName || undefined;
138
+ }
139
+ if (raw) {
140
+ return raw.toLowerCase() === 'human' ? 'HUMAN' : raw;
141
+ }
142
+ return undefined;
143
+ }
144
+ /**
145
+ * Export world configuration, agents, and chats to Markdown format
146
+ */
147
+ export async function exportWorldToMarkdown(worldName) {
148
+ await moduleInitialization;
149
+ // Load world configuration
150
+ const worldData = await getWorld(worldName);
151
+ if (!worldData) {
152
+ throw new Error(`World '${worldName}' not found`);
153
+ }
154
+ const agents = await listAgents(worldData.id);
155
+ const agentsMap = await buildAgentMap(worldData.id, agents);
156
+ // Get the current chat directly from the world, if any
157
+ const currentChat = worldData.currentChatId ? worldData.chats.get(worldData.currentChatId) : null;
158
+ const hasCurrentChat = currentChat !== null;
159
+ let markdown = `# World Export: ${worldData.name}\n\n`;
160
+ markdown += `**Exported on:** ${new Date().toISOString()}\n\n`;
161
+ // World Configuration Section
162
+ markdown += `## World Configuration\n\n`;
163
+ markdown += `- **Name:** ${worldData.name}\n`;
164
+ markdown += `- **ID:** ${worldData.id}\n`;
165
+ markdown += `- **Description:** ${worldData.description || 'No description'}\n`;
166
+ markdown += `- **Turn Limit:** ${worldData.turnLimit}\n`;
167
+ if (worldData.chatLLMProvider) {
168
+ markdown += `- **Chat LLM Provider:** ${worldData.chatLLMProvider}\n`;
169
+ }
170
+ if (worldData.chatLLMModel) {
171
+ markdown += `- **Chat LLM Model:** ${worldData.chatLLMModel}\n`;
172
+ }
173
+ markdown += `- **Total Agents:** ${agents.length}\n`;
174
+ markdown += `- **Total Chats:** ${worldData.chats.size}\n`;
175
+ markdown += `- **Current Chat:** ${currentChat ? currentChat.name : 'None'}\n`;
176
+ // Agents Section
177
+ if (agents.length > 0) {
178
+ markdown += `## Agents (${agents.length})\n\n`;
179
+ for (const agentInfo of agents) {
180
+ const fullAgent = await getAgent(worldData.id, agentInfo.id);
181
+ if (!fullAgent)
182
+ continue;
183
+ markdown += `### ${fullAgent.name}\n\n`;
184
+ markdown += `**Configuration:**\n`;
185
+ markdown += `- **ID:** ${fullAgent.id}\n`;
186
+ markdown += `- **LLM Provider:** ${fullAgent.provider}\n`;
187
+ markdown += `- **Model:** ${fullAgent.model}\n`;
188
+ markdown += `- **Temperature:** ${fullAgent.temperature || 'default'}\n`;
189
+ markdown += `- **Max Tokens:** ${fullAgent.maxTokens || 'default'}\n`;
190
+ markdown += `- **LLM Calls:** ${fullAgent.llmCallCount}\n`;
191
+ if (fullAgent.systemPrompt) {
192
+ markdown += `- **System Prompt:**\n`;
193
+ markdown += `\`\`\`\n${fullAgent.systemPrompt}\n\`\`\`\n\n`;
194
+ }
195
+ // Memory intentionally excluded from agent export
196
+ markdown += `---\n\n`;
197
+ }
198
+ }
199
+ else {
200
+ markdown += `## Agents\n\nNo agents found in this world.\n\n`;
201
+ }
202
+ // Current Chat Section
203
+ if (hasCurrentChat && currentChat) {
204
+ markdown += `## Current Chat - ${currentChat.name}\n\n`;
205
+ // Get chat messages using getMemory
206
+ try {
207
+ const chatMessages = await getMemory(worldData.id, currentChat.id);
208
+ if (chatMessages && chatMessages.length > 0) {
209
+ markdown += `**Messages (${chatMessages.length}):**\n\n`;
210
+ // Sort messages by timestamp if available
211
+ const sortedMessages = chatMessages.sort((a, b) => {
212
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
213
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
214
+ return dateA - dateB;
215
+ });
216
+ const messageMap = new Map();
217
+ const messagesWithoutId = [];
218
+ for (const message of sortedMessages) {
219
+ // Only deduplicate user messages with messageId (same as frontend)
220
+ const isUserMessage = message.role === 'user';
221
+ if (isUserMessage && message.messageId) {
222
+ const existing = messageMap.get(message.messageId);
223
+ if (existing) {
224
+ // Merge agent information for duplicate message
225
+ if (message.agentId) {
226
+ if (!existing.agentIds) {
227
+ existing.agentIds = existing.agentId ? [existing.agentId] : [];
228
+ }
229
+ if (!existing.agentIds.includes(message.agentId)) {
230
+ existing.agentIds.push(message.agentId);
231
+ }
232
+ // Collect agent names for display
233
+ const agent = agentsMap.get(message.agentId);
234
+ if (agent) {
235
+ if (!existing.agentNames) {
236
+ existing.agentNames = [];
237
+ // Add original agent's name if exists
238
+ if (existing.agentId) {
239
+ const originalAgent = agentsMap.get(existing.agentId);
240
+ if (originalAgent && !existing.agentNames.includes(originalAgent.name)) {
241
+ existing.agentNames.push(originalAgent.name);
242
+ }
243
+ }
244
+ }
245
+ if (!existing.agentNames.includes(agent.name)) {
246
+ existing.agentNames.push(agent.name);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ else {
252
+ // First occurrence - add to map
253
+ messageMap.set(message.messageId, {
254
+ ...message,
255
+ agentIds: message.agentId ? [message.agentId] : undefined,
256
+ agentNames: message.agentId && agentsMap.get(message.agentId)
257
+ ? [agentsMap.get(message.agentId).name]
258
+ : undefined
259
+ });
260
+ }
261
+ }
262
+ else {
263
+ // Keep all agent messages and messages without messageId separate
264
+ messagesWithoutId.push({
265
+ ...message,
266
+ agentIds: message.agentId ? [message.agentId] : undefined,
267
+ agentNames: message.agentId && agentsMap.get(message.agentId)
268
+ ? [agentsMap.get(message.agentId).name]
269
+ : undefined
270
+ });
271
+ }
272
+ }
273
+ // Combine deduplicated user messages with all other messages
274
+ // Maintain chronological order with logical flow: replies before incoming messages
275
+ const consolidatedMessages = [...Array.from(messageMap.values()), ...messagesWithoutId]
276
+ .sort((a, b) => {
277
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
278
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
279
+ // Primary sort: by timestamp
280
+ if (dateA !== dateB) {
281
+ return dateA - dateB;
282
+ }
283
+ // Secondary sort: when timestamps are equal, assistant (reply) comes before user (incoming)
284
+ // This ensures logical flow: agent replies first, then that reply is saved to other agents' memories
285
+ const roleOrderA = a.role === 'assistant' ? 0 : a.role === 'user' ? 1 : 2;
286
+ const roleOrderB = b.role === 'assistant' ? 0 : b.role === 'user' ? 1 : 2;
287
+ return roleOrderA - roleOrderB;
288
+ });
289
+ // Format consolidated messages with improved labeling
290
+ consolidatedMessages.forEach((message, index) => {
291
+ // Determine if this is a user message or agent message
292
+ const isUserMessage = message.role === 'user';
293
+ const isAssistantMessage = message.role === 'assistant';
294
+ // Special case: user messages with replyToMessageId are actually replies from agents
295
+ // This happens in multi-agent scenarios where agent responses are stored as 'user' messages
296
+ // in the receiving agent's memory but have threading information
297
+ const isReplyMessage = isUserMessage && message.replyToMessageId;
298
+ // Get sender information
299
+ const rawSender = message.sender?.toLowerCase() === 'human' ? 'HUMAN' : message.sender;
300
+ // Get agent/recipient information
301
+ // For user messages: show only FIRST agent (intended recipient), not all who received it
302
+ let agentNamesStr;
303
+ if (message.agentNames && message.agentNames.length > 0) {
304
+ // Show only first agent for user messages (the intended recipient)
305
+ agentNamesStr = isUserMessage ? message.agentNames[0] : message.agentNames.join(', ');
306
+ }
307
+ else if (message.agentIds && message.agentIds.length > 0) {
308
+ agentNamesStr = isUserMessage ? message.agentIds[0] : message.agentIds.join(', ');
309
+ }
310
+ else if (message.agentId) {
311
+ const agent = agentsMap.get(message.agentId);
312
+ agentNamesStr = agent ? agent.name : message.agentId;
313
+ }
314
+ // Build label based on message type
315
+ let label;
316
+ let messageType = '';
317
+ if (isUserMessage && !isReplyMessage) {
318
+ // User messages show who sent and who received
319
+ if (rawSender) {
320
+ if (rawSender === 'HUMAN') {
321
+ label = `From: ${rawSender}`;
322
+ // Frontend shows no \"To:\" line - just \"From: HUMAN\"
323
+ }
324
+ else {
325
+ // Agent sent to another agent (non-reply user message)
326
+ // This should be rare - most cross-agent messages should have replyToMessageId
327
+ label = `Agent: ${agentNamesStr || 'Unknown'} (message from ${rawSender})`;
328
+ }
329
+ }
330
+ else {
331
+ label = agentNamesStr ? `Agent: ${agentNamesStr} (message)` : 'Unknown sender';
332
+ }
333
+ }
334
+ else if (isAssistantMessage || isReplyMessage) {
335
+ // Assistant messages OR user messages with replyToMessageId are both replies
336
+ // Look up the reply target from replyToMessageId
337
+ let replyTarget = null;
338
+ if (message.replyToMessageId) {
339
+ const parentMessage = consolidatedMessages.find(m => m.messageId === message.replyToMessageId);
340
+ if (parentMessage) {
341
+ const parentSender = parentMessage.sender?.toLowerCase() === 'human' ? 'human' : parentMessage.sender;
342
+ replyTarget = parentSender || null;
343
+ }
344
+ }
345
+ if (isReplyMessage && rawSender && rawSender !== 'HUMAN') {
346
+ // Cross-agent reply: user message with replyToMessageId from another agent
347
+ // Look up the agent name for the sender
348
+ const senderAgent = agents.find(a => a.id === rawSender);
349
+ const senderName = senderAgent ? senderAgent.name : rawSender;
350
+ label = replyTarget
351
+ ? `Agent: ${senderName} (reply to ${replyTarget})`
352
+ : `Agent: ${senderName} (reply)`;
353
+ }
354
+ else if (isAssistantMessage) {
355
+ // Regular assistant message - use agentNamesStr
356
+ if (agentNamesStr) {
357
+ label = replyTarget
358
+ ? `Agent: ${agentNamesStr} (reply to ${replyTarget})`
359
+ : `Agent: ${agentNamesStr} (reply)`;
360
+ }
361
+ else {
362
+ label = replyTarget
363
+ ? `Unknown agent (reply to ${replyTarget})`
364
+ : 'Unknown agent (reply)';
365
+ }
366
+ }
367
+ else {
368
+ // isReplyMessage but from HUMAN - shouldn't happen but handle gracefully
369
+ label = replyTarget
370
+ ? `From: HUMAN (reply to ${replyTarget})`
371
+ : `From: HUMAN (reply)`;
372
+ }
373
+ }
374
+ else if (message.role === 'tool') {
375
+ // Tool result messages
376
+ label = agentNamesStr ? `Agent: ${agentNamesStr} (tool result)` : 'Tool result';
377
+ }
378
+ else {
379
+ // System or other messages
380
+ label = rawSender || message.role.toUpperCase();
381
+ }
382
+ let hasToolCalls = false;
383
+ // Check for tool_calls field first (proper AI SDK format)
384
+ if (message.tool_calls && message.tool_calls.length > 0) {
385
+ // Format tool calls with details
386
+ const toolCallDetails = message.tool_calls.map(tc => {
387
+ const toolName = tc.function?.name || 'unknown';
388
+ let toolArgs = '';
389
+ try {
390
+ const args = JSON.parse(tc.function?.arguments || '{}');
391
+ const argKeys = Object.keys(args);
392
+ if (argKeys.length > 0) {
393
+ // Show first 2-3 arguments with truncated values
394
+ const argSummary = argKeys.slice(0, 3).map(key => {
395
+ const val = args[key];
396
+ const strVal = typeof val === 'string' ? val : JSON.stringify(val);
397
+ return `${key}: ${strVal.length > 50 ? strVal.substring(0, 47) + '...' : strVal}`;
398
+ }).join(', ');
399
+ toolArgs = argKeys.length > 3 ? ` (${argSummary}, ...)` : ` (${argSummary})`;
400
+ }
401
+ }
402
+ catch {
403
+ toolArgs = '';
404
+ }
405
+ return `${toolName}${toolArgs}`;
406
+ });
407
+ if (message.tool_calls.length === 1) {
408
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n [Tool: ${toolCallDetails[0]}]\n \`\`\`\n\n`;
409
+ }
410
+ else {
411
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n [${message.tool_calls.length} tool calls:\n ${toolCallDetails.map((td, i) => `${i + 1}. ${td}`).join('\n ')}]\n \`\`\`\n\n`;
412
+ }
413
+ hasToolCalls = true;
414
+ }
415
+ // Handle tool role messages (tool results)
416
+ else if (message.role === 'tool') {
417
+ const toolCallId = message.tool_call_id || 'unknown';
418
+ // Find the tool call details from previous assistant messages
419
+ let toolName = 'unknown';
420
+ let toolArgs = '';
421
+ for (let i = index - 1; i >= 0; i--) {
422
+ const prevMsg = consolidatedMessages[i];
423
+ if (prevMsg.role === 'assistant' && prevMsg.tool_calls) {
424
+ const toolCall = prevMsg.tool_calls.find((tc) => tc.id === toolCallId);
425
+ if (toolCall) {
426
+ toolName = toolCall.function?.name || 'unknown';
427
+ try {
428
+ const args = JSON.parse(toolCall.function?.arguments || '{}');
429
+ const argKeys = Object.keys(args);
430
+ if (argKeys.length > 0) {
431
+ // Show first 2-3 arguments with truncated values
432
+ const argSummary = argKeys.slice(0, 3).map(key => {
433
+ const val = args[key];
434
+ const strVal = typeof val === 'string' ? val : JSON.stringify(val);
435
+ return `${key}: ${strVal.length > 50 ? strVal.substring(0, 47) + '...' : strVal}`;
436
+ }).join(', ');
437
+ toolArgs = argKeys.length > 3 ? ` (${argSummary}, ...)` : ` (${argSummary})`;
438
+ }
439
+ }
440
+ catch {
441
+ toolArgs = '';
442
+ }
443
+ break;
444
+ }
445
+ }
446
+ }
447
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n [Tool: ${toolName}${toolArgs}]\n \`\`\`\n\n`;
448
+ hasToolCalls = true;
449
+ }
450
+ // Fallback: check content string for tool call JSON objects
451
+ else if (typeof message.content === 'string') {
452
+ // Simple heuristic: if content is mostly JSON objects (starts with { and has multiple lines of {})
453
+ const lines = message.content.trim().split('\n');
454
+ const jsonLines = lines.filter(line => line.trim().startsWith('{') && line.trim().endsWith('}'));
455
+ if (jsonLines.length > 0 && jsonLines.length === lines.length) {
456
+ // All lines are JSON objects - likely tool calls
457
+ const validToolCalls = jsonLines.filter(line => {
458
+ try {
459
+ const parsed = JSON.parse(line.trim());
460
+ return parsed.hasOwnProperty('name') || parsed.hasOwnProperty('parameters') ||
461
+ parsed.hasOwnProperty('arguments') || parsed.hasOwnProperty('function');
462
+ }
463
+ catch {
464
+ return false;
465
+ }
466
+ });
467
+ if (validToolCalls.length > 0) {
468
+ const toolNames = validToolCalls
469
+ .map(line => {
470
+ try {
471
+ const parsed = JSON.parse(line.trim());
472
+ return parsed.function?.name || parsed.name || '';
473
+ }
474
+ catch {
475
+ return '';
476
+ }
477
+ })
478
+ .filter(name => name !== '');
479
+ if (toolNames.length > 0) {
480
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n [${toolNames.length} tool call${toolNames.length > 1 ? 's' : ''}: ${toolNames.join(', ')}]\n \`\`\`\n\n`;
481
+ }
482
+ else {
483
+ // Tool calls exist but names are empty - show count
484
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n [${validToolCalls.length} tool call${validToolCalls.length > 1 ? 's' : ''}]\n \`\`\`\n\n`;
485
+ }
486
+ hasToolCalls = true;
487
+ }
488
+ }
489
+ }
490
+ // Show regular content with proper markdown code block formatting
491
+ if (!hasToolCalls && typeof message.content === 'string' && message.content.trim()) {
492
+ // Preserve original content with newlines intact and escape any backticks
493
+ let formattedContent = message.content.trim();
494
+ // Convert literal \n to actual newlines for better readability
495
+ formattedContent = formattedContent.replace(/\\n/g, '\n');
496
+ // Escape any existing backticks in the content to prevent breaking code blocks
497
+ formattedContent = formattedContent.replace(/```/g, '\\`\\`\\`');
498
+ // Indent each line properly within the code block to maintain markdown structure
499
+ const indentedContent = formattedContent
500
+ .split('\n')
501
+ .map(line => ` ${line}`)
502
+ .join('\n');
503
+ markdown += `${index + 1}. **${label}**:\n \`\`\`\n${indentedContent}\n \`\`\`\n\n`;
504
+ }
505
+ else if (hasToolCalls && typeof message.content === 'string' && message.content.trim() && message.role === 'tool') {
506
+ // For tool messages with content, show full content in code block
507
+ let toolContent = message.content.trim();
508
+ // Convert literal \n to actual newlines and escape backticks
509
+ toolContent = toolContent.replace(/\\n/g, '\n').replace(/```/g, '\\`\\`\\`');
510
+ // Indent each line properly within the code block
511
+ const indentedToolContent = toolContent
512
+ .split('\n')
513
+ .map(line => ` ${line}`)
514
+ .join('\n');
515
+ markdown += ` \`\`\`\n${indentedToolContent}\n \`\`\`\n\n`;
516
+ }
517
+ });
518
+ const userMessageCount = sortedMessages.filter(m => m.role === 'user').length;
519
+ const deduplicatedUserCount = Array.from(messageMap.values()).length;
520
+ markdown += `*Note: ${sortedMessages.length} total messages (${userMessageCount} user, ${sortedMessages.length - userMessageCount} agent/system), `;
521
+ markdown += `${consolidatedMessages.length} after deduplication (${deduplicatedUserCount} unique user messages)*\n\n`;
522
+ }
523
+ else {
524
+ markdown += `**Messages:** No messages found for this chat\n\n`;
525
+ }
526
+ }
527
+ catch (error) {
528
+ logger.error('Failed to load chat messages', { chatId: currentChat.id, error: error instanceof Error ? error.message : error });
529
+ markdown += `**Messages:** Unable to load messages (${error instanceof Error ? error.message : 'Unknown error'})\n\n`;
530
+ }
531
+ markdown += `---\n\n`;
532
+ }
533
+ else {
534
+ markdown += `## Current Chat\n\nNo current chat found in this world.\n\n`;
535
+ }
536
+ // World Events Section
537
+ if (worldData.eventStorage && currentChat) {
538
+ try {
539
+ // Get events for current chat in chronological order
540
+ const allEvents = await worldData.eventStorage.getEventsByWorldAndChat(worldData.id, currentChat.id, { order: 'asc', limit: 1000 });
541
+ if (allEvents.length > 0) {
542
+ markdown += `## Chat Events (${allEvents.length})\n\n`;
543
+ const eventTypes = Array.from(new Set(allEvents.map((e) => e.type)));
544
+ markdown += `**Event Types:** ${eventTypes.join(', ')}\n\n`;
545
+ // Display events in chronological order (already sorted by query)
546
+ // Limit to 100 events to avoid overly large exports
547
+ const displayLimit = 100;
548
+ const eventsToDisplay = allEvents.slice(0, displayLimit);
549
+ eventsToDisplay.forEach((event, index) => {
550
+ const timestamp = event.createdAt instanceof Date
551
+ ? event.createdAt.toISOString()
552
+ : new Date(event.createdAt).toISOString();
553
+ // Format events similar to CLI: ● sender: content
554
+ let displayLine = '';
555
+ if (event.type === 'message' && event.payload) {
556
+ const sender = event.payload.sender || 'agent';
557
+ const content = typeof event.payload.content === 'string'
558
+ ? event.payload.content
559
+ : JSON.stringify(event.payload.content);
560
+ displayLine = `● [message] ${sender}: ${content}`;
561
+ }
562
+ else if (event.type === 'sse' && event.payload) {
563
+ const agentName = event.payload.agentName || 'agent';
564
+ const sseType = event.payload.type || 'unknown';
565
+ const content = event.payload.content
566
+ ? (typeof event.payload.content === 'string' ? event.payload.content : '')
567
+ : '';
568
+ displayLine = content
569
+ ? `● [sse] ${agentName}: ${sseType} ${content}`
570
+ : `● [sse] ${agentName}: ${sseType}`;
571
+ }
572
+ else if (event.type === 'world' && event.payload) {
573
+ // World activity event (response-start, response-end, idle)
574
+ const activityType = event.payload.activityType || event.payload.type || 'unknown';
575
+ const source = event.payload.source || 'world';
576
+ // Remove 'agent:' prefix if present
577
+ const displaySource = source.startsWith('agent:') ? source.substring(6) : source;
578
+ displayLine = `● [world] ${displaySource}: ${activityType} pending=${event.payload.pendingOperations || 0}`;
579
+ }
580
+ else if (event.type === 'tool' && event.payload) {
581
+ // Tool execution event
582
+ const agentName = event.payload.agentName || 'agent';
583
+ const toolType = event.payload.type || 'unknown';
584
+ displayLine = `● [tool] ${agentName} ${toolType}`;
585
+ }
586
+ else if (event.type === 'system' && event.payload) {
587
+ const content = typeof event.payload === 'string'
588
+ ? event.payload
589
+ : JSON.stringify(event.payload);
590
+ displayLine = `● [system] ${content}`;
591
+ }
592
+ else {
593
+ displayLine = `● [${event.type}]: ${JSON.stringify(event.payload)}`;
594
+ }
595
+ markdown += `${index + 1}. \`${timestamp.substring(11, 19)}\` ${displayLine}\n`;
596
+ });
597
+ if (allEvents.length > displayLimit) {
598
+ markdown += `\n*... and ${allEvents.length - displayLimit} more events (showing first ${displayLimit})*\n`;
599
+ }
600
+ markdown += `\n`;
601
+ }
602
+ else {
603
+ markdown += `## Chat Events\n\nNo events recorded for this chat.\n\n`;
604
+ }
605
+ }
606
+ catch (error) {
607
+ logger.error('Failed to load chat events', { worldId: worldData.id, chatId: currentChat?.id, error: error instanceof Error ? error.message : error });
608
+ markdown += `## Chat Events\n\nUnable to load events (${error instanceof Error ? error.message : 'Unknown error'})\n\n`;
609
+ }
610
+ }
611
+ else {
612
+ markdown += `## Chat Events\n\n${currentChat ? 'Event storage not configured for this world.' : 'No current chat to display events for.'}\n\n`;
613
+ }
614
+ // Export metadata
615
+ markdown += `## Export Metadata\n\n`;
616
+ markdown += `- **Export Format Version:** 1.1\n`;
617
+ markdown += `- **Agent World Version:** ${process.env.npm_package_version || 'Unknown'}\n`;
618
+ markdown += `- **Total Export Size:** ${markdown.length} characters\n`;
619
+ // Count events if available
620
+ let eventCount = 0;
621
+ if (worldData.eventStorage) {
622
+ try {
623
+ const events = await worldData.eventStorage.getEventsByWorldAndChat(worldData.id, null, { limit: 10000 });
624
+ eventCount = events.length;
625
+ }
626
+ catch {
627
+ // Ignore errors in metadata generation
628
+ }
629
+ }
630
+ markdown += `- **Sections:** World Configuration, Agents (${agents.length}), Current Chat (${hasCurrentChat ? 1 : 0}), Events (${eventCount})\n`;
631
+ return markdown;
632
+ }
633
+ export async function exportChatToMarkdown(worldId, chatId) {
634
+ await moduleInitialization;
635
+ const worldData = await getWorld(worldId);
636
+ if (!worldData) {
637
+ throw new Error(`World '${worldId}' not found`);
638
+ }
639
+ const chat = worldData.chats.get(chatId);
640
+ if (!chat) {
641
+ throw new Error(`Chat '${chatId}' not found in world '${worldData.name}'`);
642
+ }
643
+ const agentsMap = await buildAgentMap(worldData.id);
644
+ const chatMessages = await getMemory(worldData.id, chatId);
645
+ const messages = Array.isArray(chatMessages) ? [...chatMessages] : [];
646
+ const createdAt = chat.createdAt instanceof Date ? chat.createdAt : new Date(chat.createdAt);
647
+ const updatedAt = chat.updatedAt instanceof Date ? chat.updatedAt : new Date(chat.updatedAt);
648
+ let markdown = `# Chat Export: ${chat.name}\n\n`;
649
+ markdown += `**World:** ${worldData.name} (${worldData.id})\n`;
650
+ markdown += `**Chat ID:** ${chat.id}\n`;
651
+ markdown += `**Created:** ${createdAt.toISOString()}\n`;
652
+ markdown += `**Updated:** ${updatedAt.toISOString()}\n`;
653
+ markdown += `**Recorded Messages:** ${chat.messageCount}\n`;
654
+ markdown += `**Exported Messages:** ${messages.length}\n`;
655
+ if (chat.description) {
656
+ markdown += `**Description:** ${chat.description}\n`;
657
+ }
658
+ markdown += `\n`;
659
+ if (messages.length === 0) {
660
+ markdown += '## Messages\n\nNo messages found for this chat.\n';
661
+ return markdown;
662
+ }
663
+ const sortedMessages = messages.sort((a, b) => {
664
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
665
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
666
+ return dateA - dateB;
667
+ });
668
+ markdown += `## Messages (${sortedMessages.length})\n\n`;
669
+ sortedMessages.forEach((message, index) => {
670
+ const senderLabel = formatSenderLabel(message, agentsMap) || message.role.toUpperCase();
671
+ const timestamp = message.createdAt ? new Date(message.createdAt).toISOString() : 'Unknown';
672
+ const agentName = message.agentId ? agentsMap.get(message.agentId)?.name || message.agentId : undefined;
673
+ markdown += `### ${index + 1}. ${senderLabel}\n`;
674
+ markdown += `- **Role:** ${message.role}\n`;
675
+ markdown += `- **Timestamp:** ${timestamp}\n`;
676
+ if (agentName) {
677
+ markdown += `- **Agent:** ${agentName}\n`;
678
+ }
679
+ if (message.chatId && message.chatId !== chatId) {
680
+ markdown += `- **Chat ID:** ${message.chatId}\n`;
681
+ }
682
+ markdown += `\n${message.content}\n\n`;
683
+ });
684
+ return markdown;
685
+ }
686
+ /**
687
+ * Helper function to format dates consistently
688
+ */
689
+ function formatDate(date) {
690
+ if (!date)
691
+ return 'Unknown';
692
+ try {
693
+ if (date instanceof Date) {
694
+ return date.toISOString();
695
+ }
696
+ else if (typeof date === 'string') {
697
+ return new Date(date).toISOString();
698
+ }
699
+ return 'Invalid date';
700
+ }
701
+ catch (error) {
702
+ return 'Invalid date';
703
+ }
704
+ }
705
+ //# sourceMappingURL=export.js.map