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,2024 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Commands Implementation - Direct Core Integration with Space-Separated and Bidirectional Aliases
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Direct command mapping system with interactive parameter collection
|
|
6
|
+
* - Core function calls without command processing layer
|
|
7
|
+
* - User-friendly messages with technical details for debugging
|
|
8
|
+
* - Automatic world state management and refreshing
|
|
9
|
+
* - Help message generation with command documentation
|
|
10
|
+
* - Dual input handling for commands and messages
|
|
11
|
+
* - World instance isolation and proper cleanup during refresh
|
|
12
|
+
* - Space-separated command aliases for natural command syntax
|
|
13
|
+
* - Bidirectional aliases (e.g., /chat new and /new chat both work)
|
|
14
|
+
* - Backward compatibility with hyphenated aliases
|
|
15
|
+
* - Context-sensitive commands that adapt based on world selection
|
|
16
|
+
* - World data persistence to File Storage or SQLite with folder selection
|
|
17
|
+
*
|
|
18
|
+
* Available Commands:
|
|
19
|
+
* - World: list, show, create, update, delete, select, export, save
|
|
20
|
+
* - Agent: list, show, create, update, delete, clear
|
|
21
|
+
* - Chat: list, create, select, switch, delete, rename, export
|
|
22
|
+
* - System: help, quit, exit
|
|
23
|
+
* - Short Explicit: lsw (list worlds), lsa (list agents)
|
|
24
|
+
*
|
|
25
|
+
* Alias System:
|
|
26
|
+
* - Space-separated format: /new chat, /add agent, /list worlds (preferred)
|
|
27
|
+
* - Bidirectional aliases: /chat new = /new chat, /agent add = /add agent
|
|
28
|
+
* - Backward compatible: /new-chat, /add-agent, /list-worlds (still work)
|
|
29
|
+
* - Context-sensitive: /new creates world, /add creates agent (when world selected)
|
|
30
|
+
* - Explicit aliases like /lsw, /lsa provide unambiguous targeting
|
|
31
|
+
*
|
|
32
|
+
* World Refresh Mechanism:
|
|
33
|
+
* - Commands that modify world state signal refresh requirement via `refreshWorld: true`
|
|
34
|
+
* - CLI properly destroys old world instances and creates fresh ones
|
|
35
|
+
* - Event subscriptions are cleanly transferred to new world instances
|
|
36
|
+
* - Prevents memory leaks and ensures event isolation between old/new worlds
|
|
37
|
+
* - Agent persistence maintained across refresh cycles
|
|
38
|
+
*
|
|
39
|
+
* World Save Feature:
|
|
40
|
+
* - Interactive storage type selection (File or SQLite)
|
|
41
|
+
* - Custom folder path with default option
|
|
42
|
+
* - Saves world, all agents, all chat histories, and all events
|
|
43
|
+
* - Creates target directory if it doesn't exist
|
|
44
|
+
* - Supports migration between storage types
|
|
45
|
+
* - Event history preserved across different storage backends
|
|
46
|
+
*/
|
|
47
|
+
import { LLMProvider, createWorld, getWorld, updateWorld, publishMessage, listWorlds, deleteWorld, listAgents, getAgent, updateAgent, deleteAgent, createAgent, clearAgentMemory, listChats, updateChat, exportWorldToMarkdown, exportChatToMarkdown, newChat, restoreChat, deleteChat, getMemory } from '../core/index.js';
|
|
48
|
+
import { createStorage } from '../core/storage/storage-factory.js';
|
|
49
|
+
import { createCategoryLogger } from '../core/logger.js';
|
|
50
|
+
import readline from 'readline';
|
|
51
|
+
import enquirer from 'enquirer';
|
|
52
|
+
import fs from 'fs';
|
|
53
|
+
import path from 'path';
|
|
54
|
+
// Create CLI logger
|
|
55
|
+
const logger = createCategoryLogger('cli');
|
|
56
|
+
// Helper for world-required command validation
|
|
57
|
+
function requireWorldOrError(world, command) {
|
|
58
|
+
if (!world) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
message: 'No world selected. World is required for this command.',
|
|
62
|
+
technicalDetails: `Command ${command} requires world context`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
// Color helpers (matching cli/index.ts styles)
|
|
68
|
+
const red = (text) => `\x1b[31m${text}\x1b[0m`;
|
|
69
|
+
const green = (text) => `\x1b[32m${text}\x1b[0m`;
|
|
70
|
+
const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
|
|
71
|
+
const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
|
|
72
|
+
const gray = (text) => `\x1b[90m${text}\x1b[0m`;
|
|
73
|
+
const boldGreen = (text) => `\x1b[1m\x1b[32m${text}\x1b[0m`;
|
|
74
|
+
const boldYellow = (text) => `\x1b[1m\x1b[33m${text}\x1b[0m`;
|
|
75
|
+
const boldRed = (text) => `\x1b[1m\x1b[31m${text}\x1b[0m`;
|
|
76
|
+
/**
|
|
77
|
+
* Display chat messages in a formatted, readable way
|
|
78
|
+
* Shows sender, timestamp, and content for each message
|
|
79
|
+
* Logic:
|
|
80
|
+
* - HUMAN messages: deduplicate by messageId (they're replicated across all agents)
|
|
81
|
+
* - Agent messages: only show if sender matches agentId (the agent that created it)
|
|
82
|
+
* - System messages: deduplicate by messageId
|
|
83
|
+
*/
|
|
84
|
+
export async function displayChatMessages(worldId, chatId) {
|
|
85
|
+
try {
|
|
86
|
+
const messages = await getMemory(worldId, chatId);
|
|
87
|
+
if (!messages || messages.length === 0) {
|
|
88
|
+
console.log(gray('\n No messages in current chat.\n'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Filter and deduplicate messages
|
|
92
|
+
const humanMessageMap = new Map();
|
|
93
|
+
const agentMessages = [];
|
|
94
|
+
const messagesWithoutId = [];
|
|
95
|
+
for (const msg of messages) {
|
|
96
|
+
const isHumanMessage = msg.sender === 'human' ||
|
|
97
|
+
msg.role === 'user' ||
|
|
98
|
+
(msg.sender || '').toLowerCase() === 'human';
|
|
99
|
+
const isSystemMessage = msg.sender === 'system' || msg.role === 'system';
|
|
100
|
+
if (isHumanMessage) {
|
|
101
|
+
// Deduplicate human messages by messageId (exclude agent response copies with role=user)
|
|
102
|
+
if (msg.messageId && (msg.sender === 'human' || (msg.sender || '').toLowerCase() === 'human')) {
|
|
103
|
+
if (!humanMessageMap.has(msg.messageId)) {
|
|
104
|
+
humanMessageMap.set(msg.messageId, msg);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (!msg.messageId && msg.sender === 'human') {
|
|
108
|
+
messagesWithoutId.push(msg);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (isSystemMessage) {
|
|
112
|
+
// Deduplicate system messages by messageId
|
|
113
|
+
if (msg.messageId) {
|
|
114
|
+
if (!humanMessageMap.has(msg.messageId)) {
|
|
115
|
+
humanMessageMap.set(msg.messageId, msg);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
messagesWithoutId.push(msg);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (msg.role === 'assistant') {
|
|
123
|
+
// Agent response message: only show messages with role=assistant
|
|
124
|
+
// This filters out copies stored in other agents' memories (which have role=user)
|
|
125
|
+
agentMessages.push(msg);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Combine all message types
|
|
129
|
+
const deduplicatedMessages = [
|
|
130
|
+
...Array.from(humanMessageMap.values()),
|
|
131
|
+
...agentMessages,
|
|
132
|
+
...messagesWithoutId
|
|
133
|
+
];
|
|
134
|
+
// Sort by timestamp
|
|
135
|
+
deduplicatedMessages.sort((a, b) => {
|
|
136
|
+
const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
137
|
+
const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
138
|
+
return dateA - dateB;
|
|
139
|
+
});
|
|
140
|
+
console.log(cyan('\n=== Current Chat Messages ===\n'));
|
|
141
|
+
for (const msg of deduplicatedMessages) {
|
|
142
|
+
// Format timestamp if available
|
|
143
|
+
const timestamp = msg.createdAt
|
|
144
|
+
? new Date(msg.createdAt).toLocaleString('en-US', {
|
|
145
|
+
month: 'short',
|
|
146
|
+
day: 'numeric',
|
|
147
|
+
hour: '2-digit',
|
|
148
|
+
minute: '2-digit',
|
|
149
|
+
second: '2-digit'
|
|
150
|
+
})
|
|
151
|
+
: '';
|
|
152
|
+
// Determine sender display
|
|
153
|
+
let senderDisplay = '';
|
|
154
|
+
if (msg.sender) {
|
|
155
|
+
// Color code by sender type
|
|
156
|
+
const senderLower = msg.sender.toLowerCase();
|
|
157
|
+
if (senderLower === 'human') {
|
|
158
|
+
senderDisplay = boldYellow('HUMAN');
|
|
159
|
+
}
|
|
160
|
+
else if (msg.sender === 'system') {
|
|
161
|
+
senderDisplay = boldRed('system');
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Agent message
|
|
165
|
+
senderDisplay = boldGreen(msg.sender);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Fallback to role if no sender
|
|
170
|
+
senderDisplay = gray(msg.role || 'unknown');
|
|
171
|
+
}
|
|
172
|
+
// Display message
|
|
173
|
+
const timestampPart = timestamp ? gray(`[${timestamp}]`) : '';
|
|
174
|
+
console.log(`${timestampPart} ${senderDisplay}: ${msg.content}`);
|
|
175
|
+
}
|
|
176
|
+
console.log(gray(`\n Total: ${deduplicatedMessages.length} message${deduplicatedMessages.length !== 1 ? 's' : ''}\n`));
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.error(red(`Failed to load chat messages: ${err instanceof Error ? err.message : String(err)}`));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export const CLI_COMMAND_MAP = {
|
|
183
|
+
'world list': {
|
|
184
|
+
type: 'listWorlds',
|
|
185
|
+
requiresWorld: false,
|
|
186
|
+
description: 'List all worlds with details (ID, name, description, agents count)',
|
|
187
|
+
usage: '/world list',
|
|
188
|
+
parameters: [],
|
|
189
|
+
aliases: ['list worlds', 'lsw', 'list-worlds'],
|
|
190
|
+
category: 'world'
|
|
191
|
+
},
|
|
192
|
+
'world show': {
|
|
193
|
+
type: 'showWorld',
|
|
194
|
+
requiresWorld: false,
|
|
195
|
+
description: 'Show details for a specific world',
|
|
196
|
+
usage: '/world show <name>',
|
|
197
|
+
parameters: [
|
|
198
|
+
{ name: 'name', required: true, description: 'World name or ID', type: 'string' }
|
|
199
|
+
],
|
|
200
|
+
aliases: ['show world', 'show-world'],
|
|
201
|
+
category: 'world'
|
|
202
|
+
},
|
|
203
|
+
'world create': {
|
|
204
|
+
type: 'createWorld',
|
|
205
|
+
requiresWorld: false,
|
|
206
|
+
description: 'Create a new world',
|
|
207
|
+
usage: '/world create [name] [description] [turnLimit]',
|
|
208
|
+
parameters: [
|
|
209
|
+
{ name: 'name', required: false, description: 'World name', type: 'string' },
|
|
210
|
+
{ name: 'description', required: false, description: 'World description', type: 'string' },
|
|
211
|
+
{ name: 'turnLimit', required: false, description: 'Turn limit for the world', type: 'number' }
|
|
212
|
+
],
|
|
213
|
+
aliases: ['create world', 'new', 'create-world'],
|
|
214
|
+
category: 'world'
|
|
215
|
+
},
|
|
216
|
+
'world update': {
|
|
217
|
+
type: 'updateWorld',
|
|
218
|
+
requiresWorld: false,
|
|
219
|
+
description: 'Update world properties interactively',
|
|
220
|
+
usage: '/world update <name>',
|
|
221
|
+
parameters: [
|
|
222
|
+
{ name: 'name', required: true, description: 'World name or ID', type: 'string' }
|
|
223
|
+
],
|
|
224
|
+
aliases: ['update world', 'update-world'],
|
|
225
|
+
category: 'world'
|
|
226
|
+
},
|
|
227
|
+
'world delete': {
|
|
228
|
+
type: 'deleteWorld',
|
|
229
|
+
requiresWorld: false,
|
|
230
|
+
description: 'Delete a world after confirmation',
|
|
231
|
+
usage: '/world delete <name>',
|
|
232
|
+
parameters: [
|
|
233
|
+
{ name: 'name', required: true, description: 'World name or ID', type: 'string' }
|
|
234
|
+
],
|
|
235
|
+
aliases: ['delete world', 'delete-world'],
|
|
236
|
+
category: 'world'
|
|
237
|
+
},
|
|
238
|
+
'world select': {
|
|
239
|
+
type: 'selectWorld',
|
|
240
|
+
requiresWorld: false,
|
|
241
|
+
description: 'Show world selection menu to pick a world',
|
|
242
|
+
usage: '/world select',
|
|
243
|
+
parameters: [],
|
|
244
|
+
aliases: ['select world', 'select', 'sel'],
|
|
245
|
+
category: 'world'
|
|
246
|
+
},
|
|
247
|
+
'world export': {
|
|
248
|
+
type: 'exportWorld',
|
|
249
|
+
requiresWorld: true,
|
|
250
|
+
description: 'Export the current world and agents to a markdown file',
|
|
251
|
+
usage: '/world export [file]',
|
|
252
|
+
parameters: [
|
|
253
|
+
{ name: 'file', required: false, description: 'Output file path (defaults to [world]-timestamp.md)', type: 'string' }
|
|
254
|
+
],
|
|
255
|
+
aliases: ['export world', 'export'],
|
|
256
|
+
category: 'world'
|
|
257
|
+
},
|
|
258
|
+
'world save': {
|
|
259
|
+
type: 'saveWorld',
|
|
260
|
+
requiresWorld: true,
|
|
261
|
+
description: 'Save world data to File Storage or SQL Storage with folder selection (overwrites existing data with confirmation)',
|
|
262
|
+
usage: '/world save',
|
|
263
|
+
parameters: [],
|
|
264
|
+
aliases: ['save world', 'save'],
|
|
265
|
+
category: 'world'
|
|
266
|
+
},
|
|
267
|
+
'agent list': {
|
|
268
|
+
type: 'listAgents',
|
|
269
|
+
requiresWorld: true,
|
|
270
|
+
description: 'List all agents in the current world with details',
|
|
271
|
+
usage: '/agent list',
|
|
272
|
+
parameters: [],
|
|
273
|
+
aliases: ['list agents', 'lsa', 'list-agents'],
|
|
274
|
+
category: 'agent'
|
|
275
|
+
},
|
|
276
|
+
'agent show': {
|
|
277
|
+
type: 'showAgent',
|
|
278
|
+
requiresWorld: true,
|
|
279
|
+
description: 'Show agent details including configuration and memory statistics',
|
|
280
|
+
usage: '/agent show <name>',
|
|
281
|
+
parameters: [
|
|
282
|
+
{ name: 'name', required: true, description: 'Agent name', type: 'string' }
|
|
283
|
+
],
|
|
284
|
+
aliases: ['show agent', 'show-agent'],
|
|
285
|
+
category: 'agent'
|
|
286
|
+
},
|
|
287
|
+
'agent create': {
|
|
288
|
+
type: 'createAgent',
|
|
289
|
+
requiresWorld: true,
|
|
290
|
+
description: 'Create a new agent',
|
|
291
|
+
usage: '/agent create [name] [prompt]',
|
|
292
|
+
parameters: [
|
|
293
|
+
{ name: 'name', required: false, description: 'Agent name', type: 'string' },
|
|
294
|
+
{ name: 'prompt', required: false, description: 'Agent system prompt', type: 'string' }
|
|
295
|
+
],
|
|
296
|
+
aliases: ['create agent', 'agent new', 'add agent', 'agent add', 'add', 'add-agent'],
|
|
297
|
+
category: 'agent'
|
|
298
|
+
},
|
|
299
|
+
'agent update': {
|
|
300
|
+
type: 'updateAgent',
|
|
301
|
+
requiresWorld: true,
|
|
302
|
+
description: 'Update agent properties interactively',
|
|
303
|
+
usage: '/agent update <name>',
|
|
304
|
+
parameters: [
|
|
305
|
+
{ name: 'name', required: true, description: 'Agent name', type: 'string' }
|
|
306
|
+
],
|
|
307
|
+
aliases: ['update agent', 'update-agent'],
|
|
308
|
+
category: 'agent'
|
|
309
|
+
},
|
|
310
|
+
'agent delete': {
|
|
311
|
+
type: 'deleteAgent',
|
|
312
|
+
requiresWorld: true,
|
|
313
|
+
description: 'Delete an agent after confirmation',
|
|
314
|
+
usage: '/agent delete <name>',
|
|
315
|
+
parameters: [
|
|
316
|
+
{ name: 'name', required: true, description: 'Agent name', type: 'string' }
|
|
317
|
+
],
|
|
318
|
+
aliases: ['delete agent', 'delete-agent'],
|
|
319
|
+
category: 'agent'
|
|
320
|
+
},
|
|
321
|
+
'agent clear': {
|
|
322
|
+
type: 'clearAgentMemory',
|
|
323
|
+
requiresWorld: true,
|
|
324
|
+
description: 'Clear agent memory or all agents',
|
|
325
|
+
usage: '/agent clear <agentName|all>',
|
|
326
|
+
parameters: [
|
|
327
|
+
{ name: 'agentName', required: true, description: 'Agent name or "all" for all agents', type: 'string' }
|
|
328
|
+
],
|
|
329
|
+
aliases: ['clear'],
|
|
330
|
+
category: 'agent'
|
|
331
|
+
},
|
|
332
|
+
'chat list': {
|
|
333
|
+
type: 'listChats',
|
|
334
|
+
requiresWorld: true,
|
|
335
|
+
description: 'List chat history for the current world',
|
|
336
|
+
usage: '/chat list [--active]',
|
|
337
|
+
parameters: [
|
|
338
|
+
{ name: 'filter', required: false, description: 'Optional filter (--active for current chat only)', type: 'string' }
|
|
339
|
+
],
|
|
340
|
+
aliases: ['list chats', 'list-chats'],
|
|
341
|
+
category: 'chat'
|
|
342
|
+
},
|
|
343
|
+
'chat create': {
|
|
344
|
+
type: 'createChat',
|
|
345
|
+
requiresWorld: true,
|
|
346
|
+
description: 'Create a new chat history entry and make it current',
|
|
347
|
+
usage: '/chat create',
|
|
348
|
+
parameters: [],
|
|
349
|
+
aliases: ['create chat', 'chat new', 'new chat', 'new-chat'],
|
|
350
|
+
category: 'chat'
|
|
351
|
+
},
|
|
352
|
+
'chat select': {
|
|
353
|
+
type: 'selectChat',
|
|
354
|
+
requiresWorld: true,
|
|
355
|
+
description: 'Show chat selection menu and display messages from selected chat',
|
|
356
|
+
usage: '/chat select',
|
|
357
|
+
parameters: [],
|
|
358
|
+
aliases: ['select chat', 'select-chat'],
|
|
359
|
+
category: 'chat'
|
|
360
|
+
},
|
|
361
|
+
'chat switch': {
|
|
362
|
+
type: 'loadChat',
|
|
363
|
+
requiresWorld: true,
|
|
364
|
+
description: 'Load and restore state from a chat history entry',
|
|
365
|
+
usage: '/chat switch <chatId>',
|
|
366
|
+
parameters: [
|
|
367
|
+
{ name: 'chatId', required: true, description: 'Chat ID to load', type: 'string' }
|
|
368
|
+
],
|
|
369
|
+
aliases: ['switch chat', 'load chat', 'load-chat'],
|
|
370
|
+
category: 'chat'
|
|
371
|
+
},
|
|
372
|
+
'chat delete': {
|
|
373
|
+
type: 'deleteChat',
|
|
374
|
+
requiresWorld: true,
|
|
375
|
+
description: 'Delete a chat history entry after confirmation',
|
|
376
|
+
usage: '/chat delete <chatId>',
|
|
377
|
+
parameters: [
|
|
378
|
+
{ name: 'chatId', required: true, description: 'Chat ID to delete', type: 'string' }
|
|
379
|
+
],
|
|
380
|
+
aliases: ['delete chat', 'delete-chat'],
|
|
381
|
+
category: 'chat'
|
|
382
|
+
},
|
|
383
|
+
'chat rename': {
|
|
384
|
+
type: 'renameChat',
|
|
385
|
+
requiresWorld: true,
|
|
386
|
+
description: 'Rename a chat history entry and optionally update its description',
|
|
387
|
+
usage: '/chat rename <chatId> <name> [description]',
|
|
388
|
+
parameters: [
|
|
389
|
+
{ name: 'chatId', required: true, description: 'Chat ID to rename', type: 'string' },
|
|
390
|
+
{ name: 'name', required: true, description: 'New chat name', type: 'string' },
|
|
391
|
+
{ name: 'description', required: false, description: 'New chat description', type: 'string' }
|
|
392
|
+
],
|
|
393
|
+
aliases: ['rename chat', 'rename-chat'],
|
|
394
|
+
category: 'chat'
|
|
395
|
+
},
|
|
396
|
+
'chat export': {
|
|
397
|
+
type: 'exportChat',
|
|
398
|
+
requiresWorld: true,
|
|
399
|
+
description: 'Export a chat history to markdown (defaults to current chat)',
|
|
400
|
+
usage: '/chat export [chatId] [file]',
|
|
401
|
+
parameters: [
|
|
402
|
+
{ name: 'chatId', required: false, description: 'Chat ID to export (defaults to current chat)', type: 'string' },
|
|
403
|
+
{ name: 'file', required: false, description: 'Output file path', type: 'string' }
|
|
404
|
+
],
|
|
405
|
+
aliases: ['export chat', 'export-chat'],
|
|
406
|
+
category: 'chat'
|
|
407
|
+
},
|
|
408
|
+
'help': {
|
|
409
|
+
type: 'help',
|
|
410
|
+
requiresWorld: false,
|
|
411
|
+
description: 'Show available commands or category-specific help',
|
|
412
|
+
usage: '/help [command|category]',
|
|
413
|
+
parameters: [
|
|
414
|
+
{ name: 'command', required: false, description: 'Command or category to display', type: 'string' }
|
|
415
|
+
],
|
|
416
|
+
category: 'system'
|
|
417
|
+
},
|
|
418
|
+
'quit': {
|
|
419
|
+
type: 'quit',
|
|
420
|
+
requiresWorld: false,
|
|
421
|
+
description: 'Exit the CLI',
|
|
422
|
+
usage: '/quit',
|
|
423
|
+
parameters: [],
|
|
424
|
+
category: 'system'
|
|
425
|
+
},
|
|
426
|
+
'exit': {
|
|
427
|
+
type: 'exit',
|
|
428
|
+
requiresWorld: false,
|
|
429
|
+
description: 'Exit the CLI',
|
|
430
|
+
usage: '/exit',
|
|
431
|
+
parameters: [],
|
|
432
|
+
category: 'system'
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
export const CLI_COMMAND_ALIASES = Object.entries(CLI_COMMAND_MAP).reduce((aliases, [key, command]) => {
|
|
436
|
+
if (command.aliases) {
|
|
437
|
+
for (const rawAlias of command.aliases) {
|
|
438
|
+
const alias = rawAlias.replace(/^\//, '').toLowerCase();
|
|
439
|
+
if (!aliases[alias]) {
|
|
440
|
+
aliases[alias] = key;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return aliases;
|
|
445
|
+
}, {});
|
|
446
|
+
const CATEGORY_LABELS = Object.freeze({
|
|
447
|
+
world: 'World Management',
|
|
448
|
+
agent: 'Agent Management',
|
|
449
|
+
chat: 'Chat Management',
|
|
450
|
+
system: 'System Commands'
|
|
451
|
+
});
|
|
452
|
+
const CATEGORY_ORDER = ['world', 'agent', 'chat', 'system'];
|
|
453
|
+
// Command parsing and help generation
|
|
454
|
+
export function parseCLICommand(input) {
|
|
455
|
+
const trimmed = input.trim();
|
|
456
|
+
if (!trimmed.startsWith('/')) {
|
|
457
|
+
return {
|
|
458
|
+
command: '',
|
|
459
|
+
args: [],
|
|
460
|
+
commandType: '',
|
|
461
|
+
isValid: false,
|
|
462
|
+
error: 'Commands must start with /'
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const parts = trimmed.slice(1).split(/\s+/).filter(part => part.length > 0);
|
|
466
|
+
if (parts.length === 0) {
|
|
467
|
+
return {
|
|
468
|
+
command: '',
|
|
469
|
+
args: [],
|
|
470
|
+
commandType: '',
|
|
471
|
+
isValid: false,
|
|
472
|
+
error: 'Empty command'
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const tokens = parts.map(part => part.toLowerCase());
|
|
476
|
+
let resolvedCommand = null;
|
|
477
|
+
let consumedTokens = 0;
|
|
478
|
+
if (tokens.length >= 2) {
|
|
479
|
+
const twoWordCandidate = `${tokens[0]} ${tokens[1]}`;
|
|
480
|
+
if (CLI_COMMAND_MAP[twoWordCandidate]) {
|
|
481
|
+
resolvedCommand = twoWordCandidate;
|
|
482
|
+
consumedTokens = 2;
|
|
483
|
+
}
|
|
484
|
+
else if (CLI_COMMAND_ALIASES[twoWordCandidate]) {
|
|
485
|
+
resolvedCommand = CLI_COMMAND_ALIASES[twoWordCandidate];
|
|
486
|
+
consumedTokens = 2;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!resolvedCommand) {
|
|
490
|
+
const singleWordCandidate = tokens[0];
|
|
491
|
+
if (CLI_COMMAND_MAP[singleWordCandidate]) {
|
|
492
|
+
resolvedCommand = singleWordCandidate;
|
|
493
|
+
consumedTokens = 1;
|
|
494
|
+
}
|
|
495
|
+
else if (CLI_COMMAND_ALIASES[singleWordCandidate]) {
|
|
496
|
+
resolvedCommand = CLI_COMMAND_ALIASES[singleWordCandidate];
|
|
497
|
+
consumedTokens = 1;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (!resolvedCommand) {
|
|
501
|
+
const availableCommands = Object.keys(CLI_COMMAND_MAP).sort().map(cmd => `/${cmd}`).join(', ');
|
|
502
|
+
const attempted = tokens.slice(0, 2).join(' ');
|
|
503
|
+
return {
|
|
504
|
+
command: attempted,
|
|
505
|
+
args: parts.slice(1),
|
|
506
|
+
commandType: '',
|
|
507
|
+
isValid: false,
|
|
508
|
+
error: `Unknown command: ${attempted}. Available commands: ${availableCommands}`
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const args = parts.slice(consumedTokens);
|
|
512
|
+
return {
|
|
513
|
+
command: resolvedCommand,
|
|
514
|
+
args,
|
|
515
|
+
commandType: CLI_COMMAND_MAP[resolvedCommand].type,
|
|
516
|
+
isValid: true
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
export function generateHelpMessage(target) {
|
|
520
|
+
const formatAliases = (definition) => {
|
|
521
|
+
if (!definition.aliases || definition.aliases.length === 0) {
|
|
522
|
+
return '';
|
|
523
|
+
}
|
|
524
|
+
const aliasList = definition.aliases.map(alias => `/${alias}`).join(', ');
|
|
525
|
+
return `Aliases: ${aliasList}\n`;
|
|
526
|
+
};
|
|
527
|
+
const formatParameters = (definition) => {
|
|
528
|
+
if (!definition.parameters.length) {
|
|
529
|
+
return '';
|
|
530
|
+
}
|
|
531
|
+
let text = '\nParameters:\n';
|
|
532
|
+
for (const param of definition.parameters) {
|
|
533
|
+
const required = param.required ? 'required' : 'optional';
|
|
534
|
+
const options = param.options ? ` (options: ${param.options.join(', ')})` : '';
|
|
535
|
+
text += ` ${param.name} (${param.type}, ${required}): ${param.description}${options}\n`;
|
|
536
|
+
}
|
|
537
|
+
return text;
|
|
538
|
+
};
|
|
539
|
+
const formatCategorySection = (category) => {
|
|
540
|
+
const commands = Object.entries(CLI_COMMAND_MAP)
|
|
541
|
+
.filter(([, definition]) => definition.category === category)
|
|
542
|
+
.sort((a, b) => a[1].usage.localeCompare(b[1].usage));
|
|
543
|
+
if (commands.length === 0) {
|
|
544
|
+
return '';
|
|
545
|
+
}
|
|
546
|
+
let section = `${CATEGORY_LABELS[category]}:\n`;
|
|
547
|
+
for (const [, definition] of commands) {
|
|
548
|
+
const aliasLabel = definition.aliases && definition.aliases.length
|
|
549
|
+
? ` (aliases: ${definition.aliases.map(alias => `/${alias}`).join(', ')})`
|
|
550
|
+
: '';
|
|
551
|
+
section += ` ${definition.usage.padEnd(28)} - ${definition.description}${aliasLabel}\n`;
|
|
552
|
+
}
|
|
553
|
+
return `${section}\n`;
|
|
554
|
+
};
|
|
555
|
+
const commandKeyFromTarget = (candidate) => {
|
|
556
|
+
if (CLI_COMMAND_MAP[candidate]) {
|
|
557
|
+
return candidate;
|
|
558
|
+
}
|
|
559
|
+
if (CLI_COMMAND_ALIASES[candidate]) {
|
|
560
|
+
return CLI_COMMAND_ALIASES[candidate];
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
};
|
|
564
|
+
if (target) {
|
|
565
|
+
const normalized = target.toLowerCase();
|
|
566
|
+
const category = CATEGORY_ORDER.find(cat => cat === normalized);
|
|
567
|
+
if (category) {
|
|
568
|
+
const section = formatCategorySection(category);
|
|
569
|
+
return `\n${section}Use /help <command> for detailed information about a specific command.\n`;
|
|
570
|
+
}
|
|
571
|
+
const resolvedKey = commandKeyFromTarget(normalized);
|
|
572
|
+
if (resolvedKey) {
|
|
573
|
+
const definition = CLI_COMMAND_MAP[resolvedKey];
|
|
574
|
+
let help = `\n${definition.usage}\n`;
|
|
575
|
+
help += `Description: ${definition.description}\n`;
|
|
576
|
+
help += `Category: ${CATEGORY_LABELS[definition.category]}\n`;
|
|
577
|
+
help += formatAliases(definition);
|
|
578
|
+
help += formatParameters(definition);
|
|
579
|
+
return help;
|
|
580
|
+
}
|
|
581
|
+
return `\nUnknown command or category: ${target}. Try /help, /help world, /help agent, or /help chat.\n`;
|
|
582
|
+
}
|
|
583
|
+
let help = '\nCommand Guide\n';
|
|
584
|
+
help += 'Commands are grouped by domain: /world …, /agent …, /chat …\n';
|
|
585
|
+
help += 'Examples: /world list | /agent create Ava | /chat list --active\n\n';
|
|
586
|
+
for (const category of CATEGORY_ORDER) {
|
|
587
|
+
const section = formatCategorySection(category);
|
|
588
|
+
if (section) {
|
|
589
|
+
help += section;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
help += 'Use /help <command> for detailed information or /help <category> to filter by topic.\n';
|
|
593
|
+
return help;
|
|
594
|
+
}
|
|
595
|
+
// Export world to markdown file (CLI wrapper)
|
|
596
|
+
async function exportWorldToMarkdownFile(worldName, outputPath) {
|
|
597
|
+
try {
|
|
598
|
+
// Use the core function to generate markdown
|
|
599
|
+
const markdown = await exportWorldToMarkdown(worldName);
|
|
600
|
+
// Generate timestamp for default filename (YYYY-MM-DD_HH-MM-SS)
|
|
601
|
+
const now = new Date();
|
|
602
|
+
const datePart = now.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
603
|
+
const timePart = now
|
|
604
|
+
.toTimeString()
|
|
605
|
+
.slice(0, 8)
|
|
606
|
+
.replace(/:/g, '-'); // HH-MM-SS
|
|
607
|
+
const timestamp = `${datePart}_${timePart}`;
|
|
608
|
+
// Determine output file path
|
|
609
|
+
let filePath;
|
|
610
|
+
if (outputPath) {
|
|
611
|
+
filePath = path.resolve(outputPath);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
filePath = path.resolve(process.cwd(), `${worldName}-${timestamp}.md`);
|
|
615
|
+
}
|
|
616
|
+
// Write file
|
|
617
|
+
await fs.promises.writeFile(filePath, markdown, 'utf8');
|
|
618
|
+
// Get agent count for response data
|
|
619
|
+
const worldData = await getWorld(worldName);
|
|
620
|
+
const world = await getWorld(worldData.id);
|
|
621
|
+
if (!world)
|
|
622
|
+
throw new Error(`World ${worldName} not found`);
|
|
623
|
+
const agents = await listAgents(worldData.id);
|
|
624
|
+
return {
|
|
625
|
+
success: true,
|
|
626
|
+
message: `World '${worldName}' exported successfully to: ${filePath}`,
|
|
627
|
+
data: {
|
|
628
|
+
worldName,
|
|
629
|
+
filePath,
|
|
630
|
+
agentCount: agents.length,
|
|
631
|
+
fileSize: markdown.length
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
return {
|
|
637
|
+
success: false,
|
|
638
|
+
message: 'Failed to export world',
|
|
639
|
+
error: error instanceof Error ? error.message : String(error)
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function exportChatToMarkdownFile(worldId, worldName, chatId, outputPath) {
|
|
644
|
+
try {
|
|
645
|
+
const markdown = await exportChatToMarkdown(worldId, chatId);
|
|
646
|
+
const now = new Date();
|
|
647
|
+
const datePart = now.toISOString().slice(0, 10);
|
|
648
|
+
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, '-');
|
|
649
|
+
const timestamp = `${datePart}_${timePart}`;
|
|
650
|
+
const sanitizedWorld = worldName.replace(/\s+/g, '-');
|
|
651
|
+
const filePath = outputPath
|
|
652
|
+
? path.resolve(outputPath)
|
|
653
|
+
: path.resolve(process.cwd(), `${sanitizedWorld}-${chatId}-${timestamp}.md`);
|
|
654
|
+
await fs.promises.writeFile(filePath, markdown, 'utf8');
|
|
655
|
+
return {
|
|
656
|
+
success: true,
|
|
657
|
+
message: `Chat '${chatId}' exported successfully to: ${filePath}`,
|
|
658
|
+
data: {
|
|
659
|
+
worldId,
|
|
660
|
+
chatId,
|
|
661
|
+
worldName,
|
|
662
|
+
filePath,
|
|
663
|
+
fileSize: markdown.length
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
message: 'Failed to export chat',
|
|
671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Save world to different storage (File or SQLite)
|
|
676
|
+
async function saveWorldToStorage(world) {
|
|
677
|
+
try {
|
|
678
|
+
// This function returns data to trigger interactive selection in CLI
|
|
679
|
+
// The actual save will be handled by the CLI after user selection
|
|
680
|
+
return {
|
|
681
|
+
success: true,
|
|
682
|
+
message: 'Opening storage selection...',
|
|
683
|
+
data: {
|
|
684
|
+
saveWorld: true,
|
|
685
|
+
worldId: world.id,
|
|
686
|
+
worldName: world.name
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
return {
|
|
692
|
+
success: false,
|
|
693
|
+
message: 'Failed to initiate world save',
|
|
694
|
+
error: error instanceof Error ? error.message : String(error)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Check if target storage location exists
|
|
699
|
+
export async function checkTargetExists(targetPath, storageType, worldId) {
|
|
700
|
+
try {
|
|
701
|
+
if (storageType === 'sqlite') {
|
|
702
|
+
const dbPath = path.join(targetPath, 'database.db');
|
|
703
|
+
if (fs.existsSync(dbPath)) {
|
|
704
|
+
return {
|
|
705
|
+
exists: true,
|
|
706
|
+
message: `SQLite database already exists at ${dbPath}`
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// For file storage, check if world folder exists
|
|
712
|
+
const worldPath = path.join(targetPath, worldId);
|
|
713
|
+
if (fs.existsSync(worldPath)) {
|
|
714
|
+
return {
|
|
715
|
+
exists: true,
|
|
716
|
+
message: `World folder already exists at ${worldPath}`
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return { exists: false, message: '' };
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
return { exists: false, message: '' };
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Delete existing data at target location
|
|
727
|
+
export async function deleteExistingData(targetPath, storageType, worldId) {
|
|
728
|
+
try {
|
|
729
|
+
if (storageType === 'sqlite') {
|
|
730
|
+
const dbPath = path.join(targetPath, 'database.db');
|
|
731
|
+
if (fs.existsSync(dbPath)) {
|
|
732
|
+
// Delete the database file and related files
|
|
733
|
+
fs.unlinkSync(dbPath);
|
|
734
|
+
// Also delete WAL and SHM files if they exist
|
|
735
|
+
const walPath = `${dbPath}-wal`;
|
|
736
|
+
const shmPath = `${dbPath}-shm`;
|
|
737
|
+
if (fs.existsSync(walPath))
|
|
738
|
+
fs.unlinkSync(walPath);
|
|
739
|
+
if (fs.existsSync(shmPath))
|
|
740
|
+
fs.unlinkSync(shmPath);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
// For file storage, delete the world folder
|
|
745
|
+
const worldPath = path.join(targetPath, worldId);
|
|
746
|
+
if (fs.existsSync(worldPath)) {
|
|
747
|
+
fs.rmSync(worldPath, { recursive: true, force: true });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return { success: true };
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
return {
|
|
754
|
+
success: false,
|
|
755
|
+
error: error instanceof Error ? error.message : String(error)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Actual save implementation called from CLI after user selection
|
|
760
|
+
export async function performWorldSave(world, storageType, targetPath) {
|
|
761
|
+
try {
|
|
762
|
+
// Ensure directory exists
|
|
763
|
+
if (!fs.existsSync(targetPath)) {
|
|
764
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
765
|
+
}
|
|
766
|
+
// Create storage configuration
|
|
767
|
+
const config = {
|
|
768
|
+
type: storageType,
|
|
769
|
+
rootPath: targetPath,
|
|
770
|
+
sqlite: storageType === 'sqlite'
|
|
771
|
+
? {
|
|
772
|
+
database: path.join(targetPath, 'database.db'),
|
|
773
|
+
enableWAL: true,
|
|
774
|
+
busyTimeout: 30000,
|
|
775
|
+
cacheSize: -64000,
|
|
776
|
+
enableForeignKeys: true
|
|
777
|
+
}
|
|
778
|
+
: undefined
|
|
779
|
+
};
|
|
780
|
+
// Create storage instance
|
|
781
|
+
const storage = await createStorage(config);
|
|
782
|
+
// Save world data
|
|
783
|
+
await storage.saveWorld(world);
|
|
784
|
+
// Save all agents
|
|
785
|
+
const agents = await listAgents(world.id);
|
|
786
|
+
for (const agent of agents) {
|
|
787
|
+
await storage.saveAgent(world.id, agent);
|
|
788
|
+
}
|
|
789
|
+
// Save all chats
|
|
790
|
+
const chats = await listChats(world.id);
|
|
791
|
+
for (const chat of chats) {
|
|
792
|
+
await storage.saveChatData(world.id, chat);
|
|
793
|
+
}
|
|
794
|
+
// Save all events if eventStorage is available on both source and target
|
|
795
|
+
let eventCount = 0;
|
|
796
|
+
if (world.eventStorage && storage.eventStorage) {
|
|
797
|
+
try {
|
|
798
|
+
const sourceEventStorage = world.eventStorage;
|
|
799
|
+
const targetEventStorage = storage.eventStorage;
|
|
800
|
+
// Get events for world-level context (chatId = null)
|
|
801
|
+
const worldEvents = await sourceEventStorage.getEventsByWorldAndChat(world.id, null);
|
|
802
|
+
if (worldEvents && worldEvents.length > 0) {
|
|
803
|
+
await targetEventStorage.saveEvents(worldEvents);
|
|
804
|
+
eventCount = worldEvents.length;
|
|
805
|
+
}
|
|
806
|
+
// Get events for each specific chat
|
|
807
|
+
for (const chat of chats) {
|
|
808
|
+
const chatEvents = await sourceEventStorage.getEventsByWorldAndChat(world.id, chat.id);
|
|
809
|
+
if (chatEvents && chatEvents.length > 0) {
|
|
810
|
+
await targetEventStorage.saveEvents(chatEvents);
|
|
811
|
+
eventCount += chatEvents.length;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
// Log but don't fail the save if events can't be copied
|
|
817
|
+
console.warn(`Warning: Failed to copy events: ${error instanceof Error ? error.message : String(error)}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
success: true,
|
|
822
|
+
message: `World '${world.name}' saved successfully to ${storageType} storage at: ${targetPath}`,
|
|
823
|
+
data: {
|
|
824
|
+
worldName: world.name,
|
|
825
|
+
worldId: world.id,
|
|
826
|
+
storageType,
|
|
827
|
+
path: targetPath,
|
|
828
|
+
agentCount: agents.length,
|
|
829
|
+
chatCount: chats.length,
|
|
830
|
+
eventCount
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
return {
|
|
836
|
+
success: false,
|
|
837
|
+
message: 'Failed to save world to storage',
|
|
838
|
+
error: error instanceof Error ? error.message : String(error)
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// CLI Command Processor
|
|
843
|
+
export async function processCLICommand(input, context, promptFn) {
|
|
844
|
+
try {
|
|
845
|
+
const { command, args, commandType, isValid, error } = parseCLICommand(input);
|
|
846
|
+
if (!isValid) {
|
|
847
|
+
return {
|
|
848
|
+
success: false,
|
|
849
|
+
message: error || 'Invalid command',
|
|
850
|
+
technicalDetails: `Failed to parse: ${input}`
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (command === 'help') {
|
|
854
|
+
const helpCommand = args[0];
|
|
855
|
+
return {
|
|
856
|
+
success: true,
|
|
857
|
+
message: generateHelpMessage(helpCommand),
|
|
858
|
+
data: { command: helpCommand }
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const commandInfo = CLI_COMMAND_MAP[command];
|
|
862
|
+
// Check world requirement
|
|
863
|
+
if (commandInfo.requiresWorld && !context.currentWorldName) {
|
|
864
|
+
return {
|
|
865
|
+
success: false,
|
|
866
|
+
message: 'No world selected. World is required for this command.',
|
|
867
|
+
technicalDetails: `Command ${command} requires world context`
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
// Collect parameters from command arguments
|
|
871
|
+
const collectedParams = {};
|
|
872
|
+
for (let i = 0; i < commandInfo.parameters.length; i++) {
|
|
873
|
+
const param = commandInfo.parameters[i];
|
|
874
|
+
let value = args[i];
|
|
875
|
+
if (!value && param.required) {
|
|
876
|
+
return {
|
|
877
|
+
success: false,
|
|
878
|
+
message: `Missing required parameter: ${param.name}`,
|
|
879
|
+
technicalDetails: `Usage: ${commandInfo.usage}`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
if (value) {
|
|
883
|
+
// Type conversion
|
|
884
|
+
if (param.type === 'number') {
|
|
885
|
+
const numValue = parseInt(value);
|
|
886
|
+
if (isNaN(numValue)) {
|
|
887
|
+
return {
|
|
888
|
+
success: false,
|
|
889
|
+
message: `${param.name} must be a number`,
|
|
890
|
+
technicalDetails: `Invalid number: ${value}`
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
collectedParams[param.name] = numValue;
|
|
894
|
+
}
|
|
895
|
+
else if (param.type === 'boolean') {
|
|
896
|
+
collectedParams[param.name] = value.toLowerCase() === 'true';
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
collectedParams[param.name] = value;
|
|
900
|
+
}
|
|
901
|
+
// Validate options
|
|
902
|
+
if (param.options && !param.options.includes(collectedParams[param.name])) {
|
|
903
|
+
return {
|
|
904
|
+
success: false,
|
|
905
|
+
message: `Invalid ${param.name}. Valid options: ${param.options.join(', ')}`,
|
|
906
|
+
technicalDetails: `Invalid option: ${value}`
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// Use already loaded world from context
|
|
912
|
+
let world = null;
|
|
913
|
+
if (commandInfo.requiresWorld) {
|
|
914
|
+
if (context.currentWorld) {
|
|
915
|
+
world = context.currentWorld;
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
return {
|
|
919
|
+
success: false,
|
|
920
|
+
message: 'No world available',
|
|
921
|
+
technicalDetails: 'Command requires world context but no world is loaded'
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Execute command using core functions
|
|
926
|
+
let cliResponse;
|
|
927
|
+
switch (commandInfo.type) {
|
|
928
|
+
case 'createWorld': {
|
|
929
|
+
const shouldPrompt = collectedParams.name === undefined;
|
|
930
|
+
if (shouldPrompt) {
|
|
931
|
+
try {
|
|
932
|
+
const prompts = [
|
|
933
|
+
{
|
|
934
|
+
type: 'input',
|
|
935
|
+
name: 'name',
|
|
936
|
+
message: 'World name:',
|
|
937
|
+
required: true
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
type: 'input',
|
|
941
|
+
name: 'description',
|
|
942
|
+
message: 'World description:'
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
type: 'numeral',
|
|
946
|
+
name: 'turnLimit',
|
|
947
|
+
message: 'Turn limit:',
|
|
948
|
+
initial: 5
|
|
949
|
+
}
|
|
950
|
+
];
|
|
951
|
+
const answers = await enquirer.prompt(prompts);
|
|
952
|
+
const newWorld = await createWorld({
|
|
953
|
+
name: answers.name,
|
|
954
|
+
description: answers.description || `A world named ${answers.name}`,
|
|
955
|
+
turnLimit: answers.turnLimit
|
|
956
|
+
});
|
|
957
|
+
cliResponse = {
|
|
958
|
+
success: true,
|
|
959
|
+
message: `World '${answers.name}' created successfully`,
|
|
960
|
+
data: newWorld,
|
|
961
|
+
needsWorldRefresh: true
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
cliResponse = {
|
|
966
|
+
success: false,
|
|
967
|
+
message: 'Failed to create world',
|
|
968
|
+
error: error instanceof Error ? error.message : String(error)
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
try {
|
|
974
|
+
const newWorld = await createWorld({
|
|
975
|
+
name: collectedParams.name,
|
|
976
|
+
description: collectedParams.description || `A world named ${collectedParams.name}`,
|
|
977
|
+
turnLimit: collectedParams.turnLimit
|
|
978
|
+
});
|
|
979
|
+
cliResponse = {
|
|
980
|
+
success: true,
|
|
981
|
+
message: `World '${collectedParams.name}' created successfully`,
|
|
982
|
+
data: newWorld,
|
|
983
|
+
needsWorldRefresh: true
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
cliResponse = {
|
|
988
|
+
success: false,
|
|
989
|
+
message: 'Failed to create world',
|
|
990
|
+
error: error instanceof Error ? error.message : String(error)
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
case 'selectWorld':
|
|
997
|
+
cliResponse = {
|
|
998
|
+
success: true,
|
|
999
|
+
message: 'Opening world selection...',
|
|
1000
|
+
data: { selectWorld: true }
|
|
1001
|
+
};
|
|
1002
|
+
break;
|
|
1003
|
+
case 'createAgent': {
|
|
1004
|
+
const worldError = requireWorldOrError(world, command);
|
|
1005
|
+
if (worldError)
|
|
1006
|
+
return worldError;
|
|
1007
|
+
const shouldPrompt = collectedParams.name === undefined;
|
|
1008
|
+
if (shouldPrompt) {
|
|
1009
|
+
try {
|
|
1010
|
+
const prompts = [
|
|
1011
|
+
{
|
|
1012
|
+
type: 'input',
|
|
1013
|
+
name: 'name',
|
|
1014
|
+
message: 'Agent name:',
|
|
1015
|
+
required: true
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
type: 'select',
|
|
1019
|
+
name: 'provider',
|
|
1020
|
+
message: 'LLM Provider:',
|
|
1021
|
+
choices: Object.values(LLMProvider),
|
|
1022
|
+
initial: 'openai'
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
type: 'input',
|
|
1026
|
+
name: 'model',
|
|
1027
|
+
message: 'Model:',
|
|
1028
|
+
initial: 'gpt-4'
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
type: 'input',
|
|
1032
|
+
name: 'systemPrompt',
|
|
1033
|
+
message: 'System prompt (or press Enter for default):'
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
type: 'numeral',
|
|
1037
|
+
name: 'temperature',
|
|
1038
|
+
message: 'Temperature (0.0-2.0):',
|
|
1039
|
+
initial: 0.7
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
type: 'numeral',
|
|
1043
|
+
name: 'maxTokens',
|
|
1044
|
+
message: 'Max tokens:',
|
|
1045
|
+
initial: 4096
|
|
1046
|
+
}
|
|
1047
|
+
];
|
|
1048
|
+
const answers = await enquirer.prompt(prompts);
|
|
1049
|
+
const agent = await createAgent(world.id, {
|
|
1050
|
+
name: answers.name,
|
|
1051
|
+
type: 'conversational',
|
|
1052
|
+
provider: answers.provider,
|
|
1053
|
+
model: answers.model,
|
|
1054
|
+
systemPrompt: answers.systemPrompt || `You are ${answers.name}, an agent in the ${world.name} world.`,
|
|
1055
|
+
temperature: answers.temperature,
|
|
1056
|
+
maxTokens: answers.maxTokens
|
|
1057
|
+
});
|
|
1058
|
+
cliResponse = {
|
|
1059
|
+
success: true,
|
|
1060
|
+
message: `Agent '${answers.name}' created successfully`,
|
|
1061
|
+
data: agent,
|
|
1062
|
+
needsWorldRefresh: true
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
cliResponse = {
|
|
1067
|
+
success: false,
|
|
1068
|
+
message: 'Failed to create agent',
|
|
1069
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
try {
|
|
1075
|
+
const agent = await createAgent(world.id, {
|
|
1076
|
+
name: collectedParams.name,
|
|
1077
|
+
type: 'conversational',
|
|
1078
|
+
provider: LLMProvider.OPENAI,
|
|
1079
|
+
model: 'gpt-4',
|
|
1080
|
+
systemPrompt: collectedParams.prompt || `You are ${collectedParams.name}, an agent in the ${world.name} world.`
|
|
1081
|
+
});
|
|
1082
|
+
cliResponse = {
|
|
1083
|
+
success: true,
|
|
1084
|
+
message: `Agent '${collectedParams.name}' created successfully`,
|
|
1085
|
+
data: agent,
|
|
1086
|
+
needsWorldRefresh: true
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
cliResponse = {
|
|
1091
|
+
success: false,
|
|
1092
|
+
message: 'Failed to create agent',
|
|
1093
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
case 'clearAgentMemory':
|
|
1100
|
+
{
|
|
1101
|
+
const worldError = requireWorldOrError(world, command);
|
|
1102
|
+
if (worldError)
|
|
1103
|
+
return worldError;
|
|
1104
|
+
}
|
|
1105
|
+
logger.debug('clearAgentMemory command started', {
|
|
1106
|
+
agentName: collectedParams.agentName,
|
|
1107
|
+
worldName: world.name,
|
|
1108
|
+
worldId: world.id,
|
|
1109
|
+
agentsInWorld: Array.from(world.agents.keys())
|
|
1110
|
+
});
|
|
1111
|
+
// Handle /clear all to clear all agents' memory
|
|
1112
|
+
if (collectedParams.agentName.toLowerCase() === 'all') {
|
|
1113
|
+
const clearedAgents = [];
|
|
1114
|
+
for (const [agentName] of world.agents) {
|
|
1115
|
+
logger.debug('Clearing memory for agent', { agentName });
|
|
1116
|
+
await clearAgentMemory(world.id, agentName);
|
|
1117
|
+
clearedAgents.push(agentName);
|
|
1118
|
+
}
|
|
1119
|
+
cliResponse = {
|
|
1120
|
+
success: true,
|
|
1121
|
+
message: `Memory cleared for all agents: ${clearedAgents.join(', ')}`,
|
|
1122
|
+
data: { clearedAgents },
|
|
1123
|
+
needsWorldRefresh: true
|
|
1124
|
+
};
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
// Handle single agent clear
|
|
1128
|
+
logger.debug('Looking for agent in world.agents Map', {
|
|
1129
|
+
searchName: collectedParams.agentName,
|
|
1130
|
+
availableAgents: Array.from(world.agents.keys()),
|
|
1131
|
+
agentExists: world.agents.has(collectedParams.agentName)
|
|
1132
|
+
});
|
|
1133
|
+
const agentForClear = world.agents.get(collectedParams.agentName);
|
|
1134
|
+
if (!agentForClear) {
|
|
1135
|
+
logger.debug('Agent not found in world.agents Map');
|
|
1136
|
+
cliResponse = { success: false, message: `Agent '${collectedParams.agentName}' not found`, data: null };
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
logger.debug('Found agent, calling clearAgentMemory', {
|
|
1140
|
+
agentName: agentForClear.name,
|
|
1141
|
+
agentId: agentForClear.id,
|
|
1142
|
+
memoryCount: agentForClear.memory?.length || 0
|
|
1143
|
+
});
|
|
1144
|
+
try {
|
|
1145
|
+
const result = await clearAgentMemory(world.id, collectedParams.agentName);
|
|
1146
|
+
logger.debug('clearAgentMemory result', {
|
|
1147
|
+
success: !!result,
|
|
1148
|
+
resultAgentId: result?.id,
|
|
1149
|
+
resultMemoryCount: result?.memory?.length || 0
|
|
1150
|
+
});
|
|
1151
|
+
cliResponse = {
|
|
1152
|
+
success: true,
|
|
1153
|
+
message: `Agent '${collectedParams.agentName}' memory cleared successfully`,
|
|
1154
|
+
data: null,
|
|
1155
|
+
needsWorldRefresh: true
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
logger.error('clearAgentMemory error', { agentName: collectedParams.agentName, error: error instanceof Error ? error.message : error });
|
|
1160
|
+
cliResponse = {
|
|
1161
|
+
success: false,
|
|
1162
|
+
message: `Failed to clear agent memory: ${error instanceof Error ? error.message : error}`,
|
|
1163
|
+
data: null
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
break;
|
|
1167
|
+
// New World CRUD commands
|
|
1168
|
+
case 'listWorlds':
|
|
1169
|
+
try {
|
|
1170
|
+
const worlds = await listWorlds();
|
|
1171
|
+
if (worlds.length === 0) {
|
|
1172
|
+
cliResponse = {
|
|
1173
|
+
success: true,
|
|
1174
|
+
message: 'No worlds found.',
|
|
1175
|
+
data: { worlds: [] }
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
else {
|
|
1179
|
+
let output = '\nAvailable Worlds:\n';
|
|
1180
|
+
worlds.forEach((worldInfo) => {
|
|
1181
|
+
output += ` ID: ${worldInfo.id}\n`;
|
|
1182
|
+
output += ` Name: ${worldInfo.name}\n`;
|
|
1183
|
+
output += ` Description: ${worldInfo.description || 'No description'}\n`;
|
|
1184
|
+
output += ` Turn Limit: ${worldInfo.turnLimit}\n`;
|
|
1185
|
+
output += ` Agents: ${worldInfo.totalAgents}\n`;
|
|
1186
|
+
output += ` ---\n`;
|
|
1187
|
+
});
|
|
1188
|
+
cliResponse = {
|
|
1189
|
+
success: true,
|
|
1190
|
+
message: output,
|
|
1191
|
+
data: { worlds }
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
catch (error) {
|
|
1196
|
+
cliResponse = {
|
|
1197
|
+
success: false,
|
|
1198
|
+
message: 'Failed to list worlds',
|
|
1199
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
break;
|
|
1203
|
+
case 'showWorld':
|
|
1204
|
+
try {
|
|
1205
|
+
const worldData = await getWorld(collectedParams.name);
|
|
1206
|
+
if (!worldData) {
|
|
1207
|
+
cliResponse = {
|
|
1208
|
+
success: false,
|
|
1209
|
+
message: `World '${collectedParams.name}' not found`
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
// Get agent count
|
|
1214
|
+
const world = await getWorld(worldData.id);
|
|
1215
|
+
if (!world)
|
|
1216
|
+
throw new Error(`World ${collectedParams.name} not found`);
|
|
1217
|
+
const agents = await listAgents(worldData.id);
|
|
1218
|
+
let output = `\nWorld Details:\n`;
|
|
1219
|
+
output += ` ID: ${worldData.id}\n`;
|
|
1220
|
+
output += ` Name: ${worldData.name}\n`;
|
|
1221
|
+
output += ` Description: ${worldData.description || 'No description'}\n`;
|
|
1222
|
+
output += ` Turn Limit: ${worldData.turnLimit}\n`;
|
|
1223
|
+
output += ` Agents: ${agents.length}\n`;
|
|
1224
|
+
if (agents.length > 0) {
|
|
1225
|
+
output += `\nAgents in this world:\n`;
|
|
1226
|
+
agents.forEach(agent => {
|
|
1227
|
+
output += ` - ${agent.name} (${agent.id}) - ${agent.status || 'active'}\n`;
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
cliResponse = {
|
|
1231
|
+
success: true,
|
|
1232
|
+
message: output,
|
|
1233
|
+
data: { world: worldData, agents }
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
catch (error) {
|
|
1238
|
+
cliResponse = {
|
|
1239
|
+
success: false,
|
|
1240
|
+
message: 'Failed to get world details',
|
|
1241
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
break;
|
|
1245
|
+
case 'updateWorld':
|
|
1246
|
+
try {
|
|
1247
|
+
const existingWorld = await getWorld(collectedParams.name);
|
|
1248
|
+
if (!existingWorld) {
|
|
1249
|
+
cliResponse = {
|
|
1250
|
+
success: false,
|
|
1251
|
+
message: `World '${collectedParams.name}' not found`
|
|
1252
|
+
};
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1255
|
+
// Use enquirer for interactive prompts
|
|
1256
|
+
const prompts = [
|
|
1257
|
+
{
|
|
1258
|
+
type: 'input',
|
|
1259
|
+
name: 'name',
|
|
1260
|
+
message: 'World name:',
|
|
1261
|
+
initial: existingWorld.name
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
type: 'input',
|
|
1265
|
+
name: 'description',
|
|
1266
|
+
message: 'World description:',
|
|
1267
|
+
initial: existingWorld.description || ''
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
type: 'numeral',
|
|
1271
|
+
name: 'turnLimit',
|
|
1272
|
+
message: 'Turn limit:',
|
|
1273
|
+
initial: existingWorld.turnLimit
|
|
1274
|
+
}
|
|
1275
|
+
];
|
|
1276
|
+
const answers = await enquirer.prompt(prompts);
|
|
1277
|
+
const updatedWorld = await updateWorld(existingWorld.id, {
|
|
1278
|
+
name: answers.name,
|
|
1279
|
+
description: answers.description,
|
|
1280
|
+
turnLimit: answers.turnLimit
|
|
1281
|
+
});
|
|
1282
|
+
if (updatedWorld) {
|
|
1283
|
+
cliResponse = {
|
|
1284
|
+
success: true,
|
|
1285
|
+
message: `World '${answers.name}' updated successfully`,
|
|
1286
|
+
data: updatedWorld,
|
|
1287
|
+
needsWorldRefresh: true
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
cliResponse = {
|
|
1292
|
+
success: false,
|
|
1293
|
+
message: 'Failed to update world'
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
catch (error) {
|
|
1298
|
+
cliResponse = {
|
|
1299
|
+
success: false,
|
|
1300
|
+
message: 'Failed to update world',
|
|
1301
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
break;
|
|
1305
|
+
case 'deleteWorld':
|
|
1306
|
+
try {
|
|
1307
|
+
const existingWorld = await getWorld(collectedParams.name);
|
|
1308
|
+
if (!existingWorld) {
|
|
1309
|
+
cliResponse = {
|
|
1310
|
+
success: false,
|
|
1311
|
+
message: `World '${collectedParams.name}' not found`
|
|
1312
|
+
};
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
// Confirmation prompt
|
|
1316
|
+
const confirmPrompt = {
|
|
1317
|
+
type: 'confirm',
|
|
1318
|
+
name: 'confirmed',
|
|
1319
|
+
message: `Are you sure you want to delete world '${existingWorld.name}'? This action cannot be undone.`,
|
|
1320
|
+
initial: false
|
|
1321
|
+
};
|
|
1322
|
+
const { confirmed } = await enquirer.prompt(confirmPrompt);
|
|
1323
|
+
if (!confirmed) {
|
|
1324
|
+
cliResponse = {
|
|
1325
|
+
success: true,
|
|
1326
|
+
message: 'World deletion cancelled'
|
|
1327
|
+
};
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
const deleted = await deleteWorld(existingWorld.id);
|
|
1331
|
+
if (deleted) {
|
|
1332
|
+
cliResponse = {
|
|
1333
|
+
success: true,
|
|
1334
|
+
message: `World '${existingWorld.name}' deleted successfully`,
|
|
1335
|
+
needsWorldRefresh: true
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
cliResponse = {
|
|
1340
|
+
success: false,
|
|
1341
|
+
message: 'Failed to delete world'
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
catch (error) {
|
|
1346
|
+
cliResponse = {
|
|
1347
|
+
success: false,
|
|
1348
|
+
message: 'Failed to delete world',
|
|
1349
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
break;
|
|
1353
|
+
// New Agent CRUD commands
|
|
1354
|
+
case 'listAgents':
|
|
1355
|
+
{
|
|
1356
|
+
const worldError = requireWorldOrError(world, command);
|
|
1357
|
+
if (worldError)
|
|
1358
|
+
return worldError;
|
|
1359
|
+
}
|
|
1360
|
+
try {
|
|
1361
|
+
// Get world instance first
|
|
1362
|
+
const worldInstance = await getWorld(world.id);
|
|
1363
|
+
if (!worldInstance)
|
|
1364
|
+
throw new Error(`World not found`);
|
|
1365
|
+
const agents = await listAgents(world.id);
|
|
1366
|
+
if (agents.length === 0) {
|
|
1367
|
+
cliResponse = {
|
|
1368
|
+
success: true,
|
|
1369
|
+
message: `No agents found in world '${world.name}'.`
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
let output = `\nAgents in world '${world.name}':\n`;
|
|
1374
|
+
agents.forEach(agent => {
|
|
1375
|
+
output += ` Name: ${agent.name} (${agent.id})\n`;
|
|
1376
|
+
output += ` Type: ${agent.type}\n`;
|
|
1377
|
+
output += ` Model: ${agent.model}\n`;
|
|
1378
|
+
output += ` Status: ${agent.status || 'active'}\n`;
|
|
1379
|
+
output += ` Memory Size: ${agent.memory?.length || 0} messages\n`;
|
|
1380
|
+
output += ` LLM Calls: ${agent.llmCallCount}\n`;
|
|
1381
|
+
output += ` Created: ${agent.createdAt ? agent.createdAt.toISOString().split('T')[0] : 'Unknown'}\n`;
|
|
1382
|
+
output += ` Last Active: ${agent.lastActive ? agent.lastActive.toISOString().split('T')[0] : 'Unknown'}\n`;
|
|
1383
|
+
output += ` ---\n`;
|
|
1384
|
+
});
|
|
1385
|
+
cliResponse = {
|
|
1386
|
+
success: true,
|
|
1387
|
+
message: output,
|
|
1388
|
+
data: { agents }
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
catch (error) {
|
|
1393
|
+
cliResponse = {
|
|
1394
|
+
success: false,
|
|
1395
|
+
message: 'Failed to list agents',
|
|
1396
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
break;
|
|
1400
|
+
case 'showAgent':
|
|
1401
|
+
{
|
|
1402
|
+
const worldError = requireWorldOrError(world, command);
|
|
1403
|
+
if (worldError)
|
|
1404
|
+
return worldError;
|
|
1405
|
+
}
|
|
1406
|
+
try {
|
|
1407
|
+
const worldInstance = await getWorld(world.id);
|
|
1408
|
+
if (!worldInstance)
|
|
1409
|
+
throw new Error(`World not found`);
|
|
1410
|
+
const agent = await getAgent(world.id, collectedParams.name);
|
|
1411
|
+
if (!agent) {
|
|
1412
|
+
cliResponse = {
|
|
1413
|
+
success: false,
|
|
1414
|
+
message: `Agent '${collectedParams.name}' not found`
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
let output = `\nAgent Details:\n`;
|
|
1419
|
+
output += ` Name: ${agent.name}\n`;
|
|
1420
|
+
output += ` ID: ${agent.id}\n`;
|
|
1421
|
+
output += ` Type: ${agent.type}\n`;
|
|
1422
|
+
output += ` Provider: ${agent.provider}\n`;
|
|
1423
|
+
output += ` Model: ${agent.model}\n`;
|
|
1424
|
+
output += ` Status: ${agent.status || 'active'}\n`;
|
|
1425
|
+
output += ` Temperature: ${agent.temperature || 'default'}\n`;
|
|
1426
|
+
output += ` Max Tokens: ${agent.maxTokens || 'default'}\n`;
|
|
1427
|
+
output += ` Memory Size: ${agent.memory.length} messages\n`;
|
|
1428
|
+
output += ` LLM Calls: ${agent.llmCallCount}\n`;
|
|
1429
|
+
output += ` Created: ${agent.createdAt ? agent.createdAt.toISOString() : 'Unknown'}\n`;
|
|
1430
|
+
output += ` Last Active: ${agent.lastActive ? agent.lastActive.toISOString() : 'Unknown'}\n`;
|
|
1431
|
+
if (agent.systemPrompt) {
|
|
1432
|
+
output += `\nSystem Prompt:\n${agent.systemPrompt}\n`;
|
|
1433
|
+
}
|
|
1434
|
+
cliResponse = {
|
|
1435
|
+
success: true,
|
|
1436
|
+
message: output,
|
|
1437
|
+
data: { agent }
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
catch (error) {
|
|
1442
|
+
cliResponse = {
|
|
1443
|
+
success: false,
|
|
1444
|
+
message: 'Failed to get agent details',
|
|
1445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
break;
|
|
1449
|
+
case 'updateAgent':
|
|
1450
|
+
{
|
|
1451
|
+
const worldError = requireWorldOrError(world, command);
|
|
1452
|
+
if (worldError)
|
|
1453
|
+
return worldError;
|
|
1454
|
+
}
|
|
1455
|
+
try {
|
|
1456
|
+
const worldInstance = await getWorld(world.id);
|
|
1457
|
+
if (!worldInstance)
|
|
1458
|
+
throw new Error(`World not found`);
|
|
1459
|
+
const existingAgent = await getAgent(world.id, collectedParams.name);
|
|
1460
|
+
if (!existingAgent) {
|
|
1461
|
+
cliResponse = {
|
|
1462
|
+
success: false,
|
|
1463
|
+
message: `Agent '${collectedParams.name}' not found`
|
|
1464
|
+
};
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
// Use enquirer for interactive prompts with multiline support
|
|
1468
|
+
const prompts = [
|
|
1469
|
+
{
|
|
1470
|
+
type: 'input',
|
|
1471
|
+
name: 'name',
|
|
1472
|
+
message: 'Agent name:',
|
|
1473
|
+
initial: existingAgent.name
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
type: 'select',
|
|
1477
|
+
name: 'provider',
|
|
1478
|
+
message: 'LLM Provider:',
|
|
1479
|
+
choices: Object.values(LLMProvider),
|
|
1480
|
+
initial: existingAgent.provider
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
type: 'input',
|
|
1484
|
+
name: 'model',
|
|
1485
|
+
message: 'Model:',
|
|
1486
|
+
initial: existingAgent.model
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
type: 'input',
|
|
1490
|
+
name: 'systemPrompt',
|
|
1491
|
+
message: 'System prompt (or press Enter for default):',
|
|
1492
|
+
initial: existingAgent.systemPrompt || ''
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
type: 'numeral',
|
|
1496
|
+
name: 'temperature',
|
|
1497
|
+
message: 'Temperature (0.0-2.0):',
|
|
1498
|
+
initial: existingAgent.temperature || 0.7
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
type: 'numeral',
|
|
1502
|
+
name: 'maxTokens',
|
|
1503
|
+
message: 'Max tokens:',
|
|
1504
|
+
initial: existingAgent.maxTokens || 4096
|
|
1505
|
+
}
|
|
1506
|
+
];
|
|
1507
|
+
const answers = await enquirer.prompt(prompts);
|
|
1508
|
+
const updatedAgent = await updateAgent(world.id, existingAgent.id, {
|
|
1509
|
+
name: answers.name,
|
|
1510
|
+
provider: answers.provider,
|
|
1511
|
+
model: answers.model,
|
|
1512
|
+
systemPrompt: answers.systemPrompt,
|
|
1513
|
+
temperature: answers.temperature,
|
|
1514
|
+
maxTokens: answers.maxTokens
|
|
1515
|
+
});
|
|
1516
|
+
if (updatedAgent) {
|
|
1517
|
+
cliResponse = {
|
|
1518
|
+
success: true,
|
|
1519
|
+
message: `Agent '${answers.name}' updated successfully`,
|
|
1520
|
+
data: updatedAgent,
|
|
1521
|
+
needsWorldRefresh: true
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
cliResponse = {
|
|
1526
|
+
success: false,
|
|
1527
|
+
message: 'Failed to update agent'
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
cliResponse = {
|
|
1533
|
+
success: false,
|
|
1534
|
+
message: 'Failed to update agent',
|
|
1535
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
break;
|
|
1539
|
+
case 'deleteAgent':
|
|
1540
|
+
{
|
|
1541
|
+
const worldError = requireWorldOrError(world, command);
|
|
1542
|
+
if (worldError)
|
|
1543
|
+
return worldError;
|
|
1544
|
+
}
|
|
1545
|
+
try {
|
|
1546
|
+
const worldInstance = await getWorld(world.id);
|
|
1547
|
+
if (!worldInstance)
|
|
1548
|
+
throw new Error(`World not found`);
|
|
1549
|
+
const existingAgent = await getAgent(world.id, collectedParams.name);
|
|
1550
|
+
if (!existingAgent) {
|
|
1551
|
+
cliResponse = {
|
|
1552
|
+
success: false,
|
|
1553
|
+
message: `Agent '${collectedParams.name}' not found`
|
|
1554
|
+
};
|
|
1555
|
+
break;
|
|
1556
|
+
}
|
|
1557
|
+
// Confirmation prompt
|
|
1558
|
+
const confirmPrompt = {
|
|
1559
|
+
type: 'confirm',
|
|
1560
|
+
name: 'confirmed',
|
|
1561
|
+
message: `Are you sure you want to delete agent '${existingAgent.name}'? This action cannot be undone.`,
|
|
1562
|
+
initial: false
|
|
1563
|
+
};
|
|
1564
|
+
const { confirmed } = await enquirer.prompt(confirmPrompt);
|
|
1565
|
+
if (!confirmed) {
|
|
1566
|
+
cliResponse = {
|
|
1567
|
+
success: true,
|
|
1568
|
+
message: 'Agent deletion cancelled'
|
|
1569
|
+
};
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
const deleted = await deleteAgent(world.id, existingAgent.id);
|
|
1573
|
+
if (deleted) {
|
|
1574
|
+
cliResponse = {
|
|
1575
|
+
success: true,
|
|
1576
|
+
message: `Agent '${existingAgent.name}' deleted successfully`,
|
|
1577
|
+
needsWorldRefresh: true
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
cliResponse = {
|
|
1582
|
+
success: false,
|
|
1583
|
+
message: 'Failed to delete agent'
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
catch (error) {
|
|
1588
|
+
cliResponse = {
|
|
1589
|
+
success: false,
|
|
1590
|
+
message: 'Failed to delete agent',
|
|
1591
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
break;
|
|
1595
|
+
case 'exportWorld':
|
|
1596
|
+
{
|
|
1597
|
+
const worldError = requireWorldOrError(world, command);
|
|
1598
|
+
if (worldError)
|
|
1599
|
+
return worldError;
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
const fileParam = collectedParams.file;
|
|
1603
|
+
cliResponse = await exportWorldToMarkdownFile(world.name, fileParam);
|
|
1604
|
+
}
|
|
1605
|
+
catch (error) {
|
|
1606
|
+
cliResponse = {
|
|
1607
|
+
success: false,
|
|
1608
|
+
message: 'Failed to export world',
|
|
1609
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
break;
|
|
1613
|
+
case 'saveWorld':
|
|
1614
|
+
{
|
|
1615
|
+
const worldError = requireWorldOrError(world, command);
|
|
1616
|
+
if (worldError)
|
|
1617
|
+
return worldError;
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
cliResponse = await saveWorldToStorage(world);
|
|
1621
|
+
}
|
|
1622
|
+
catch (error) {
|
|
1623
|
+
cliResponse = {
|
|
1624
|
+
success: false,
|
|
1625
|
+
message: 'Failed to save world',
|
|
1626
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
break;
|
|
1630
|
+
// Chat history commands
|
|
1631
|
+
case 'listChats': {
|
|
1632
|
+
const worldError = requireWorldOrError(world, command);
|
|
1633
|
+
if (worldError)
|
|
1634
|
+
return worldError;
|
|
1635
|
+
const filter = collectedParams.filter ? String(collectedParams.filter).toLowerCase() : undefined;
|
|
1636
|
+
if (filter && filter !== '--active') {
|
|
1637
|
+
cliResponse = {
|
|
1638
|
+
success: false,
|
|
1639
|
+
message: `Unknown filter '${collectedParams.filter}'. Did you mean --active?`
|
|
1640
|
+
};
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
const chats = await listChats(world.id);
|
|
1645
|
+
const worldState = await getWorld(world.id);
|
|
1646
|
+
const currentChatId = worldState?.currentChatId || null;
|
|
1647
|
+
if (filter === '--active') {
|
|
1648
|
+
if (!currentChatId) {
|
|
1649
|
+
cliResponse = {
|
|
1650
|
+
success: true,
|
|
1651
|
+
message: `World '${world.name}' does not have an active chat.`
|
|
1652
|
+
};
|
|
1653
|
+
break;
|
|
1654
|
+
}
|
|
1655
|
+
const activeChat = chats.find(chat => chat.id === currentChatId);
|
|
1656
|
+
if (!activeChat) {
|
|
1657
|
+
cliResponse = {
|
|
1658
|
+
success: false,
|
|
1659
|
+
message: `Active chat '${currentChatId}' not found.`,
|
|
1660
|
+
technicalDetails: 'Active chat ID missing from storage'
|
|
1661
|
+
};
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
let output = `\nCurrent chat in world '${world.name}':\n`;
|
|
1665
|
+
output += ` ID: ${activeChat.id}\n`;
|
|
1666
|
+
output += ` Name: ${activeChat.name}\n`;
|
|
1667
|
+
if (activeChat.description)
|
|
1668
|
+
output += ` Description: ${activeChat.description}\n`;
|
|
1669
|
+
output += ` Messages: ${activeChat.messageCount}\n`;
|
|
1670
|
+
output += ` Updated: ${activeChat.updatedAt.toISOString()}\n`;
|
|
1671
|
+
cliResponse = {
|
|
1672
|
+
success: true,
|
|
1673
|
+
message: output,
|
|
1674
|
+
data: { chats: [activeChat], currentChatId }
|
|
1675
|
+
};
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
if (chats.length === 0) {
|
|
1679
|
+
cliResponse = {
|
|
1680
|
+
success: true,
|
|
1681
|
+
message: `No chat history found in world '${world.name}'.`
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
let output = `\nChat history in world '${world.name}':\n`;
|
|
1686
|
+
chats.forEach((chat) => {
|
|
1687
|
+
const isCurrent = currentChatId && chat.id === currentChatId;
|
|
1688
|
+
output += ` ID: ${chat.id}${isCurrent ? ' (current)' : ''}\n`;
|
|
1689
|
+
output += ` Name: ${chat.name}\n`;
|
|
1690
|
+
if (chat.description)
|
|
1691
|
+
output += ` Description: ${chat.description}\n`;
|
|
1692
|
+
output += ` Messages: ${chat.messageCount}\n`;
|
|
1693
|
+
output += ` Created: ${chat.createdAt.toISOString().split('T')[0]}\n`;
|
|
1694
|
+
output += ` Updated: ${chat.updatedAt.toISOString().split('T')[0]}\n`;
|
|
1695
|
+
output += ` ---\n`;
|
|
1696
|
+
});
|
|
1697
|
+
cliResponse = {
|
|
1698
|
+
success: true,
|
|
1699
|
+
message: output,
|
|
1700
|
+
data: { chats, currentChatId }
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
catch (error) {
|
|
1705
|
+
cliResponse = {
|
|
1706
|
+
success: false,
|
|
1707
|
+
message: 'Failed to list chat history',
|
|
1708
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
case 'createChat':
|
|
1714
|
+
{
|
|
1715
|
+
const worldError = requireWorldOrError(world, command);
|
|
1716
|
+
if (worldError)
|
|
1717
|
+
return worldError;
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
// Create a simple new chat using the available API
|
|
1721
|
+
const updatedWorld = await newChat(world.id);
|
|
1722
|
+
if (updatedWorld) {
|
|
1723
|
+
cliResponse = {
|
|
1724
|
+
success: true,
|
|
1725
|
+
message: `New chat created successfully for world '${updatedWorld.name}'`,
|
|
1726
|
+
data: { worldId: updatedWorld.id, currentChatId: updatedWorld.currentChatId }
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
else {
|
|
1730
|
+
cliResponse = {
|
|
1731
|
+
success: false,
|
|
1732
|
+
message: 'Failed to create new chat',
|
|
1733
|
+
error: 'Unknown error occurred'
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1738
|
+
cliResponse = {
|
|
1739
|
+
success: false,
|
|
1740
|
+
message: 'Failed to create chat',
|
|
1741
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
break;
|
|
1745
|
+
case 'selectChat':
|
|
1746
|
+
{
|
|
1747
|
+
const worldError = requireWorldOrError(world, command);
|
|
1748
|
+
if (worldError)
|
|
1749
|
+
return worldError;
|
|
1750
|
+
}
|
|
1751
|
+
try {
|
|
1752
|
+
// Get all chats for the world
|
|
1753
|
+
const chats = await listChats(world.id);
|
|
1754
|
+
if (chats.length === 0) {
|
|
1755
|
+
cliResponse = {
|
|
1756
|
+
success: true,
|
|
1757
|
+
message: `No chat history found in world '${world.name}'.`
|
|
1758
|
+
};
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
// Get current chat ID
|
|
1762
|
+
const worldState = await getWorld(world.id);
|
|
1763
|
+
const currentChatId = worldState?.currentChatId || null;
|
|
1764
|
+
// If only one chat, auto-select it
|
|
1765
|
+
if (chats.length === 1) {
|
|
1766
|
+
const chat = chats[0];
|
|
1767
|
+
console.log(`\n${boldGreen('Auto-selecting the only available chat:')} ${cyan(chat.name)} (${gray(chat.id)})`);
|
|
1768
|
+
// Restore the chat
|
|
1769
|
+
const restored = await restoreChat(world.id, chat.id);
|
|
1770
|
+
if (!restored) {
|
|
1771
|
+
cliResponse = {
|
|
1772
|
+
success: false,
|
|
1773
|
+
message: `Failed to restore chat '${chat.id}'`
|
|
1774
|
+
};
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
// Display chat messages
|
|
1778
|
+
await displayChatMessages(world.id, chat.id);
|
|
1779
|
+
cliResponse = {
|
|
1780
|
+
success: true,
|
|
1781
|
+
message: `Chat '${chat.name}' selected and loaded`,
|
|
1782
|
+
data: { worldId: restored.id, currentChatId: restored.currentChatId },
|
|
1783
|
+
needsWorldRefresh: true
|
|
1784
|
+
};
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
// Return data for interactive chat selection in CLI
|
|
1788
|
+
cliResponse = {
|
|
1789
|
+
success: true,
|
|
1790
|
+
message: 'Opening chat selection...',
|
|
1791
|
+
data: {
|
|
1792
|
+
selectChat: true,
|
|
1793
|
+
chats,
|
|
1794
|
+
currentChatId
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
cliResponse = {
|
|
1800
|
+
success: false,
|
|
1801
|
+
message: 'Failed to select chat',
|
|
1802
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
break;
|
|
1806
|
+
case 'loadChat':
|
|
1807
|
+
{
|
|
1808
|
+
const worldError = requireWorldOrError(world, command);
|
|
1809
|
+
if (worldError)
|
|
1810
|
+
return worldError;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
// Simplified: use restoreChat function directly
|
|
1814
|
+
const restored = await restoreChat(world.id, collectedParams.chatId);
|
|
1815
|
+
if (!restored) {
|
|
1816
|
+
cliResponse = {
|
|
1817
|
+
success: false,
|
|
1818
|
+
message: `Chat '${collectedParams.chatId}' not found or could not be restored`
|
|
1819
|
+
};
|
|
1820
|
+
break;
|
|
1821
|
+
}
|
|
1822
|
+
cliResponse = {
|
|
1823
|
+
success: true,
|
|
1824
|
+
message: `Successfully restored world state from chat '${collectedParams.chatId}'`,
|
|
1825
|
+
data: { worldId: restored.id, currentChatId: restored.currentChatId },
|
|
1826
|
+
needsWorldRefresh: true
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
catch (error) {
|
|
1830
|
+
cliResponse = {
|
|
1831
|
+
success: false,
|
|
1832
|
+
message: 'Failed to load chat',
|
|
1833
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
break;
|
|
1837
|
+
case 'deleteChat':
|
|
1838
|
+
{
|
|
1839
|
+
const worldError = requireWorldOrError(world, command);
|
|
1840
|
+
if (worldError)
|
|
1841
|
+
return worldError;
|
|
1842
|
+
}
|
|
1843
|
+
try {
|
|
1844
|
+
// Simplified: use deleteChat function directly
|
|
1845
|
+
const deleted = await deleteChat(world.id, collectedParams.chatId);
|
|
1846
|
+
if (deleted) {
|
|
1847
|
+
cliResponse = {
|
|
1848
|
+
success: true,
|
|
1849
|
+
message: `Chat '${collectedParams.chatId}' deleted successfully`
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
else {
|
|
1853
|
+
cliResponse = {
|
|
1854
|
+
success: false,
|
|
1855
|
+
message: 'Failed to delete chat - chat may not exist'
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
catch (error) {
|
|
1860
|
+
cliResponse = {
|
|
1861
|
+
success: false,
|
|
1862
|
+
message: 'Failed to delete chat',
|
|
1863
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
break;
|
|
1867
|
+
case 'renameChat': {
|
|
1868
|
+
const worldError = requireWorldOrError(world, command);
|
|
1869
|
+
if (worldError)
|
|
1870
|
+
return worldError;
|
|
1871
|
+
try {
|
|
1872
|
+
const updatedChat = await updateChat(world.id, collectedParams.chatId, {
|
|
1873
|
+
name: collectedParams.name,
|
|
1874
|
+
description: collectedParams.description
|
|
1875
|
+
});
|
|
1876
|
+
if (!updatedChat) {
|
|
1877
|
+
cliResponse = {
|
|
1878
|
+
success: false,
|
|
1879
|
+
message: `Chat '${collectedParams.chatId}' not found`
|
|
1880
|
+
};
|
|
1881
|
+
break;
|
|
1882
|
+
}
|
|
1883
|
+
cliResponse = {
|
|
1884
|
+
success: true,
|
|
1885
|
+
message: `Chat '${collectedParams.chatId}' renamed to '${updatedChat.name}'`,
|
|
1886
|
+
data: updatedChat,
|
|
1887
|
+
needsWorldRefresh: true
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
catch (error) {
|
|
1891
|
+
cliResponse = {
|
|
1892
|
+
success: false,
|
|
1893
|
+
message: 'Failed to rename chat',
|
|
1894
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
case 'exportChat': {
|
|
1900
|
+
const worldError = requireWorldOrError(world, command);
|
|
1901
|
+
if (worldError)
|
|
1902
|
+
return worldError;
|
|
1903
|
+
const chatId = collectedParams.chatId || world.currentChatId;
|
|
1904
|
+
if (!chatId) {
|
|
1905
|
+
cliResponse = {
|
|
1906
|
+
success: false,
|
|
1907
|
+
message: `No chat selected. Use /chat list --active to see the current chat.`
|
|
1908
|
+
};
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
try {
|
|
1912
|
+
cliResponse = await exportChatToMarkdownFile(world.id, world.name, chatId, collectedParams.file);
|
|
1913
|
+
}
|
|
1914
|
+
catch (error) {
|
|
1915
|
+
cliResponse = {
|
|
1916
|
+
success: false,
|
|
1917
|
+
message: 'Failed to export chat',
|
|
1918
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
break;
|
|
1922
|
+
}
|
|
1923
|
+
case 'quit':
|
|
1924
|
+
case 'exit':
|
|
1925
|
+
cliResponse = {
|
|
1926
|
+
success: true,
|
|
1927
|
+
message: 'Exiting CLI...',
|
|
1928
|
+
data: { exit: true }
|
|
1929
|
+
};
|
|
1930
|
+
break;
|
|
1931
|
+
default:
|
|
1932
|
+
cliResponse = { success: false, message: `Unknown command type: ${commandInfo.type}`, data: null };
|
|
1933
|
+
}
|
|
1934
|
+
// Signal CLI to refresh subscription if needed
|
|
1935
|
+
if (cliResponse.needsWorldRefresh) {
|
|
1936
|
+
cliResponse.refreshWorld = true;
|
|
1937
|
+
}
|
|
1938
|
+
return cliResponse;
|
|
1939
|
+
}
|
|
1940
|
+
catch (error) {
|
|
1941
|
+
return {
|
|
1942
|
+
success: false,
|
|
1943
|
+
message: 'Command execution failed',
|
|
1944
|
+
technicalDetails: error instanceof Error ? error.message : String(error)
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
// Main CLI input processor - handles both commands and messages
|
|
1949
|
+
export async function processCLIInput(input, world, sender = 'human') {
|
|
1950
|
+
const context = {
|
|
1951
|
+
currentWorld: world,
|
|
1952
|
+
currentWorldName: world?.name
|
|
1953
|
+
};
|
|
1954
|
+
// Simple prompt function for CLI
|
|
1955
|
+
const promptFunction = async (question, options) => {
|
|
1956
|
+
console.log(question);
|
|
1957
|
+
if (options && options.length > 0) {
|
|
1958
|
+
console.log(`Options: ${options.join(', ')}`);
|
|
1959
|
+
}
|
|
1960
|
+
return new Promise((resolve) => {
|
|
1961
|
+
const rl = readline.createInterface({
|
|
1962
|
+
input: process.stdin,
|
|
1963
|
+
output: process.stdout
|
|
1964
|
+
});
|
|
1965
|
+
rl.question('> ', (answer) => {
|
|
1966
|
+
rl.close();
|
|
1967
|
+
resolve(answer);
|
|
1968
|
+
});
|
|
1969
|
+
});
|
|
1970
|
+
};
|
|
1971
|
+
// Process commands (starting with '/')
|
|
1972
|
+
if (input.trim().startsWith('/')) {
|
|
1973
|
+
const parsed = parseCLICommand(input);
|
|
1974
|
+
if (!parsed.isValid) {
|
|
1975
|
+
return {
|
|
1976
|
+
success: false,
|
|
1977
|
+
message: parsed.error || 'Invalid command',
|
|
1978
|
+
technicalDetails: `Failed to parse command: ${input}`
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
return await processCLICommand(input, context, promptFunction);
|
|
1982
|
+
}
|
|
1983
|
+
// Handle messages to the current world
|
|
1984
|
+
if (!world) {
|
|
1985
|
+
return {
|
|
1986
|
+
success: false,
|
|
1987
|
+
message: 'Cannot send message - no world selected',
|
|
1988
|
+
technicalDetails: 'Message requires world context'
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
const currentChatId = String(world?.currentChatId || '').trim();
|
|
1993
|
+
if (!currentChatId) {
|
|
1994
|
+
return {
|
|
1995
|
+
success: false,
|
|
1996
|
+
message: 'Cannot send message - no active chat session',
|
|
1997
|
+
technicalDetails: 'Message requires an active chat context'
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
const restoredWorld = await restoreChat(world.id, currentChatId);
|
|
2001
|
+
if (!restoredWorld || restoredWorld.currentChatId !== currentChatId) {
|
|
2002
|
+
return {
|
|
2003
|
+
success: false,
|
|
2004
|
+
message: `Cannot send message - chat not found: ${currentChatId}`,
|
|
2005
|
+
technicalDetails: `Failed to restore active chat '${currentChatId}' before message publish`
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
publishMessage(world, input, sender, currentChatId);
|
|
2009
|
+
return {
|
|
2010
|
+
success: true,
|
|
2011
|
+
message: '',
|
|
2012
|
+
data: { sender, chatId: currentChatId },
|
|
2013
|
+
technicalDetails: `Message published to world '${world.name}' (chat '${currentChatId}')`
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
catch (error) {
|
|
2017
|
+
return {
|
|
2018
|
+
success: false,
|
|
2019
|
+
message: 'Failed to send message',
|
|
2020
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2021
|
+
technicalDetails: `Error publishing message to world '${world.name}': ${error instanceof Error ? error.message : error}`
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
}
|