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