@desplega.ai/agent-swarm 1.157.0 → 1.158.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 (256) hide show
  1. package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
  2. package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
  3. package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
  4. package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
  5. package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
  6. package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
  7. package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
  8. package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
  9. package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
  10. package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
  11. package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
  12. package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
  13. package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
  14. package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
  15. package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
  16. package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
  17. package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
  18. package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
  19. package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
  20. package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
  21. package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
  22. package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
  23. package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
  24. package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
  25. package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
  26. package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
  27. package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
  28. package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
  29. package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
  30. package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
  31. package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
  32. package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
  33. package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
  34. package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
  35. package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
  36. package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
  37. package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
  38. package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
  39. package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
  40. package/dist/cli-t1ze64h3.js +811 -0
  41. package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
  42. package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
  43. package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
  44. package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
  45. package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
  46. package/dist/cli.js +29 -15
  47. package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
  48. package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
  49. package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
  50. package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
  51. package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
  52. package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
  53. package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
  54. package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
  55. package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
  56. package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
  57. package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
  58. package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
  59. package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
  60. package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
  61. package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
  62. package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
  63. package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
  64. package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
  65. package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
  66. package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
  67. package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
  68. package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
  69. package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
  70. package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
  71. package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
  72. package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
  73. package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
  74. package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
  75. package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
  76. package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
  77. package/openapi.json +1626 -629
  78. package/package.json +2 -1
  79. package/src/apps/catalog.generated.json +229 -0
  80. package/src/apps/page-validator.ts +71 -16
  81. package/src/apps/row-store.ts +66 -11
  82. package/src/apps/sync.ts +59 -17
  83. package/src/artifact-sdk/browser-sdk.ts +436 -2
  84. package/src/be/approval-sweeps.ts +109 -0
  85. package/src/be/boot-seeds.ts +40 -0
  86. package/src/be/chunking.ts +1 -1
  87. package/src/be/db/agents.ts +20 -0
  88. package/src/be/db/approvals.ts +78 -0
  89. package/src/be/db/tasks/read.ts +197 -39
  90. package/src/be/db/tasks/write.ts +41 -9
  91. package/src/be/db-client.ts +107 -48
  92. package/src/be/db.ts +219 -76
  93. package/src/be/events.ts +33 -1
  94. package/src/be/memory/graph-expansion.ts +10 -3
  95. package/src/be/memory/providers/sqlite-store.ts +55 -13
  96. package/src/be/memory/raters/retrieval.ts +5 -2
  97. package/src/be/memory/reranker.ts +26 -2
  98. package/src/be/memory/types.ts +2 -0
  99. package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
  100. package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
  101. package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
  102. package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
  103. package/src/be/models-catalog.ts +2 -0
  104. package/src/be/modelsdev-cache.json +1857 -0
  105. package/src/be/modelsdev-cache.ts +14 -1
  106. package/src/be/pricing-normalize.ts +2 -0
  107. package/src/be/scripts/typecheck.ts +1 -0
  108. package/src/be/seed-pricing.ts +3 -1
  109. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  110. package/src/be/steering.ts +53 -3
  111. package/src/be/swarm-config-guard.ts +3 -0
  112. package/src/be/task-citations.ts +29 -0
  113. package/src/cli.tsx +14 -1
  114. package/src/commands/onboard/compose-generator.ts +8 -3
  115. package/src/commands/onboard/container-engine.ts +220 -0
  116. package/src/commands/onboard/manifest.ts +1 -0
  117. package/src/commands/onboard/steps/health-check.tsx +4 -1
  118. package/src/commands/onboard/steps/prereq-check.tsx +50 -54
  119. package/src/commands/onboard/steps/start.tsx +31 -16
  120. package/src/commands/onboard/types.ts +7 -2
  121. package/src/commands/onboard.tsx +39 -4
  122. package/src/commands/profile-sync.ts +79 -26
  123. package/src/commands/runner.ts +8 -1
  124. package/src/extensions/catalog.generated.json +17 -0
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/heartbeat/heartbeat.ts +263 -103
  127. package/src/hooks/hook.ts +21 -12
  128. package/src/http/agents.ts +35 -0
  129. package/src/http/approval-requests.ts +136 -64
  130. package/src/http/apps.ts +14 -3
  131. package/src/http/config.ts +15 -1
  132. package/src/http/events.ts +62 -2
  133. package/src/http/index.ts +6 -4
  134. package/src/http/memory.ts +21 -7
  135. package/src/http/schedules.ts +1 -1
  136. package/src/http/session-data.ts +9 -4
  137. package/src/http/sessions.ts +25 -18
  138. package/src/http/stats.ts +19 -7
  139. package/src/http/status.ts +11 -1
  140. package/src/http/tasks.ts +40 -8
  141. package/src/http/webhooks.ts +67 -46
  142. package/src/http/workflows.ts +15 -20
  143. package/src/linear/outbound.ts +37 -15
  144. package/src/otel-impl.ts +151 -1
  145. package/src/otel.ts +14 -3
  146. package/src/prompts/memories.ts +19 -2
  147. package/src/providers/claude-managed-models.ts +8 -0
  148. package/src/providers/modelsdev-reasoning.json +551 -0
  149. package/src/rbac/legacy-policy.ts +16 -0
  150. package/src/rbac/permissions.ts +4 -0
  151. package/src/scheduler/schedule-task.ts +3 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/swarm-sdk.ts +9 -0
  154. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  155. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  156. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  157. package/src/server.ts +12 -7
  158. package/src/slack/app.ts +35 -4
  159. package/src/slack/connection-state.ts +17 -0
  160. package/src/tasks/context-key.ts +1 -0
  161. package/src/tasks/worker-follow-up.ts +6 -2
  162. package/src/tests/agent-task-activity.test.ts +74 -0
  163. package/src/tests/agents-list-model-display.test.ts +6 -1
  164. package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
  165. package/src/tests/approval-requests.test.ts +395 -0
  166. package/src/tests/approval-sweeps-config.test.ts +47 -0
  167. package/src/tests/approval-sweeps.test.ts +508 -0
  168. package/src/tests/apps-kibo-blocks.test.ts +77 -0
  169. package/src/tests/apps-spike.test.ts +56 -1
  170. package/src/tests/apps-sync-engine.test.ts +20 -9
  171. package/src/tests/boot-seeds.test.ts +170 -0
  172. package/src/tests/cancel-approval-request-tool.test.ts +115 -0
  173. package/src/tests/claude-managed-adapter.test.ts +6 -0
  174. package/src/tests/config-key-filter.test.ts +80 -0
  175. package/src/tests/context-window.test.ts +1 -0
  176. package/src/tests/db-client-busy-retry.test.ts +96 -0
  177. package/src/tests/db-client.test.ts +60 -1
  178. package/src/tests/db-task-read-contract.test.ts +10 -0
  179. package/src/tests/db-task-write-contract.test.ts +9 -0
  180. package/src/tests/docs-index-footprint.test.js +3 -3
  181. package/src/tests/events-http.test.ts +103 -0
  182. package/src/tests/extensions-catalog.test.ts +22 -17
  183. package/src/tests/extensions-pre-task.test.ts +107 -0
  184. package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
  185. package/src/tests/follow-up-config-inheritance.test.ts +260 -0
  186. package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
  187. package/src/tests/heartbeat-reboot-live.test.ts +95 -0
  188. package/src/tests/heartbeat-stall-cas.test.ts +80 -0
  189. package/src/tests/heartbeat.test.ts +99 -3
  190. package/src/tests/hook-mcp-config.test.ts +29 -0
  191. package/src/tests/http-api-integration.test.ts +16 -2
  192. package/src/tests/kv-storage.test.ts +62 -0
  193. package/src/tests/linear-outbound-sync.test.ts +120 -5
  194. package/src/tests/list-endpoint-slimming.test.ts +91 -0
  195. package/src/tests/memory-graph-organic-route.test.ts +155 -0
  196. package/src/tests/memory-http-recall-gating.test.ts +14 -4
  197. package/src/tests/memory-hybrid.test.ts +161 -1
  198. package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
  199. package/src/tests/memory.test.ts +22 -0
  200. package/src/tests/onboard-container-engine.test.ts +240 -0
  201. package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
  202. package/src/tests/pricing-routes.test.ts +14 -0
  203. package/src/tests/profile-sync-warning.test.ts +8 -4
  204. package/src/tests/promote-draft-task-route.test.ts +19 -0
  205. package/src/tests/provider-registration.test.ts +296 -42
  206. package/src/tests/rbac-engine.test.ts +30 -0
  207. package/src/tests/reasoning-effort.test.ts +8 -0
  208. package/src/tests/script-connections.test.ts +7 -0
  209. package/src/tests/sdk-allowlist.test.ts +39 -0
  210. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  211. package/src/tests/sessions-detail-batch.test.ts +250 -0
  212. package/src/tests/sessions.test.ts +118 -0
  213. package/src/tests/slack-config.test.ts +22 -0
  214. package/src/tests/sonnet-5-5-registry.test.ts +44 -0
  215. package/src/tests/status.test.ts +36 -1
  216. package/src/tests/swarm-diff.test.ts +316 -10
  217. package/src/tests/task-completion-idempotency.test.ts +2 -1
  218. package/src/tests/tasks-requested-by-filter.test.ts +3 -1
  219. package/src/tests/tool-annotations.test.ts +1 -1
  220. package/src/tests/ui-list-slim-projections.test.ts +158 -0
  221. package/src/tests/webhooks-task-blocked.test.ts +127 -0
  222. package/src/tests/workflow-approval-recovery.test.ts +203 -0
  223. package/src/tests/workflow-hitl-routing.test.ts +22 -1
  224. package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
  225. package/src/tests/workflow-http-v2.test.ts +99 -8
  226. package/src/tests/workflow-retry-validation.test.ts +56 -0
  227. package/src/tests/workflow-tla-races.test.ts +389 -0
  228. package/src/tests/workflow-validation-port-routing.test.ts +113 -1
  229. package/src/tools/cancel-approval-request.ts +57 -0
  230. package/src/tools/memory-search.ts +1 -0
  231. package/src/tools/request-human-input.ts +4 -2
  232. package/src/tools/send-task.ts +4 -0
  233. package/src/tools/templates.ts +19 -0
  234. package/src/tools/tool-config.ts +2 -1
  235. package/src/tools/workflows/list-workflow-runs.ts +1 -0
  236. package/src/types.ts +38 -0
  237. package/src/utils/context-window.ts +1 -0
  238. package/src/workflows/approval-cancel.ts +152 -0
  239. package/src/workflows/approval-notifications.ts +84 -0
  240. package/src/workflows/checkpoint.ts +10 -5
  241. package/src/workflows/definition.ts +21 -0
  242. package/src/workflows/engine.ts +152 -21
  243. package/src/workflows/executors/human-in-the-loop.ts +1 -1
  244. package/src/workflows/recovery.ts +100 -45
  245. package/src/workflows/resume.ts +49 -18
  246. package/src/workflows/retry-poller.ts +71 -21
  247. package/templates/extensions/README.md +1 -0
  248. package/templates/extensions/github-sender-allowlist/README.md +18 -0
  249. package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
  250. package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
  251. package/templates/skills/apps/content.md +20 -1
  252. package/templates/skills/code-quality/content.md +26 -1
  253. package/templates/skills/pages/content.md +16 -4
  254. package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
  255. package/dist/cli-2daksqps.js +0 -378
  256. package/dist/rbac-roles-3gxmnb20.js +0 -37
