agent-world 0.12.3 → 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 +105 -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 +9 -2
  5. package/dist/cli/hitl.js +61 -20
  6. package/dist/cli/index.js +250 -96
  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 +38 -2
  10. package/dist/core/activity-tracker.d.ts.map +1 -1
  11. package/dist/core/activity-tracker.js +62 -9
  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 +28 -25
  22. package/dist/core/create-agent-tool.d.ts.map +1 -1
  23. package/dist/core/create-agent-tool.js +264 -141
  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 +214 -38
  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 -61
  40. package/dist/core/events/persistence.js.map +1 -1
  41. package/dist/core/events/publishers.d.ts +13 -16
  42. package/dist/core/events/publishers.d.ts.map +1 -1
  43. package/dist/core/events/publishers.js +54 -55
  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 +68 -147
  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 +73 -0
  81. package/dist/core/hitl-tool.d.ts.map +1 -0
  82. package/dist/core/hitl-tool.js +284 -0
  83. package/dist/core/hitl-tool.js.map +1 -0
  84. package/dist/core/hitl.d.ts +85 -8
  85. package/dist/core/hitl.d.ts.map +1 -1
  86. package/dist/core/hitl.js +375 -61
  87. package/dist/core/hitl.js.map +1 -1
  88. package/dist/core/index.d.ts +12 -7
  89. package/dist/core/index.d.ts.map +1 -1
  90. package/dist/core/index.js +11 -6
  91. package/dist/core/index.js.map +1 -1
  92. package/dist/core/llm-manager.d.ts +17 -0
  93. package/dist/core/llm-manager.d.ts.map +1 -1
  94. package/dist/core/llm-manager.js +335 -43
  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 +41 -52
  105. package/dist/core/managers.d.ts.map +1 -1
  106. package/dist/core/managers.js +422 -533
  107. package/dist/core/managers.js.map +1 -1
  108. package/dist/core/mcp-server-registry.d.ts +19 -2
  109. package/dist/core/mcp-server-registry.d.ts.map +1 -1
  110. package/dist/core/mcp-server-registry.js +168 -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 +9 -2
  228. package/dist/core/tool-utils.d.ts.map +1 -1
  229. package/dist/core/tool-utils.js +122 -28
  230. package/dist/core/tool-utils.js.map +1 -1
  231. package/dist/core/types.d.ts +69 -36
  232. package/dist/core/types.d.ts.map +1 -1
  233. package/dist/core/types.js +3 -2
  234. package/dist/core/types.js.map +1 -1
  235. package/dist/core/utils.d.ts +16 -0
  236. package/dist/core/utils.d.ts.map +1 -1
  237. package/dist/core/utils.js +99 -24
  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 +288 -58
  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 +13 -2
  257. package/dist/server/sse-handler.js +194 -26
  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-BO20H4xt.js +0 -96
  263. package/dist/public/assets/index-ETY7W5_S.css +0 -1
@@ -15,15 +15,16 @@
15
15
  * Endpoints: /health + API routes from ./api.ts
16
16
  *
17
17
  * Recent Changes:
18
+ * - 2026-02-26: Added specific global API error mapping for oversized JSON payloads, invalid JSON bodies, and readonly database errors to avoid generic INTERNAL_ERROR responses.
18
19
  * - 2026-02-08: Fixed auto-run detection to only trigger on direct execution
19
20
  * - 2026-02-08: Made browser auto-open configurable via AGENT_WORLD_AUTO_OPEN env var
20
21
  * - 2026-02-08: Prevented duplicate process signal handler registration using WeakSet
21
22
  * - 2026-02-08: Added shutdown guard to prevent race conditions during graceful shutdown
22
23
  */
23
24
  import { Server } from 'http';
25
+ export { getErrorResponse } from './error-response.js';
24
26
  type StartWebServerOptions = {
25
27
  openBrowser?: boolean;
26
28
  registerProcessHandlers?: boolean;
27
29
  };
