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.
Files changed (263) hide show
  1. package/README.md +90 -17
  2. package/dist/cli/commands.d.ts +7 -1
  3. package/dist/cli/commands.js +27 -10
  4. package/dist/cli/hitl.d.ts +4 -1
  5. package/dist/cli/hitl.js +55 -20
  6. package/dist/cli/index.js +249 -97
  7. package/dist/cli/system-events.d.ts +27 -0
  8. package/dist/cli/system-events.js +63 -0
  9. package/dist/core/activity-tracker.d.ts +26 -0
  10. package/dist/core/activity-tracker.d.ts.map +1 -1
  11. package/dist/core/activity-tracker.js +21 -4
  12. package/dist/core/activity-tracker.js.map +1 -1
  13. package/dist/core/anthropic-direct.d.ts +2 -0
  14. package/dist/core/anthropic-direct.d.ts.map +1 -1
  15. package/dist/core/anthropic-direct.js +43 -1
  16. package/dist/core/anthropic-direct.js.map +1 -1
  17. package/dist/core/chat-constants.d.ts +12 -0
  18. package/dist/core/chat-constants.d.ts.map +1 -1
  19. package/dist/core/chat-constants.js +5 -0
  20. package/dist/core/chat-constants.js.map +1 -1
  21. package/dist/core/create-agent-tool.d.ts +5 -0
  22. package/dist/core/create-agent-tool.d.ts.map +1 -1
  23. package/dist/core/create-agent-tool.js +57 -34
  24. package/dist/core/create-agent-tool.js.map +1 -1
  25. package/dist/core/events/index.d.ts +5 -2
  26. package/dist/core/events/index.d.ts.map +1 -1
  27. package/dist/core/events/index.js +5 -2
  28. package/dist/core/events/index.js.map +1 -1
  29. package/dist/core/events/memory-manager.d.ts +26 -1
  30. package/dist/core/events/memory-manager.d.ts.map +1 -1
  31. package/dist/core/events/memory-manager.js +877 -72
  32. package/dist/core/events/memory-manager.js.map +1 -1
  33. package/dist/core/events/orchestrator.d.ts +8 -0
  34. package/dist/core/events/orchestrator.d.ts.map +1 -1
  35. package/dist/core/events/orchestrator.js +203 -36
  36. package/dist/core/events/orchestrator.js.map +1 -1
  37. package/dist/core/events/persistence.d.ts +21 -14
  38. package/dist/core/events/persistence.d.ts.map +1 -1
  39. package/dist/core/events/persistence.js +100 -35
  40. package/dist/core/events/persistence.js.map +1 -1
  41. package/dist/core/events/publishers.d.ts +13 -7
  42. package/dist/core/events/publishers.d.ts.map +1 -1
  43. package/dist/core/events/publishers.js +53 -37
  44. package/dist/core/events/publishers.js.map +1 -1
  45. package/dist/core/events/subscribers.d.ts +17 -14
  46. package/dist/core/events/subscribers.d.ts.map +1 -1
  47. package/dist/core/events/subscribers.js +61 -148
  48. package/dist/core/events/subscribers.js.map +1 -1
  49. package/dist/core/events/title-scheduler.d.ts +27 -0
  50. package/dist/core/events/title-scheduler.d.ts.map +1 -0
  51. package/dist/core/events/title-scheduler.js +135 -0
  52. package/dist/core/events/title-scheduler.js.map +1 -0
  53. package/dist/core/events/tool-bridge-logging.d.ts +4 -1
  54. package/dist/core/events/tool-bridge-logging.d.ts.map +1 -1
  55. package/dist/core/events/tool-bridge-logging.js +112 -13
  56. package/dist/core/events/tool-bridge-logging.js.map +1 -1
  57. package/dist/core/events-metadata.d.ts.map +1 -1
  58. package/dist/core/events-metadata.js +8 -4
  59. package/dist/core/events-metadata.js.map +1 -1
  60. package/dist/core/export.d.ts +1 -1
  61. package/dist/core/export.d.ts.map +1 -1
  62. package/dist/core/export.js +2 -15
  63. package/dist/core/export.js.map +1 -1
  64. package/dist/core/feature-path-logging.d.ts +50 -0
  65. package/dist/core/feature-path-logging.d.ts.map +1 -0
  66. package/dist/core/feature-path-logging.js +130 -0
  67. package/dist/core/feature-path-logging.js.map +1 -0
  68. package/dist/core/file-tools.d.ts +57 -1
  69. package/dist/core/file-tools.d.ts.map +1 -1
  70. package/dist/core/file-tools.js +329 -29
  71. package/dist/core/file-tools.js.map +1 -1
  72. package/dist/core/google-direct.d.ts +6 -1
  73. package/dist/core/google-direct.d.ts.map +1 -1
  74. package/dist/core/google-direct.js +76 -7
  75. package/dist/core/google-direct.js.map +1 -1
  76. package/dist/core/heartbeat.d.ts +34 -0
  77. package/dist/core/heartbeat.d.ts.map +1 -0
  78. package/dist/core/heartbeat.js +153 -0
  79. package/dist/core/heartbeat.js.map +1 -0
  80. package/dist/core/hitl-tool.d.ts +6 -12
  81. package/dist/core/hitl-tool.d.ts.map +1 -1
  82. package/dist/core/hitl-tool.js +66 -88
  83. package/dist/core/hitl-tool.js.map +1 -1
  84. package/dist/core/hitl.d.ts +61 -4
  85. package/dist/core/hitl.d.ts.map +1 -1
  86. package/dist/core/hitl.js +324 -60
  87. package/dist/core/hitl.js.map +1 -1
  88. package/dist/core/index.d.ts +11 -7
  89. package/dist/core/index.d.ts.map +1 -1
  90. package/dist/core/index.js +10 -6
  91. package/dist/core/index.js.map +1 -1
  92. package/dist/core/llm-manager.d.ts +15 -0
  93. package/dist/core/llm-manager.d.ts.map +1 -1
  94. package/dist/core/llm-manager.js +325 -40
  95. package/dist/core/llm-manager.js.map +1 -1
  96. package/dist/core/load-skill-tool.d.ts +36 -3
  97. package/dist/core/load-skill-tool.d.ts.map +1 -1
  98. package/dist/core/load-skill-tool.js +807 -93
  99. package/dist/core/load-skill-tool.js.map +1 -1
  100. package/dist/core/logger.d.ts +14 -0
  101. package/dist/core/logger.d.ts.map +1 -1
  102. package/dist/core/logger.js +15 -0
  103. package/dist/core/logger.js.map +1 -1
  104. package/dist/core/managers.d.ts +18 -50
  105. package/dist/core/managers.d.ts.map +1 -1
  106. package/dist/core/managers.js +340 -502
  107. package/dist/core/managers.js.map +1 -1
  108. package/dist/core/mcp-server-registry.d.ts +16 -1
  109. package/dist/core/mcp-server-registry.d.ts.map +1 -1
  110. package/dist/core/mcp-server-registry.js +162 -12
  111. package/dist/core/mcp-server-registry.js.map +1 -1
  112. package/dist/core/message-cutoff.d.ts +29 -0
  113. package/dist/core/message-cutoff.d.ts.map +1 -0
  114. package/dist/core/message-cutoff.js +63 -0
  115. package/dist/core/message-cutoff.js.map +1 -0
  116. package/dist/core/message-edit-manager.d.ts +54 -0
  117. package/dist/core/message-edit-manager.d.ts.map +1 -0
  118. package/dist/core/message-edit-manager.js +602 -0
  119. package/dist/core/message-edit-manager.js.map +1 -0
  120. package/dist/core/message-prep.d.ts +2 -0
  121. package/dist/core/message-prep.d.ts.map +1 -1
  122. package/dist/core/message-prep.js +39 -12
  123. package/dist/core/message-prep.js.map +1 -1
  124. package/dist/core/message-processing-control.d.ts +1 -0
  125. package/dist/core/message-processing-control.d.ts.map +1 -1
  126. package/dist/core/message-processing-control.js +23 -6
  127. package/dist/core/message-processing-control.js.map +1 -1
  128. package/dist/core/openai-direct.d.ts +9 -3
  129. package/dist/core/openai-direct.d.ts.map +1 -1
  130. package/dist/core/openai-direct.js +267 -33
  131. package/dist/core/openai-direct.js.map +1 -1
  132. package/dist/core/optional-tracers/opik-runtime.d.ts +32 -0
  133. package/dist/core/optional-tracers/opik-runtime.d.ts.map +1 -0
  134. package/dist/core/optional-tracers/opik-runtime.js +141 -0
  135. package/dist/core/optional-tracers/opik-runtime.js.map +1 -0
  136. package/dist/core/queue-manager.d.ts +84 -0
  137. package/dist/core/queue-manager.d.ts.map +1 -0
  138. package/dist/core/queue-manager.js +814 -0
  139. package/dist/core/queue-manager.js.map +1 -0
  140. package/dist/core/reasoning-controls.d.ts +30 -0
  141. package/dist/core/reasoning-controls.d.ts.map +1 -0
  142. package/dist/core/reasoning-controls.js +118 -0
  143. package/dist/core/reasoning-controls.js.map +1 -0
  144. package/dist/core/reliability-config.d.ts +82 -0
  145. package/dist/core/reliability-config.d.ts.map +1 -0
  146. package/dist/core/reliability-config.js +106 -0
  147. package/dist/core/reliability-config.js.map +1 -0
  148. package/dist/core/reliability-runtime.d.ts +53 -0
  149. package/dist/core/reliability-runtime.d.ts.map +1 -0
  150. package/dist/core/reliability-runtime.js +92 -0
  151. package/dist/core/reliability-runtime.js.map +1 -0
  152. package/dist/core/security/guardrails.d.ts +21 -0
  153. package/dist/core/security/guardrails.d.ts.map +1 -0
  154. package/dist/core/security/guardrails.js +111 -0
  155. package/dist/core/security/guardrails.js.map +1 -0
  156. package/dist/core/send-message-tool.d.ts +79 -0
  157. package/dist/core/send-message-tool.d.ts.map +1 -0
  158. package/dist/core/send-message-tool.js +222 -0
  159. package/dist/core/send-message-tool.js.map +1 -0
  160. package/dist/core/shell-cmd-tool.d.ts +82 -1
  161. package/dist/core/shell-cmd-tool.d.ts.map +1 -1
  162. package/dist/core/shell-cmd-tool.js +854 -42
  163. package/dist/core/shell-cmd-tool.js.map +1 -1
  164. package/dist/core/skill-registry.d.ts +2 -0
  165. package/dist/core/skill-registry.d.ts.map +1 -1
  166. package/dist/core/skill-registry.js +52 -2
  167. package/dist/core/skill-registry.js.map +1 -1
  168. package/dist/core/storage/eventStorage/fileEventStorage.d.ts +5 -0
  169. package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -1
  170. package/dist/core/storage/eventStorage/fileEventStorage.js +61 -0
  171. package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -1
  172. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +5 -0
  173. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -1
  174. package/dist/core/storage/eventStorage/memoryEventStorage.js +34 -0
  175. package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -1
  176. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +1 -0
  177. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -1
  178. package/dist/core/storage/eventStorage/sqliteEventStorage.js +19 -2
  179. package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -1
  180. package/dist/core/storage/eventStorage/types.d.ts +6 -0
  181. package/dist/core/storage/eventStorage/types.d.ts.map +1 -1
  182. package/dist/core/storage/eventStorage/types.js +1 -0
  183. package/dist/core/storage/eventStorage/types.js.map +1 -1
  184. package/dist/core/storage/eventStorage/validation.d.ts.map +1 -1
  185. package/dist/core/storage/eventStorage/validation.js +2 -1
  186. package/dist/core/storage/eventStorage/validation.js.map +1 -1
  187. package/dist/core/storage/github-world-import.d.ts +84 -0
  188. package/dist/core/storage/github-world-import.d.ts.map +1 -0
  189. package/dist/core/storage/github-world-import.js +365 -0
  190. package/dist/core/storage/github-world-import.js.map +1 -0
  191. package/dist/core/storage/memory-storage.d.ts +19 -8
  192. package/dist/core/storage/memory-storage.d.ts.map +1 -1
  193. package/dist/core/storage/memory-storage.js +147 -49
  194. package/dist/core/storage/memory-storage.js.map +1 -1
  195. package/dist/core/storage/queue-storage.d.ts +1 -0
  196. package/dist/core/storage/queue-storage.d.ts.map +1 -1
  197. package/dist/core/storage/queue-storage.js +3 -2
  198. package/dist/core/storage/queue-storage.js.map +1 -1
  199. package/dist/core/storage/sqlite-storage.d.ts +14 -9
  200. package/dist/core/storage/sqlite-storage.d.ts.map +1 -1
  201. package/dist/core/storage/sqlite-storage.js +131 -154
  202. package/dist/core/storage/sqlite-storage.js.map +1 -1
  203. package/dist/core/storage/storage-factory.d.ts +3 -0
  204. package/dist/core/storage/storage-factory.d.ts.map +1 -1
  205. package/dist/core/storage/storage-factory.js +175 -89
  206. package/dist/core/storage/storage-factory.js.map +1 -1
  207. package/dist/core/storage/world-storage.d.ts +1 -1
  208. package/dist/core/storage/world-storage.d.ts.map +1 -1
  209. package/dist/core/storage/world-storage.js +5 -1
  210. package/dist/core/storage/world-storage.js.map +1 -1
  211. package/dist/core/storage-init.d.ts +11 -0
  212. package/dist/core/storage-init.d.ts.map +1 -0
  213. package/dist/core/storage-init.js +122 -0
  214. package/dist/core/storage-init.js.map +1 -0
  215. package/dist/core/subscription.d.ts +8 -1
  216. package/dist/core/subscription.d.ts.map +1 -1
  217. package/dist/core/subscription.js +130 -23
  218. package/dist/core/subscription.js.map +1 -1
  219. package/dist/core/tool-approval.d.ts +45 -0
  220. package/dist/core/tool-approval.d.ts.map +1 -0
  221. package/dist/core/tool-approval.js +223 -0
  222. package/dist/core/tool-approval.js.map +1 -0
  223. package/dist/core/tool-execution-envelope.d.ts +87 -0
  224. package/dist/core/tool-execution-envelope.d.ts.map +1 -0
  225. package/dist/core/tool-execution-envelope.js +168 -0
  226. package/dist/core/tool-execution-envelope.js.map +1 -0
  227. package/dist/core/tool-utils.d.ts +7 -2
  228. package/dist/core/tool-utils.d.ts.map +1 -1
  229. package/dist/core/tool-utils.js +81 -17
  230. package/dist/core/tool-utils.js.map +1 -1
  231. package/dist/core/types.d.ts +67 -19
  232. package/dist/core/types.d.ts.map +1 -1
  233. package/dist/core/types.js +3 -0
  234. package/dist/core/types.js.map +1 -1
  235. package/dist/core/utils.d.ts +7 -0
  236. package/dist/core/utils.d.ts.map +1 -1
  237. package/dist/core/utils.js +71 -21
  238. package/dist/core/utils.js.map +1 -1
  239. package/dist/core/web-fetch-tool.d.ts +72 -0
  240. package/dist/core/web-fetch-tool.d.ts.map +1 -0
  241. package/dist/core/web-fetch-tool.js +491 -0
  242. package/dist/core/web-fetch-tool.js.map +1 -0
  243. package/dist/core/world-registry.d.ts +84 -0
  244. package/dist/core/world-registry.d.ts.map +1 -0
  245. package/dist/core/world-registry.js +247 -0
  246. package/dist/core/world-registry.js.map +1 -0
  247. package/dist/public/assets/index-Be-1xtV-.js +104 -0
  248. package/dist/public/assets/index-tsDdiXDU.css +1 -0
  249. package/dist/public/index.html +2 -2
  250. package/dist/public/mcp-sandbox-proxy.html +148 -0
  251. package/dist/server/api.js +260 -18
  252. package/dist/server/error-response.d.ts +27 -0
  253. package/dist/server/error-response.js +77 -0
  254. package/dist/server/index.d.ts +2 -1
  255. package/dist/server/index.js +6 -2
  256. package/dist/server/sse-handler.d.ts +11 -1
  257. package/dist/server/sse-handler.js +194 -34
  258. package/migrations/0015_add_message_queue.sql +36 -0
  259. package/migrations/0016_add_world_heartbeat.sql +13 -0
  260. package/migrations/0017_add_title_provenance.sql +7 -0
  261. package/package.json +31 -10
  262. package/dist/public/assets/index-BW41BxMy.css +0 -1
  263. 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-20 - Keep `hitl-option-request` system events bypassing strict chat scope filtering so HITL prompts are always delivered.
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
- const isHitlRequestEvent = (eventData) => {
128
- if (!eventData)
129
- return false;
130
- const content = eventData.content;
131
- if (content && typeof content === 'object') {
132
- const eventType = String(content.eventType || '').trim();
133
- return eventType === 'hitl-option-request';
134
- }
135
- if (typeof content === 'string') {
136
- const eventType = content.trim();
137
- return eventType === 'hitl-option-request';
138
- }
139
- return false;
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
- // Give a small delay for any final events to be sent
177
- setTimeout(() => {
178
- endResponse();
179
- }, 500);
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 when tool-stream data arrives (keeps long-running tools alive)
215
- if (eventData.type === 'tool-stream') {
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
- const isHitlRequest = isHitlRequestEvent(eventData);
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
- world.eventEmitter.on(EventType.SYSTEM, systemListener);
230
- listeners.set(EventType.SYSTEM, systemListener);
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.13.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 --workspace=core && tsc --project tsconfig.build.json && npm run build --workspace=web",
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 main:build --prefix electron",
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
- "pkill": "pkill -f tsx"
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
- "openai": "^6.15.0",
83
- "commander": "^14.0.0",
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
- "zod": "^4.2.1",
88
- "tsx": "^4.21.0"
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",