@@ -152,8 +152,9 @@ window.swarmSdk = new SwarmSDK();
152
152
  //
153
153
  // Auto-injected alongside the SDK. Exposes a tiny set of declarative web
154
154
  // components agents can drop into HTML pages without bundling anything. v1:
155
- // only \`<swarm-diff>\` (unified-diff renderer) + \`<swarm-diff-jumps>\` (a
156
- // sibling-anchor jump list). All zero-dep, pure DOM — Tailwind utility
155
+ // \`<swarm-diff>\` (unified-diff renderer), \`<swarm-diff-jumps>\` (a
156
+ // sibling-anchor jump list), and \`<swarm-cursors>\` (shared page pointers).
157
+ // All zero-dep, pure DOM — Tailwind utility
157
158
  // classes are used freely since the Play CDN is already loaded by
158
159
  // PAGE_HEAD_DEFAULTS, but every visual aspect has inline-style fallbacks so
159
160
  // the component is still legible if Tailwind fails to load.
@@ -176,6 +177,201 @@ export const SWARM_UI_JS = `
176
177
  if (typeof window === 'undefined' || !window.customElements) return;
177
178
  if (window.customElements.get('swarm-diff')) return;
178
179
 
180
+ // Keep page-owned presence fields alongside the cursor. Room.presence.set
181
+ // replaces its payload, so retain each room's latest page data and merge
182
+ // the reserved __cursor field into it when the cursor moves.
183
+ var roomPresenceTrackers = new WeakMap();
184
+
185
+ function clonePresenceRecord(data) {
186
+ return data && typeof data === 'object' && !Array.isArray(data)
187
+ ? structuredClone(data)
188
+ : {};
189
+ }
190
+
191
+ function mergeCursorPresence(data, cursor) {
192
+ var merged = clonePresenceRecord(data);
193
+ if (cursor) merged.__cursor = { x: cursor.x, y: cursor.y };
194
+ else delete merged.__cursor;
195
+ return merged;
196
+ }
197
+
198
+ function trackRoomPresence(room) {
199
+ if (!room || !room.presence || typeof room.presence.set !== 'function') return null;
200
+ var existing = roomPresenceTrackers.get(room);
201
+ if (existing) return existing;
202
+
203
+ var presence = room.presence;
204
+ var originalSet = presence.set;
205
+ var pageData = {};
206
+ var cursor = null;
207
+ var tracker = {
208
+ setCursor: function(nextCursor) {
209
+ cursor = nextCursor;
210
+ return originalSet.call(presence, mergeCursorPresence(pageData, cursor));
211
+ },
212
+ clearCursor: function() {
213
+ if (!cursor) return;
214
+ return tracker.setCursor(null);
215
+ },
216
+ };
217
+
218
+ presence.set = function(data) {
219
+ pageData = clonePresenceRecord(data);
220
+ return originalSet.call(presence, cursor ? mergeCursorPresence(pageData, cursor) : data);
221
+ };
222
+ roomPresenceTrackers.set(room, tracker);
223
+ return tracker;
224
+ }
225
+
226
+ // Wrap room creation before page scripts run so presence.set calls made by
227
+ // page code are tracked even when they happen before <swarm-cursors> connects.
228
+ var cursorRoomRecords = new Map();
229
+ var sdkRoomMethods = new WeakMap();
230
+ var observedRoomClosers = new WeakSet();
231
+ var originalRoom = null;
232
+ function roomRecordKey(name) {
233
+ return name === undefined ? 'default' : name;
234
+ }
235
+
236
+ function observeRoom(record, room) {
237
+ record.hasResolvedRoom = true;
238
+ if (!cursorRoomRecords.has(record.name)) cursorRoomRecords.set(record.name, record);
239
+ trackRoomPresence(room);
240
+ if (room && typeof room.close === 'function' && !observedRoomClosers.has(room)) {
241
+ var originalClose = room.close;
242
+ room.close = function() {
243
+ if (cursorRoomRecords.get(record.name) === record) cursorRoomRecords.delete(record.name);
244
+ return originalClose.apply(room, arguments);
245
+ };
246
+ observedRoomClosers.add(room);
247
+ }
248
+ return room;
249
+ }
250
+
251
+ function observeRoomPromise(record, result) {
252
+ var observed = Promise.resolve(result).then(function(room) {
253
+ var resolvedRoom = observeRoom(record, room);
254
+ record.pageRequests--;
255
+ record.hasPageUse = true;
256
+ record.pageUsed = true;
257
+ record.promise = Promise.resolve(resolvedRoom);
258
+ return resolvedRoom;
259
+ }).catch(function(error) {
260
+ record.pageRequests--;
261
+ record.pageUsed = record.hasPageUse || record.pageRequests > 0;
262
+ if (!record.hasResolvedRoom
263
+ && record.pageRequests === 0
264
+ && cursorRoomRecords.get(record.name) === record)
265
+ cursorRoomRecords.delete(record.name);
266
+ if (record.cursorRefs === 0 && record.openedByCursor && !record.pageUsed)
267
+ closeCursorRoomIfUnused(record);
268
+ throw error;
269
+ });
270
+ record.pageRequests++;
271
+ record.pageUsed = true;
272
+ if (!record.promise) record.promise = observed;
273
+ return observed;
274
+ }
275
+
276
+ function closeCursorRoomIfUnused(record) {
277
+ if (record.closing || record.cursorRefs || !record.openedByCursor || record.pageUsed) return;
278
+ record.closing = true;
279
+ record.promise.then(function(room) {
280
+ if (record.cursorRefs || record.pageUsed) {
281
+ record.closing = false;
282
+ return;
283
+ }
284
+ if (cursorRoomRecords.get(record.name) === record) cursorRoomRecords.delete(record.name);
285
+ if (room && typeof room.close === 'function') return room.close();
286
+ }).catch(function() {
287
+ record.closing = false;
288
+ });
289
+ }
290
+
291
+ function instrumentSdk(sdk) {
292
+ if (!sdk || typeof sdk.room !== 'function') return null;
293
+ var existing = sdkRoomMethods.get(sdk);
294
+ if (existing) return existing;
295
+
296
+ var sdkOriginalRoom = sdk.room;
297
+ var wrappedRoom = function(name, options) {
298
+ var key = roomRecordKey(name);
299
+ var record = cursorRoomRecords.get(key);
300
+ var result = sdkOriginalRoom.call(sdk, name, options);
301
+ if (!record) {
302
+ record = {
303
+ name: key,
304
+ cursorRefs: 0,
305
+ openedByCursor: false,
306
+ pageUsed: false,
307
+ hasPageUse: false,
308
+ pageRequests: 0,
309
+ hasResolvedRoom: false,
310
+ promise: null,
311
+ };
312
+ cursorRoomRecords.set(key, record);
313
+ }
314
+ return observeRoomPromise(record, result);
315
+ };
316
+ sdk.room = wrappedRoom;
317
+ var methods = { original: sdkOriginalRoom, wrapped: wrappedRoom };
318
+ sdkRoomMethods.set(sdk, methods);
319
+ return methods;
320
+ }
321
+
322
+ if (window.swarmSdk) {
323
+ var singletonRoomMethods = instrumentSdk(window.swarmSdk);
324
+ originalRoom = singletonRoomMethods && singletonRoomMethods.original;
325
+ }
326
+ if (typeof window.SwarmSDK === 'function') {
327
+ var OriginalSwarmSDK = window.SwarmSDK;
328
+ window.SwarmSDK = new Proxy(OriginalSwarmSDK, {
329
+ construct: function(target, args, newTarget) {
330
+ var sdk = Reflect.construct(target, args, newTarget);
331
+ instrumentSdk(sdk);
332
+ return sdk;
333
+ },
334
+ });
335
+ }
336
+
337
+ function acquireCursorRoom(name, options) {
338
+ var key = roomRecordKey(name);
339
+ var record = cursorRoomRecords.get(key);
340
+ if (record) {
341
+ record.cursorRefs++;
342
+ return { record: record, promise: record.promise };
343
+ }
344
+
345
+ record = {
346
+ name: key,
347
+ cursorRefs: 1,
348
+ openedByCursor: true,
349
+ pageUsed: false,
350
+ hasPageUse: false,
351
+ pageRequests: 0,
352
+ hasResolvedRoom: false,
353
+ closing: false,
354
+ promise: null,
355
+ };
356
+ cursorRoomRecords.set(key, record);
357
+ record.promise = Promise.resolve().then(function() {
358
+ return originalRoom(name, options);
359
+ }).then(function(room) {
360
+ return observeRoom(record, room);
361
+ }).catch(function(error) {
362
+ if (cursorRoomRecords.get(key) === record) cursorRoomRecords.delete(key);
363
+ throw error;
364
+ });
365
+ return { record: record, promise: record.promise };
366
+ }
367
+
368
+ function releaseCursorRoom(record, presence) {
369
+ if (!record || record.cursorRefs < 1) return;
370
+ if (record.cursorRefs === 1 && presence) presence.clearCursor();
371
+ record.cursorRefs--;
372
+ closeCursorRoomIfUnused(record);
373
+ }
374
+
179
375
  var SEV_COLOR = {
180
376
  error: '#ef4444',
181
377
  warn: '#f59e0b',
@@ -418,5 +614,243 @@ export const SWARM_UI_JS = `
418
614
  }