28
30
  export declare function startWebServer(port?: number, host?: string, options?: StartWebServerOptions): Promise<Server>;
29
- export {};
@@ -15,6 +15,7 @@
15
15
  * Endpoints: /health + API routes from ./api.ts
16
16
  *
17
17
  * Recent Changes:
18
+ * - 2026-02-26: Added specific global API error mapping for oversized JSON payloads, invalid JSON bodies, and readonly database errors to avoid generic INTERNAL_ERROR responses.
18
19
  * - 2026-02-08: Fixed auto-run detection to only trigger on direct execution
19
20
  * - 2026-02-08: Made browser auto-open configurable via AGENT_WORLD_AUTO_OPEN env var
20
21
  * - 2026-02-08: Prevented duplicate process signal handler registration using WeakSet
@@ -32,11 +33,13 @@ import path from 'path';
32
33
  import { fileURLToPath, pathToFileURL } from 'url';
33
34
  import apiRouter from './api.js';
34
35
  import { initializeMCPRegistry, shutdownAllMCPServers } from '../core/mcp-server-registry.js';
36
+ import { getErrorResponse } from './error-response.js';
37
+ export { getErrorResponse } from './error-response.js';
35
38
  // ES modules setup
36
39
  const __filename = fileURLToPath(import.meta.url);
37
40
  const __dirname = path.dirname(__filename);
38
41
  // Configuration
39
- const PORT = Number(process.env.PORT) || 0;
42
+ const PORT = Number(process.env.PORT) || 3000;
40
43
  const HOST = process.env.HOST || '127.0.0.1';
41
44
  // Create server logger after logger auto-initialization
42
45
  const serverLogger = createCategoryLogger('server');
