@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.
- package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- 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
|
-
//
|
|
156
|
-
// sibling-anchor jump list)
|
|
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
|
+
}
|
package/src/be/chunking.ts
CHANGED
|
@@ -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
|
}
|
package/src/be/db/agents.ts
CHANGED
|
@@ -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
|
+
}
|