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,1218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Management Module
|
|
3
|
+
*
|
|
4
|
+
* Handles agent memory operations, LLM call management, and chat title generation.
|
|
5
|
+
* Provides functions for saving messages, continuing LLM after tool execution, and text response handling.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Save incoming messages to agent memory with auto-save
|
|
9
|
+
* - Continue LLM execution after tool results (auto-execution flow)
|
|
10
|
+
* - Handle text responses with auto-mention logic
|
|
11
|
+
* - Reset LLM call count for human/world messages
|
|
12
|
+
* - Generate chat titles from message content using LLM
|
|
13
|
+
*
|
|
14
|
+
* Dependencies (Layer 4):
|
|
15
|
+
* - types.ts (Layer 1)
|
|
16
|
+
* - mention-logic.ts (Layer 2)
|
|
17
|
+
* - publishers.ts (Layer 3)
|
|
18
|
+
* - utils.ts, logger.ts
|
|
19
|
+
* - llm-manager.ts (runtime)
|
|
20
|
+
* - storage (runtime)
|
|
21
|
+
*
|
|
22
|
+
* Changes:
|
|
23
|
+
* - 2026-02-16: Added plain-text tool-intent fallback parser in continuation to synthesize executable `tool_calls` when providers return `Calling tool: ...` text.
|
|
24
|
+
* - 2026-02-16: Max tool-hop guardrail now emits UI/tool errors and injects transient LLM context, then continues loop instead of returning.
|
|
25
|
+
* - 2026-02-16: Removed plain-text tool-intent reminder/retry path; continuation now relies only on tool-call loop + hop guardrail.
|
|
26
|
+
* - 2026-02-16: Empty/invalid continuation tool_calls now write a synthetic tool-error result back to memory before continuing the LLM loop.
|
|
27
|
+
* - 2026-02-16: Added bounded retry when continuation returns empty/invalid `tool_calls` so agent loops do not stop silently.
|
|
28
|
+
* - 2026-02-16: Added bounded retry when post-tool continuation returns empty text so tool loops (e.g., load_skill) do not stop silently.
|
|
29
|
+
* - 2026-02-16: Added multi-hop tool continuation support when post-tool LLM responses contain additional tool_calls.
|
|
30
|
+
* - 2026-02-15: Sanitized agent self-mentions in `handleTextResponse` before auto-mentioning to prevent `@self` prefixes.
|
|
31
|
+
* - 2026-02-13: Added per-agent `autoReply` gate; disables sender auto-mention when set to false.
|
|
32
|
+
* - 2026-02-13: Hardened title output normalization with markdown/prefix stripping and low-quality fallback hierarchy.
|
|
33
|
+
* - 2026-02-13: Canceled title-generation calls now exit without fallback renaming.
|
|
34
|
+
* - 2026-02-13: Added deterministic chat-title prompt shaping (role filtering, de-duplication, bounded window).
|
|
35
|
+
* - 2026-02-13: Made chat-title generation explicitly chat-scoped by requiring target `chatId`.
|
|
36
|
+
* - 2026-02-13: Title generation LLM calls now use chat-scoped queue context for cancellation alignment.
|
|
37
|
+
* - 2026-02-13: Added abort-signal guards so stop requests prevent post-tool LLM continuation and suppress cancellation noise.
|
|
38
|
+
* - 2026-02-13: Passed explicit `chatId` through LLM calls for chat-scoped stop cancellation support.
|
|
39
|
+
* - 2026-02-08: Removed stale manual tool-intervention terminology from comments and transient types
|
|
40
|
+
* - 2026-02-06: Renamed resumeLLMAfterManualDecision to continueLLMAfterToolExecution
|
|
41
|
+
* - 2025-01-09: Extracted from events.ts for modular architecture
|
|
42
|
+
*/
|
|
43
|
+
import { SenderType } from '../types.js';
|
|
44
|
+
import { generateId, determineSenderType, prepareMessagesForLLM, getEnvValueFromText, getDefaultWorkingDirectory, } from '../utils.js';
|
|
45
|
+
import { parseMessageContent } from '../message-prep.js';
|
|
46
|
+
import { createCategoryLogger } from '../logger.js';
|
|
47
|
+
import { beginWorldActivity } from '../activity-tracker.js';
|
|
48
|
+
import { createStorageWithWrappers } from '../storage/storage-factory.js';
|
|
49
|
+
import { generateAgentResponse } from '../llm-manager.js';
|
|
50
|
+
import { isMessageProcessingCanceledError, throwIfMessageProcessingStopped } from '../message-processing-control.js';
|
|
51
|
+
import { shouldAutoMention, addAutoMention, hasAnyMentionAtBeginning, removeSelfMentions } from './mention-logic.js';
|
|
52
|
+
import { publishMessageWithId, publishSSE, publishEvent, publishToolEvent, isStreamingEnabled } from './publishers.js';
|
|
53
|
+
import { logToolBridge } from './tool-bridge-logging.js';
|
|
54
|
+
const loggerMemory = createCategoryLogger('memory');
|
|
55
|
+
const loggerAgent = createCategoryLogger('agent');
|
|
56
|
+
const loggerTurnLimit = createCategoryLogger('turnlimit');
|
|
57
|
+
const loggerChatTitle = createCategoryLogger('chattitle');
|
|
58
|
+
const loggerAutoMention = createCategoryLogger('automention');
|
|
59
|
+
const TITLE_PROMPT_MAX_TURNS = 24;
|
|
60
|
+
const TITLE_PROMPT_MAX_CHARS_PER_TURN = 240;
|
|
61
|
+
// Storage wrapper instance - initialized lazily
|
|
62
|
+
let storageWrappers = null;
|
|
63
|
+
async function getStorageWrappers() {
|
|
64
|
+
if (!storageWrappers) {
|
|
65
|
+
storageWrappers = await createStorageWithWrappers();
|
|
66
|
+
}
|
|
67
|
+
return storageWrappers;
|
|
68
|
+
}
|
|
69
|
+
const GENERIC_TITLES = new Set([
|
|
70
|
+
'chat',
|
|
71
|
+
'new chat',
|
|
72
|
+
'conversation',
|
|
73
|
+
'untitled',
|
|
74
|
+
'title',
|
|
75
|
+
'assistant chat',
|
|
76
|
+
'user chat',
|
|
77
|
+
'chat title'
|
|
78
|
+
]);
|
|
79
|
+
function normalizeTitlePromptText(content) {
|
|
80
|
+
return content
|
|
81
|
+
.replace(/[\r\n\t]+/g, ' ')
|
|
82
|
+
.replace(/\s+/g, ' ')
|
|
83
|
+
.trim();
|
|
84
|
+
}
|
|
85
|
+
function clipTitlePromptText(content) {
|
|
86
|
+
if (content.length <= TITLE_PROMPT_MAX_CHARS_PER_TURN) {
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
return `${content.substring(0, TITLE_PROMPT_MAX_CHARS_PER_TURN - 3)}...`;
|
|
90
|
+
}
|
|
91
|
+
function buildTitlePromptMessages(messages) {
|
|
92
|
+
const dedupKeys = new Set();
|
|
93
|
+
const filtered = [];
|
|
94
|
+
for (const message of messages) {
|
|
95
|
+
if (message.role !== 'user' && message.role !== 'assistant') {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (typeof message.content !== 'string') {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const normalized = normalizeTitlePromptText(message.content);
|
|
102
|
+
if (!normalized) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const clipped = clipTitlePromptText(normalized);
|
|
106
|
+
const dedupKey = message.messageId
|
|
107
|
+
? `id:${message.messageId}`
|
|
108
|
+
: `${message.role}:${clipped.toLowerCase()}`;
|
|
109
|
+
if (dedupKeys.has(dedupKey)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
dedupKeys.add(dedupKey);
|
|
113
|
+
filtered.push({
|
|
114
|
+
role: message.role,
|
|
115
|
+
content: clipped
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return filtered.slice(-TITLE_PROMPT_MAX_TURNS);
|
|
119
|
+
}
|
|
120
|
+
function sanitizeGeneratedTitle(rawTitle) {
|
|
121
|
+
const firstLine = String(rawTitle || '').split(/\r?\n/).find((line) => line.trim()) || '';
|
|
122
|
+
let title = firstLine
|
|
123
|
+
.trim()
|
|
124
|
+
.replace(/^#+\s*/, '')
|
|
125
|
+
.replace(/^[-*]\s+/, '')
|
|
126
|
+
.replace(/^\d+[.)]\s+/, '')
|
|
127
|
+
.replace(/^title\s*[:\-]\s*/i, '')
|
|
128
|
+
.replace(/^["'`]+|["'`]+$/g, '')
|
|
129
|
+
.replace(/[\r\n\*`_]+/g, ' ')
|
|
130
|
+
.replace(/\s+/g, ' ')
|
|
131
|
+
.trim();
|
|
132
|
+
title = title.replace(/[.!?]+$/g, '').trim();
|
|
133
|
+
return title;
|
|
134
|
+
}
|
|
135
|
+
function isLowQualityTitle(title) {
|
|
136
|
+
if (!title)
|
|
137
|
+
return true;
|
|
138
|
+
const normalized = title.trim().toLowerCase();
|
|
139
|
+
if (!normalized)
|
|
140
|
+
return true;
|
|
141
|
+
if (GENERIC_TITLES.has(normalized))
|
|
142
|
+
return true;
|
|
143
|
+
if (normalized.length < 3)
|
|
144
|
+
return true;
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
function pickFallbackTitle(content, promptMessages) {
|
|
148
|
+
const contentCandidate = sanitizeGeneratedTitle(content);
|
|
149
|
+
if (!isLowQualityTitle(contentCandidate)) {
|
|
150
|
+
return contentCandidate;
|
|
151
|
+
}
|
|
152
|
+
for (const message of promptMessages) {
|
|
153
|
+
if (message.role !== 'user')
|
|
154
|
+
continue;
|
|
155
|
+
const candidate = sanitizeGeneratedTitle(message.content);
|
|
156
|
+
if (!isLowQualityTitle(candidate)) {
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return 'Chat Session';
|
|
161
|
+
}
|
|
162
|
+
function isTitleGenerationCanceledError(error) {
|
|
163
|
+
if (!error)
|
|
164
|
+
return false;
|
|
165
|
+
if (error instanceof DOMException && error.name === 'AbortError')
|
|
166
|
+
return true;
|
|
167
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
168
|
+
return true;
|
|
169
|
+
if (error instanceof Error) {
|
|
170
|
+
const message = error.message || '';
|
|
171
|
+
if (message.includes('LLM call canceled for world'))
|
|
172
|
+
return true;
|
|
173
|
+
if (message.includes('LLM call canceled for agent'))
|
|
174
|
+
return true;
|
|
175
|
+
if (message.includes('Message processing canceled by user'))
|
|
176
|
+
return true;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
function parseToolCallArguments(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 {};
|
|
189
|
+
}
|
|
190
|
+
const trimmed = rawArguments.trim();
|
|
191
|
+
if (!trimmed)
|
|
192
|
+
return {};
|
|
193
|
+
const parsed = JSON.parse(trimmed);
|
|
194
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
195
|
+
return parsed;
|
|
196
|
+
}
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
function decodeControlTokens(value) {
|
|
200
|
+
return value.replace(/<ctrl(\d+)>/gi, (_match, codeRaw) => {
|
|
201
|
+
const code = Number(codeRaw);
|
|
202
|
+
if (!Number.isFinite(code))
|
|
203
|
+
return '';
|
|
204
|
+
try {
|
|
205
|
+
return String.fromCharCode(code);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function parseLooseScalar(rawValue) {
|
|
213
|
+
const decoded = decodeControlTokens(String(rawValue || '').trim());
|
|
214
|
+
if (!decoded)
|
|
215
|
+
return '';
|
|
216
|
+
if ((decoded.startsWith('"') && decoded.endsWith('"'))
|
|
217
|
+
|| (decoded.startsWith("'") && decoded.endsWith("'"))) {
|
|
218
|
+
return decoded.slice(1, -1);
|
|
219
|
+
}
|
|
220
|
+
if (/^(true|false)$/i.test(decoded)) {
|
|
221
|
+
return decoded.toLowerCase() === 'true';
|
|
222
|
+
}
|
|
223
|
+
if (/^null$/i.test(decoded)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
if (/^-?\d+(\.\d+)?$/.test(decoded)) {
|
|
227
|
+
return Number(decoded);
|
|
228
|
+
}
|
|
229
|
+
return decoded;
|
|
230
|
+
}
|
|
231
|
+
function splitTopLevelCommaSeparated(body) {
|
|
232
|
+
const parts = [];
|
|
233
|
+
let buffer = '';
|
|
234
|
+
let quote = null;
|
|
235
|
+
let escapeNext = false;
|
|
236
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
237
|
+
const current = body[index];
|
|
238
|
+
if (escapeNext) {
|
|
239
|
+
buffer += current;
|
|
240
|
+
escapeNext = false;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (current === '\\') {
|
|
244
|
+
buffer += current;
|
|
245
|
+
escapeNext = true;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if ((current === '"' || current === "'")) {
|
|
249
|
+
if (!quote) {
|
|
250
|
+
quote = current;
|
|
251
|
+
}
|
|
252
|
+
else if (quote === current) {
|
|
253
|
+
quote = null;
|
|
254
|
+
}
|
|
255
|
+
buffer += current;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (current === ',' && !quote) {
|
|
259
|
+
if (buffer.trim()) {
|
|
260
|
+
parts.push(buffer.trim());
|
|
261
|
+
}
|
|
262
|
+
buffer = '';
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
buffer += current;
|
|
266
|
+
}
|
|
267
|
+
if (buffer.trim()) {
|
|
268
|
+
parts.push(buffer.trim());
|
|
269
|
+
}
|
|
270
|
+
return parts;
|
|
271
|
+
}
|
|
272
|
+
function parseLooseObjectLiteral(rawObject) {
|
|
273
|
+
const decoded = decodeControlTokens(rawObject.trim());
|
|
274
|
+
if (!decoded.startsWith('{') || !decoded.endsWith('}')) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const innerBody = decoded.slice(1, -1).trim();
|
|
278
|
+
if (!innerBody) {
|
|
279
|
+
return {};
|
|
280
|
+
}
|
|
281
|
+
const entries = splitTopLevelCommaSeparated(innerBody);
|
|
282
|
+
const parsed = {};
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
const separatorIndex = entry.indexOf(':');
|
|
285
|
+
if (separatorIndex <= 0) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const keyRaw = entry.slice(0, separatorIndex).trim();
|
|
289
|
+
const valueRaw = entry.slice(separatorIndex + 1).trim();
|
|
290
|
+
if (!keyRaw)
|
|
291
|
+
continue;
|
|
292
|
+
const normalizedKey = keyRaw.replace(/^['"]|['"]$/g, '').trim();
|
|
293
|
+
if (!normalizedKey)
|
|
294
|
+
continue;
|
|
295
|
+
parsed[normalizedKey] = parseLooseScalar(valueRaw);
|
|
296
|
+
}
|
|
297
|
+
return parsed;
|
|
298
|
+
}
|
|
299
|
+
function parsePlainTextToolIntent(content) {
|
|
300
|
+
const normalized = String(content || '').trim();
|
|
301
|
+
if (!normalized)
|
|
302
|
+
return null;
|
|
303
|
+
const match = normalized.match(/^calling\s+tool\s*:\s*([a-zA-Z0-9_\-]+)\s*(\{[\s\S]*\})?\s*$/i);
|
|
304
|
+
if (!match) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const toolName = String(match[1] || '').trim();
|
|
308
|
+
if (!toolName) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const rawArgs = String(match[2] || '').trim();
|
|
312
|
+
if (!rawArgs) {
|
|
313
|
+
return { toolName, toolArgs: {} };
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const strictParsed = parseToolCallArguments(rawArgs);
|
|
317
|
+
return { toolName, toolArgs: strictParsed };
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
const looseParsed = parseLooseObjectLiteral(rawArgs);
|
|
321
|
+
if (looseParsed && typeof looseParsed === 'object' && !Array.isArray(looseParsed)) {
|
|
322
|
+
return { toolName, toolArgs: looseParsed };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { toolName, toolArgs: {} };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Save incoming message to agent memory with auto-save
|
|
329
|
+
* Uses explicit chatId from the message event for concurrency-safe saving
|
|
330
|
+
*/
|
|
331
|
+
export async function saveIncomingMessageToMemory(world, agent, messageEvent) {
|
|
332
|
+
try {
|
|
333
|
+
if (messageEvent.sender?.toLowerCase() === agent.id.toLowerCase())
|
|
334
|
+
return;
|
|
335
|
+
if (!messageEvent.messageId) {
|
|
336
|
+
loggerMemory.error('Message missing messageId', {
|
|
337
|
+
agentId: agent.id,
|
|
338
|
+
sender: messageEvent.sender,
|
|
339
|
+
worldId: world.id
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Derive chatId from the message event for concurrency-safe processing
|
|
343
|
+
// This ensures messages stay bound to their originating session
|
|
344
|
+
const targetChatId = messageEvent.chatId ?? world.currentChatId ?? null;
|
|
345
|
+
if (!targetChatId) {
|
|
346
|
+
loggerMemory.warn('Saving message without chatId', {
|
|
347
|
+
agentId: agent.id,
|
|
348
|
+
messageId: messageEvent.messageId
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Parse message content to detect enhanced format (e.g., tool results)
|
|
352
|
+
const { message: parsedMessage } = parseMessageContent(messageEvent.content, 'user');
|
|
353
|
+
const userMessage = {
|
|
354
|
+
...parsedMessage,
|
|
355
|
+
sender: messageEvent.sender,
|
|
356
|
+
createdAt: messageEvent.timestamp,
|
|
357
|
+
chatId: targetChatId,
|
|
358
|
+
messageId: messageEvent.messageId,
|
|
359
|
+
replyToMessageId: messageEvent.replyToMessageId,
|
|
360
|
+
agentId: agent.id
|
|
361
|
+
};
|
|
362
|
+
agent.memory.push(userMessage);
|
|
363
|
+
try {
|
|
364
|
+
const storage = await getStorageWrappers();
|
|
365
|
+
await storage.saveAgent(world.id, agent);
|
|
366
|
+
loggerMemory.debug('Agent saved successfully', {
|
|
367
|
+
agentId: agent.id,
|
|
368
|
+
messageId: messageEvent.messageId
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
loggerMemory.error('Failed to auto-save memory', { agentId: agent.id, error: error instanceof Error ? error.message : error });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
loggerMemory.error('Could not save incoming message to memory', { agentId: agent.id, error: error instanceof Error ? error.message : error });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Continue LLM execution after tool execution
|
|
381
|
+
* Calls the LLM with the updated memory (including tool result) to continue the execution loop
|
|
382
|
+
* Used for auto-execution flow where tools are executed automatically
|
|
383
|
+
*/
|
|
384
|
+
export async function continueLLMAfterToolExecution(world, agent, chatId, options) {
|
|
385
|
+
const completeActivity = beginWorldActivity(world, `agent:${agent.id}`);
|
|
386
|
+
try {
|
|
387
|
+
let hopCount = options?.hopCount ?? 0;
|
|
388
|
+
const maxToolHops = 50;
|
|
389
|
+
const emptyTextRetryCount = options?.emptyTextRetryCount ?? 0;
|
|
390
|
+
const maxEmptyTextRetries = 2;
|
|
391
|
+
const emptyToolCallRetryCount = options?.emptyToolCallRetryCount ?? 0;
|
|
392
|
+
const maxEmptyToolCallRetries = 2;
|
|
393
|
+
let transientGuardrailError;
|
|
394
|
+
if (hopCount > maxToolHops) {
|
|
395
|
+
const guardrailErrorMessage = `[Error] Tool continuation exceeded ${maxToolHops} hops. Guardrail triggered; reporting error and continuing.`;
|
|
396
|
+
const guardrailToolCallId = generateId();
|
|
397
|
+
loggerAgent.error('Tool continuation hop limit reached; reporting error and continuing loop', {
|
|
398
|
+
agentId: agent.id,
|
|
399
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
400
|
+
hopCount,
|
|
401
|
+
maxToolHops,
|
|
402
|
+
});
|
|
403
|
+
publishEvent(world, 'system', {
|
|
404
|
+
message: guardrailErrorMessage,
|
|
405
|
+
type: 'error',
|
|
406
|
+
});
|
|
407
|
+
publishToolEvent(world, {
|
|
408
|
+
agentName: agent.id,
|
|
409
|
+
type: 'tool-error',
|
|
410
|
+
messageId: guardrailToolCallId,
|
|
411
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
412
|
+
toolExecution: {
|
|
413
|
+
toolName: '__tool_continuation_guardrail__',
|
|
414
|
+
toolCallId: guardrailToolCallId,
|
|
415
|
+
error: guardrailErrorMessage,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
logToolBridge('CONTINUE HOP_GUARDRAIL', {
|
|
419
|
+
worldId: world.id,
|
|
420
|
+
agentId: agent.id,
|
|
421
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
422
|
+
hopCount,
|
|
423
|
+
maxToolHops,
|
|
424
|
+
guardrailToolCallId,
|
|
425
|
+
});
|
|
426
|
+
transientGuardrailError =
|
|
427
|
+
`System error: tool continuation exceeded ${maxToolHops} hops and was guardrailed. Continue the task and avoid unnecessary additional tool calls.`;
|
|
428
|
+
hopCount = 0;
|
|
429
|
+
}
|
|
430
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
431
|
+
// Use explicit chatId when provided, fallback to world.currentChatId.
|
|
432
|
+
const targetChatId = chatId !== undefined ? chatId : world.currentChatId;
|
|
433
|
+
// Filter memory to current chat only
|
|
434
|
+
const currentChatMessages = agent.memory.filter(m => m.chatId === targetChatId);
|
|
435
|
+
loggerAgent.debug('Continuing LLM execution with tool result in memory', {
|
|
436
|
+
agentId: agent.id,
|
|
437
|
+
targetChatId,
|
|
438
|
+
worldCurrentChatId: world.currentChatId,
|
|
439
|
+
totalMemoryLength: agent.memory.length,
|
|
440
|
+
currentChatLength: currentChatMessages.length,
|
|
441
|
+
lastFewMessages: currentChatMessages.slice(-5).map(m => ({
|
|
442
|
+
role: m.role,
|
|
443
|
+
hasContent: !!m.content,
|
|
444
|
+
hasToolCalls: !!m.tool_calls,
|
|
445
|
+
toolCallId: m.tool_call_id
|
|
446
|
+
}))
|
|
447
|
+
});
|
|
448
|
+
// Tool execution already happened before this function was called
|
|
449
|
+
// The tool result is already in memory with the actual stdout/stderr
|
|
450
|
+
// Now prepare messages for LLM - loads fresh data from storage
|
|
451
|
+
// Prepare messages with system prompt and complete conversation history
|
|
452
|
+
const messages = await prepareMessagesForLLM(world.id, agent, targetChatId ?? null);
|
|
453
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
454
|
+
const llmMessages = transientGuardrailError
|
|
455
|
+
? [
|
|
456
|
+
...messages,
|
|
457
|
+
{
|
|
458
|
+
role: 'user',
|
|
459
|
+
content: transientGuardrailError,
|
|
460
|
+
},
|
|
461
|
+
]
|
|
462
|
+
: messages;
|
|
463
|
+
loggerAgent.debug('Calling LLM with memory after tool execution', {
|
|
464
|
+
agentId: agent.id,
|
|
465
|
+
targetChatId,
|
|
466
|
+
preparedMessageCount: llmMessages.length,
|
|
467
|
+
systemMessagesInPrepared: llmMessages.filter(m => m.role === 'system').length,
|
|
468
|
+
userMessages: llmMessages.filter(m => m.role === 'user').length,
|
|
469
|
+
assistantMessages: llmMessages.filter(m => m.role === 'assistant').length,
|
|
470
|
+
toolMessages: llmMessages.filter(m => m.role === 'tool').length,
|
|
471
|
+
lastThreeMessages: llmMessages.slice(-3).map((m) => ({
|
|
472
|
+
role: m.role,
|
|
473
|
+
hasContent: !!m.content,
|
|
474
|
+
contentPreview: m.content?.substring(0, 100),
|
|
475
|
+
hasToolCalls: !!m.tool_calls,
|
|
476
|
+
toolCallId: m.tool_call_id
|
|
477
|
+
}))
|
|
478
|
+
});
|
|
479
|
+
// Increment LLM call count
|
|
480
|
+
agent.llmCallCount++;
|
|
481
|
+
agent.lastLLMCall = new Date();
|
|
482
|
+
try {
|
|
483
|
+
const storage = await getStorageWrappers();
|
|
484
|
+
await storage.saveAgent(world.id, agent);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
loggerAgent.error('Failed to save agent after LLM call increment', {
|
|
488
|
+
agentId: agent.id,
|
|
489
|
+
error: error instanceof Error ? error.message : error
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// Generate LLM response (streaming or non-streaming)
|
|
493
|
+
let messageId;
|
|
494
|
+
let llmResponse;
|
|
495
|
+
// Create a wrapped publishSSE that captures the targetChatId for concurrency-safe event routing
|
|
496
|
+
// This ensures SSE events stay bound to the originating session during tool continuation
|
|
497
|
+
const publishSSEWithChatId = (w, data) => {
|
|
498
|
+
publishSSE(w, { ...data, chatId: targetChatId });
|
|
499
|
+
};
|
|
500
|
+
if (isStreamingEnabled()) {
|
|
501
|
+
const { streamAgentResponse } = await import('../llm-manager.js');
|
|
502
|
+
const result = await streamAgentResponse(world, agent, llmMessages, publishSSEWithChatId, targetChatId ?? null, options?.abortSignal);
|
|
503
|
+
llmResponse = result.response;
|
|
504
|
+
messageId = result.messageId;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
const { generateAgentResponse } = await import('../llm-manager.js');
|
|
508
|
+
const result = await generateAgentResponse(world, agent, llmMessages, undefined, false, targetChatId ?? null, options?.abortSignal);
|
|
509
|
+
llmResponse = result.response;
|
|
510
|
+
messageId = result.messageId;
|
|
511
|
+
}
|
|
512
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
513
|
+
loggerAgent.debug('LLM response received after tool execution', {
|
|
514
|
+
agentId: agent.id,
|
|
515
|
+
responseType: llmResponse.type,
|
|
516
|
+
hasContent: !!llmResponse.content,
|
|
517
|
+
toolCallCount: llmResponse.tool_calls?.length || 0
|
|
518
|
+
});
|
|
519
|
+
logToolBridge('LLM -> CONTINUE', {
|
|
520
|
+
worldId: world.id,
|
|
521
|
+
agentId: agent.id,
|
|
522
|
+
chatId: targetChatId,
|
|
523
|
+
responseType: llmResponse.type,
|
|
524
|
+
hasContent: !!llmResponse.content,
|
|
525
|
+
contentPreview: String(llmResponse.content || '').substring(0, 200),
|
|
526
|
+
toolCallCount: Array.isArray(llmResponse.tool_calls) ? llmResponse.tool_calls.length : 0,
|
|
527
|
+
});
|
|
528
|
+
if (llmResponse.type === 'text' && typeof llmResponse.content === 'string' && llmResponse.content.trim()) {
|
|
529
|
+
const parsedPlainTextToolIntent = parsePlainTextToolIntent(llmResponse.content);
|
|
530
|
+
if (parsedPlainTextToolIntent) {
|
|
531
|
+
const syntheticToolCallId = generateId();
|
|
532
|
+
loggerAgent.warn('Continuation received plain-text tool intent; synthesizing tool_call fallback', {
|
|
533
|
+
agentId: agent.id,
|
|
534
|
+
chatId: targetChatId,
|
|
535
|
+
toolName: parsedPlainTextToolIntent.toolName,
|
|
536
|
+
syntheticToolCallId,
|
|
537
|
+
});
|
|
538
|
+
logToolBridge('CONTINUE PLAINTEXT_TOOL_INTENT_FALLBACK', {
|
|
539
|
+
worldId: world.id,
|
|
540
|
+
agentId: agent.id,
|
|
541
|
+
chatId: targetChatId,
|
|
542
|
+
toolName: parsedPlainTextToolIntent.toolName,
|
|
543
|
+
toolArgs: parsedPlainTextToolIntent.toolArgs,
|
|
544
|
+
syntheticToolCallId,
|
|
545
|
+
});
|
|
546
|
+
llmResponse = {
|
|
547
|
+
type: 'tool_calls',
|
|
548
|
+
content: llmResponse.content,
|
|
549
|
+
tool_calls: [{
|
|
550
|
+
id: syntheticToolCallId,
|
|
551
|
+
type: 'function',
|
|
552
|
+
function: {
|
|
553
|
+
name: parsedPlainTextToolIntent.toolName,
|
|
554
|
+
arguments: JSON.stringify(parsedPlainTextToolIntent.toolArgs || {}),
|
|
555
|
+
},
|
|
556
|
+
}],
|
|
557
|
+
assistantMessage: {
|
|
558
|
+
role: 'assistant',
|
|
559
|
+
content: llmResponse.content,
|
|
560
|
+
tool_calls: [{
|
|
561
|
+
id: syntheticToolCallId,
|
|
562
|
+
type: 'function',
|
|
563
|
+
function: {
|
|
564
|
+
name: parsedPlainTextToolIntent.toolName,
|
|
565
|
+
arguments: JSON.stringify(parsedPlainTextToolIntent.toolArgs || {}),
|
|
566
|
+
},
|
|
567
|
+
}],
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (llmResponse.type === 'tool_calls') {
|
|
573
|
+
const returnedToolCalls = Array.isArray(llmResponse.tool_calls) ? llmResponse.tool_calls : [];
|
|
574
|
+
const validReturnedToolCalls = returnedToolCalls.filter((tc) => {
|
|
575
|
+
const name = String(tc?.function?.name || '').trim();
|
|
576
|
+
return name.length > 0;
|
|
577
|
+
});
|
|
578
|
+
const executableToolCalls = validReturnedToolCalls.slice(0, 1);
|
|
579
|
+
if (returnedToolCalls.length > validReturnedToolCalls.length) {
|
|
580
|
+
loggerAgent.warn('Continuation LLM returned invalid tool calls; dropping calls with empty names', {
|
|
581
|
+
agentId: agent.id,
|
|
582
|
+
returnedToolCallCount: returnedToolCalls.length,
|
|
583
|
+
validToolCallCount: validReturnedToolCalls.length,
|
|
584
|
+
emptyToolCallRetryCount,
|
|
585
|
+
maxEmptyToolCallRetries,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (validReturnedToolCalls.length > executableToolCalls.length) {
|
|
589
|
+
loggerAgent.warn('Continuation LLM returned multiple tool calls; processing first call only', {
|
|
590
|
+
agentId: agent.id,
|
|
591
|
+
returnedToolCallCount: validReturnedToolCalls.length,
|
|
592
|
+
processedToolCallIds: executableToolCalls.map(tc => tc.id),
|
|
593
|
+
droppedToolCallIds: validReturnedToolCalls.slice(1).map(tc => tc.id)
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
const toolCall = executableToolCalls[0];
|
|
597
|
+
if (!toolCall) {
|
|
598
|
+
const firstInvalidToolCall = returnedToolCalls[0];
|
|
599
|
+
const toolCallId = String(firstInvalidToolCall?.id || generateId());
|
|
600
|
+
const rawToolName = String(firstInvalidToolCall?.function?.name || '').trim();
|
|
601
|
+
const fallbackToolName = rawToolName || '__invalid_tool_call__';
|
|
602
|
+
const fallbackToolArguments = typeof firstInvalidToolCall?.function?.arguments === 'string'
|
|
603
|
+
? firstInvalidToolCall.function.arguments
|
|
604
|
+
: '{}';
|
|
605
|
+
const malformedToolErrorContent = rawToolName
|
|
606
|
+
? `Error executing tool: Invalid tool call payload for '${rawToolName}'`
|
|
607
|
+
: 'Error executing tool: Invalid tool call payload - empty or missing tool name';
|
|
608
|
+
loggerAgent.warn('Continuation returned tool_calls without executable tool; reporting tool error back to LLM context', {
|
|
609
|
+
agentId: agent.id,
|
|
610
|
+
messageId,
|
|
611
|
+
targetChatId,
|
|
612
|
+
emptyToolCallRetryCount,
|
|
613
|
+
maxEmptyToolCallRetries,
|
|
614
|
+
returnedToolCallCount: returnedToolCalls.length,
|
|
615
|
+
toolCallId,
|
|
616
|
+
fallbackToolName,
|
|
617
|
+
});
|
|
618
|
+
const assistantMalformedToolCallMessage = {
|
|
619
|
+
role: 'assistant',
|
|
620
|
+
content: llmResponse.content || `Calling tool: ${fallbackToolName}`,
|
|
621
|
+
sender: agent.id,
|
|
622
|
+
createdAt: new Date(),
|
|
623
|
+
chatId: targetChatId,
|
|
624
|
+
messageId,
|
|
625
|
+
tool_calls: [{
|
|
626
|
+
id: toolCallId,
|
|
627
|
+
type: 'function',
|
|
628
|
+
function: {
|
|
629
|
+
name: fallbackToolName,
|
|
630
|
+
arguments: fallbackToolArguments,
|
|
631
|
+
},
|
|
632
|
+
}],
|
|
633
|
+
agentId: agent.id,
|
|
634
|
+
toolCallStatus: {
|
|
635
|
+
[toolCallId]: {
|
|
636
|
+
complete: true,
|
|
637
|
+
result: malformedToolErrorContent,
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
agent.memory.push(assistantMalformedToolCallMessage);
|
|
642
|
+
const malformedToolCallEvent = {
|
|
643
|
+
content: assistantMalformedToolCallMessage.content || '',
|
|
644
|
+
sender: agent.id,
|
|
645
|
+
timestamp: assistantMalformedToolCallMessage.createdAt || new Date(),
|
|
646
|
+
messageId: assistantMalformedToolCallMessage.messageId,
|
|
647
|
+
chatId: assistantMalformedToolCallMessage.chatId,
|
|
648
|
+
};
|
|
649
|
+
malformedToolCallEvent.role = 'assistant';
|
|
650
|
+
malformedToolCallEvent.tool_calls = assistantMalformedToolCallMessage.tool_calls;
|
|
651
|
+
malformedToolCallEvent.toolCallStatus = assistantMalformedToolCallMessage.toolCallStatus;
|
|
652
|
+
world.eventEmitter.emit('message', malformedToolCallEvent);
|
|
653
|
+
const malformedToolResultMessage = {
|
|
654
|
+
role: 'tool',
|
|
655
|
+
content: malformedToolErrorContent,
|
|
656
|
+
tool_call_id: toolCallId,
|
|
657
|
+
sender: agent.id,
|
|
658
|
+
createdAt: new Date(),
|
|
659
|
+
chatId: targetChatId,
|
|
660
|
+
messageId: generateId(),
|
|
661
|
+
replyToMessageId: messageId,
|
|
662
|
+
agentId: agent.id,
|
|
663
|
+
};
|
|
664
|
+
agent.memory.push(malformedToolResultMessage);
|
|
665
|
+
publishToolEvent(world, {
|
|
666
|
+
agentName: agent.id,
|
|
667
|
+
type: 'tool-error',
|
|
668
|
+
messageId: toolCallId,
|
|
669
|
+
chatId: targetChatId,
|
|
670
|
+
toolExecution: {
|
|
671
|
+
toolName: fallbackToolName,
|
|
672
|
+
toolCallId,
|
|
673
|
+
input: fallbackToolArguments,
|
|
674
|
+
error: malformedToolErrorContent,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
logToolBridge('CONTINUE TOOL_CALLS_INVALID', {
|
|
678
|
+
worldId: world.id,
|
|
679
|
+
agentId: agent.id,
|
|
680
|
+
chatId: targetChatId,
|
|
681
|
+
toolCallId,
|
|
682
|
+
fallbackToolName,
|
|
683
|
+
emptyToolCallRetryCount,
|
|
684
|
+
maxEmptyToolCallRetries,
|
|
685
|
+
});
|
|
686
|
+
try {
|
|
687
|
+
const storage = await getStorageWrappers();
|
|
688
|
+
await storage.saveAgent(world.id, agent);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
loggerMemory.error('Failed to save malformed continuation tool error context', {
|
|
692
|
+
agentId: agent.id,
|
|
693
|
+
error: error instanceof Error ? error.message : String(error),
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
if (emptyToolCallRetryCount < maxEmptyToolCallRetries) {
|
|
697
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
698
|
+
await continueLLMAfterToolExecution(world, agent, targetChatId, {
|
|
699
|
+
...options,
|
|
700
|
+
hopCount: hopCount + 1,
|
|
701
|
+
emptyToolCallRetryCount: emptyToolCallRetryCount + 1,
|
|
702
|
+
});
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
publishEvent(world, 'system', {
|
|
706
|
+
message: '[Warning] Agent repeatedly returned invalid tool calls after tool execution. Please refine the prompt.',
|
|
707
|
+
type: 'warning',
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const assistantToolCallMessage = {
|
|
712
|
+
role: 'assistant',
|
|
713
|
+
content: llmResponse.content || `Calling tool: ${toolCall.function.name}`,
|
|
714
|
+
sender: agent.id,
|
|
715
|
+
createdAt: new Date(),
|
|
716
|
+
chatId: targetChatId,
|
|
717
|
+
messageId,
|
|
718
|
+
tool_calls: executableToolCalls,
|
|
719
|
+
agentId: agent.id,
|
|
720
|
+
toolCallStatus: executableToolCalls.reduce((acc, tc) => {
|
|
721
|
+
acc[tc.id] = { complete: false, result: null };
|
|
722
|
+
return acc;
|
|
723
|
+
}, {}),
|
|
724
|
+
};
|
|
725
|
+
agent.memory.push(assistantToolCallMessage);
|
|
726
|
+
try {
|
|
727
|
+
const storage = await getStorageWrappers();
|
|
728
|
+
await storage.saveAgent(world.id, agent);
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
loggerMemory.error('Failed to save assistant tool_call message during continuation', {
|
|
732
|
+
agentId: agent.id,
|
|
733
|
+
error: error instanceof Error ? error.message : String(error),
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
const toolCallEvent = {
|
|
737
|
+
content: assistantToolCallMessage.content || '',
|
|
738
|
+
sender: agent.id,
|
|
739
|
+
timestamp: assistantToolCallMessage.createdAt || new Date(),
|
|
740
|
+
messageId: assistantToolCallMessage.messageId,
|
|
741
|
+
chatId: assistantToolCallMessage.chatId,
|
|
742
|
+
};
|
|
743
|
+
toolCallEvent.role = 'assistant';
|
|
744
|
+
toolCallEvent.tool_calls = assistantToolCallMessage.tool_calls;
|
|
745
|
+
toolCallEvent.toolCallStatus = assistantToolCallMessage.toolCallStatus;
|
|
746
|
+
world.eventEmitter.emit('message', toolCallEvent);
|
|
747
|
+
const { getMCPToolsForWorld } = await import('../mcp-server-registry.js');
|
|
748
|
+
const mcpTools = await getMCPToolsForWorld(world.id);
|
|
749
|
+
const toolDef = mcpTools[toolCall.function.name];
|
|
750
|
+
const trustedWorkingDirectory = String(getEnvValueFromText(world.variables, 'working_directory') || getDefaultWorkingDirectory()).trim() || getDefaultWorkingDirectory();
|
|
751
|
+
if (!toolDef) {
|
|
752
|
+
const missingToolResult = {
|
|
753
|
+
role: 'tool',
|
|
754
|
+
content: `Error executing tool: Tool not found: ${toolCall.function.name}`,
|
|
755
|
+
tool_call_id: toolCall.id,
|
|
756
|
+
sender: agent.id,
|
|
757
|
+
createdAt: new Date(),
|
|
758
|
+
chatId: targetChatId,
|
|
759
|
+
messageId: generateId(),
|
|
760
|
+
replyToMessageId: messageId,
|
|
761
|
+
agentId: agent.id,
|
|
762
|
+
};
|
|
763
|
+
agent.memory.push(missingToolResult);
|
|
764
|
+
if (assistantToolCallMessage.toolCallStatus) {
|
|
765
|
+
assistantToolCallMessage.toolCallStatus[toolCall.id] = {
|
|
766
|
+
complete: true,
|
|
767
|
+
result: missingToolResult.content,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
publishToolEvent(world, {
|
|
771
|
+
agentName: agent.id,
|
|
772
|
+
type: 'tool-error',
|
|
773
|
+
messageId: toolCall.id,
|
|
774
|
+
chatId: targetChatId,
|
|
775
|
+
toolExecution: {
|
|
776
|
+
toolName: toolCall.function.name,
|
|
777
|
+
toolCallId: toolCall.id,
|
|
778
|
+
error: `Tool not found: ${toolCall.function.name}`,
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
const storage = await getStorageWrappers();
|
|
782
|
+
await storage.saveAgent(world.id, agent);
|
|
783
|
+
await continueLLMAfterToolExecution(world, agent, targetChatId, {
|
|
784
|
+
...options,
|
|
785
|
+
hopCount: hopCount + 1,
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
let toolArgs = {};
|
|
790
|
+
try {
|
|
791
|
+
toolArgs = parseToolCallArguments(toolCall.function.arguments);
|
|
792
|
+
}
|
|
793
|
+
catch (parseError) {
|
|
794
|
+
const parseErrorResult = {
|
|
795
|
+
role: 'tool',
|
|
796
|
+
content: `Error executing tool: Invalid JSON in tool arguments: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
797
|
+
tool_call_id: toolCall.id,
|
|
798
|
+
sender: agent.id,
|
|
799
|
+
createdAt: new Date(),
|
|
800
|
+
chatId: targetChatId,
|
|
801
|
+
messageId: generateId(),
|
|
802
|
+
replyToMessageId: messageId,
|
|
803
|
+
agentId: agent.id,
|
|
804
|
+
};
|
|
805
|
+
agent.memory.push(parseErrorResult);
|
|
806
|
+
if (assistantToolCallMessage.toolCallStatus) {
|
|
807
|
+
assistantToolCallMessage.toolCallStatus[toolCall.id] = {
|
|
808
|
+
complete: true,
|
|
809
|
+
result: parseErrorResult.content,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
publishToolEvent(world, {
|
|
813
|
+
agentName: agent.id,
|
|
814
|
+
type: 'tool-error',
|
|
815
|
+
messageId: toolCall.id,
|
|
816
|
+
chatId: targetChatId,
|
|
817
|
+
toolExecution: {
|
|
818
|
+
toolName: toolCall.function.name,
|
|
819
|
+
toolCallId: toolCall.id,
|
|
820
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
const storage = await getStorageWrappers();
|
|
824
|
+
await storage.saveAgent(world.id, agent);
|
|
825
|
+
await continueLLMAfterToolExecution(world, agent, targetChatId, {
|
|
826
|
+
...options,
|
|
827
|
+
hopCount: hopCount + 1,
|
|
828
|
+
});
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
publishToolEvent(world, {
|
|
832
|
+
agentName: agent.id,
|
|
833
|
+
type: 'tool-start',
|
|
834
|
+
messageId: toolCall.id,
|
|
835
|
+
chatId: targetChatId,
|
|
836
|
+
toolExecution: {
|
|
837
|
+
toolName: toolCall.function.name,
|
|
838
|
+
toolCallId: toolCall.id,
|
|
839
|
+
input: toolArgs,
|
|
840
|
+
metadata: {
|
|
841
|
+
isStreaming: isStreamingEnabled(),
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
try {
|
|
846
|
+
const toolContext = {
|
|
847
|
+
world,
|
|
848
|
+
messages: agent.memory,
|
|
849
|
+
toolCallId: toolCall.id,
|
|
850
|
+
chatId: targetChatId,
|
|
851
|
+
abortSignal: options?.abortSignal,
|
|
852
|
+
workingDirectory: trustedWorkingDirectory,
|
|
853
|
+
};
|
|
854
|
+
const toolResult = await toolDef.execute(toolArgs, undefined, undefined, toolContext);
|
|
855
|
+
const serializedToolResult = typeof toolResult === 'string'
|
|
856
|
+
? toolResult
|
|
857
|
+
: JSON.stringify(toolResult) ?? String(toolResult);
|
|
858
|
+
const toolResultMessage = {
|
|
859
|
+
role: 'tool',
|
|
860
|
+
content: serializedToolResult,
|
|
861
|
+
tool_call_id: toolCall.id,
|
|
862
|
+
sender: agent.id,
|
|
863
|
+
createdAt: new Date(),
|
|
864
|
+
chatId: targetChatId,
|
|
865
|
+
messageId: generateId(),
|
|
866
|
+
replyToMessageId: messageId,
|
|
867
|
+
agentId: agent.id,
|
|
868
|
+
};
|
|
869
|
+
agent.memory.push(toolResultMessage);
|
|
870
|
+
if (assistantToolCallMessage.toolCallStatus) {
|
|
871
|
+
assistantToolCallMessage.toolCallStatus[toolCall.id] = {
|
|
872
|
+
complete: true,
|
|
873
|
+
result: serializedToolResult,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
publishToolEvent(world, {
|
|
877
|
+
agentName: agent.id,
|
|
878
|
+
type: 'tool-result',
|
|
879
|
+
messageId: toolCall.id,
|
|
880
|
+
chatId: targetChatId,
|
|
881
|
+
toolExecution: {
|
|
882
|
+
toolName: toolCall.function.name,
|
|
883
|
+
toolCallId: toolCall.id,
|
|
884
|
+
input: toolArgs,
|
|
885
|
+
result: serializedToolResult.slice(0, 4000),
|
|
886
|
+
resultType: typeof toolResult === 'string'
|
|
887
|
+
? 'string'
|
|
888
|
+
: Array.isArray(toolResult)
|
|
889
|
+
? 'array'
|
|
890
|
+
: toolResult === null
|
|
891
|
+
? 'null'
|
|
892
|
+
: 'object',
|
|
893
|
+
resultSize: serializedToolResult.length,
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
catch (toolError) {
|
|
898
|
+
const errorContent = `Error executing tool: ${toolError instanceof Error ? toolError.message : String(toolError)}`;
|
|
899
|
+
const toolErrorMessage = {
|
|
900
|
+
role: 'tool',
|
|
901
|
+
content: errorContent,
|
|
902
|
+
tool_call_id: toolCall.id,
|
|
903
|
+
sender: agent.id,
|
|
904
|
+
createdAt: new Date(),
|
|
905
|
+
chatId: targetChatId,
|
|
906
|
+
messageId: generateId(),
|
|
907
|
+
replyToMessageId: messageId,
|
|
908
|
+
agentId: agent.id,
|
|
909
|
+
};
|
|
910
|
+
agent.memory.push(toolErrorMessage);
|
|
911
|
+
if (assistantToolCallMessage.toolCallStatus) {
|
|
912
|
+
assistantToolCallMessage.toolCallStatus[toolCall.id] = {
|
|
913
|
+
complete: true,
|
|
914
|
+
result: errorContent,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
publishToolEvent(world, {
|
|
918
|
+
agentName: agent.id,
|
|
919
|
+
type: 'tool-error',
|
|
920
|
+
messageId: toolCall.id,
|
|
921
|
+
chatId: targetChatId,
|
|
922
|
+
toolExecution: {
|
|
923
|
+
toolName: toolCall.function.name,
|
|
924
|
+
toolCallId: toolCall.id,
|
|
925
|
+
input: toolArgs,
|
|
926
|
+
error: toolError instanceof Error ? toolError.message : String(toolError),
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
const storage = await getStorageWrappers();
|
|
932
|
+
await storage.saveAgent(world.id, agent);
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
loggerMemory.error('Failed to save continuation tool result to memory', {
|
|
936
|
+
agentId: agent.id,
|
|
937
|
+
error: error instanceof Error ? error.message : String(error),
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
941
|
+
await continueLLMAfterToolExecution(world, agent, targetChatId, {
|
|
942
|
+
...options,
|
|
943
|
+
hopCount: hopCount + 1,
|
|
944
|
+
});
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
if (llmResponse.type !== 'text' || !llmResponse.content) {
|
|
948
|
+
if (llmResponse.type === 'text' && !llmResponse.content && emptyTextRetryCount < maxEmptyTextRetries) {
|
|
949
|
+
loggerAgent.warn('Post-tool continuation returned empty text; retrying continuation call', {
|
|
950
|
+
agentId: agent.id,
|
|
951
|
+
chatId: targetChatId,
|
|
952
|
+
hopCount,
|
|
953
|
+
emptyTextRetryCount,
|
|
954
|
+
maxEmptyTextRetries,
|
|
955
|
+
});
|
|
956
|
+
logToolBridge('CONTINUE EMPTY_TEXT_RETRY', {
|
|
957
|
+
worldId: world.id,
|
|
958
|
+
agentId: agent.id,
|
|
959
|
+
chatId: targetChatId,
|
|
960
|
+
emptyTextRetryCount,
|
|
961
|
+
maxEmptyTextRetries,
|
|
962
|
+
});
|
|
963
|
+
throwIfMessageProcessingStopped(options?.abortSignal);
|
|
964
|
+
await continueLLMAfterToolExecution(world, agent, targetChatId, {
|
|
965
|
+
...options,
|
|
966
|
+
emptyTextRetryCount: emptyTextRetryCount + 1,
|
|
967
|
+
});
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
loggerAgent.warn('LLM response after tool execution is not text or empty - no message will be published', {
|
|
971
|
+
agentId: agent.id,
|
|
972
|
+
responseType: llmResponse.type,
|
|
973
|
+
hasContent: !!llmResponse.content,
|
|
974
|
+
contentLength: llmResponse.content?.length || 0,
|
|
975
|
+
hasToolCalls: !!llmResponse.tool_calls,
|
|
976
|
+
toolCallCount: llmResponse.tool_calls?.length || 0,
|
|
977
|
+
emptyTextRetryCount,
|
|
978
|
+
maxEmptyTextRetries,
|
|
979
|
+
});
|
|
980
|
+
if (llmResponse.type === 'text' && !llmResponse.content && emptyTextRetryCount >= maxEmptyTextRetries) {
|
|
981
|
+
publishEvent(world, 'system', {
|
|
982
|
+
message: '[Warning] Agent returned empty follow-up after tool execution. Please retry or refine the prompt.',
|
|
983
|
+
type: 'warning'
|
|
984
|
+
});
|
|
985
|
+
logToolBridge('CONTINUE EMPTY_TEXT_STOP', {
|
|
986
|
+
worldId: world.id,
|
|
987
|
+
agentId: agent.id,
|
|
988
|
+
chatId: targetChatId,
|
|
989
|
+
emptyTextRetryCount,
|
|
990
|
+
maxEmptyTextRetries,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const responseText = llmResponse.content;
|
|
996
|
+
const sanitizedResponse = removeSelfMentions(responseText, agent.id);
|
|
997
|
+
// Save response to agent memory with all required fields
|
|
998
|
+
agent.memory.push({
|
|
999
|
+
role: 'assistant',
|
|
1000
|
+
content: sanitizedResponse,
|
|
1001
|
+
messageId,
|
|
1002
|
+
sender: agent.id,
|
|
1003
|
+
createdAt: new Date(),
|
|
1004
|
+
chatId: targetChatId,
|
|
1005
|
+
agentId: agent.id
|
|
1006
|
+
});
|
|
1007
|
+
try {
|
|
1008
|
+
const storage = await getStorageWrappers();
|
|
1009
|
+
await storage.saveAgent(world.id, agent);
|
|
1010
|
+
loggerMemory.debug('Agent response saved to memory after tool execution', {
|
|
1011
|
+
agentId: agent.id,
|
|
1012
|
+
messageId,
|
|
1013
|
+
memorySize: agent.memory.length
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
loggerMemory.error('Failed to save agent response after tool execution', {
|
|
1018
|
+
agentId: agent.id,
|
|
1019
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
// Publish the response message using the same messageId from streaming
|
|
1023
|
+
publishMessageWithId(world, sanitizedResponse, agent.id, messageId, targetChatId, undefined);
|
|
1024
|
+
loggerAgent.debug('Agent response published after tool execution', {
|
|
1025
|
+
agentId: agent.id,
|
|
1026
|
+
messageId,
|
|
1027
|
+
responseLength: sanitizedResponse.length
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
if (isMessageProcessingCanceledError(error) || options?.abortSignal?.aborted) {
|
|
1032
|
+
loggerAgent.info('Skipped continuation after stop request', {
|
|
1033
|
+
agentId: agent.id,
|
|
1034
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
1035
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1036
|
+
});
|
|
1037
|
+
logToolBridge('CONTINUE CANCELED', {
|
|
1038
|
+
worldId: world.id,
|
|
1039
|
+
agentId: agent.id,
|
|
1040
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
1041
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1042
|
+
});
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
loggerAgent.error('Failed to continue LLM after tool execution', {
|
|
1046
|
+
agentId: agent.id,
|
|
1047
|
+
error: error instanceof Error ? error.message : error
|
|
1048
|
+
});
|
|
1049
|
+
publishEvent(world, 'system', {
|
|
1050
|
+
message: `[Error] ${error.message}`,
|
|
1051
|
+
type: 'error'
|
|
1052
|
+
});
|
|
1053
|
+
logToolBridge('CONTINUE ERROR', {
|
|
1054
|
+
worldId: world.id,
|
|
1055
|
+
agentId: agent.id,
|
|
1056
|
+
chatId: chatId ?? world.currentChatId ?? null,
|
|
1057
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
finally {
|
|
1061
|
+
completeActivity();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Handle text response from LLM (extracted for clarity)
|
|
1066
|
+
* @param chatId - Explicit chat ID for concurrency-safe processing. If not provided, uses messageEvent.chatId or world.currentChatId.
|
|
1067
|
+
*/
|
|
1068
|
+
export async function handleTextResponse(world, agent, responseText, messageId, messageEvent, chatId) {
|
|
1069
|
+
// Derive target chatId: explicit parameter > message event > world.currentChatId
|
|
1070
|
+
const targetChatId = chatId !== undefined ? chatId : (messageEvent.chatId ?? world.currentChatId ?? null);
|
|
1071
|
+
const sanitizedResponse = removeSelfMentions(responseText, agent.id);
|
|
1072
|
+
// Apply auto-mention logic if needed
|
|
1073
|
+
let finalResponse = sanitizedResponse;
|
|
1074
|
+
if (agent.autoReply !== false && shouldAutoMention(sanitizedResponse, messageEvent.sender, agent.id)) {
|
|
1075
|
+
finalResponse = addAutoMention(sanitizedResponse, messageEvent.sender);
|
|
1076
|
+
loggerAutoMention.debug('Auto-mention applied', {
|
|
1077
|
+
agentId: agent.id,
|
|
1078
|
+
originalSender: messageEvent.sender,
|
|
1079
|
+
responsePreview: finalResponse.substring(0, 100)
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
loggerAutoMention.debug('Auto-mention not needed', {
|
|
1084
|
+
agentId: agent.id,
|
|
1085
|
+
autoReply: agent.autoReply !== false,
|
|
1086
|
+
hasAnyMention: hasAnyMentionAtBeginning(sanitizedResponse)
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
// Save response to agent memory with all required fields
|
|
1090
|
+
agent.memory.push({
|
|
1091
|
+
role: 'assistant',
|
|
1092
|
+
content: finalResponse,
|
|
1093
|
+
messageId,
|
|
1094
|
+
sender: agent.id,
|
|
1095
|
+
createdAt: new Date(),
|
|
1096
|
+
chatId: targetChatId,
|
|
1097
|
+
replyToMessageId: messageEvent.messageId,
|
|
1098
|
+
agentId: agent.id
|
|
1099
|
+
});
|
|
1100
|
+
try {
|
|
1101
|
+
const storage = await getStorageWrappers();
|
|
1102
|
+
await storage.saveAgent(world.id, agent);
|
|
1103
|
+
loggerMemory.debug('Agent response saved to memory', {
|
|
1104
|
+
agentId: agent.id,
|
|
1105
|
+
messageId,
|
|
1106
|
+
memorySize: agent.memory.length
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
loggerMemory.error('Failed to save agent response', {
|
|
1111
|
+
agentId: agent.id,
|
|
1112
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
// Publish the response message using the same messageId from streaming
|
|
1116
|
+
publishMessageWithId(world, finalResponse, agent.id, messageId, targetChatId, messageEvent.messageId);
|
|
1117
|
+
loggerAgent.debug('Agent response published', {
|
|
1118
|
+
agentId: agent.id,
|
|
1119
|
+
messageId,
|
|
1120
|
+
responseLength: finalResponse.length
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Reset LLM call count for human/world messages with persistence
|
|
1125
|
+
*/
|
|
1126
|
+
export async function resetLLMCallCountIfNeeded(world, agent, messageEvent) {
|
|
1127
|
+
const senderType = determineSenderType(messageEvent.sender);
|
|
1128
|
+
if ((senderType === SenderType.HUMAN || senderType === SenderType.WORLD) && agent.llmCallCount > 0) {
|
|
1129
|
+
loggerTurnLimit.debug('Resetting LLM call count', { agentId: agent.id, oldCount: agent.llmCallCount });
|
|
1130
|
+
agent.llmCallCount = 0;
|
|
1131
|
+
try {
|
|
1132
|
+
const storage = await getStorageWrappers();
|
|
1133
|
+
await storage.saveAgent(world.id, agent);
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
loggerTurnLimit.warn('Failed to auto-save agent after turn limit reset', { agentId: agent.id, error: error instanceof Error ? error.message : error });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Generate chat title from message content with LLM support and fallback
|
|
1142
|
+
*/
|
|
1143
|
+
export async function generateChatTitleFromMessages(world, content, targetChatId) {
|
|
1144
|
+
loggerChatTitle.debug('Generating chat title', {
|
|
1145
|
+
worldId: world.id,
|
|
1146
|
+
targetChatId,
|
|
1147
|
+
contentStart: content.substring(0, 50)
|
|
1148
|
+
});
|
|
1149
|
+
let title = '';
|
|
1150
|
+
let messages = [];
|
|
1151
|
+
let promptMessages = [];
|
|
1152
|
+
let titleGenerationCanceled = false;
|
|
1153
|
+
const maxLength = 100; // Max title length
|
|
1154
|
+
try {
|
|
1155
|
+
const firstAgent = Array.from(world.agents.values())[0];
|
|
1156
|
+
const storage = await getStorageWrappers();
|
|
1157
|
+
// Load messages for the target chat only, not all messages.
|
|
1158
|
+
messages = targetChatId ? await storage.getMemory(world.id, targetChatId) : [];
|
|
1159
|
+
if (content) {
|
|
1160
|
+
messages.push({ role: 'user', content });
|
|
1161
|
+
}
|
|
1162
|
+
promptMessages = buildTitlePromptMessages(messages);
|
|
1163
|
+
loggerChatTitle.debug('Calling LLM for title generation', {
|
|
1164
|
+
messageCount: messages.length,
|
|
1165
|
+
promptMessageCount: promptMessages.length,
|
|
1166
|
+
targetChatId,
|
|
1167
|
+
provider: world.chatLLMProvider || firstAgent?.provider,
|
|
1168
|
+
model: world.chatLLMModel || firstAgent?.model
|
|
1169
|
+
});
|
|
1170
|
+
const tempAgent = {
|
|
1171
|
+
provider: world.chatLLMProvider || firstAgent?.provider || 'openai',
|
|
1172
|
+
model: world.chatLLMModel || firstAgent?.model || 'gpt-4',
|
|
1173
|
+
systemPrompt: 'You are a helpful assistant that turns conversations into concise titles.',
|
|
1174
|
+
maxTokens: 20,
|
|
1175
|
+
};
|
|
1176
|
+
const userPrompt = {
|
|
1177
|
+
role: 'user',
|
|
1178
|
+
content: `Below is a conversation between a user and an assistant. Generate a short, punchy title (3–6 words) that captures its main topic.
|
|
1179
|
+
|
|
1180
|
+
${promptMessages.map(msg => `-${msg.role}: ${msg.content}`).join('\n')}
|
|
1181
|
+
`
|
|
1182
|
+
};
|
|
1183
|
+
const { response: titleResponse } = await generateAgentResponse(world, tempAgent, [userPrompt], undefined, true, targetChatId); // skipTools = true for title generation
|
|
1184
|
+
// Title generation should return plain text when skipTools=true; keep a guard for safety.
|
|
1185
|
+
title = typeof titleResponse === 'string' ? titleResponse : '';
|
|
1186
|
+
loggerChatTitle.debug('LLM generated title', { rawTitle: title });
|
|
1187
|
+
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
if (isTitleGenerationCanceledError(error)) {
|
|
1190
|
+
titleGenerationCanceled = true;
|
|
1191
|
+
loggerChatTitle.info('Title generation canceled', {
|
|
1192
|
+
worldId: world.id,
|
|
1193
|
+
targetChatId,
|
|
1194
|
+
error: error instanceof Error ? error.message : error
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
loggerChatTitle.warn('Failed to generate LLM title, using fallback', {
|
|
1199
|
+
error: error instanceof Error ? error.message : error
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (titleGenerationCanceled) {
|
|
1204
|
+
return '';
|
|
1205
|
+
}
|
|
1206
|
+
title = sanitizeGeneratedTitle(title);
|
|
1207
|
+
if (isLowQualityTitle(title)) {
|
|
1208
|
+
title = pickFallbackTitle(content, promptMessages);
|
|
1209
|
+
}
|
|
1210
|
+
title = sanitizeGeneratedTitle(title);
|
|
1211
|
+
// Truncate if too long
|
|
1212
|
+
if (title.length > maxLength) {
|
|
1213
|
+
title = title.substring(0, maxLength - 3) + '...';
|
|
1214
|
+
}
|
|
1215
|
+
loggerChatTitle.debug('Final processed title', { title, originalLength: title.length });
|
|
1216
|
+
return title;
|
|
1217
|
+
}
|
|
1218
|
+
//# sourceMappingURL=memory-manager.js.map
|