agent-world 0.13.0 → 0.15.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 +90 -17
- package/dist/cli/commands.d.ts +7 -1
- package/dist/cli/commands.js +27 -10
- package/dist/cli/hitl.d.ts +4 -1
- package/dist/cli/hitl.js +55 -20
- package/dist/cli/index.js +249 -97
- package/dist/cli/system-events.d.ts +27 -0
- package/dist/cli/system-events.js +63 -0
- package/dist/core/activity-tracker.d.ts +26 -0
- package/dist/core/activity-tracker.d.ts.map +1 -1
- package/dist/core/activity-tracker.js +21 -4
- package/dist/core/activity-tracker.js.map +1 -1
- package/dist/core/anthropic-direct.d.ts +2 -0
- package/dist/core/anthropic-direct.d.ts.map +1 -1
- package/dist/core/anthropic-direct.js +43 -1
- package/dist/core/anthropic-direct.js.map +1 -1
- package/dist/core/chat-constants.d.ts +12 -0
- package/dist/core/chat-constants.d.ts.map +1 -1
- package/dist/core/chat-constants.js +5 -0
- package/dist/core/chat-constants.js.map +1 -1
- package/dist/core/create-agent-tool.d.ts +5 -0
- package/dist/core/create-agent-tool.d.ts.map +1 -1
- package/dist/core/create-agent-tool.js +57 -34
- package/dist/core/create-agent-tool.js.map +1 -1
- package/dist/core/events/index.d.ts +5 -2
- package/dist/core/events/index.d.ts.map +1 -1
- package/dist/core/events/index.js +5 -2
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/events/memory-manager.d.ts +26 -1
- package/dist/core/events/memory-manager.d.ts.map +1 -1
- package/dist/core/events/memory-manager.js +877 -72
- package/dist/core/events/memory-manager.js.map +1 -1
- package/dist/core/events/orchestrator.d.ts +8 -0
- package/dist/core/events/orchestrator.d.ts.map +1 -1
- package/dist/core/events/orchestrator.js +203 -36
- package/dist/core/events/orchestrator.js.map +1 -1
- package/dist/core/events/persistence.d.ts +21 -14
- package/dist/core/events/persistence.d.ts.map +1 -1
- package/dist/core/events/persistence.js +100 -35
- package/dist/core/events/persistence.js.map +1 -1
- package/dist/core/events/publishers.d.ts +13 -7
- package/dist/core/events/publishers.d.ts.map +1 -1
- package/dist/core/events/publishers.js +53 -37
- package/dist/core/events/publishers.js.map +1 -1
- package/dist/core/events/subscribers.d.ts +17 -14
- package/dist/core/events/subscribers.d.ts.map +1 -1
- package/dist/core/events/subscribers.js +61 -148
- package/dist/core/events/subscribers.js.map +1 -1
- package/dist/core/events/title-scheduler.d.ts +27 -0
- package/dist/core/events/title-scheduler.d.ts.map +1 -0
- package/dist/core/events/title-scheduler.js +135 -0
- package/dist/core/events/title-scheduler.js.map +1 -0
- package/dist/core/events/tool-bridge-logging.d.ts +4 -1
- package/dist/core/events/tool-bridge-logging.d.ts.map +1 -1
- package/dist/core/events/tool-bridge-logging.js +112 -13
- package/dist/core/events/tool-bridge-logging.js.map +1 -1
- package/dist/core/events-metadata.d.ts.map +1 -1
- package/dist/core/events-metadata.js +8 -4
- package/dist/core/events-metadata.js.map +1 -1
- package/dist/core/export.d.ts +1 -1
- package/dist/core/export.d.ts.map +1 -1
- package/dist/core/export.js +2 -15
- package/dist/core/export.js.map +1 -1
- package/dist/core/feature-path-logging.d.ts +50 -0
- package/dist/core/feature-path-logging.d.ts.map +1 -0
- package/dist/core/feature-path-logging.js +130 -0
- package/dist/core/feature-path-logging.js.map +1 -0
- package/dist/core/file-tools.d.ts +57 -1
- package/dist/core/file-tools.d.ts.map +1 -1
- package/dist/core/file-tools.js +329 -29
- package/dist/core/file-tools.js.map +1 -1
- package/dist/core/google-direct.d.ts +6 -1
- package/dist/core/google-direct.d.ts.map +1 -1
- package/dist/core/google-direct.js +76 -7
- package/dist/core/google-direct.js.map +1 -1
- package/dist/core/heartbeat.d.ts +34 -0
- package/dist/core/heartbeat.d.ts.map +1 -0
- package/dist/core/heartbeat.js +153 -0
- package/dist/core/heartbeat.js.map +1 -0
- package/dist/core/hitl-tool.d.ts +6 -12
- package/dist/core/hitl-tool.d.ts.map +1 -1
- package/dist/core/hitl-tool.js +66 -88
- package/dist/core/hitl-tool.js.map +1 -1
- package/dist/core/hitl.d.ts +61 -4
- package/dist/core/hitl.d.ts.map +1 -1
- package/dist/core/hitl.js +324 -60
- package/dist/core/hitl.js.map +1 -1
- package/dist/core/index.d.ts +11 -7
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +10 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/llm-manager.d.ts +15 -0
- package/dist/core/llm-manager.d.ts.map +1 -1
- package/dist/core/llm-manager.js +325 -40
- package/dist/core/llm-manager.js.map +1 -1
- package/dist/core/load-skill-tool.d.ts +36 -3
- package/dist/core/load-skill-tool.d.ts.map +1 -1
- package/dist/core/load-skill-tool.js +807 -93
- package/dist/core/load-skill-tool.js.map +1 -1
- package/dist/core/logger.d.ts +14 -0
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +15 -0
- package/dist/core/logger.js.map +1 -1
- package/dist/core/managers.d.ts +18 -50
- package/dist/core/managers.d.ts.map +1 -1
- package/dist/core/managers.js +340 -502
- package/dist/core/managers.js.map +1 -1
- package/dist/core/mcp-server-registry.d.ts +16 -1
- package/dist/core/mcp-server-registry.d.ts.map +1 -1
- package/dist/core/mcp-server-registry.js +162 -12
- package/dist/core/mcp-server-registry.js.map +1 -1
- package/dist/core/message-cutoff.d.ts +29 -0
- package/dist/core/message-cutoff.d.ts.map +1 -0
- package/dist/core/message-cutoff.js +63 -0
- package/dist/core/message-cutoff.js.map +1 -0
- package/dist/core/message-edit-manager.d.ts +54 -0
- package/dist/core/message-edit-manager.d.ts.map +1 -0
- package/dist/core/message-edit-manager.js +602 -0
- package/dist/core/message-edit-manager.js.map +1 -0
- package/dist/core/message-prep.d.ts +2 -0
- package/dist/core/message-prep.d.ts.map +1 -1
- package/dist/core/message-prep.js +39 -12
- package/dist/core/message-prep.js.map +1 -1
- package/dist/core/message-processing-control.d.ts +1 -0
- package/dist/core/message-processing-control.d.ts.map +1 -1
- package/dist/core/message-processing-control.js +23 -6
- package/dist/core/message-processing-control.js.map +1 -1
- package/dist/core/openai-direct.d.ts +9 -3
- package/dist/core/openai-direct.d.ts.map +1 -1
- package/dist/core/openai-direct.js +267 -33
- package/dist/core/openai-direct.js.map +1 -1
- package/dist/core/optional-tracers/opik-runtime.d.ts +32 -0
- package/dist/core/optional-tracers/opik-runtime.d.ts.map +1 -0
- package/dist/core/optional-tracers/opik-runtime.js +141 -0
- package/dist/core/optional-tracers/opik-runtime.js.map +1 -0
- package/dist/core/queue-manager.d.ts +84 -0
- package/dist/core/queue-manager.d.ts.map +1 -0
- package/dist/core/queue-manager.js +814 -0
- package/dist/core/queue-manager.js.map +1 -0
- package/dist/core/reasoning-controls.d.ts +30 -0
- package/dist/core/reasoning-controls.d.ts.map +1 -0
- package/dist/core/reasoning-controls.js +118 -0
- package/dist/core/reasoning-controls.js.map +1 -0
- package/dist/core/reliability-config.d.ts +82 -0
- package/dist/core/reliability-config.d.ts.map +1 -0
- package/dist/core/reliability-config.js +106 -0
- package/dist/core/reliability-config.js.map +1 -0
- package/dist/core/reliability-runtime.d.ts +53 -0
- package/dist/core/reliability-runtime.d.ts.map +1 -0
- package/dist/core/reliability-runtime.js +92 -0
- package/dist/core/reliability-runtime.js.map +1 -0
- package/dist/core/security/guardrails.d.ts +21 -0
- package/dist/core/security/guardrails.d.ts.map +1 -0
- package/dist/core/security/guardrails.js +111 -0
- package/dist/core/security/guardrails.js.map +1 -0
- package/dist/core/send-message-tool.d.ts +79 -0
- package/dist/core/send-message-tool.d.ts.map +1 -0
- package/dist/core/send-message-tool.js +222 -0
- package/dist/core/send-message-tool.js.map +1 -0
- package/dist/core/shell-cmd-tool.d.ts +82 -1
- package/dist/core/shell-cmd-tool.d.ts.map +1 -1
- package/dist/core/shell-cmd-tool.js +854 -42
- package/dist/core/shell-cmd-tool.js.map +1 -1
- package/dist/core/skill-registry.d.ts +2 -0
- package/dist/core/skill-registry.d.ts.map +1 -1
- package/dist/core/skill-registry.js +52 -2
- package/dist/core/skill-registry.js.map +1 -1
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts +5 -0
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/fileEventStorage.js +61 -0
- package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +5 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/memoryEventStorage.js +34 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +1 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/sqliteEventStorage.js +19 -2
- package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/types.d.ts +6 -0
- package/dist/core/storage/eventStorage/types.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/types.js +1 -0
- package/dist/core/storage/eventStorage/types.js.map +1 -1
- package/dist/core/storage/eventStorage/validation.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/validation.js +2 -1
- package/dist/core/storage/eventStorage/validation.js.map +1 -1
- package/dist/core/storage/github-world-import.d.ts +84 -0
- package/dist/core/storage/github-world-import.d.ts.map +1 -0
- package/dist/core/storage/github-world-import.js +365 -0
- package/dist/core/storage/github-world-import.js.map +1 -0
- package/dist/core/storage/memory-storage.d.ts +19 -8
- package/dist/core/storage/memory-storage.d.ts.map +1 -1
- package/dist/core/storage/memory-storage.js +147 -49
- package/dist/core/storage/memory-storage.js.map +1 -1
- package/dist/core/storage/queue-storage.d.ts +1 -0
- package/dist/core/storage/queue-storage.d.ts.map +1 -1
- package/dist/core/storage/queue-storage.js +3 -2
- package/dist/core/storage/queue-storage.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +14 -9
- package/dist/core/storage/sqlite-storage.d.ts.map +1 -1
- package/dist/core/storage/sqlite-storage.js +131 -154
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/storage-factory.d.ts +3 -0
- package/dist/core/storage/storage-factory.d.ts.map +1 -1
- package/dist/core/storage/storage-factory.js +175 -89
- package/dist/core/storage/storage-factory.js.map +1 -1
- package/dist/core/storage/world-storage.d.ts +1 -1
- package/dist/core/storage/world-storage.d.ts.map +1 -1
- package/dist/core/storage/world-storage.js +5 -1
- package/dist/core/storage/world-storage.js.map +1 -1
- package/dist/core/storage-init.d.ts +11 -0
- package/dist/core/storage-init.d.ts.map +1 -0
- package/dist/core/storage-init.js +122 -0
- package/dist/core/storage-init.js.map +1 -0
- package/dist/core/subscription.d.ts +8 -1
- package/dist/core/subscription.d.ts.map +1 -1
- package/dist/core/subscription.js +130 -23
- package/dist/core/subscription.js.map +1 -1
- package/dist/core/tool-approval.d.ts +45 -0
- package/dist/core/tool-approval.d.ts.map +1 -0
- package/dist/core/tool-approval.js +223 -0
- package/dist/core/tool-approval.js.map +1 -0
- package/dist/core/tool-execution-envelope.d.ts +87 -0
- package/dist/core/tool-execution-envelope.d.ts.map +1 -0
- package/dist/core/tool-execution-envelope.js +168 -0
- package/dist/core/tool-execution-envelope.js.map +1 -0
- package/dist/core/tool-utils.d.ts +7 -2
- package/dist/core/tool-utils.d.ts.map +1 -1
- package/dist/core/tool-utils.js +81 -17
- package/dist/core/tool-utils.js.map +1 -1
- package/dist/core/types.d.ts +67 -19
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +71 -21
- package/dist/core/utils.js.map +1 -1
- package/dist/core/web-fetch-tool.d.ts +72 -0
- package/dist/core/web-fetch-tool.d.ts.map +1 -0
- package/dist/core/web-fetch-tool.js +491 -0
- package/dist/core/web-fetch-tool.js.map +1 -0
- package/dist/core/world-registry.d.ts +84 -0
- package/dist/core/world-registry.d.ts.map +1 -0
- package/dist/core/world-registry.js +247 -0
- package/dist/core/world-registry.js.map +1 -0
- package/dist/public/assets/index-Be-1xtV-.js +104 -0
- package/dist/public/assets/index-tsDdiXDU.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/public/mcp-sandbox-proxy.html +148 -0
- package/dist/server/api.js +260 -18
- package/dist/server/error-response.d.ts +27 -0
- package/dist/server/error-response.js +77 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +6 -2
- package/dist/server/sse-handler.d.ts +11 -1
- package/dist/server/sse-handler.js +194 -34
- package/migrations/0015_add_message_queue.sql +36 -0
- package/migrations/0016_add_world_heartbeat.sql +13 -0
- package/migrations/0017_add_title_provenance.sql +7 -0
- package/package.json +31 -10
- package/dist/public/assets/index-BW41BxMy.css +0 -1
- package/dist/public/assets/index-kO6UJFwK.js +0 -96
package/dist/core/managers.js
CHANGED
|
@@ -8,25 +8,41 @@
|
|
|
8
8
|
* - Automatic ID normalization to kebab-case for consistency
|
|
9
9
|
* - Environment-aware storage operations through storage-factory
|
|
10
10
|
* - Agent message management with automatic agentId assignment
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
11
|
+
*
|
|
12
|
+
* Queue management, message editing, and storage initialization are handled by
|
|
13
|
+
* dedicated sub-modules (queue-manager.ts, message-edit-manager.ts, storage-init.ts)
|
|
14
|
+
* whose public APIs are re-exported here for backward compatibility.
|
|
14
15
|
*
|
|
15
16
|
* API: World (create/get/update/delete/list), Agent (create/get/update/delete/list/updateMemory/clearMemory),
|
|
16
|
-
* Chat (newChat/listChats/deleteChat/restoreChat),
|
|
17
|
-
*
|
|
17
|
+
* Chat (newChat/listChats/deleteChat/restoreChat),
|
|
18
|
+
* re-exports: migrateMessageIds, removeMessagesFrom, editUserMessage, logEditError, getEditErrors,
|
|
19
|
+
* addToQueue, enqueueAndProcessUserTurn, dispatchImmediateChatMessage, getQueueMessages,
|
|
20
|
+
* pauseChatQueue, resumeChatQueue, stopChatQueue, clearChatQueue, retryQueueMessage,
|
|
21
|
+
* recoverQueueSendingMessages
|
|
18
22
|
*
|
|
19
23
|
* Implementation Details:
|
|
20
24
|
* - Ensures all agent messages include agentId for proper export functionality
|
|
21
25
|
* - Compatible with both SQLite and memory storage backends
|
|
22
26
|
* - Automatic agent identification for message source tracking
|
|
23
|
-
* - Idempotent message ID migration supporting both file and SQL storage
|
|
24
|
-
* - Comprehensive error tracking for partial failures
|
|
25
|
-
* - Error log persistence with 100-entry retention policy
|
|
26
27
|
*
|
|
27
28
|
* Recent Changes:
|
|
28
|
-
* - 2026-
|
|
29
|
-
*
|
|
29
|
+
* - 2026-03-11: Synchronized active runtime world metadata during `updateWorld` so live chats pick up
|
|
30
|
+
* updated `variables`/`working_directory` and other top-level config without requiring a runtime refresh.
|
|
31
|
+
* - 2026-03-10: Renamed queue-backed ingress to `enqueueAndProcessUserTurn` and split non-user immediate dispatch into `dispatchImmediateChatMessage`.
|
|
32
|
+
* - 2026-03-10: Removed restore-time user-last resend from persisted chat memory; queue-owned rows are now the only automatic resume authority.
|
|
33
|
+
* - 2026-03-10: Added `restoreChat(..., { suppressAutoResume: true })` support for edit/delete mutation flows so failed last-turn messages are not replayed before mutation.
|
|
34
|
+
* - 2026-03-09: God-module decomposition — extracted queue management (queue-manager.ts),
|
|
35
|
+
* message edit/migration logic (message-edit-manager.ts), and storage singleton +
|
|
36
|
+
* identifier resolution (storage-init.ts) into focused sub-modules. managers.ts reduced
|
|
37
|
+
* from 2928 → 1300 lines; all public exports preserved via re-exports.
|
|
38
|
+
* - 2026-03-09: Added `activateChatResources` helper to consolidate the resource-reactivation
|
|
39
|
+
* sequence (memory sync, skill approvals, HITL replay, queue resume) shared between the
|
|
40
|
+
* two branches of `restoreChat`.
|
|
41
|
+
* - 2026-03-09: Added SSE terminal-state guard in `triggerPendingLastMessageResume` to prevent
|
|
42
|
+
* infinite auto-resume loops when the last SSE event is already terminal.
|
|
43
|
+
* - 2026-03-06: Moved chat-selection runtime control flow onto explicit/persisted chat helpers.
|
|
44
|
+
* - 2026-03-06: Removed runtime `world.currentChatId` fallback from exported chat-memory helpers.
|
|
45
|
+
* - 2026-02-20: Added `claimAgentCreationSlot` / `allowWhileWorldProcessing` for approval-gated tool calls.
|
|
30
46
|
* - 2026-02-16: Added `branchChatFromMessage` to create a new chat branched from an assistant message and copy source-chat history up to the target message.
|
|
31
47
|
* - 2026-02-14: Updated `editUserMessage` to be fully core-managed for clear+resend behavior without client-side subscription refresh logic.
|
|
32
48
|
* - Edit resubmission now prefers active subscribed world runtimes.
|
|
@@ -68,43 +84,123 @@
|
|
|
68
84
|
* - logEditError/getEditErrors: Error persistence in edit-errors.json
|
|
69
85
|
*
|
|
70
86
|
* Note: Export functionality has been moved to core/export.ts
|
|
71
|
-
*/
|
|
72
|
-
|
|
87
|
+
*/
|
|
88
|
+
// Core module imports
|
|
89
|
+
import { createCategoryLogger } from './logger.js';
|
|
73
90
|
import { EventEmitter } from 'events';
|
|
74
|
-
import { createStorageWithWrappers } from './storage/storage-factory.js';
|
|
75
91
|
import * as utils from './utils.js';
|
|
76
|
-
import {
|
|
77
|
-
import
|
|
78
|
-
import
|
|
79
|
-
import {
|
|
80
|
-
import {
|
|
81
|
-
import {
|
|
82
|
-
import {
|
|
83
|
-
|
|
92
|
+
import { NEW_CHAT_TITLE, isDefaultChatTitle, TITLE_PROVENANCE_MANUAL } from './chat-constants.js';
|
|
93
|
+
import { hasActiveChatMessageProcessing } from './message-processing-control.js';
|
|
94
|
+
import { replayPendingHitlRequests, listPendingHitlPromptEventsFromMessages } from './hitl.js';
|
|
95
|
+
import { clearChatSkillApprovals, reconstructSkillApprovalsFromMessages } from './load-skill-tool.js';
|
|
96
|
+
import { resumePendingToolCallsForChat } from './events/memory-manager.js';
|
|
97
|
+
import { stopWorldRuntimesByWorldId } from './world-registry.js';
|
|
98
|
+
import { storageWrappers, ensureInitialization, getResolvedWorldId, getResolvedAgentId, } from './storage-init.js';
|
|
99
|
+
import { triggerPendingQueueResume, autoPauseQueueForChat, clearQueuePauseForChat, } from './queue-manager.js';
|
|
100
|
+
import { syncRuntimeAgentMemoryFromStorage, migrateMessageIds, } from './message-edit-manager.js';
|
|
101
|
+
// Per-module state
|
|
84
102
|
const logger = createCategoryLogger('core.managers');
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
const loggerRestore = createCategoryLogger('chat.restore');
|
|
104
|
+
const loggerRestoreResume = createCategoryLogger('chat.restore.resume');
|
|
105
|
+
const inFlightToolResumeKeys = new Set();
|
|
106
|
+
function triggerPendingToolCallResume(world, chatId, targetAssistantMessageId) {
|
|
107
|
+
if (!chatId) {
|
|
108
|
+
return;
|
|
90
109
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
storageWrappers = await createStorageWithWrappers();
|
|
110
|
+
if (hasActiveChatMessageProcessing(world.id, chatId)) {
|
|
111
|
+
return;
|
|
94
112
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
const resumeKey = `${world.id}:${chatId}`;
|
|
114
|
+
if (inFlightToolResumeKeys.has(resumeKey)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
inFlightToolResumeKeys.add(resumeKey);
|
|
118
|
+
void (async () => {
|
|
119
|
+
try {
|
|
120
|
+
const resumedCount = await resumePendingToolCallsForChat(world, chatId, targetAssistantMessageId);
|
|
121
|
+
loggerRestoreResume.debug('Attempted pending tool-call resume after chat restore', {
|
|
122
|
+
worldId: world.id,
|
|
123
|
+
chatId,
|
|
124
|
+
targetAssistantMessageId: targetAssistantMessageId || null,
|
|
125
|
+
resumedCount,
|
|
126
|
+
});
|
|
127
|
+
if (resumedCount > 0) {
|
|
128
|
+
loggerRestoreResume.debug('Resumed pending persisted tool calls after chat restore', {
|
|
129
|
+
worldId: world.id,
|
|
130
|
+
chatId,
|
|
131
|
+
targetAssistantMessageId,
|
|
132
|
+
resumedCount,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
loggerRestoreResume.warn('Failed to resume pending persisted tool calls after chat restore', {
|
|
138
|
+
worldId: world.id,
|
|
139
|
+
chatId,
|
|
140
|
+
targetAssistantMessageId,
|
|
141
|
+
error: error instanceof Error ? error.message : String(error),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
inFlightToolResumeKeys.delete(resumeKey);
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
}
|
|
149
|
+
function hasPendingToolCallsOnLastAssistantMessage(lastMessage, chatMemory) {
|
|
150
|
+
if (lastMessage.role !== 'assistant' || !Array.isArray(lastMessage.tool_calls) || lastMessage.tool_calls.length === 0) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const completedToolCallIds = new Set();
|
|
154
|
+
for (const message of chatMemory) {
|
|
155
|
+
if (message.role !== 'tool' || typeof message.tool_call_id !== 'string') {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const toolCallId = message.tool_call_id.trim();
|
|
159
|
+
if (toolCallId) {
|
|
160
|
+
completedToolCallIds.add(toolCallId);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const toolCall of lastMessage.tool_calls) {
|
|
164
|
+
const toolCallId = String(toolCall?.id || '').trim();
|
|
165
|
+
if (!toolCallId) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (!completedToolCallIds.has(toolCallId)) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
99
171
|
}
|
|
172
|
+
return false;
|
|
100
173
|
}
|
|
101
|
-
function
|
|
102
|
-
if (!
|
|
103
|
-
|
|
174
|
+
function triggerPendingToolCallResumeFromLastMessage(world, chatId) {
|
|
175
|
+
if (!chatId) {
|
|
176
|
+
return;
|
|
104
177
|
}
|
|
105
|
-
|
|
178
|
+
void (async () => {
|
|
179
|
+
try {
|
|
180
|
+
const chatMemory = await storageWrappers.getMemory(world.id, chatId);
|
|
181
|
+
if (!chatMemory || chatMemory.length === 0) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const lastMessage = chatMemory[chatMemory.length - 1];
|
|
185
|
+
if (!hasPendingToolCallsOnLastAssistantMessage(lastMessage, chatMemory)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
loggerRestoreResume.debug('Detected pending tool-call-last message during chat-restore inspection', {
|
|
189
|
+
worldId: world.id,
|
|
190
|
+
chatId,
|
|
191
|
+
messageId: lastMessage.messageId || null,
|
|
192
|
+
});
|
|
193
|
+
triggerPendingToolCallResume(world, chatId, lastMessage.messageId);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
loggerRestoreResume.warn('Failed to inspect last message for pending tool-call resume during chat restore', {
|
|
197
|
+
worldId: world.id,
|
|
198
|
+
chatId,
|
|
199
|
+
error: error instanceof Error ? error.message : String(error),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
106
203
|
}
|
|
107
|
-
const NEW_CHAT_CONFIG = { REUSABLE_CHAT_TITLE: NEW_CHAT_TITLE };
|
|
108
204
|
// TOCTOU guard: prevents two concurrent createAgent calls from both passing the
|
|
109
205
|
// `agentExists` check before either write lands. Maps worldId → Set<agentId>.
|
|
110
206
|
const pendingAgentCreates = new Map();
|
|
@@ -142,137 +238,6 @@ export async function claimAgentCreationSlot(worldId, agentName) {
|
|
|
142
238
|
},
|
|
143
239
|
};
|
|
144
240
|
}
|
|
145
|
-
function extractGeneratedChatTitleFromSystemPayload(payload) {
|
|
146
|
-
if (!payload || typeof payload !== 'object')
|
|
147
|
-
return null;
|
|
148
|
-
if (payload.eventType !== 'chat-title-updated')
|
|
149
|
-
return null;
|
|
150
|
-
const title = typeof payload.title === 'string' ? payload.title.trim() : '';
|
|
151
|
-
return title || null;
|
|
152
|
-
}
|
|
153
|
-
async function resetAutoGeneratedChatTitleForEditResubmission(world, chatId) {
|
|
154
|
-
const chat = world.chats.get(chatId) ?? await storageWrappers.loadChatData(world.id, chatId);
|
|
155
|
-
if (!chat)
|
|
156
|
-
return;
|
|
157
|
-
const currentTitle = String(chat.name || '').trim();
|
|
158
|
-
if (!currentTitle || isDefaultChatTitle(currentTitle)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const eventStorage = world.eventStorage;
|
|
162
|
-
if (!eventStorage) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
let latestGeneratedTitle = null;
|
|
166
|
-
try {
|
|
167
|
-
const systemEvents = await eventStorage.getEventsByWorldAndChat(world.id, chatId, {
|
|
168
|
-
types: ['system'],
|
|
169
|
-
order: 'desc',
|
|
170
|
-
limit: 25
|
|
171
|
-
});
|
|
172
|
-
for (const event of systemEvents) {
|
|
173
|
-
const generatedTitle = extractGeneratedChatTitleFromSystemPayload(event?.payload);
|
|
174
|
-
if (generatedTitle) {
|
|
175
|
-
latestGeneratedTitle = generatedTitle;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
logger.debug('Skipping auto-title reset because system events could not be queried', {
|
|
182
|
-
worldId: world.id,
|
|
183
|
-
chatId,
|
|
184
|
-
error: error instanceof Error ? error.message : String(error)
|
|
185
|
-
});
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (!latestGeneratedTitle || latestGeneratedTitle !== currentTitle) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
let resetSucceeded = false;
|
|
192
|
-
if (typeof storageWrappers.updateChatNameIfCurrent === 'function') {
|
|
193
|
-
resetSucceeded = await storageWrappers.updateChatNameIfCurrent(world.id, chatId, currentTitle, NEW_CHAT_TITLE);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
const updated = await storageWrappers.updateChatData(world.id, chatId, { name: NEW_CHAT_TITLE });
|
|
197
|
-
resetSucceeded = !!updated;
|
|
198
|
-
}
|
|
199
|
-
if (!resetSucceeded) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const runtimeChat = world.chats.get(chatId);
|
|
203
|
-
if (runtimeChat) {
|
|
204
|
-
runtimeChat.name = NEW_CHAT_TITLE;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
async function syncRuntimeAgentMemoryFromStorage(world, worldId) {
|
|
208
|
-
if (!world?.agents || world.agents.size === 0)
|
|
209
|
-
return;
|
|
210
|
-
for (const runtimeAgent of world.agents.values()) {
|
|
211
|
-
const persistedAgent = await storageWrappers.loadAgent(worldId, runtimeAgent.id);
|
|
212
|
-
runtimeAgent.memory = Array.isArray(persistedAgent?.memory)
|
|
213
|
-
? [...persistedAgent.memory]
|
|
214
|
-
: [];
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Resolve a world identifier to the persisted world ID.
|
|
219
|
-
* Accepts either world ID or world name and supports historical rename drift.
|
|
220
|
-
*/
|
|
221
|
-
async function resolveWorldIdentifier(worldIdOrName) {
|
|
222
|
-
const normalizedInput = utils.toKebabCase(worldIdOrName);
|
|
223
|
-
if (!normalizedInput)
|
|
224
|
-
return null;
|
|
225
|
-
// Fast path: direct normalized ID lookup
|
|
226
|
-
const directWorld = await storageWrappers.loadWorld(normalizedInput);
|
|
227
|
-
if (directWorld?.id) {
|
|
228
|
-
return directWorld.id;
|
|
229
|
-
}
|
|
230
|
-
// Fallback: scan worlds and match by normalized ID or normalized name
|
|
231
|
-
const worlds = await storageWrappers.listWorlds();
|
|
232
|
-
const matched = worlds.find((world) => {
|
|
233
|
-
const storedId = String(world.id || '');
|
|
234
|
-
const storedName = String(world.name || '');
|
|
235
|
-
return (storedId === worldIdOrName ||
|
|
236
|
-
storedName === worldIdOrName ||
|
|
237
|
-
utils.toKebabCase(storedId) === normalizedInput ||
|
|
238
|
-
utils.toKebabCase(storedName) === normalizedInput);
|
|
239
|
-
});
|
|
240
|
-
return matched?.id || null;
|
|
241
|
-
}
|
|
242
|
-
async function getResolvedWorldId(worldIdOrName) {
|
|
243
|
-
const resolved = await resolveWorldIdentifier(worldIdOrName);
|
|
244
|
-
return resolved || utils.toKebabCase(worldIdOrName);
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Resolve an agent identifier to the persisted agent ID within a world.
|
|
248
|
-
* Accepts either agent ID or agent name and supports historical rename drift.
|
|
249
|
-
*/
|
|
250
|
-
async function resolveAgentIdentifier(worldIdOrName, agentIdOrName) {
|
|
251
|
-
const resolvedWorldId = await getResolvedWorldId(worldIdOrName);
|
|
252
|
-
const normalizedInput = utils.toKebabCase(agentIdOrName);
|
|
253
|
-
if (!normalizedInput)
|
|
254
|
-
return null;
|
|
255
|
-
// Fast path: direct normalized ID lookup
|
|
256
|
-
const directAgent = await storageWrappers.loadAgent(resolvedWorldId, normalizedInput);
|
|
257
|
-
if (directAgent?.id) {
|
|
258
|
-
return directAgent.id;
|
|
259
|
-
}
|
|
260
|
-
// Fallback: scan agents and match by normalized ID or normalized name
|
|
261
|
-
const agents = await storageWrappers.listAgents(resolvedWorldId);
|
|
262
|
-
const matched = agents.find((agent) => {
|
|
263
|
-
const storedId = String(agent.id || '');
|
|
264
|
-
const storedName = String(agent.name || '');
|
|
265
|
-
return (storedId === agentIdOrName ||
|
|
266
|
-
storedName === agentIdOrName ||
|
|
267
|
-
utils.toKebabCase(storedId) === normalizedInput ||
|
|
268
|
-
utils.toKebabCase(storedName) === normalizedInput);
|
|
269
|
-
});
|
|
270
|
-
return matched?.id || null;
|
|
271
|
-
}
|
|
272
|
-
async function getResolvedAgentId(worldIdOrName, agentIdOrName) {
|
|
273
|
-
const resolved = await resolveAgentIdentifier(worldIdOrName, agentIdOrName);
|
|
274
|
-
return resolved || utils.toKebabCase(agentIdOrName);
|
|
275
|
-
}
|
|
276
241
|
/**
|
|
277
242
|
* Create new world with configuration and automatically create a new chat
|
|
278
243
|
*/
|
|
@@ -293,6 +258,9 @@ export async function createWorld(params) {
|
|
|
293
258
|
chatLLMModel: params.chatLLMModel,
|
|
294
259
|
mcpConfig: params.mcpConfig,
|
|
295
260
|
variables: params.variables,
|
|
261
|
+
heartbeatEnabled: params.heartbeatEnabled === true,
|
|
262
|
+
heartbeatInterval: params.heartbeatInterval ? String(params.heartbeatInterval).trim() : null,
|
|
263
|
+
heartbeatPrompt: params.heartbeatPrompt ? String(params.heartbeatPrompt) : null,
|
|
296
264
|
createdAt: new Date(),
|
|
297
265
|
lastUpdated: new Date(),
|
|
298
266
|
totalAgents: 0,
|
|
@@ -337,8 +305,59 @@ export async function updateWorld(worldId, updates) {
|
|
|
337
305
|
lastUpdated: new Date()
|
|
338
306
|
};
|
|
339
307
|
await storageWrappers.saveWorld(updatedData);
|
|
308
|
+
const { getActiveSubscribedWorlds } = await import('./subscription.js');
|
|
309
|
+
const activeRuntimeWorlds = getActiveSubscribedWorlds(resolvedWorldId);
|
|
310
|
+
if (activeRuntimeWorlds.length > 0) {
|
|
311
|
+
const shouldSyncCurrentChatId = normalizedUpdates.currentChatId !== undefined;
|
|
312
|
+
for (const activeRuntimeWorld of activeRuntimeWorlds) {
|
|
313
|
+
syncActiveRuntimeWorldMetadata(activeRuntimeWorld, updatedData);
|
|
314
|
+
if (shouldSyncCurrentChatId) {
|
|
315
|
+
activeRuntimeWorld.currentChatId = updatedData.currentChatId ?? null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return activeRuntimeWorlds[0] ?? null;
|
|
319
|
+
}
|
|
340
320
|
return getWorld(resolvedWorldId);
|
|
341
321
|
}
|
|
322
|
+
function syncActiveRuntimeWorldMetadata(targetWorld, persistedWorld) {
|
|
323
|
+
targetWorld.name = persistedWorld.name;
|
|
324
|
+
targetWorld.description = persistedWorld.description;
|
|
325
|
+
targetWorld.turnLimit = persistedWorld.turnLimit;
|
|
326
|
+
targetWorld.mainAgent = persistedWorld.mainAgent ?? null;
|
|
327
|
+
targetWorld.chatLLMProvider = persistedWorld.chatLLMProvider;
|
|
328
|
+
targetWorld.chatLLMModel = persistedWorld.chatLLMModel;
|
|
329
|
+
targetWorld.mcpConfig = persistedWorld.mcpConfig ?? null;
|
|
330
|
+
targetWorld.variables = persistedWorld.variables;
|
|
331
|
+
targetWorld.uiMode = persistedWorld.uiMode;
|
|
332
|
+
targetWorld.dashboardZones = persistedWorld.dashboardZones;
|
|
333
|
+
targetWorld.heartbeatEnabled = persistedWorld.heartbeatEnabled === true;
|
|
334
|
+
targetWorld.heartbeatInterval = persistedWorld.heartbeatInterval ?? null;
|
|
335
|
+
targetWorld.heartbeatPrompt = persistedWorld.heartbeatPrompt ?? null;
|
|
336
|
+
targetWorld.lastUpdated = persistedWorld.lastUpdated;
|
|
337
|
+
}
|
|
338
|
+
async function getPersistedCurrentChatId(worldId) {
|
|
339
|
+
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
340
|
+
const worldData = await storageWrappers.loadWorld(resolvedWorldId);
|
|
341
|
+
const chatId = String(worldData?.currentChatId || '').trim();
|
|
342
|
+
return chatId || null;
|
|
343
|
+
}
|
|
344
|
+
async function setPersistedCurrentChatId(worldId, chatId) {
|
|
345
|
+
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
346
|
+
const worldData = await storageWrappers.loadWorld(resolvedWorldId);
|
|
347
|
+
if (!worldData) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const normalizedChatId = String(chatId || '').trim() || null;
|
|
351
|
+
const existingChatId = String(worldData.currentChatId || '').trim() || null;
|
|
352
|
+
if (existingChatId === normalizedChatId) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
await storageWrappers.saveWorld({
|
|
356
|
+
...worldData,
|
|
357
|
+
currentChatId: normalizedChatId,
|
|
358
|
+
lastUpdated: new Date(),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
342
361
|
/**
|
|
343
362
|
* Set the raw .env-style variables text for a world
|
|
344
363
|
*/
|
|
@@ -391,7 +410,11 @@ export async function deleteWorld(worldId) {
|
|
|
391
410
|
if (worldData?._activityListenerCleanup) {
|
|
392
411
|
worldData._activityListenerCleanup();
|
|
393
412
|
}
|
|
394
|
-
|
|
413
|
+
const result = await storageWrappers.deleteWorld(resolvedWorldId);
|
|
414
|
+
// Stop any active runtime so subsequent subscriptions get a fresh world
|
|
415
|
+
// instead of reusing state from the deleted incarnation.
|
|
416
|
+
await stopWorldRuntimesByWorldId(resolvedWorldId);
|
|
417
|
+
return result;
|
|
395
418
|
}
|
|
396
419
|
/**
|
|
397
420
|
* Get all world IDs and basic information
|
|
@@ -455,6 +478,8 @@ export async function getWorld(worldId) {
|
|
|
455
478
|
world._eventPersistenceCleanup = setupEventPersistence(world);
|
|
456
479
|
world._activityListenerCleanup = setupWorldActivityListener(world);
|
|
457
480
|
}
|
|
481
|
+
// Initialize queued chat set; per-chat seeding deferred to explicit queue operations
|
|
482
|
+
world._queuedChatIds = new Set();
|
|
458
483
|
return world;
|
|
459
484
|
}
|
|
460
485
|
/**
|
|
@@ -705,7 +730,7 @@ async function createChat(worldId, params) {
|
|
|
705
730
|
const chatData = {
|
|
706
731
|
id: chatId,
|
|
707
732
|
worldId,
|
|
708
|
-
name:
|
|
733
|
+
name: NEW_CHAT_TITLE,
|
|
709
734
|
description: params.description,
|
|
710
735
|
createdAt: now,
|
|
711
736
|
updatedAt: now,
|
|
@@ -730,7 +755,8 @@ export async function newChat(worldId) {
|
|
|
730
755
|
if (existingChat) {
|
|
731
756
|
const messages = await storageWrappers.getMemory(resolvedWorldId, existingChat.id);
|
|
732
757
|
if (messages.length === 0) {
|
|
733
|
-
|
|
758
|
+
await setPersistedCurrentChatId(resolvedWorldId, existingChat.id);
|
|
759
|
+
return await getWorld(resolvedWorldId);
|
|
734
760
|
}
|
|
735
761
|
// If chat has messages, fall through to create a new one
|
|
736
762
|
}
|
|
@@ -738,7 +764,8 @@ export async function newChat(worldId) {
|
|
|
738
764
|
name: NEW_CHAT_TITLE,
|
|
739
765
|
captureChat: false
|
|
740
766
|
});
|
|
741
|
-
|
|
767
|
+
await setPersistedCurrentChatId(resolvedWorldId, chatData.id);
|
|
768
|
+
return await getWorld(resolvedWorldId);
|
|
742
769
|
}
|
|
743
770
|
/**
|
|
744
771
|
* Create a branched chat from a source chat up to (and including) the provided message.
|
|
@@ -795,13 +822,10 @@ export async function branchChatFromMessage(worldId, sourceChatId, messageId) {
|
|
|
795
822
|
};
|
|
796
823
|
const cutoffTimestamp = toEpochMillis(targetMessage?.createdAt);
|
|
797
824
|
const updatedWorld = await newChat(resolvedWorldId);
|
|
798
|
-
|
|
825
|
+
const newChatId = await getPersistedCurrentChatId(resolvedWorldId);
|
|
826
|
+
if (!updatedWorld || !newChatId) {
|
|
799
827
|
throw new Error('Failed to create branch chat.');
|
|
800
828
|
}
|
|
801
|
-
const newChatId = String(updatedWorld.currentChatId || '').trim();
|
|
802
|
-
if (!newChatId) {
|
|
803
|
-
throw new Error('Failed to resolve new chat ID for branch.');
|
|
804
|
-
}
|
|
805
829
|
let copiedMessageCount = 0;
|
|
806
830
|
const agents = await listAgents(resolvedWorldId);
|
|
807
831
|
try {
|
|
@@ -868,6 +892,11 @@ export async function listChats(worldId) {
|
|
|
868
892
|
export async function updateChat(worldId, chatId, updates) {
|
|
869
893
|
await ensureInitialization();
|
|
870
894
|
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
895
|
+
// When the caller explicitly sets a name, treat it as a manual title so the
|
|
896
|
+
// auto-title scheduler will not overwrite it.
|
|
897
|
+
if (typeof updates.name === 'string' && updates.name.trim().length > 0) {
|
|
898
|
+
updates = { ...updates, titleProvenance: TITLE_PROVENANCE_MANUAL };
|
|
899
|
+
}
|
|
871
900
|
const chat = await storageWrappers.updateChatData(resolvedWorldId, chatId, updates);
|
|
872
901
|
if (!chat) {
|
|
873
902
|
return null;
|
|
@@ -888,12 +917,10 @@ export async function deleteChat(worldId, chatId) {
|
|
|
888
917
|
// First, delete all agent memory items associated with this chat
|
|
889
918
|
const deletedMemoryCount = await storageWrappers.deleteMemoryByChatId(resolvedWorldId, chatId);
|
|
890
919
|
logger.debug('Deleted memory items for chat', { worldId, resolvedWorldId, chatId, deletedMemoryCount });
|
|
891
|
-
|
|
920
|
+
const persistedCurrentChatId = await getPersistedCurrentChatId(resolvedWorldId);
|
|
921
|
+
// Get the world to update in-memory chat cache only
|
|
892
922
|
const world = await getWorld(resolvedWorldId);
|
|
893
|
-
|
|
894
|
-
if (world && world.currentChatId === chatId) {
|
|
895
|
-
shouldSetNewCurrentChat = true;
|
|
896
|
-
}
|
|
923
|
+
const shouldSetNewCurrentChat = persistedCurrentChatId === chatId;
|
|
897
924
|
// Then delete the chat itself
|
|
898
925
|
const chatDeleted = await storageWrappers.deleteChatData(resolvedWorldId, chatId);
|
|
899
926
|
// Remove from world's in-memory chat map
|
|
@@ -906,7 +933,7 @@ export async function deleteChat(worldId, chatId) {
|
|
|
906
933
|
if (remainingChats.length > 0) {
|
|
907
934
|
// Set the most recently updated chat as current
|
|
908
935
|
const latestChat = remainingChats.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
|
|
909
|
-
await
|
|
936
|
+
await setPersistedCurrentChatId(resolvedWorldId, latestChat.id);
|
|
910
937
|
}
|
|
911
938
|
else {
|
|
912
939
|
// No chats left, create a new one
|
|
@@ -916,14 +943,64 @@ export async function deleteChat(worldId, chatId) {
|
|
|
916
943
|
}
|
|
917
944
|
return chatDeleted;
|
|
918
945
|
}
|
|
919
|
-
|
|
946
|
+
async function activateChatResources(world, resolvedWorldId, chatId, options) {
|
|
947
|
+
await syncRuntimeAgentMemoryFromStorage(world, resolvedWorldId);
|
|
948
|
+
loggerRestore.debug('Restore chat memory sync complete', { worldId: world.id, chatId });
|
|
949
|
+
const memoryForApprovals = await storageWrappers.getMemory(resolvedWorldId, chatId);
|
|
950
|
+
clearChatSkillApprovals(resolvedWorldId, chatId);
|
|
951
|
+
reconstructSkillApprovalsFromMessages(resolvedWorldId, chatId, Array.isArray(memoryForApprovals) ? memoryForApprovals : []);
|
|
952
|
+
const { getActiveSubscribedWorld } = await import('./subscription.js');
|
|
953
|
+
const runtimeWorld = getActiveSubscribedWorld(resolvedWorldId, chatId) || world;
|
|
954
|
+
const replayedHitlPromptCount = replayPendingHitlRequests(runtimeWorld, chatId);
|
|
955
|
+
loggerRestore.debug('Restore chat pending HITL replay triggered', { worldId: world.id, chatId });
|
|
956
|
+
if (options?.suppressAutoResume === true) {
|
|
957
|
+
loggerRestore.debug('Restore chat auto-resume suppressed for mutation flow', {
|
|
958
|
+
worldId: world.id,
|
|
959
|
+
chatId,
|
|
960
|
+
});
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (replayedHitlPromptCount > 0) {
|
|
964
|
+
loggerRestore.debug('Restore chat auto-resume skipped because chat is awaiting HITL input', {
|
|
965
|
+
worldId: world.id,
|
|
966
|
+
chatId,
|
|
967
|
+
replayedHitlPromptCount,
|
|
968
|
+
});
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
triggerPendingToolCallResumeFromLastMessage(runtimeWorld, chatId);
|
|
972
|
+
clearQueuePauseForChat(runtimeWorld.id, chatId);
|
|
973
|
+
triggerPendingQueueResume(runtimeWorld, chatId, { recoverStaleSending: true });
|
|
974
|
+
}
|
|
975
|
+
export async function restoreChat(worldId, chatId, options) {
|
|
920
976
|
await ensureInitialization();
|
|
921
977
|
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
978
|
+
const restoreStartedAt = Date.now();
|
|
979
|
+
loggerRestore.debug('Restore chat started', {
|
|
980
|
+
worldId: resolvedWorldId,
|
|
981
|
+
requestedChatId: chatId,
|
|
982
|
+
});
|
|
922
983
|
let world = await getWorld(resolvedWorldId);
|
|
984
|
+
const persistedCurrentChatId = await getPersistedCurrentChatId(resolvedWorldId);
|
|
923
985
|
if (!world) {
|
|
986
|
+
loggerRestore.debug('Restore chat aborted: world missing', {
|
|
987
|
+
worldId: resolvedWorldId,
|
|
988
|
+
requestedChatId: chatId,
|
|
989
|
+
});
|
|
924
990
|
return null;
|
|
925
991
|
}
|
|
926
|
-
if (
|
|
992
|
+
if (persistedCurrentChatId === chatId) {
|
|
993
|
+
loggerRestore.debug('Restore chat detected already-current chat', {
|
|
994
|
+
worldId: world.id,
|
|
995
|
+
chatId,
|
|
996
|
+
action: 'sync-memory+resume',
|
|
997
|
+
});
|
|
998
|
+
await activateChatResources(world, resolvedWorldId, chatId, options);
|
|
999
|
+
loggerRestore.debug('Restore chat resume inspection triggered', {
|
|
1000
|
+
worldId: world.id,
|
|
1001
|
+
chatId,
|
|
1002
|
+
elapsedMs: Date.now() - restoreStartedAt,
|
|
1003
|
+
});
|
|
927
1004
|
return world;
|
|
928
1005
|
}
|
|
929
1006
|
const runtimeChatExists = world.chats.has(chatId);
|
|
@@ -931,13 +1008,81 @@ export async function restoreChat(worldId, chatId) {
|
|
|
931
1008
|
? true
|
|
932
1009
|
: !!(await storageWrappers.loadChatData(resolvedWorldId, chatId));
|
|
933
1010
|
if (!persistedChatExists) {
|
|
1011
|
+
loggerRestore.debug('Restore chat aborted: chat missing', {
|
|
1012
|
+
worldId: resolvedWorldId,
|
|
1013
|
+
requestedChatId: chatId,
|
|
1014
|
+
runtimeChatExists,
|
|
1015
|
+
});
|
|
934
1016
|
return null;
|
|
935
1017
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1018
|
+
// Auto-pause the old chat's queue when switching away (FR-8 safety)
|
|
1019
|
+
const previousChatId = persistedCurrentChatId;
|
|
1020
|
+
if (previousChatId && previousChatId !== chatId) {
|
|
1021
|
+
autoPauseQueueForChat(resolvedWorldId, previousChatId);
|
|
1022
|
+
loggerRestore.debug('Auto-paused queue for previous chat on switch', {
|
|
1023
|
+
worldId: resolvedWorldId,
|
|
1024
|
+
previousChatId,
|
|
1025
|
+
newChatId: chatId,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
loggerRestore.debug('Restore chat switching current chat', {
|
|
1029
|
+
worldId: resolvedWorldId,
|
|
1030
|
+
fromChatId: previousChatId,
|
|
1031
|
+
toChatId: chatId,
|
|
1032
|
+
});
|
|
1033
|
+
await setPersistedCurrentChatId(resolvedWorldId, chatId);
|
|
1034
|
+
world = await getWorld(resolvedWorldId);
|
|
1035
|
+
if (world) {
|
|
1036
|
+
await activateChatResources(world, resolvedWorldId, chatId, options);
|
|
1037
|
+
loggerRestore.debug('Restore chat resume inspection triggered', {
|
|
1038
|
+
worldId: world.id,
|
|
1039
|
+
chatId,
|
|
1040
|
+
elapsedMs: Date.now() - restoreStartedAt,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
loggerRestore.warn('Restore chat update-world returned null', {
|
|
1045
|
+
worldId: resolvedWorldId,
|
|
1046
|
+
requestedChatId: chatId,
|
|
1047
|
+
elapsedMs: Date.now() - restoreStartedAt,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
loggerRestore.debug('Restore chat completed', {
|
|
1051
|
+
worldId: resolvedWorldId,
|
|
1052
|
+
requestedChatId: chatId,
|
|
1053
|
+
restoredCurrentChatId: await getPersistedCurrentChatId(resolvedWorldId),
|
|
1054
|
+
elapsedMs: Date.now() - restoreStartedAt,
|
|
938
1055
|
});
|
|
939
1056
|
return world;
|
|
940
1057
|
}
|
|
1058
|
+
export async function activateChatWithSnapshot(worldId, chatId) {
|
|
1059
|
+
const world = await restoreChat(worldId, chatId);
|
|
1060
|
+
if (!world) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
const resolvedChatId = String(chatId || '').trim();
|
|
1064
|
+
if (!resolvedChatId) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
const memory = await storageWrappers.getMemory(world.id, resolvedChatId);
|
|
1068
|
+
const safeMemory = Array.isArray(memory) ? memory : [];
|
|
1069
|
+
// Message-authoritative: pending HITL state is derived from persisted messages only.
|
|
1070
|
+
// Runtime pending map serves transport/notification purposes; the snapshot always uses
|
|
1071
|
+
// messages as the single source of truth (AD-1, AD-4).
|
|
1072
|
+
const hitlPrompts = listPendingHitlPromptEventsFromMessages(safeMemory, resolvedChatId);
|
|
1073
|
+
loggerRestore.debug('Activate chat snapshot assembled', {
|
|
1074
|
+
worldId: world.id,
|
|
1075
|
+
chatId: resolvedChatId,
|
|
1076
|
+
memoryCount: safeMemory.length,
|
|
1077
|
+
hitlPromptCount: hitlPrompts.length,
|
|
1078
|
+
});
|
|
1079
|
+
return {
|
|
1080
|
+
world,
|
|
1081
|
+
chatId: resolvedChatId,
|
|
1082
|
+
memory: safeMemory,
|
|
1083
|
+
hitlPrompts,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
941
1086
|
export async function getMemory(worldId, chatId) {
|
|
942
1087
|
await ensureInitialization();
|
|
943
1088
|
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
@@ -945,7 +1090,10 @@ export async function getMemory(worldId, chatId) {
|
|
|
945
1090
|
if (!world) {
|
|
946
1091
|
return null;
|
|
947
1092
|
}
|
|
948
|
-
const resolvedChatId = chatId ||
|
|
1093
|
+
const resolvedChatId = String(chatId || '').trim();
|
|
1094
|
+
if (!resolvedChatId) {
|
|
1095
|
+
throw new Error('getMemory: chatId is required.');
|
|
1096
|
+
}
|
|
949
1097
|
const memory = await storageWrappers.getMemory(resolvedWorldId, resolvedChatId);
|
|
950
1098
|
// Auto-repair legacy memories so downstream clients can rely on messageId without UI fallbacks.
|
|
951
1099
|
if (memory.some(message => !message.messageId)) {
|
|
@@ -958,316 +1106,6 @@ export async function getMemory(worldId, chatId) {
|
|
|
958
1106
|
}
|
|
959
1107
|
return memory;
|
|
960
1108
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
* Idempotent - safe to run multiple times
|
|
965
|
-
*
|
|
966
|
-
* @param worldId - World ID to migrate messages for
|
|
967
|
-
* @returns Number of messages migrated
|
|
968
|
-
*/
|
|
969
|
-
export async function migrateMessageIds(worldId) {
|
|
970
|
-
await ensureInitialization();
|
|
971
|
-
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
972
|
-
let totalMigrated = 0;
|
|
973
|
-
const world = await getWorld(resolvedWorldId);
|
|
974
|
-
if (!world) {
|
|
975
|
-
throw new Error(`World '${worldId}' not found`);
|
|
976
|
-
}
|
|
977
|
-
// Get all agents in the world
|
|
978
|
-
const agents = await listAgents(resolvedWorldId);
|
|
979
|
-
// Get all chats for the world
|
|
980
|
-
const chats = await storageWrappers.listChats(resolvedWorldId);
|
|
981
|
-
// Migrate messages for each chat
|
|
982
|
-
for (const chat of chats) {
|
|
983
|
-
const chatId = chat.id;
|
|
984
|
-
// Get all memory for this chat
|
|
985
|
-
const memory = await storageWrappers.getMemory(resolvedWorldId, chatId);
|
|
986
|
-
if (!memory || memory.length === 0) {
|
|
987
|
-
continue;
|
|
988
|
-
}
|
|
989
|
-
// Check which messages need messageId
|
|
990
|
-
let needsMigration = false;
|
|
991
|
-
const updatedMemory = [];
|
|
992
|
-
for (const message of memory) {
|
|
993
|
-
if (!message.messageId) {
|
|
994
|
-
needsMigration = true;
|
|
995
|
-
updatedMemory.push({
|
|
996
|
-
...message,
|
|
997
|
-
messageId: nanoid(10)
|
|
998
|
-
});
|
|
999
|
-
totalMigrated++;
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
updatedMemory.push(message);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
// If any messages were updated, save the entire memory back
|
|
1006
|
-
if (needsMigration) {
|
|
1007
|
-
// For each agent, update their memory with the migrated messages
|
|
1008
|
-
for (const agent of agents) {
|
|
1009
|
-
const agentMessages = updatedMemory.filter(m => m.agentId === agent.id);
|
|
1010
|
-
if (agentMessages.length > 0) {
|
|
1011
|
-
await storageWrappers.saveAgentMemory(resolvedWorldId, agent.id, agentMessages);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
logger.info(`Migrated ${totalMigrated} messages with messageId for world '${resolvedWorldId}'`);
|
|
1017
|
-
return totalMigrated;
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Remove a message and all subsequent messages from all agents in a world
|
|
1021
|
-
* Used for user message editing feature
|
|
1022
|
-
*
|
|
1023
|
-
* @param worldId - World ID
|
|
1024
|
-
* @param messageId - ID of the message to remove (and all after it)
|
|
1025
|
-
* @param chatId - Chat ID to filter messages
|
|
1026
|
-
* @returns RemovalResult with per-agent removal details
|
|
1027
|
-
*/
|
|
1028
|
-
export async function removeMessagesFrom(worldId, messageId, chatId) {
|
|
1029
|
-
await ensureInitialization();
|
|
1030
|
-
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
1031
|
-
const world = await getWorld(resolvedWorldId);
|
|
1032
|
-
if (!world) {
|
|
1033
|
-
throw new Error(`World '${worldId}' not found`);
|
|
1034
|
-
}
|
|
1035
|
-
// Get all agents
|
|
1036
|
-
const agents = await listAgents(resolvedWorldId);
|
|
1037
|
-
// Track results per agent
|
|
1038
|
-
const processedAgents = [];
|
|
1039
|
-
const failedAgents = [];
|
|
1040
|
-
let messagesRemovedTotal = 0;
|
|
1041
|
-
let foundTargetInAnyAgent = false;
|
|
1042
|
-
let targetTimestampValue = null;
|
|
1043
|
-
const loadedAgentsById = new Map();
|
|
1044
|
-
const toTimestamp = (value) => {
|
|
1045
|
-
if (value instanceof Date) {
|
|
1046
|
-
return value.getTime();
|
|
1047
|
-
}
|
|
1048
|
-
if (value) {
|
|
1049
|
-
const parsed = new Date(value).getTime();
|
|
1050
|
-
if (Number.isFinite(parsed)) {
|
|
1051
|
-
return parsed;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
return Date.now();
|
|
1055
|
-
};
|
|
1056
|
-
// First pass: load each agent and discover the deletion cutoff timestamp
|
|
1057
|
-
for (const agent of agents) {
|
|
1058
|
-
try {
|
|
1059
|
-
const fullAgent = await storageWrappers.loadAgent(resolvedWorldId, agent.id);
|
|
1060
|
-
if (!fullAgent || !fullAgent.memory || fullAgent.memory.length === 0) {
|
|
1061
|
-
continue;
|
|
1062
|
-
}
|
|
1063
|
-
loadedAgentsById.set(agent.id, fullAgent);
|
|
1064
|
-
// Find the target message in this chat for global cutoff derivation
|
|
1065
|
-
const targetMsg = fullAgent.memory.find(m => m.messageId === messageId && m.chatId === chatId);
|
|
1066
|
-
if (targetMsg) {
|
|
1067
|
-
foundTargetInAnyAgent = true;
|
|
1068
|
-
const candidateTimestamp = toTimestamp(targetMsg.createdAt);
|
|
1069
|
-
if (targetTimestampValue === null || candidateTimestamp < targetTimestampValue) {
|
|
1070
|
-
targetTimestampValue = candidateTimestamp;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1076
|
-
failedAgents.push({
|
|
1077
|
-
agentId: agent.id,
|
|
1078
|
-
error: errorMsg
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
if (!foundTargetInAnyAgent || targetTimestampValue === null) {
|
|
1083
|
-
const notFoundFailures = agents.length > 0
|
|
1084
|
-
? [
|
|
1085
|
-
...failedAgents,
|
|
1086
|
-
{ agentId: 'all', error: `Message with ID '${messageId}' not found in chat '${chatId}'` }
|
|
1087
|
-
]
|
|
1088
|
-
: failedAgents;
|
|
1089
|
-
return {
|
|
1090
|
-
success: false,
|
|
1091
|
-
messageId,
|
|
1092
|
-
totalAgents: agents.length,
|
|
1093
|
-
processedAgents,
|
|
1094
|
-
failedAgents: notFoundFailures,
|
|
1095
|
-
messagesRemovedTotal,
|
|
1096
|
-
requiresRetry: false,
|
|
1097
|
-
resubmissionStatus: 'skipped',
|
|
1098
|
-
newMessageId: undefined
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
// Second pass: apply cutoff to all agents in the target chat
|
|
1102
|
-
for (const agent of agents) {
|
|
1103
|
-
if (failedAgents.some(entry => entry.agentId === agent.id)) {
|
|
1104
|
-
continue;
|
|
1105
|
-
}
|
|
1106
|
-
const fullAgent = loadedAgentsById.get(agent.id);
|
|
1107
|
-
if (!fullAgent || !Array.isArray(fullAgent.memory) || fullAgent.memory.length === 0) {
|
|
1108
|
-
processedAgents.push(agent.id);
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
try {
|
|
1112
|
-
const messagesToKeep = fullAgent.memory.filter(m => {
|
|
1113
|
-
if (m.chatId !== chatId) {
|
|
1114
|
-
return true;
|
|
1115
|
-
}
|
|
1116
|
-
const msgTimestamp = toTimestamp(m.createdAt);
|
|
1117
|
-
return msgTimestamp < targetTimestampValue;
|
|
1118
|
-
});
|
|
1119
|
-
const removedCount = fullAgent.memory.length - messagesToKeep.length;
|
|
1120
|
-
if (removedCount === 0) {
|
|
1121
|
-
processedAgents.push(agent.id);
|
|
1122
|
-
continue;
|
|
1123
|
-
}
|
|
1124
|
-
await storageWrappers.saveAgentMemory(resolvedWorldId, agent.id, messagesToKeep);
|
|
1125
|
-
messagesRemovedTotal += removedCount;
|
|
1126
|
-
processedAgents.push(agent.id);
|
|
1127
|
-
}
|
|
1128
|
-
catch (error) {
|
|
1129
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1130
|
-
failedAgents.push({
|
|
1131
|
-
agentId: agent.id,
|
|
1132
|
-
error: errorMsg
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
logger.info('Message removal completed', {
|
|
1137
|
-
messageId,
|
|
1138
|
-
success: failedAgents.length === 0,
|
|
1139
|
-
totalAgents: agents.length,
|
|
1140
|
-
processedAgents: processedAgents.length,
|
|
1141
|
-
failedAgents: failedAgents.length,
|
|
1142
|
-
messagesRemovedTotal
|
|
1143
|
-
});
|
|
1144
|
-
return {
|
|
1145
|
-
success: failedAgents.length === 0,
|
|
1146
|
-
messageId,
|
|
1147
|
-
totalAgents: agents.length,
|
|
1148
|
-
processedAgents,
|
|
1149
|
-
failedAgents,
|
|
1150
|
-
messagesRemovedTotal,
|
|
1151
|
-
requiresRetry: failedAgents.length > 0,
|
|
1152
|
-
resubmissionStatus: 'skipped', // Will be updated by editUserMessage
|
|
1153
|
-
newMessageId: undefined
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
/**
|
|
1157
|
-
* Edit a user message by removing it and all subsequent messages, then resubmitting with new content
|
|
1158
|
-
* Combines removal and resubmission in a single operation with comprehensive error tracking
|
|
1159
|
-
*
|
|
1160
|
-
* @param worldId - World ID
|
|
1161
|
-
* @param messageId - ID of the message to edit
|
|
1162
|
-
* @param newContent - New message content
|
|
1163
|
-
* @param chatId - Chat ID for the message
|
|
1164
|
-
* @returns RemovalResult with removal and resubmission details
|
|
1165
|
-
*/
|
|
1166
|
-
export async function editUserMessage(worldId, messageId, newContent, chatId, targetWorld) {
|
|
1167
|
-
await ensureInitialization();
|
|
1168
|
-
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
1169
|
-
const { getActiveSubscribedWorld } = await import('./subscription.js');
|
|
1170
|
-
const activeSubscribedWorld = targetWorld || getActiveSubscribedWorld(resolvedWorldId);
|
|
1171
|
-
const world = activeSubscribedWorld || await getWorld(resolvedWorldId);
|
|
1172
|
-
if (!world) {
|
|
1173
|
-
throw new Error(`World '${worldId}' not found`);
|
|
1174
|
-
}
|
|
1175
|
-
if (hasActiveChatMessageProcessing(resolvedWorldId, chatId)) {
|
|
1176
|
-
stopMessageProcessing(resolvedWorldId, chatId);
|
|
1177
|
-
}
|
|
1178
|
-
// Step 1: Remove the message and all subsequent messages
|
|
1179
|
-
const removalResult = await removeMessagesFrom(resolvedWorldId, messageId, chatId);
|
|
1180
|
-
if (!removalResult.success) {
|
|
1181
|
-
return removalResult;
|
|
1182
|
-
}
|
|
1183
|
-
await syncRuntimeAgentMemoryFromStorage(activeSubscribedWorld || world, resolvedWorldId);
|
|
1184
|
-
// Step 2: Reset auto-generated chat title so post-resubmission title generation can run again.
|
|
1185
|
-
await resetAutoGeneratedChatTitleForEditResubmission(world, chatId);
|
|
1186
|
-
const worldForResubmission = activeSubscribedWorld || world;
|
|
1187
|
-
if (!activeSubscribedWorld) {
|
|
1188
|
-
const { subscribeAgentToMessages, subscribeWorldToMessages } = await import('./events/index.js');
|
|
1189
|
-
for (const agent of worldForResubmission.agents.values()) {
|
|
1190
|
-
subscribeAgentToMessages(worldForResubmission, agent);
|
|
1191
|
-
}
|
|
1192
|
-
subscribeWorldToMessages(worldForResubmission);
|
|
1193
|
-
}
|
|
1194
|
-
// Step 3: Attempt resubmission using publishMessage directly
|
|
1195
|
-
try {
|
|
1196
|
-
const { publishMessage } = await import('./events/index.js');
|
|
1197
|
-
const messageEvent = publishMessage(worldForResubmission, newContent, 'human', chatId);
|
|
1198
|
-
logger.info(`Resubmitted edited message to world '${resolvedWorldId}' with new messageId '${messageEvent.messageId}'`);
|
|
1199
|
-
return {
|
|
1200
|
-
...removalResult,
|
|
1201
|
-
resubmissionStatus: 'success',
|
|
1202
|
-
newMessageId: messageEvent.messageId
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
catch (error) {
|
|
1206
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1207
|
-
logger.error(`Failed to resubmit message to world '${resolvedWorldId}': ${errorMsg}`);
|
|
1208
|
-
return {
|
|
1209
|
-
...removalResult,
|
|
1210
|
-
resubmissionStatus: 'failed',
|
|
1211
|
-
resubmissionError: errorMsg
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Log an error from a message edit operation for troubleshooting and retry
|
|
1217
|
-
* Stores errors in data/worlds/{worldName}/edit-errors.json
|
|
1218
|
-
* Keeps only the last 100 errors
|
|
1219
|
-
*
|
|
1220
|
-
* @param worldId - World ID
|
|
1221
|
-
* @param errorLog - EditErrorLog to persist
|
|
1222
|
-
*/
|
|
1223
|
-
export async function logEditError(worldId, errorLog) {
|
|
1224
|
-
await ensureInitialization();
|
|
1225
|
-
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
1226
|
-
const rootPath = getDefaultRootPath();
|
|
1227
|
-
const worldDir = getWorldDir(rootPath, resolvedWorldId);
|
|
1228
|
-
const errorsFile = path.join(worldDir, 'edit-errors.json');
|
|
1229
|
-
try {
|
|
1230
|
-
// Read existing errors
|
|
1231
|
-
let errors = [];
|
|
1232
|
-
if (fs.existsSync(errorsFile)) {
|
|
1233
|
-
const data = fs.readFileSync(errorsFile, 'utf-8');
|
|
1234
|
-
errors = JSON.parse(data);
|
|
1235
|
-
}
|
|
1236
|
-
// Add new error
|
|
1237
|
-
errors.push(errorLog);
|
|
1238
|
-
// Keep only last 100 errors
|
|
1239
|
-
if (errors.length > 100) {
|
|
1240
|
-
errors = errors.slice(-100);
|
|
1241
|
-
}
|
|
1242
|
-
// Write back to file
|
|
1243
|
-
fs.writeFileSync(errorsFile, JSON.stringify(errors, null, 2), 'utf-8');
|
|
1244
|
-
logger.debug(`Logged edit error for world '${resolvedWorldId}'`);
|
|
1245
|
-
}
|
|
1246
|
-
catch (error) {
|
|
1247
|
-
logger.error(`Failed to log edit error for world '${resolvedWorldId}': ${error instanceof Error ? error.message : error}`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Get edit error logs for a world
|
|
1252
|
-
*
|
|
1253
|
-
* @param worldId - World ID
|
|
1254
|
-
* @returns Array of EditErrorLog entries
|
|
1255
|
-
*/
|
|
1256
|
-
export async function getEditErrors(worldId) {
|
|
1257
|
-
await ensureInitialization();
|
|
1258
|
-
const resolvedWorldId = await getResolvedWorldId(worldId);
|
|
1259
|
-
const rootPath = getDefaultRootPath();
|
|
1260
|
-
const worldDir = getWorldDir(rootPath, resolvedWorldId);
|
|
1261
|
-
const errorsFile = path.join(worldDir, 'edit-errors.json');
|
|
1262
|
-
try {
|
|
1263
|
-
if (!fs.existsSync(errorsFile)) {
|
|
1264
|
-
return [];
|
|
1265
|
-
}
|
|
1266
|
-
const data = fs.readFileSync(errorsFile, 'utf-8');
|
|
1267
|
-
return JSON.parse(data);
|
|
1268
|
-
}
|
|
1269
|
-
catch (error) {
|
|
1270
|
-
logger.error(`Failed to read edit errors for world '${resolvedWorldId}': ${error instanceof Error ? error.message : error}`);
|
|
1271
|
-
return [];
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1109
|
+
// Re-exports from extracted modules
|
|
1110
|
+
export { migrateMessageIds, removeMessagesFrom, editUserMessage, logEditError, getEditErrors } from './message-edit-manager.js';
|
|
1111
|
+
export { addToQueue, recoverQueueSendingMessages, enqueueAndProcessUserTurn, dispatchImmediateChatMessage, getQueueMessages, removeFromQueue, pauseChatQueue, resumeChatQueue, stopChatQueue, clearChatQueue, retryQueueMessage } from './queue-manager.js';
|