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
|
@@ -29,16 +29,23 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
31
|
* Created: 2025-11-10 - Extracted from api.ts for reusability
|
|
32
|
+
* Updated: 2026-03-01 - Skip synthesis for 'edit' context to prevent duplicate "From human" messages after message edits.
|
|
33
|
+
* Updated: 2026-03-11 - Exposed a readiness promise so chat/edit dispatch waits until SSE listeners are attached, preventing the web client from missing the initial user-message echo.
|
|
34
|
+
* Updated: 2026-03-11 - Replaced optional-call resolution of the readiness promise with an explicit helper to satisfy
|
|
35
|
+
* strict TypeScript control-flow analysis in the build config.
|
|
36
|
+
* Updated: 2026-02-27 - Scoped realtime log forwarding by world/chat to prevent cross-chat log leakage in chat-scoped streams.
|
|
37
|
+
* Updated: 2026-02-26 - Added realtime log-stream forwarding (`type: 'log'`) to SSE clients to align web error visibility with Electron.
|
|
32
38
|
* Updated: 2026-02-20 - Removed stale legacy event-channel SSE forwarding from this handler.
|
|
33
|
-
* Updated: 2026-02-
|
|
39
|
+
* Updated: 2026-02-21 - Refresh fallback timeout on shell assistant-stream SSE activity (`start`/`chunk`/`end` + `toolName='shell_cmd'`) as well as legacy `tool-stream`.
|
|
34
40
|
* Updated: 2026-02-11 - Extended fallback timeout on tool-stream events to prevent premature timeout
|
|
35
41
|
* Updated: 2026-02-08 - Removed manual tool-intervention SSE commentary and kept generic tool_call forwarding
|
|
36
42
|
* Updated: 2025-11-10 - Added tool event forwarding to SSE channel
|
|
37
43
|
*/
|
|
38
|
-
import { createCategoryLogger, EventType } from '../core/index.js';
|
|
44
|
+
import { addLogStreamCallback, createCategoryLogger, EventType, getMemory, listPendingHitlPromptEventsFromMessages } from '../core/index.js';
|
|
39
45
|
const loggerStream = createCategoryLogger('api.stream');
|
|
40
46
|
// Timeout constants for streaming (fallback only)
|
|
41
47
|
const STREAM_TIMEOUT_NO_EVENTS_MS = 15000;
|
|
48
|
+
const STREAM_IDLE_CLOSE_DELAY_MS = 2000;
|
|
42
49
|
/**
|
|
43
50
|
* Create and configure an SSE handler for streaming world events
|
|
44
51
|
*
|
|
@@ -56,6 +63,7 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
56
63
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
57
64
|
res.setHeader('Access-Control-Allow-Headers', 'Cache-Control');
|
|
58
65
|
let timeoutTimer;
|
|
66
|
+
let idleCloseTimer;
|
|
59
67
|
let hasReceivedEvents = false;
|
|
60
68
|
let isResponseEnded = false;
|
|
61
69
|
let lastEventTime = Date.now();
|
|
@@ -90,6 +98,10 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
90
98
|
clearTimeout(timeoutTimer);
|
|
91
99
|
timeoutTimer = undefined;
|
|
92
100
|
}
|
|
101
|
+
if (idleCloseTimer) {
|
|
102
|
+
clearTimeout(idleCloseTimer);
|
|
103
|
+
idleCloseTimer = undefined;
|
|
104
|
+
}
|
|
93
105
|
loggerStream.debug(`[${context}] Ending SSE response. Stats: events=${hasReceivedEvents}, awaitingWorldIdle=${awaitingWorldIdle}`);
|
|
94
106
|
try {
|
|
95
107
|
if (!res.destroyed) {
|
|
@@ -124,21 +136,22 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
124
136
|
const normalizedEventChatId = eventChatId === null ? null : String(eventChatId);
|
|
125
137
|
return normalizedEventChatId === normalizedScopedChatId;
|
|
126
138
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
// Track already-sent ids to avoid double-emitting synthesized -> live events
|
|
140
|
+
const sentMessageIds = new Set();
|
|
141
|
+
const sentToolCallIds = new Set();
|
|
142
|
+
let resolveReady = null;
|
|
143
|
+
const ready = new Promise((resolve) => {
|
|
144
|
+
resolveReady = resolve;
|
|
145
|
+
});
|
|
146
|
+
const markReady = () => {
|
|
147
|
+
if (!resolveReady) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const resolve = resolveReady;
|
|
151
|
+
resolveReady = null;
|
|
152
|
+
resolve();
|
|
140
153
|
};
|
|
141
|
-
// Attach direct listeners to world.eventEmitter
|
|
154
|
+
// Attach direct listeners to world.eventEmitter (defined inside attach to allow synth-before-attach)
|
|
142
155
|
const worldListener = (eventData) => {
|
|
143
156
|
// Check if this is a tool event (tool-start, tool-result, tool-error, tool-progress)
|
|
144
157
|
const isToolEvent = eventData?.type && ['tool-start', 'tool-result', 'tool-error', 'tool-progress'].includes(eventData.type);
|
|
@@ -161,6 +174,10 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
161
174
|
}
|
|
162
175
|
// Handle world activity events for stream completion
|
|
163
176
|
if (eventData?.type === 'response-start') {
|
|
177
|
+
if (idleCloseTimer) {
|
|
178
|
+
clearTimeout(idleCloseTimer);
|
|
179
|
+
idleCloseTimer = undefined;
|
|
180
|
+
}
|
|
164
181
|
awaitingWorldIdle = true;
|
|
165
182
|
loggerStream.debug(`[${context}] World processing started`, {
|
|
166
183
|
activityId: eventData.activityId,
|
|
@@ -171,20 +188,24 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
171
188
|
loggerStream.debug(`[${context}] World idle detected, ending stream`, {
|
|
172
189
|
activityId: eventData.activityId
|
|
173
190
|
});
|
|
174
|
-
// Stream all pending events, then end
|
|
175
191
|
sendSSE({ type: EventType.WORLD, data: eventData });
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
192
|
+
awaitingWorldIdle = false;
|
|
193
|
+
if (idleCloseTimer) {
|
|
194
|
+
clearTimeout(idleCloseTimer);
|
|
195
|
+
}
|
|
196
|
+
idleCloseTimer = setTimeout(() => {
|
|
197
|
+
// If no new response-start arrived during the grace window,
|
|
198
|
+
// treat this idle as final and close the stream.
|
|
199
|
+
if (!awaitingWorldIdle) {
|
|
200
|
+
endResponse();
|
|
201
|
+
}
|
|
202
|
+
}, STREAM_IDLE_CLOSE_DELAY_MS);
|
|
180
203
|
return;
|
|
181
204
|
}
|
|
182
205
|
if (isChatEventInScope(eventData?.chatId, true)) {
|
|
183
206
|
sendSSE({ type: EventType.WORLD, data: eventData });
|
|
184
207
|
}
|
|
185
208
|
};
|
|
186
|
-
world.eventEmitter.on(EventType.WORLD, worldListener);
|
|
187
|
-
listeners.set(EventType.WORLD, worldListener);
|
|
188
209
|
const messageListener = (eventData) => {
|
|
189
210
|
if (!isChatEventInScope(eventData?.chatId, false)) {
|
|
190
211
|
return;
|
|
@@ -201,39 +222,177 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
201
222
|
replyToMessageId: eventData.replyToMessageId,
|
|
202
223
|
createdAt: eventData.timestamp || new Date().toISOString(),
|
|
203
224
|
role: eventData.role,
|
|
204
|
-
tool_calls: eventData.tool_calls
|
|
225
|
+
tool_calls: eventData.tool_calls,
|
|
226
|
+
tool_call_id: eventData.tool_call_id
|
|
205
227
|
};
|
|
206
228
|
sendSSE({ type: EventType.MESSAGE, data: messageData });
|
|
207
229
|
};
|
|
208
|
-
world.eventEmitter.on(EventType.MESSAGE, messageListener);
|
|
209
|
-
listeners.set(EventType.MESSAGE, messageListener);
|
|
210
230
|
const sseListener = (eventData) => {
|
|
211
231
|
if (!isChatEventInScope(eventData?.chatId, false)) {
|
|
212
232
|
return;
|
|
213
233
|
}
|
|
214
|
-
// Extend fallback timeout
|
|
215
|
-
|
|
234
|
+
// Extend fallback timeout for long-running shell stream activity.
|
|
235
|
+
const isLegacyToolStream = eventData.type === 'tool-stream';
|
|
236
|
+
const isShellAssistantStream = eventData.toolName === 'shell_cmd' &&
|
|
237
|
+
(eventData.type === 'start' || eventData.type === 'chunk' || eventData.type === 'end');
|
|
238
|
+
if (isLegacyToolStream || isShellAssistantStream) {
|
|
216
239
|
startTimeoutFallback();
|
|
217
240
|
}
|
|
218
241
|
sendSSE({ type: EventType.SSE, data: eventData });
|
|
219
242
|
};
|
|
220
|
-
world.eventEmitter.on(EventType.SSE, sseListener);
|
|
221
|
-
listeners.set(EventType.SSE, sseListener);
|
|
222
243
|
const systemListener = (eventData) => {
|
|
223
|
-
|
|
224
|
-
if (!isHitlRequest && !isChatEventInScope(eventData?.chatId, true)) {
|
|
244
|
+
if (!isChatEventInScope(eventData?.chatId, false)) {
|
|
225
245
|
return;
|
|
226
246
|
}
|
|
227
247
|
sendSSE({ type: EventType.SYSTEM, data: eventData });
|
|
228
248
|
};
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
// Mirror Electron's global log forwarding pattern:
|
|
250
|
+
// stream backend logger events as SSE `type: 'log'` payloads during the active request.
|
|
251
|
+
const logListener = (logEvent) => {
|
|
252
|
+
const logData = logEvent?.data && typeof logEvent.data === 'object' ? logEvent.data : null;
|
|
253
|
+
const logWorldId = (typeof logEvent?.worldId === 'string' && logEvent.worldId.trim()) ? logEvent.worldId.trim()
|
|
254
|
+
: (typeof logData?.worldId === 'string' && String(logData.worldId).trim())
|
|
255
|
+
? String(logData.worldId).trim()
|
|
256
|
+
: undefined;
|
|
257
|
+
if (logWorldId && logWorldId !== world.id) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const logChatId = (typeof logEvent?.chatId === 'string' && logEvent.chatId.trim()) ? logEvent.chatId.trim()
|
|
261
|
+
: (logEvent?.chatId === null ? null
|
|
262
|
+
: (typeof logData?.chatId === 'string' && logData.chatId.trim()) ? logData.chatId.trim()
|
|
263
|
+
: (logData?.chatId === null ? null : undefined));
|
|
264
|
+
if (!isChatEventInScope(logChatId, false)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
sendSSE({
|
|
268
|
+
type: EventType.SSE,
|
|
269
|
+
data: {
|
|
270
|
+
type: 'log',
|
|
271
|
+
chatId: logChatId,
|
|
272
|
+
messageId: logEvent?.messageId || `log-${Date.now()}`,
|
|
273
|
+
logEvent: {
|
|
274
|
+
level: logEvent?.level || 'info',
|
|
275
|
+
category: logEvent?.category || 'unknown',
|
|
276
|
+
message: logEvent?.message || '',
|
|
277
|
+
timestamp: logEvent?.timestamp || new Date().toISOString(),
|
|
278
|
+
data: logData ?? null,
|
|
279
|
+
messageId: logEvent?.messageId || `log-${Date.now()}`,
|
|
280
|
+
chatId: logChatId
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
let unsubscribeLogStream = null;
|
|
286
|
+
// NOTE: listeners are attached after an initial synthesis step below to avoid race
|
|
287
|
+
// where core resume emits events before the client has subscribed.
|
|
288
|
+
async function attachListeners() {
|
|
289
|
+
world.eventEmitter.on(EventType.WORLD, worldListener);
|
|
290
|
+
listeners.set(EventType.WORLD, worldListener);
|
|
291
|
+
world.eventEmitter.on(EventType.MESSAGE, messageListener);
|
|
292
|
+
listeners.set(EventType.MESSAGE, messageListener);
|
|
293
|
+
world.eventEmitter.on(EventType.SSE, sseListener);
|
|
294
|
+
listeners.set(EventType.SSE, sseListener);
|
|
295
|
+
world.eventEmitter.on(EventType.SYSTEM, systemListener);
|
|
296
|
+
listeners.set(EventType.SYSTEM, systemListener);
|
|
297
|
+
unsubscribeLogStream = addLogStreamCallback(logListener);
|
|
298
|
+
}
|
|
299
|
+
// Synthesis: use persisted memory as the source of truth to restore UI state
|
|
300
|
+
(async () => {
|
|
301
|
+
try {
|
|
302
|
+
// Skip synthesis for edit operations: the edit flow removes old messages and immediately
|
|
303
|
+
// publishes a fresh user message via publishMessage, so synthesizing the pre-edit last
|
|
304
|
+
// user message would cause a duplicate "From human" message in the UI.
|
|
305
|
+
if (context === 'edit') {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (!normalizedScopedChatId) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Try to read canonical chat memory using public core helper
|
|
312
|
+
const memory = await getMemory(world.id, normalizedScopedChatId);
|
|
313
|
+
if (Array.isArray(memory) && memory.length > 0) {
|
|
314
|
+
// Send the most recent user message if the last message is a user message
|
|
315
|
+
const lastMessage = memory[memory.length - 1];
|
|
316
|
+
if (lastMessage && lastMessage.messageId) {
|
|
317
|
+
// Mark as sent to avoid double-emitting when live listeners arrive
|
|
318
|
+
sentMessageIds.add(String(lastMessage.messageId));
|
|
319
|
+
if (lastMessage.role === 'user') {
|
|
320
|
+
const messageData = {
|
|
321
|
+
type: 'message',
|
|
322
|
+
sender: lastMessage.sender,
|
|
323
|
+
content: lastMessage.content,
|
|
324
|
+
messageId: lastMessage.messageId,
|
|
325
|
+
chatId: lastMessage.chatId,
|
|
326
|
+
replyToMessageId: lastMessage.replyToMessageId,
|
|
327
|
+
createdAt: lastMessage.createdAt || new Date().toISOString(),
|
|
328
|
+
role: lastMessage.role,
|
|
329
|
+
tool_calls: lastMessage.tool_calls
|
|
330
|
+
};
|
|
331
|
+
sendSSE({ type: EventType.MESSAGE, data: messageData });
|
|
332
|
+
}
|
|
333
|
+
else if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls.length > 0) {
|
|
334
|
+
// Detect unresolved tool calls by scanning persisted memory for completed tool messages
|
|
335
|
+
const completedToolCallIds = new Set();
|
|
336
|
+
for (const m of memory) {
|
|
337
|
+
if (m.role === 'tool' && typeof m.tool_call_id === 'string') {
|
|
338
|
+
completedToolCallIds.add(String(m.tool_call_id));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const tc of lastMessage.tool_calls) {
|
|
342
|
+
const toolCallId = String(tc?.id || '').trim();
|
|
343
|
+
const toolName = String(tc?.function?.name || '').trim();
|
|
344
|
+
if (!toolCallId || completedToolCallIds.has(toolCallId))
|
|
345
|
+
continue;
|
|
346
|
+
// Synthesize a tool-start event so the UI can show pending work / HITL
|
|
347
|
+
sentToolCallIds.add(toolCallId);
|
|
348
|
+
sendSSE({
|
|
349
|
+
type: EventType.SSE,
|
|
350
|
+
data: {
|
|
351
|
+
type: 'tool-start',
|
|
352
|
+
messageId: toolCallId,
|
|
353
|
+
agentName: lastMessage.agentId || undefined,
|
|
354
|
+
chatId: lastMessage.chatId,
|
|
355
|
+
toolExecution: {
|
|
356
|
+
toolName,
|
|
357
|
+
toolCallId,
|
|
358
|
+
input: tc?.function?.arguments || {}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Also synthesize any pending HITL prompts derived from memory
|
|
365
|
+
try {
|
|
366
|
+
const pendingHitl = listPendingHitlPromptEventsFromMessages(memory || [], normalizedScopedChatId === undefined ? null : normalizedScopedChatId);
|
|
367
|
+
for (const h of pendingHitl) {
|
|
368
|
+
sendSSE({ type: EventType.SYSTEM, data: h });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (hitlErr) {
|
|
372
|
+
loggerStream.debug(`[${context}] failed to synthesize HITL prompts:`, hitlErr);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
loggerStream.debug(`[${context}] failed to synthesize persisted state:`, error);
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
// Attach live listeners after synthesis to avoid race where resume emits before client subscribed
|
|
382
|
+
attachListeners();
|
|
383
|
+
markReady();
|
|
384
|
+
}
|
|
385
|
+
})();
|
|
231
386
|
// Cleanup function to remove all listeners
|
|
232
387
|
const cleanupListeners = () => {
|
|
233
388
|
for (const [eventType, listener] of listeners.entries()) {
|
|
234
389
|
world.eventEmitter.removeListener(eventType, listener);
|
|
235
390
|
}
|
|
236
391
|
listeners.clear();
|
|
392
|
+
if (unsubscribeLogStream) {
|
|
393
|
+
unsubscribeLogStream();
|
|
394
|
+
unsubscribeLogStream = null;
|
|
395
|
+
}
|
|
237
396
|
};
|
|
238
397
|
// Handle client disconnect
|
|
239
398
|
req.on('close', () => {
|
|
@@ -244,6 +403,7 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
|
|
|
244
403
|
// Start the fallback timeout
|
|
245
404
|
startTimeoutFallback();
|
|
246
405
|
return {
|
|
406
|
+
ready,
|
|
247
407
|
sendSSE,
|
|
248
408
|
endResponse,
|
|
249
409
|
isEnded: () => isResponseEnded
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- Migration: Create message_queue table for user message queue feature
|
|
2
|
+
-- Version: 15
|
|
3
|
+
-- Date: 2026-03-01
|
|
4
|
+
--
|
|
5
|
+
-- Creates a dedicated message_queue table to persist user messages that are
|
|
6
|
+
-- queued for sequential processing. A separate table (rather than an
|
|
7
|
+
-- agent_memory status column) is used to avoid the agent_id FK constraint
|
|
8
|
+
-- on agent_memory, since queued messages are not yet associated with a
|
|
9
|
+
-- specific agent.
|
|
10
|
+
--
|
|
11
|
+
-- Status lifecycle:
|
|
12
|
+
-- queued -> sending -> (row deleted when processed successfully)
|
|
13
|
+
-- queued -> cancelled (when user stops/clears queue)
|
|
14
|
+
-- sending -> queued (startup recovery: interrupted sessions reset)
|
|
15
|
+
-- sending -> error (after max retries exhausted)
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS message_queue (
|
|
18
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19
|
+
world_id TEXT NOT NULL,
|
|
20
|
+
chat_id TEXT NOT NULL,
|
|
21
|
+
message_id TEXT NOT NULL,
|
|
22
|
+
content TEXT NOT NULL,
|
|
23
|
+
sender TEXT NOT NULL DEFAULT 'human',
|
|
24
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
25
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
27
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
FOREIGN KEY (world_id) REFERENCES worlds(id) ON DELETE CASCADE,
|
|
29
|
+
FOREIGN KEY (chat_id) REFERENCES world_chats(id) ON DELETE CASCADE
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_message_queue_message_id
|
|
33
|
+
ON message_queue(message_id);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_message_queue_chat
|
|
36
|
+
ON message_queue(world_id, chat_id, status);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Migration: Add world heartbeat configuration fields
|
|
2
|
+
-- Version: 16
|
|
3
|
+
-- Date: 2026-03-04
|
|
4
|
+
--
|
|
5
|
+
-- Adds persisted world heartbeat controls used by Electron main runtime.
|
|
6
|
+
-- Fields:
|
|
7
|
+
-- heartbeat_enabled - whether heartbeat scheduling is enabled
|
|
8
|
+
-- heartbeat_interval - cron expression (product policy: strict 5-field)
|
|
9
|
+
-- heartbeat_prompt - message content published on each tick
|
|
10
|
+
|
|
11
|
+
ALTER TABLE worlds ADD COLUMN heartbeat_enabled INTEGER DEFAULT 0;
|
|
12
|
+
ALTER TABLE worlds ADD COLUMN heartbeat_interval TEXT;
|
|
13
|
+
ALTER TABLE worlds ADD COLUMN heartbeat_prompt TEXT;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Migration: Add title_provenance column to world_chats
|
|
2
|
+
-- Purpose: Track whether a chat title was set by default, auto-generated, or manually assigned.
|
|
3
|
+
-- Notes:
|
|
4
|
+
-- - DEFAULT 'default' ensures existing rows are treated as legacy (favor-preserve) per REQ-11.
|
|
5
|
+
-- - Valid values: 'default' (untitled/legacy), 'auto' (auto-generated), 'manual' (user-renamed).
|
|
6
|
+
ALTER TABLE world_chats
|
|
7
|
+
ADD COLUMN title_provenance TEXT DEFAULT 'default';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-world",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"workspaces": [
|
|
19
19
|
"core",
|
|
20
|
-
"web"
|
|
20
|
+
"web",
|
|
21
|
+
"packages/opik"
|
|
21
22
|
],
|
|
22
23
|
"bin": {
|
|
23
24
|
"agent-world": "bin/agent-world.js",
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
"agent-world-server": "bin/agent-world-server.js"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
29
|
+
"install:all": "npm install && npm install --prefix electron",
|
|
28
30
|
"dev": "npm run web:dev",
|
|
29
31
|
"dev:reset": "for p in 3000 8080; do pids=$(lsof -tiTCP:$p -sTCP:LISTEN 2>/dev/null || true); if [ -n \"$pids\" ]; then kill $pids; fi; done",
|
|
30
32
|
"web:dev": "npm-run-all --parallel server:watch web:vite:wait",
|
|
@@ -35,25 +37,39 @@
|
|
|
35
37
|
"web:start": "npm run build && npm run server:start",
|
|
36
38
|
"cli:start": "npm run build && node dist/cli/index.js",
|
|
37
39
|
"electron:start": "npm run build:core && npm run start --prefix electron",
|
|
38
|
-
"build": "npm run build --
|
|
40
|
+
"electron:main:build": "npm run main:build --prefix electron",
|
|
41
|
+
"electron:renderer:build": "npm run renderer:build --prefix electron",
|
|
42
|
+
"build": "npm run build --workspace=core && npm run build --workspace=packages/opik && tsc --project tsconfig.build.json && npm run build --workspace=web && npm run main:build --prefix electron",
|
|
39
43
|
"build:core": "npm run build --workspace=core",
|
|
40
44
|
"build:core:watch": "npm run build --workspace=core -- --watch --preserveWatchOutput",
|
|
41
|
-
"check": "tsc --noEmit --project tsconfig.build.json && npm run check --workspace=core && npm run check --workspace=web && npm run
|
|
45
|
+
"check": "tsc --noEmit --project tsconfig.build.json && npm run check --workspace=core && npm run check --workspace=web && npm run check --prefix electron",
|
|
42
46
|
"server:dev": "npx tsx server/index.ts",
|
|
43
47
|
"server:watch": "npx tsx --watch server/index.ts",
|
|
44
48
|
"server:start": "node dist/server/index.js",
|
|
45
49
|
"web:vite": "npm run dev --workspace=web",
|
|
50
|
+
"web:vite:e2e": "vite dev --config web/vite.config.js --host 127.0.0.1 --port 8080 --strictPort",
|
|
46
51
|
"web:vite:wait": "wait-on tcp:127.0.0.1:3000 && npm run web:vite",
|
|
52
|
+
"web:e2e:serve": "node tests/web-e2e/support/start-web-servers.mjs",
|
|
47
53
|
"electron:vite": "npm run dev --prefix electron",
|
|
48
54
|
"test": "vitest run",
|
|
49
55
|
"test:watch": "vitest",
|
|
50
56
|
"test:ui": "vitest --ui",
|
|
51
57
|
"test:coverage": "vitest run --coverage",
|
|
58
|
+
"coverage:scorecard": "node scripts/coverage-scorecard.mjs",
|
|
59
|
+
"test:coverage:gate": "npm run test:coverage && npm run coverage:scorecard",
|
|
52
60
|
"test:db": "npx tsx tests/db/migration-tests.ts",
|
|
53
61
|
"test:e2e": "npx tsx tests/e2e/test-agent-response-rules.ts",
|
|
54
62
|
"test:e2e:interactive": "npx tsx tests/e2e/test-agent-response-rules.ts -i",
|
|
63
|
+
"test:web:e2e:run": "playwright test --config playwright.web.config.ts",
|
|
64
|
+
"test:web:e2e": "npm run test:web:e2e:run",
|
|
65
|
+
"test:electron:e2e:run": "playwright test --config playwright.electron.config.ts",
|
|
66
|
+
"test:electron:e2e": "npm-run-all --sequential build:core electron:main:build electron:renderer:build test:electron:e2e:run",
|
|
55
67
|
"integration": "vitest run --config vitest.integration.config.ts",
|
|
56
|
-
"
|
|
68
|
+
"ci:test": "npm run test:coverage:gate && npm run integration",
|
|
69
|
+
"pkill": "pkill -f tsx",
|
|
70
|
+
"opik:eval": "npx tsx tests/opik/eval-robustness.ts",
|
|
71
|
+
"opik:storage-export-world": "npx tsx scripts/opik-export-world-storage.ts",
|
|
72
|
+
"test:integration": "npm run integration"
|
|
57
73
|
},
|
|
58
74
|
"description": "World-mediated agent management system with clean API surface",
|
|
59
75
|
"keywords": [
|
|
@@ -71,23 +87,28 @@
|
|
|
71
87
|
"@google/generative-ai": "^0.24.1",
|
|
72
88
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
73
89
|
"chalk": "^5.5.0",
|
|
90
|
+
"commander": "^14.0.0",
|
|
74
91
|
"cors": "^2.8.5",
|
|
75
92
|
"dotenv": "^17.2.3",
|
|
76
93
|
"enquirer": "^2.4.1",
|
|
77
|
-
"express": "^4.22.1",
|
|
78
94
|
"events": "^3.3.0",
|
|
95
|
+
"express": "^4.22.1",
|
|
79
96
|
"fast-glob": "^3.3.3",
|
|
80
97
|
"nanoid": "^5.1.5",
|
|
81
98
|
"open": "^11.0.0",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
99
|
+
"node-cron": "^4.2.1",
|
|
100
|
+
"openai": "^6.25.0",
|
|
84
101
|
"pino": "^10.0.0",
|
|
85
102
|
"pino-pretty": "^13.1.3",
|
|
86
103
|
"sqlite3": "^5.1.7",
|
|
87
|
-
"
|
|
88
|
-
"
|
|
104
|
+
"turndown": "^7.2.0",
|
|
105
|
+
"turndown-plugin-gfm": "^1.0.2",
|
|
106
|
+
"tsx": "^4.21.0",
|
|
107
|
+
"zod": "^4.2.1"
|
|
89
108
|
},
|
|
90
109
|
"devDependencies": {
|
|
110
|
+
"@types/node-cron": "^3.0.11",
|
|
111
|
+
"@playwright/test": "^1.54.2",
|
|
91
112
|
"@tailwindcss/postcss": "^4.1.18",
|
|
92
113
|
"@types/cors": "^2.8.19",
|
|
93
114
|
"@types/express": "^4.17.25",
|