@@ -117,7 +120,8 @@ app.get('*', (req, res) => {
117
120
  // Error handling middleware
118
121
  app.use((error, req, res, next) => {
119
122
  console.error('Unhandled error:', error);
120
- res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
123
+ const { status, payload } = getErrorResponse(error);
124
+ res.status(status).json(payload);
121
125
  });
122
126
  // 404 handler
123
127
  app.use((req, res) => {
@@ -5,10 +5,9 @@
5
5
  *
6
6
  * Features:
7
7
  * - Sets up SSE response headers and connection
8
- * - Wires world event listeners (MESSAGE, SSE, SYSTEM, WORLD, CRUD)
8
+ * - Wires world event listeners (MESSAGE, SSE, SYSTEM, WORLD)
9
9
  * - Handles world activity state tracking (response-start, idle)
10
10
  * - Forwards tool events (tool-start, tool-result, tool-error, tool-progress) as SSE events
11
- * - Forwards CRUD events so frontends can refresh world/agent state in real time
12
11
  * - Automatic stream completion when world becomes idle
13
12
  * - Timeout fallback (60s) if world never becomes idle
14
13
  * - Proper cleanup on client disconnect or stream end
@@ -30,6 +29,14 @@
30
29
  * ```
31
30
  *
32
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.
38
+ * Updated: 2026-02-20 - Removed stale legacy event-channel SSE forwarding from this handler.
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`.
33
40
  * Updated: 2026-02-11 - Extended fallback timeout on tool-stream events to prevent premature timeout
34
41
  * Updated: 2026-02-08 - Removed manual tool-intervention SSE commentary and kept generic tool_call forwarding
35
42
  * Updated: 2025-11-10 - Added tool event forwarding to SSE channel
@@ -37,6 +44,10 @@
37
44
  import { Request, Response } from 'express';
38
45
  import { World } from '../core/index.js';
39
46
  export interface SSEHandler {
47
+ /**
48
+ * Resolves once synthesis has finished and live listeners are attached.
49
+ */
50
+ ready: Promise<void>;
40
51
  /**
41
52
  * Send a Server-Sent Event to the client
42
53
  * @param data - Data object to send (will be JSON stringified)
@@ -5,10 +5,9 @@
5
5
  *
6
6
  * Features:
7
7
  * - Sets up SSE response headers and connection
8
- * - Wires world event listeners (MESSAGE, SSE, SYSTEM, WORLD, CRUD)
8
+ * - Wires world event listeners (MESSAGE, SSE, SYSTEM, WORLD)
9
9
  * - Handles world activity state tracking (response-start, idle)
10
10
  * - Forwards tool events (tool-start, tool-result, tool-error, tool-progress) as SSE events
11
- * - Forwards CRUD events so frontends can refresh world/agent state in real time
12
11
  * - Automatic stream completion when world becomes idle
13
12
  * - Timeout fallback (60s) if world never becomes idle
14
13
  * - Proper cleanup on client disconnect or stream end
@@ -30,14 +29,23 @@
30
29
  * ```
31
30
  *
32
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.
38
+ * Updated: 2026-02-20 - Removed stale legacy event-channel SSE forwarding from this handler.
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`.
33
40
  * Updated: 2026-02-11 - Extended fallback timeout on tool-stream events to prevent premature timeout
34
41
  * Updated: 2026-02-08 - Removed manual tool-intervention SSE commentary and kept generic tool_call forwarding
35
42
  * Updated: 2025-11-10 - Added tool event forwarding to SSE channel
36
43
  */
37
- import { createCategoryLogger, EventType } from '../core/index.js';
44
+ import { addLogStreamCallback, createCategoryLogger, EventType, getMemory, listPendingHitlPromptEventsFromMessages } from '../core/index.js';
38
45
  const loggerStream = createCategoryLogger('api.stream');
39
46
  // Timeout constants for streaming (fallback only)
40
47
  const STREAM_TIMEOUT_NO_EVENTS_MS = 15000;
48
+ const STREAM_IDLE_CLOSE_DELAY_MS = 2000;
41
49
  /**
42
50
  * Create and configure an SSE handler for streaming world events
43
51
  *
@@ -55,6 +63,7 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
55
63
  res.setHeader('Access-Control-Allow-Origin', '*');
56
64
  res.setHeader('Access-Control-Allow-Headers', 'Cache-Control');
57
65
  let timeoutTimer;
66
+ let idleCloseTimer;
58
67
  let hasReceivedEvents = false;
59
68
  let isResponseEnded = false;
60
69
  let lastEventTime = Date.now();
@@ -89,6 +98,10 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
89
98
  clearTimeout(timeoutTimer);
90
99
  timeoutTimer = undefined;
91
100
  }
101
+ if (idleCloseTimer) {
102
+ clearTimeout(idleCloseTimer);
103
+ idleCloseTimer = undefined;
104
+ }
92
105
  loggerStream.debug(`[${context}] Ending SSE response. Stats: events=${hasReceivedEvents}, awaitingWorldIdle=${awaitingWorldIdle}`);
93
106
  try {
94
107
  if (!res.destroyed) {
@@ -123,7 +136,22 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
123
136
  const normalizedEventChatId = eventChatId === null ? null : String(eventChatId);
124
137
  return normalizedEventChatId === normalizedScopedChatId;
125
138
  };
126
- // Attach direct listeners to world.eventEmitter
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();
153
+ };
154
+ // Attach direct listeners to world.eventEmitter (defined inside attach to allow synth-before-attach)
127
155
  const worldListener = (eventData) => {
128
156
  // Check if this is a tool event (tool-start, tool-result, tool-error, tool-progress)
129
157
  const isToolEvent = eventData?.type && ['tool-start', 'tool-result', 'tool-error', 'tool-progress'].includes(eventData.type);
@@ -146,6 +174,10 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
146
174
  }
147
175
  // Handle world activity events for stream completion
148
176
  if (eventData?.type === 'response-start') {
177
+ if (idleCloseTimer) {
178
+ clearTimeout(idleCloseTimer);
179
+ idleCloseTimer = undefined;
180
+ }
149
181
  awaitingWorldIdle = true;
150
182
  loggerStream.debug(`[${context}] World processing started`, {
151
183
  activityId: eventData.activityId,
@@ -156,20 +188,24 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
156
188
  loggerStream.debug(`[${context}] World idle detected, ending stream`, {
157
189
  activityId: eventData.activityId
158
190
  });
159
- // Stream all pending events, then end
160
191
  sendSSE({ type: EventType.WORLD, data: eventData });
161
- // Give a small delay for any final events to be sent
162
- setTimeout(() => {
163
- endResponse();
164
- }, 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);
165
203
  return;
166
204
  }
167
205
  if (isChatEventInScope(eventData?.chatId, true)) {
168
206
  sendSSE({ type: EventType.WORLD, data: eventData });
169
207
  }
170
208
  };
171
- world.eventEmitter.on(EventType.WORLD, worldListener);
172
- listeners.set(EventType.WORLD, worldListener);
173
209
  const messageListener = (eventData) => {
174
210
  if (!isChatEventInScope(eventData?.chatId, false)) {
175
211
  return;
@@ -186,46 +222,177 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
186
222
  replyToMessageId: eventData.replyToMessageId,
187
223
  createdAt: eventData.timestamp || new Date().toISOString(),
188
224
  role: eventData.role,
189
- tool_calls: eventData.tool_calls
225
+ tool_calls: eventData.tool_calls,
226
+ tool_call_id: eventData.tool_call_id
190
227
  };
191
228
  sendSSE({ type: EventType.MESSAGE, data: messageData });
192
229
  };
193
- world.eventEmitter.on(EventType.MESSAGE, messageListener);
194
- listeners.set(EventType.MESSAGE, messageListener);
195
230
  const sseListener = (eventData) => {
196
231
  if (!isChatEventInScope(eventData?.chatId, false)) {
197
232
  return;
198
233
  }
199
- // Extend fallback timeout when tool-stream data arrives (keeps long-running tools alive)
200
- 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) {
201
239
  startTimeoutFallback();
202
240
  }
203
241
  sendSSE({ type: EventType.SSE, data: eventData });
204
242
  };
205
- world.eventEmitter.on(EventType.SSE, sseListener);
206
- listeners.set(EventType.SSE, sseListener);
207
243
  const systemListener = (eventData) => {
208
- if (!isChatEventInScope(eventData?.chatId, true)) {
244
+ if (!isChatEventInScope(eventData?.chatId, false)) {
209
245
  return;
210
246
  }
211
247
  sendSSE({ type: EventType.SYSTEM, data: eventData });
212
248
  };
213
- world.eventEmitter.on(EventType.SYSTEM, systemListener);
214
- listeners.set(EventType.SYSTEM, systemListener);
215
- const crudListener = (eventData) => {
216
- if (!isChatEventInScope(eventData?.chatId, true)) {
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) {
217
258
  return;
218
259
  }
219
- sendSSE({ type: EventType.CRUD, data: eventData });
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
+ });
220
284
  };
221
- world.eventEmitter.on(EventType.CRUD, crudListener);
222
- listeners.set(EventType.CRUD, crudListener);
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
+ })();
223
386
  // Cleanup function to remove all listeners
224
387
  const cleanupListeners = () => {
225
388
  for (const [eventType, listener] of listeners.entries()) {
226
389
  world.eventEmitter.removeListener(eventType, listener);
227
390
  }
228
391
  listeners.clear();
392
+ if (unsubscribeLogStream) {
393
+ unsubscribeLogStream();
394
+ unsubscribeLogStream = null;
395
+ }
229
396
  };
230
397
  // Handle client disconnect
231
398
  req.on('close', () => {
@@ -236,6 +403,7 @@ export function createSSEHandler(req, res, world, context = 'sse', scopedChatId)
236
403
  // Start the fallback timeout
237
404
  startTimeoutFallback();
238
405
  return {
406
+ ready,
239
407
  sendSSE,
240
408
  endResponse,
241
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.12.3",
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",