419
615
  }
420
616
  window.customElements.define('swarm-diff-jumps', SwarmDiffJumpsElement);
617
+
618
+ var CURSOR_IDLE_MS = 3000;
619
+ var CURSOR_SMOOTHING = 0.28;
620
+
621
+ function cursorColor(userId) {
622
+ var hash = 2166136261;
623
+ var value = String(userId || '');
624
+ for (var i = 0; i < value.length; i++) {
625
+ hash ^= value.charCodeAt(i);
626
+ hash = Math.imul(hash, 16777619);
627
+ }
628
+ return 'hsl(' + ((hash >>> 0) % 360) + ', 72%, 42%)';
629
+ }
630
+
631
+ function documentSize() {
632
+ var root = document.documentElement || {};
633
+ return {
634
+ width: Math.max(1, root.scrollWidth || root.clientWidth || window.innerWidth || 1),
635
+ height: Math.max(1, root.scrollHeight || root.clientHeight || window.innerHeight || 1),
636
+ };
637
+ }
638
+
639
+ function cursorMarkup(name, color) {
640
+ // CursorPointer + CursorBody label structure adapted from Kibo UI (MIT):
641
+ // https://github.com/shadcnblocks/kibo/blob/main/packages/cursor/index.tsx
642
+ return '<svg aria-hidden="true" focusable="false" height="20" width="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" style="width:20px;height:20px;flex:none;filter:drop-shadow(0 1px 1px rgba(0,0,0,.2));">'
643
+ + '<path d="M19.438 6.716 1.115.05A.832.832 0 0 0 .05 1.116L6.712 19.45a.834.834 0 0 0 1.557.025l3.198-8 7.995-3.2a.833.833 0 0 0 0-1.559h-.024Z" fill="currentColor"></path>'
644
+ + '</svg>'
645
+ + '<span style="position:relative;margin-left:14px;display:flex;flex-direction:column;white-space:nowrap;border-radius:12px;padding:4px 12px 4px 10px;font-family:system-ui,sans-serif;font-size:12px;font-weight:500;line-height:1.4;background:' + color + ';color:#fff;box-shadow:0 2px 6px rgba(0,0,0,.18);">'
646
+ + esc(name || 'Guest')
647
+ + '</span>';
648
+ }
649
+
650
+ class SwarmCursorsElement extends HTMLElement {
651
+ connectedCallback() {
652
+ if (this._startPromise) return;
653
+ var roomName = this.getAttribute('room');
654
+ if (!roomName || !originalRoom) return;
655
+
656
+ var self = this;
657
+ var lifecycle = (this._lifecycle || 0) + 1;
658
+ this._lifecycle = lifecycle;
659
+ var begin = function() {
660
+ self._onDomReady = null;
661
+ if (!self.isConnected || self._lifecycle !== lifecycle) return;
662
+ var schemaVersion = self.getAttribute('schema-version');
663
+ var options = schemaVersion == null ? {} : { schemaVersion: Number(schemaVersion) };
664
+ var acquired = acquireCursorRoom(roomName, options);
665
+ self._roomRecord = acquired.record;
666
+ self._startPromise = acquired.promise.then(function(room) {
667
+ if (!self.isConnected || self._lifecycle !== lifecycle) return;
668
+ self._startPromise = null;
669
+ self._room = room;
670
+ self._presence = trackRoomPresence(room);
671
+ if (!self._presence) return;
672
+ self._pointers = new Map();
673
+ self._cursorStates = new Map();
674
+
675
+ self._overlay = document.createElement('div');
676
+ self._overlay.setAttribute('aria-hidden', 'true');
677
+ self._overlay.style.cssText = 'position:fixed;inset:0;z-index:2147483647;pointer-events:none;overflow:hidden;';
678
+ var parent = document.body || document.documentElement;
679
+ if (parent) parent.appendChild(self._overlay);
680
+
681
+ self._onPresence = function(peers) { self._renderPeers(peers || []); };
682
+ self._onPointerMove = function(event) {
683
+ var size = documentSize();
684
+ var scrollX = window.scrollX || window.pageXOffset || 0;
685
+ var scrollY = window.scrollY || window.pageYOffset || 0;
686
+ var pageX = typeof event.pageX === 'number' ? event.pageX : event.clientX + scrollX;
687
+ var pageY = typeof event.pageY === 'number' ? event.pageY : event.clientY + scrollY;
688
+ var x = Math.max(0, Math.min(1, pageX / size.width));
689
+ var y = Math.max(0, Math.min(1, pageY / size.height));
690
+ self._presence.setCursor({ x: x, y: y });
691
+ };
692
+ self._onPointerLeave = function() {
693
+ self._presence.clearCursor();
694
+ };
695
+
696
+ self._unsubscribe = room.on('presence', self._onPresence);
697
+ document.addEventListener('pointermove', self._onPointerMove, { passive: true });
698
+ document.addEventListener('pointerleave', self._onPointerLeave);
699
+ self._renderPeers(room.presence.peers || []);
700
+ }).catch(function(error) {
701
+ self._startPromise = null;
702
+ console.warn('[swarm-cursors] failed to join room:', error);
703
+ });
704
+ };
705
+ if (document.readyState === 'loading') {
706
+ this._onDomReady = function() { queueMicrotask(begin); };
707
+ this._startPromise = Promise.resolve();
708
+ document.addEventListener('DOMContentLoaded', this._onDomReady, { once: true });
709
+ } else {
710
+ begin();
711
+ }
712
+ }
713
+
714
+ _renderPeers(peers) {
715
+ if (!this._pointers) return;
716
+ var seen = new Set();
717
+ var activePeerIds = new Set();
718
+ var includeAgents = this.hasAttribute('include-agents');
719
+ var ownId = this._room && this._room.me && this._room.me.userId;
720
+ var now = Date.now();
721
+
722
+ for (var i = 0; i < peers.length; i++) {
723
+ var peer = peers[i];
724
+ if (!peer || !peer.userId || peer.userId === ownId) continue;
725
+ if (peer.kind === 'agent' && !includeAgents) continue;
726
+ var data = peer.data && typeof peer.data === 'object' ? peer.data : null;
727
+ var cursor = data && data.__cursor;
728
+ if (!cursor || typeof cursor.x !== 'number' || typeof cursor.y !== 'number'
729
+ || !Number.isFinite(cursor.x) || !Number.isFinite(cursor.y)) continue;
730
+
731
+ var id = String(peer.userId);
732
+ activePeerIds.add(id);
733
+ var state = this._cursorStates.get(id);
734
+ if (!state || state.x !== cursor.x || state.y !== cursor.y) {
735
+ state = { x: cursor.x, y: cursor.y, lastSeen: now };
736
+ this._cursorStates.set(id, state);
737
+ }
738
+ if (now - state.lastSeen > CURSOR_IDLE_MS) continue;
739
+
740
+ var entry = this._pointers.get(id);
741
+ if (!entry) {
742
+ entry = this._createPointer(peer);
743
+ this._pointers.set(id, entry);
744
+ }
745
+ entry.cursor = { x: state.x, y: state.y };
746
+ entry.lastSeen = state.lastSeen;
747
+ if (entry.name !== peer.name) {
748
+ entry.name = peer.name || 'Guest';
749
+ entry.node.innerHTML = cursorMarkup(entry.name, entry.color);
750
+ }
751
+ seen.add(id);
752
+ }
753
+
754
+ var ids = Array.from(this._pointers.keys());
755
+ for (var j = 0; j < ids.length; j++) {
756
+ if (!seen.has(ids[j])) this._removePointer(ids[j]);
757
+ }
758
+ var stateIds = Array.from(this._cursorStates.keys());
759
+ for (var k = 0; k < stateIds.length; k++) {
760
+ if (!activePeerIds.has(stateIds[k])) this._cursorStates.delete(stateIds[k]);
761
+ }
762
+ this._scheduleFrame();
763
+ }
764
+
765
+ _createPointer(peer) {
766
+ var color = cursorColor(peer.userId);
767
+ var node = document.createElement('div');
768
+ node.style.cssText = 'position:absolute;left:0;top:0;display:flex;align-items:flex-start;pointer-events:none;user-select:none;will-change:transform;color:' + color + ';';
769
+ node.innerHTML = cursorMarkup(peer.name || 'Guest', color);
770
+ this._overlay.appendChild(node);
771
+ return {
772
+ node: node,
773
+ color: color,
774
+ name: peer.name || 'Guest',
775
+ cursor: null,
776
+ x: null,
777
+ y: null,
778
+ lastSeen: Date.now(),
779
+ };
780
+ }
781
+
782
+ _removePointer(id) {
783
+ var entry = this._pointers.get(id);
784
+ if (entry && entry.node.parentNode) entry.node.parentNode.removeChild(entry.node);
785
+ this._pointers.delete(id);
786
+ }
787
+
788
+ _scheduleFrame() {
789
+ if (this._frameId != null || !this._pointers || !this._pointers.size
790
+ || typeof window.requestAnimationFrame !== 'function') return;
791
+ var self = this;
792
+ this._frameId = window.requestAnimationFrame(function() { self._animate(); });
793
+ }
794
+
795
+ _animate() {
796
+ this._frameId = null;
797
+ if (!this._pointers || !this._overlay) return;
798
+ var now = Date.now();
799
+ var size = documentSize();
800
+ var scrollX = window.scrollX || window.pageXOffset || 0;
801
+ var scrollY = window.scrollY || window.pageYOffset || 0;
802
+ var ids = Array.from(this._pointers.keys());
803
+
804
+ for (var i = 0; i < ids.length; i++) {
805
+ var id = ids[i];
806
+ var entry = this._pointers.get(id);
807
+ if (now - entry.lastSeen > CURSOR_IDLE_MS) {
808
+ this._removePointer(id);
809
+ continue;
810
+ }
811
+ var targetX = entry.cursor.x * size.width - scrollX;
812
+ var targetY = entry.cursor.y * size.height - scrollY;
813
+ if (entry.x == null || entry.y == null) {
814
+ entry.x = targetX;
815
+ entry.y = targetY;
816
+ } else {
817
+ // Exponential interpolation keeps 50 ms presence updates from jumping.
818
+ entry.x += (targetX - entry.x) * CURSOR_SMOOTHING;
819
+ entry.y += (targetY - entry.y) * CURSOR_SMOOTHING;
820
+ }
821
+ entry.node.style.transform = 'translate3d(' + Math.round(entry.x) + 'px,' + Math.round(entry.y) + 'px,0)';
822
+ }
823
+ this._scheduleFrame();
824
+ }
825
+
826
+ disconnectedCallback() {
827
+ this._lifecycle = (this._lifecycle || 0) + 1;
828
+ this._startPromise = null;
829
+ if (this._onDomReady) {
830
+ document.removeEventListener('DOMContentLoaded', this._onDomReady);
831
+ this._onDomReady = null;
832
+ }
833
+ if (this._unsubscribe) this._unsubscribe();
834
+ if (this._onPointerMove) document.removeEventListener('pointermove', this._onPointerMove);
835
+ if (this._onPointerLeave) document.removeEventListener('pointerleave', this._onPointerLeave);
836
+ if (this._roomRecord) releaseCursorRoom(this._roomRecord, this._presence);
837
+ if (this._frameId != null && typeof window.cancelAnimationFrame === 'function')
838
+ window.cancelAnimationFrame(this._frameId);
839
+ if (this._pointers) {
840
+ var ids = Array.from(this._pointers.keys());
841
+ for (var i = 0; i < ids.length; i++) this._removePointer(ids[i]);
842
+ }
843
+ if (this._overlay && this._overlay.parentNode)
844
+ this._overlay.parentNode.removeChild(this._overlay);
845
+ this._room = null;
846
+ this._roomRecord = null;
847
+ this._presence = null;
848
+ this._overlay = null;
849
+ this._pointers = null;
850
+ this._cursorStates = null;
851
+ this._frameId = null;
852
+ }
853
+ }
854
+ window.customElements.define('swarm-cursors', SwarmCursorsElement);
421
855
  })();
