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.
- package/README.md +17 -7
- package/dist/cli/commands.d.ts +109 -0
- package/dist/cli/commands.js +2024 -0
- package/dist/cli/display.d.ts +124 -0
- package/dist/cli/display.js +381 -0
- package/dist/cli/hitl.d.ts +33 -0
- package/dist/cli/hitl.js +81 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/stream.d.ts +41 -0
- package/dist/cli/stream.js +222 -0
- package/dist/core/activity-tracker.d.ts +16 -0
- package/dist/core/activity-tracker.d.ts.map +1 -0
- package/dist/core/activity-tracker.js +91 -0
- package/dist/core/activity-tracker.js.map +1 -0
- package/dist/core/ai-commands.d.ts +16 -0
- package/dist/core/ai-commands.d.ts.map +1 -0
- package/dist/core/ai-commands.js +24 -0
- package/dist/core/ai-commands.js.map +1 -0
- package/dist/core/ai-sdk-patch.d.ts +24 -0
- package/dist/core/ai-sdk-patch.d.ts.map +1 -0
- package/dist/core/ai-sdk-patch.js +169 -0
- package/dist/core/ai-sdk-patch.js.map +1 -0
- package/dist/core/anthropic-direct.d.ts +52 -0
- package/dist/core/anthropic-direct.d.ts.map +1 -0
- package/dist/core/anthropic-direct.js +301 -0
- package/dist/core/anthropic-direct.js.map +1 -0
- package/dist/core/approval-cache.d.ts +104 -0
- package/dist/core/approval-cache.d.ts.map +1 -0
- package/dist/core/approval-cache.js +150 -0
- package/dist/core/approval-cache.js.map +1 -0
- package/dist/core/chat-constants.d.ts +20 -0
- package/dist/core/chat-constants.d.ts.map +1 -0
- package/dist/core/chat-constants.js +22 -0
- package/dist/core/chat-constants.js.map +1 -0
- package/dist/core/create-agent-tool.d.ts +66 -0
- package/dist/core/create-agent-tool.d.ts.map +1 -0
- package/dist/core/create-agent-tool.js +212 -0
- package/dist/core/create-agent-tool.js.map +1 -0
- package/dist/core/events/approval-checker.d.ts +61 -0
- package/dist/core/events/approval-checker.d.ts.map +1 -0
- package/dist/core/events/approval-checker.js +226 -0
- package/dist/core/events/approval-checker.js.map +1 -0
- package/dist/core/events/index.d.ts +25 -0
- package/dist/core/events/index.d.ts.map +1 -0
- package/dist/core/events/index.js +30 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/events/memory-manager.d.ts +73 -0
- package/dist/core/events/memory-manager.d.ts.map +1 -0
- package/dist/core/events/memory-manager.js +1218 -0
- package/dist/core/events/memory-manager.js.map +1 -0
- package/dist/core/events/mention-logic.d.ts +39 -0
- package/dist/core/events/mention-logic.d.ts.map +1 -0
- package/dist/core/events/mention-logic.js +163 -0
- package/dist/core/events/mention-logic.js.map +1 -0
- package/dist/core/events/orchestrator.d.ts +69 -0
- package/dist/core/events/orchestrator.d.ts.map +1 -0
- package/dist/core/events/orchestrator.js +883 -0
- package/dist/core/events/orchestrator.js.map +1 -0
- package/dist/core/events/persistence.d.ts +41 -0
- package/dist/core/events/persistence.d.ts.map +1 -0
- package/dist/core/events/persistence.js +296 -0
- package/dist/core/events/persistence.js.map +1 -0
- package/dist/core/events/publishers.d.ts +81 -0
- package/dist/core/events/publishers.d.ts.map +1 -0
- package/dist/core/events/publishers.js +272 -0
- package/dist/core/events/publishers.js.map +1 -0
- package/dist/core/events/subscribers.d.ts +45 -0
- package/dist/core/events/subscribers.d.ts.map +1 -0
- package/dist/core/events/subscribers.js +288 -0
- package/dist/core/events/subscribers.js.map +1 -0
- package/dist/core/events/tool-bridge-logging.d.ts +28 -0
- package/dist/core/events/tool-bridge-logging.d.ts.map +1 -0
- package/dist/core/events/tool-bridge-logging.js +94 -0
- package/dist/core/events/tool-bridge-logging.js.map +1 -0
- package/dist/core/events-metadata.d.ts +72 -0
- package/dist/core/events-metadata.d.ts.map +1 -0
- package/dist/core/events-metadata.js +167 -0
- package/dist/core/events-metadata.js.map +1 -0
- package/dist/core/events.d.ts +186 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +1248 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/export.d.ts +106 -0
- package/dist/core/export.d.ts.map +1 -0
- package/dist/core/export.js +705 -0
- package/dist/core/export.js.map +1 -0
- package/dist/core/file-tools.d.ts +114 -0
- package/dist/core/file-tools.d.ts.map +1 -0
- package/dist/core/file-tools.js +370 -0
- package/dist/core/file-tools.js.map +1 -0
- package/dist/core/google-direct.d.ts +58 -0
- package/dist/core/google-direct.d.ts.map +1 -0
- package/dist/core/google-direct.js +298 -0
- package/dist/core/google-direct.js.map +1 -0
- package/dist/core/hitl.d.ts +54 -0
- package/dist/core/hitl.d.ts.map +1 -0
- package/dist/core/hitl.js +153 -0
- package/dist/core/hitl.js.map +1 -0
- package/dist/core/index.d.ts +59 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +70 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/llm-config.d.ts +128 -0
- package/dist/core/llm-config.d.ts.map +1 -0
- package/dist/core/llm-config.js +164 -0
- package/dist/core/llm-config.js.map +1 -0
- package/dist/core/llm-manager.d.ts +163 -0
- package/dist/core/llm-manager.d.ts.map +1 -0
- package/dist/core/llm-manager.js +669 -0
- package/dist/core/llm-manager.js.map +1 -0
- package/dist/core/load-skill-tool.d.ts +55 -0
- package/dist/core/load-skill-tool.d.ts.map +1 -0
- package/dist/core/load-skill-tool.js +468 -0
- package/dist/core/load-skill-tool.js.map +1 -0
- package/dist/core/logger.d.ts +88 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +358 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/managers.d.ts +131 -0
- package/dist/core/managers.d.ts.map +1 -0
- package/dist/core/managers.js +1223 -0
- package/dist/core/managers.js.map +1 -0
- package/dist/core/mcp-server-registry.d.ts +304 -0
- package/dist/core/mcp-server-registry.d.ts.map +1 -0
- package/dist/core/mcp-server-registry.js +1769 -0
- package/dist/core/mcp-server-registry.js.map +1 -0
- package/dist/core/mcp-tools.d.ts +56 -0
- package/dist/core/mcp-tools.d.ts.map +1 -0
- package/dist/core/mcp-tools.js +186 -0
- package/dist/core/mcp-tools.js.map +1 -0
- package/dist/core/message-prep.d.ts +81 -0
- package/dist/core/message-prep.d.ts.map +1 -0
- package/dist/core/message-prep.js +223 -0
- package/dist/core/message-prep.js.map +1 -0
- package/dist/core/message-processing-control.d.ts +54 -0
- package/dist/core/message-processing-control.d.ts.map +1 -0
- package/dist/core/message-processing-control.js +139 -0
- package/dist/core/message-processing-control.js.map +1 -0
- package/dist/core/openai-direct.d.ts +80 -0
- package/dist/core/openai-direct.d.ts.map +1 -0
- package/dist/core/openai-direct.js +374 -0
- package/dist/core/openai-direct.js.map +1 -0
- package/dist/core/shell-cmd-tool.d.ts +235 -0
- package/dist/core/shell-cmd-tool.d.ts.map +1 -0
- package/dist/core/shell-cmd-tool.js +1157 -0
- package/dist/core/shell-cmd-tool.js.map +1 -0
- package/dist/core/shell-process-registry.d.ts +88 -0
- package/dist/core/shell-process-registry.d.ts.map +1 -0
- package/dist/core/shell-process-registry.js +309 -0
- package/dist/core/shell-process-registry.js.map +1 -0
- package/dist/core/skill-registry.d.ts +75 -0
- package/dist/core/skill-registry.d.ts.map +1 -0
- package/dist/core/skill-registry.js +369 -0
- package/dist/core/skill-registry.js.map +1 -0
- package/dist/core/skill-script-runner.d.ts +89 -0
- package/dist/core/skill-script-runner.d.ts.map +1 -0
- package/dist/core/skill-script-runner.js +274 -0
- package/dist/core/skill-script-runner.js.map +1 -0
- package/dist/core/skill-selector.d.ts +65 -0
- package/dist/core/skill-selector.d.ts.map +1 -0
- package/dist/core/skill-selector.js +190 -0
- package/dist/core/skill-selector.js.map +1 -0
- package/dist/core/skill-settings.d.ts +20 -0
- package/dist/core/skill-settings.d.ts.map +1 -0
- package/dist/core/skill-settings.js +40 -0
- package/dist/core/skill-settings.js.map +1 -0
- package/dist/core/storage/agent-storage.d.ts +134 -0
- package/dist/core/storage/agent-storage.d.ts.map +1 -0
- package/dist/core/storage/agent-storage.js +498 -0
- package/dist/core/storage/agent-storage.js.map +1 -0
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts +100 -0
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/fileEventStorage.js +494 -0
- package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -0
- package/dist/core/storage/eventStorage/index.d.ts +31 -0
- package/dist/core/storage/eventStorage/index.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/index.js +31 -0
- package/dist/core/storage/eventStorage/index.js.map +1 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +87 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.js +244 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +45 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.js +301 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -0
- package/dist/core/storage/eventStorage/types.d.ts +142 -0
- package/dist/core/storage/eventStorage/types.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/types.js +43 -0
- package/dist/core/storage/eventStorage/types.js.map +1 -0
- package/dist/core/storage/eventStorage/validation.d.ts +30 -0
- package/dist/core/storage/eventStorage/validation.d.ts.map +1 -0
- package/dist/core/storage/eventStorage/validation.js +68 -0
- package/dist/core/storage/eventStorage/validation.js.map +1 -0
- package/dist/core/storage/legacy-migrations.d.ts +45 -0
- package/dist/core/storage/legacy-migrations.d.ts.map +1 -0
- package/dist/core/storage/legacy-migrations.js +295 -0
- package/dist/core/storage/legacy-migrations.js.map +1 -0
- package/dist/core/storage/memory-storage.d.ts +105 -0
- package/dist/core/storage/memory-storage.d.ts.map +1 -0
- package/dist/core/storage/memory-storage.js +415 -0
- package/dist/core/storage/memory-storage.js.map +1 -0
- package/dist/core/storage/migration-runner.d.ts +96 -0
- package/dist/core/storage/migration-runner.d.ts.map +1 -0
- package/dist/core/storage/migration-runner.js +306 -0
- package/dist/core/storage/migration-runner.js.map +1 -0
- package/dist/core/storage/queue-storage.d.ts +147 -0
- package/dist/core/storage/queue-storage.d.ts.map +1 -0
- package/dist/core/storage/queue-storage.js +290 -0
- package/dist/core/storage/queue-storage.js.map +1 -0
- package/dist/core/storage/skill-storage.d.ts +136 -0
- package/dist/core/storage/skill-storage.d.ts.map +1 -0
- package/dist/core/storage/skill-storage.js +474 -0
- package/dist/core/storage/skill-storage.js.map +1 -0
- package/dist/core/storage/sqlite-schema.d.ts +95 -0
- package/dist/core/storage/sqlite-schema.d.ts.map +1 -0
- package/dist/core/storage/sqlite-schema.js +156 -0
- package/dist/core/storage/sqlite-schema.js.map +1 -0
- package/dist/core/storage/sqlite-storage.d.ts +146 -0
- package/dist/core/storage/sqlite-storage.d.ts.map +1 -0
- package/dist/core/storage/sqlite-storage.js +709 -0
- package/dist/core/storage/sqlite-storage.js.map +1 -0
- package/dist/core/storage/storage-factory.d.ts +61 -0
- package/dist/core/storage/storage-factory.d.ts.map +1 -0
- package/dist/core/storage/storage-factory.js +794 -0
- package/dist/core/storage/storage-factory.js.map +1 -0
- package/dist/core/storage/validation.d.ts +36 -0
- package/dist/core/storage/validation.d.ts.map +1 -0
- package/dist/core/storage/validation.js +79 -0
- package/dist/core/storage/validation.js.map +1 -0
- package/dist/core/storage/world-storage.d.ts +114 -0
- package/dist/core/storage/world-storage.d.ts.map +1 -0
- package/dist/core/storage/world-storage.js +378 -0
- package/dist/core/storage/world-storage.js.map +1 -0
- package/dist/core/subscription.d.ts +43 -0
- package/dist/core/subscription.d.ts.map +1 -0
- package/dist/core/subscription.js +227 -0
- package/dist/core/subscription.js.map +1 -0
- package/dist/core/tool-utils.d.ts +80 -0
- package/dist/core/tool-utils.d.ts.map +1 -0
- package/dist/core/tool-utils.js +273 -0
- package/dist/core/tool-utils.js.map +1 -0
- package/dist/core/types.d.ts +595 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +158 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/utils.d.ts +138 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +478 -0
- package/dist/core/utils.js.map +1 -0
- package/dist/core/world-class.d.ts +43 -0
- package/dist/core/world-class.d.ts.map +1 -0
- package/dist/core/world-class.js +90 -0
- package/dist/core/world-class.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/public/assets/agent-sprites-DJFgj-zP.png +0 -0
- package/dist/public/assets/border-KHK37r8y.svg +83 -0
- package/dist/public/assets/index-C9kPXL6G.css +1 -0
- package/dist/public/assets/index-DOQEHGWt.js +96 -0
- package/dist/public/index.html +21 -0
- package/dist/server/api.d.ts +2 -0
- package/dist/server/api.js +1124 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/sse-handler.d.ts +62 -0
- package/dist/server/sse-handler.js +234 -0
- package/package.json +15 -3
- 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
|