422
856
  `;
@@ -0,0 +1,109 @@
1
+ import { cancelApprovalRequestAndRun } from "../workflows/approval-cancel";
2
+ import { createApprovalFollowUpTask } from "../workflows/approval-notifications";
3
+ import {
4
+ type ApprovalRequest,
5
+ getDbClient,
6
+ getExpiredPendingApprovals,
7
+ getStaleApprovalRequests,
8
+ resolveApprovalRequest,
9
+ } from "./db";
10
+
11
+ export const DEFAULT_APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS = 7;
12
+
13
+ // Days a pending approval request with no explicit expiresAt may wait before
14
+ // the heartbeat sweep cancels it. 0 turns auto-cancellation off.
15
+ //
16
+ // Read DYNAMICALLY, not captured at module load: this module is imported by
17
+ // the heartbeat before `loadGlobalConfigsIntoEnv()` hydrates swarm_config
18
+ // into `process.env`, so a module-level capture would leave a
19
+ // dashboard-saved override permanently inert (even across restarts).
20
+ export function approvalRequestAutoCancellationDays(): number {
21
+ const raw = process.env.APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS;
22
+ if (raw != null && /^\d+$/.test(raw.trim())) {
23
+ return Number.parseInt(raw.trim(), 10);
24
+ }
25
+ return DEFAULT_APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS;
26
+ }
27
+
28
+ /**
29
+ * Cancel every pending request with no explicit expiresAt that is older than
30
+ * APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS. A request that gates a live
31
+ * workflow run cancels that run too. Creates no follow-up task and emits no
32
+ * workflow event.
33
+ */
34
+ export async function autoCancelStaleApprovalRequests(
35
+ now = new Date(),
36
+ ): Promise<{ cancelled: ApprovalRequest[]; runsCancelled: string[] }> {
37
+ const days = approvalRequestAutoCancellationDays();
38
+ if (days === 0) return { cancelled: [], runsCancelled: [] };
39
+
40
+ const cutoff = new Date(now.getTime() - days * 86_400_000).toISOString();
41
+ const stale = await getStaleApprovalRequests({ cutoff });
42
+ const reason = `Auto-cancelled by the approval sweep after ${days} days with no response`;
43
+
44
+ // Each request and its live run cancel in 1 transaction. A failed row
45
+ // rolls back alone and the next tick retries it.
46
+ const cancelled: ApprovalRequest[] = [];
47
+ const runsCancelled: string[] = [];
48
+ for (const row of stale) {
49
+ try {
50
+ const result = await cancelApprovalRequestAndRun(row.id, {
51
+ reason,
52
+ resolvedBy: null,
53
+ slackReason: "auto-cancelled with no response",
54
+ });
55
+ if (!result || "timedOut" in result) continue;
56
+ cancelled.push(result.request);
57
+ if (result.runCancelled && result.request.workflowRunId) {
58
+ runsCancelled.push(result.request.workflowRunId);
59
+ }
60
+ } catch (err) {
61
+ console.error(
62
+ `[approval-sweep] could not cancel approval request ${row.id}: ${err instanceof Error ? err.message : String(err)}`,
63
+ );
64
+ }
65
+ }
66
+
67
+ if (cancelled.length > 0) {
68
+ console.log(
69
+ `[approval-sweep] auto-cancelled ${cancelled.length} stale approval requests and ${runsCancelled.length} workflow runs`,
70
+ );
71
+ }
72
+ return { cancelled, runsCancelled };
73
+ }
74
+
75
+ /**
76
+ * Set `timeout` on every pending request whose explicit expiresAt passed,
77
+ * whatever the state of its workflow run. A standalone request whose source
78
+ * task is still active gets a `hitl.timeout` follow-up task.
79
+ */
80
+ export async function timeoutExpiredApprovalRequests(
81
+ now = new Date(),
82
+ ): Promise<{ timedOut: ApprovalRequest[] }> {
83
+ const expired = await getExpiredPendingApprovals({ now: now.toISOString() });
84
+ const timedOut: ApprovalRequest[] = [];
85
+ for (const row of expired) {
86
+ // The status change and the follow-up task commit together: a crash
87
+ // between them cannot leave a timeout with no follow-up task.
88
+ try {
89
+ const updated = await getDbClient().transaction(async () => {
90
+ const request = await resolveApprovalRequest(row.id, {
91
+ status: "timeout",
92
+ resolutionReason: `Timed out by the approval sweep: no answer before ${row.expiresAt}`,
93
+ });
94
+ if (request) await createApprovalFollowUpTask(request, "hitl.timeout");
95
+ return request;
96
+ });
97
+ if (updated) timedOut.push(updated);
98
+ } catch (err) {
99
+ console.error(
100
+ `[approval-sweep] could not time out approval request ${row.id}: ${err instanceof Error ? err.message : String(err)}`,
101
+ );
102
+ }
103
+ }
104
+
105
+ if (timedOut.length > 0) {
106
+ console.log(`[approval-sweep] timed out ${timedOut.length} expired approval requests`);
107
+ }
108
+ return { timedOut };
109
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Once-per-database guards for seed work that belongs to boot.
3
+ *
4
+ * `createServer()` runs on every new MCP session (`POST /mcp`), and it used to
5
+ * re-run the pricing seed (an 8.6 MB `JSON.parse` plus a 3,400-row
6
+ * `BEGIN IMMEDIATE` insert transaction) and the RBAC seed sync on each one.
7
+ * All of that is synchronous on the API's single event loop, so every agent
8
+ * session start stalled every other route. Both seeds are idempotent, so the
9
+ * first caller for a given database handle does the work and later callers
10
+ * return immediately.
11
+ *
12
+ * The guard is keyed on the live `Database` handle rather than a bare boolean:
13
+ * `closeDb()` + `initDb()` (tests, DB swaps) yields a new handle that has not
14
+ * been seeded, so it is seeded again. A failed seed does not mark the handle
15
+ * done, so the next caller retries and still sees the error.
16
+ */
17
+
18
+ import type { Database } from "bun:sqlite";
19
+ import { getDb } from "./db";
20
+ import { ensureRbacSeedsSynced } from "./rbac-roles";
21
+ import { seedPricingFromModelsDev } from "./seed-pricing";
22
+
23
+ const pricingSeededDbs = new WeakSet<Database>();
24
+ const rbacSyncedDbs = new WeakSet<Database>();
25
+
26
+ /** Seed the pricing table from the vendored models.dev snapshot. */
27
+ export function ensurePricingSeeded(): void {
28
+ const db = getDb();
29
+ if (pricingSeededDbs.has(db)) return;
30
+ seedPricingFromModelsDev();
31
+ pricingSeededDbs.add(db);
32
+ }
33
+
34
+ /** Sync the built-in RBAC roles, permissions and default-role trigger. */
35
+ export function ensureRbacSeeded(): void {
36
+ const db = getDb();
37
+ if (rbacSyncedDbs.has(db)) return;
38
+ ensureRbacSeedsSynced();
39
+ rbacSyncedDbs.add(db);
40
+ }
@@ -167,8 +167,8 @@ function hardSplit(text: string): string[] {
167
167
  while (start < text.length) {
168
168
  const end = Math.min(start + MAX_CHUNK_SIZE, text.length);
169
169
  chunks.push(text.slice(start, end).trim());
170
+ if (end === text.length) break;
170
171
  start = end - CHUNK_OVERLAP;
171
- if (start >= text.length - MIN_CHUNK_SIZE) break;
172
172
  }
173
173
  return chunks.filter((c) => c.length > 0);
174
174
  }
@@ -636,3 +636,23 @@ export function isAgentEligibleForTask(
636
636
 
637
637
  return hasRequiredCapabilities();
638
638
  }
639
+
640
+ /**
641
+ * Tasks created per UTC day for one agent, oldest first, over the last `days`
642
+ * days. Days with no tasks are omitted; the caller fills the gaps. Backs the
643
+ * dashboard activity heatmap on /agents/:id.
644
+ */
645
+ export async function getAgentDailyTaskCounts(
646
+ agentId: string,
647
+ days: number,
648
+ ): Promise<{ date: string; count: number }[]> {
649
+ const since = new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10);
650
+ return getDbClient().query<{ date: string; count: number }>(
651
+ `SELECT substr(createdAt, 1, 10) AS date, COUNT(*) AS count
652
+ FROM agent_tasks
653
+ WHERE agentId = ? AND createdAt >= ?
654
+ GROUP BY date
655
+ ORDER BY date`,
656
+ [agentId, since],
657
+ );
658
+ }