@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
|
@@ -32,7 +32,11 @@ type StubInstance = {
|
|
|
32
32
|
innerHTML: string;
|
|
33
33
|
textContent: string;
|
|
34
34
|
isConnected: boolean;
|
|
35
|
+
style: { cssText: string; transform: string };
|
|
36
|
+
children: StubInstance[];
|
|
37
|
+
hasAttribute?: (name: string) => boolean;
|
|
35
38
|
connectedCallback?: () => void;
|
|
39
|
+
disconnectedCallback?: () => void;
|
|
36
40
|
dispatchEvent: (evt: unknown) => boolean;
|
|
37
41
|
};
|
|
38
42
|
|
|
@@ -44,24 +48,52 @@ type StubInstance = {
|
|
|
44
48
|
* callers can simulate the real-browser parse-order race
|
|
45
49
|
* (`connectedCallback` fires → children get appended → microtask drains).
|
|
46
50
|
*/
|
|
47
|
-
function makeRig(
|
|
51
|
+
function makeRig(
|
|
52
|
+
swarmSdk?: unknown,
|
|
53
|
+
readyState = "complete",
|
|
54
|
+
sdkConstructor?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> },
|
|
55
|
+
): {
|
|
48
56
|
Ctor: new () => StubInstance;
|
|
57
|
+
CursorsCtor: new () => StubInstance;
|
|
58
|
+
SdkCtor?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
|
|
59
|
+
body: StubInstance;
|
|
49
60
|
setText: (el: StubInstance, attrs: Record<string, string>, text: string) => void;
|
|
61
|
+
fireDocumentEvent: (name: string, event: unknown) => void;
|
|
62
|
+
fireNextFrame: () => void;
|
|
50
63
|
flushMicrotasks: () => Promise<void>;
|
|
51
64
|
} {
|
|
52
65
|
const registry = new Map<string, new () => StubInstance>();
|
|
66
|
+
const documentListeners = new Map<string, ((event: unknown) => void)[]>();
|
|
67
|
+
const animationFrames = new Map<number, (time: number) => void>();
|
|
68
|
+
let nextAnimationFrame = 1;
|
|
53
69
|
|
|
54
70
|
class StubHTMLElement {
|
|
55
71
|
innerHTML = "";
|
|
56
72
|
textContent = "";
|
|
57
73
|
isConnected = true;
|
|
74
|
+
style = { cssText: "", transform: "" };
|
|
75
|
+
parentNode: StubHTMLElement | null = null;
|
|
76
|
+
children: StubHTMLElement[] = [];
|
|
58
77
|
_attrs: Record<string, string> = {};
|
|
59
78
|
getAttribute(name: string): string | null {
|
|
60
79
|
return this._attrs[name] ?? null;
|
|
61
80
|
}
|
|
81
|
+
hasAttribute(name: string): boolean {
|
|
82
|
+
return Object.hasOwn(this._attrs, name);
|
|
83
|
+
}
|
|
62
84
|
setAttribute(name: string, value: string): void {
|
|
63
85
|
this._attrs[name] = value;
|
|
64
86
|
}
|
|
87
|
+
appendChild(child: StubHTMLElement): StubHTMLElement {
|
|
88
|
+
child.parentNode = this;
|
|
89
|
+
this.children.push(child);
|
|
90
|
+
return child;
|
|
91
|
+
}
|
|
92
|
+
removeChild(child: StubHTMLElement): StubHTMLElement {
|
|
93
|
+
this.children = this.children.filter((item) => item !== child);
|
|
94
|
+
child.parentNode = null;
|
|
95
|
+
return child;
|
|
96
|
+
}
|
|
65
97
|
connectedCallback?(): void;
|
|
66
98
|
closest(_selector: string): null {
|
|
67
99
|
return null;
|
|
@@ -85,6 +117,20 @@ function makeRig(): {
|
|
|
85
117
|
|
|
86
118
|
const win = {
|
|
87
119
|
customElements,
|
|
120
|
+
swarmSdk: swarmSdk as
|
|
121
|
+
| { room?: (name: string, options?: unknown) => Promise<unknown> }
|
|
122
|
+
| undefined,
|
|
123
|
+
SwarmSDK: sdkConstructor,
|
|
124
|
+
innerWidth: 1000,
|
|
125
|
+
innerHeight: 500,
|
|
126
|
+
scrollX: 0,
|
|
127
|
+
scrollY: 0,
|
|
128
|
+
requestAnimationFrame: (callback: (time: number) => void) => {
|
|
129
|
+
const id = nextAnimationFrame++;
|
|
130
|
+
animationFrames.set(id, callback);
|
|
131
|
+
return id;
|
|
132
|
+
},
|
|
133
|
+
cancelAnimationFrame: (id: number) => animationFrames.delete(id),
|
|
88
134
|
swarmUi: undefined as { renderDiff?: (rootEl: unknown, data: unknown) => void } | undefined,
|
|
89
135
|
HTMLElement: StubHTMLElement,
|
|
90
136
|
CustomEvent: class {
|
|
@@ -96,10 +142,27 @@ function makeRig(): {
|
|
|
96
142
|
};
|
|
97
143
|
|
|
98
144
|
// Provide `document` stubs the jump-list path uses.
|
|
145
|
+
const body = new StubHTMLElement();
|
|
99
146
|
const doc = {
|
|
147
|
+
readyState,
|
|
148
|
+
documentElement: { scrollWidth: 1000, scrollHeight: 500, clientWidth: 1000, clientHeight: 500 },
|
|
149
|
+
body,
|
|
150
|
+
createElement: () => new StubHTMLElement(),
|
|
100
151
|
querySelectorAll: () => [],
|
|
101
|
-
addEventListener: () => {
|
|
102
|
-
|
|
152
|
+
addEventListener: (name: string, handler: (event: unknown) => void) => {
|
|
153
|
+
const handlers = documentListeners.get(name) ?? [];
|
|
154
|
+
handlers.push(handler);
|
|
155
|
+
documentListeners.set(name, handlers);
|
|
156
|
+
},
|
|
157
|
+
removeEventListener: (name: string, handler: (event: unknown) => void) => {
|
|
158
|
+
documentListeners.set(
|
|
159
|
+
name,
|
|
160
|
+
(documentListeners.get(name) ?? []).filter((item) => item !== handler),
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
fireEvent: (name: string, event: unknown) => {
|
|
164
|
+
for (const handler of documentListeners.get(name) ?? []) handler(event);
|
|
165
|
+
},
|
|
103
166
|
};
|
|
104
167
|
|
|
105
168
|
// Evaluate SWARM_UI_JS with our stubs in scope. The IIFE inside the
|
|
@@ -113,9 +176,9 @@ function makeRig(): {
|
|
|
113
176
|
"CustomEvent",
|
|
114
177
|
"queueMicrotask",
|
|
115
178
|
"console",
|
|
116
|
-
`${SWARM_UI_JS}\nreturn window.customElements.get('swarm-diff');`,
|
|
179
|
+
`${SWARM_UI_JS}\nreturn { diff: window.customElements.get('swarm-diff'), cursors: window.customElements.get('swarm-cursors'), sdk: window.SwarmSDK };`,
|
|
117
180
|
);
|
|
118
|
-
const
|
|
181
|
+
const constructors = factory(
|
|
119
182
|
win,
|
|
120
183
|
customElements,
|
|
121
184
|
doc,
|
|
@@ -123,20 +186,39 @@ function makeRig(): {
|
|
|
123
186
|
win.CustomEvent,
|
|
124
187
|
queueMicrotask,
|
|
125
188
|
console,
|
|
126
|
-
) as
|
|
189
|
+
) as {
|
|
190
|
+
diff?: new () => StubInstance;
|
|
191
|
+
cursors?: new () => StubInstance;
|
|
192
|
+
sdk?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
|
|
193
|
+
};
|
|
194
|
+
const Ctor = constructors.diff;
|
|
127
195
|
if (!Ctor) throw new Error("custom element did not register");
|
|
196
|
+
const CursorsCtor = constructors.cursors;
|
|
197
|
+
if (!CursorsCtor) throw new Error("swarm-cursors custom element did not register");
|
|
128
198
|
|
|
129
199
|
return {
|
|
130
200
|
Ctor,
|
|
201
|
+
CursorsCtor,
|
|
202
|
+
SdkCtor: constructors.sdk,
|
|
203
|
+
body,
|
|
131
204
|
setText(el, attrs, text) {
|
|
132
205
|
(el as unknown as { _attrs: Record<string, string> })._attrs = attrs;
|
|
133
206
|
el.textContent = text;
|
|
134
207
|
},
|
|
208
|
+
fireDocumentEvent(name, event) {
|
|
209
|
+
doc.fireEvent(name, event);
|
|
210
|
+
},
|
|
211
|
+
fireNextFrame() {
|
|
212
|
+
const next = animationFrames.entries().next().value as
|
|
213
|
+
| [number, (time: number) => void]
|
|
214
|
+
| undefined;
|
|
215
|
+
if (!next) return;
|
|
216
|
+
animationFrames.delete(next[0]);
|
|
217
|
+
next[1](0);
|
|
218
|
+
},
|
|
135
219
|
async flushMicrotasks() {
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
await Promise.resolve();
|
|
139
|
-
await Promise.resolve();
|
|
220
|
+
// Drain the chained async room-open and render callbacks.
|
|
221
|
+
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
140
222
|
},
|
|
141
223
|
};
|
|
142
224
|
}
|
|
@@ -164,6 +246,7 @@ describe("SWARM_UI_JS", () => {
|
|
|
164
246
|
test("defines swarm-diff + swarm-diff-jumps custom elements", () => {
|
|
165
247
|
expect(SWARM_UI_JS).toContain("customElements.define('swarm-diff'");
|
|
166
248
|
expect(SWARM_UI_JS).toContain("customElements.define('swarm-diff-jumps'");
|
|
249
|
+
expect(SWARM_UI_JS).toContain("customElements.define('swarm-cursors'");
|
|
167
250
|
});
|
|
168
251
|
|
|
169
252
|
test("exposes window.swarmUi.renderDiff as a programmatic entry point", () => {
|
|
@@ -172,6 +255,229 @@ describe("SWARM_UI_JS", () => {
|
|
|
172
255
|
});
|
|
173
256
|
});
|
|
174
257
|
|
|
258
|
+
describe("<swarm-cursors> presence merge", () => {
|
|
259
|
+
test("reuses a page room's schema and preserves page presence fields", async () => {
|
|
260
|
+
const sent: unknown[] = [];
|
|
261
|
+
const roomOptions: unknown[] = [];
|
|
262
|
+
const roomHandlers = new Map<string, (value: unknown) => void>();
|
|
263
|
+
let closeCount = 0;
|
|
264
|
+
const room = {
|
|
265
|
+
me: { userId: "self" },
|
|
266
|
+
presence: {
|
|
267
|
+
peers: [] as unknown[],
|
|
268
|
+
set(data: unknown) {
|
|
269
|
+
sent.push(structuredClone(data));
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
on(name: string, handler: (value: unknown) => void) {
|
|
273
|
+
roomHandlers.set(name, handler);
|
|
274
|
+
return () => roomHandlers.delete(name);
|
|
275
|
+
},
|
|
276
|
+
async close() {
|
|
277
|
+
closeCount++;
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
let pageRoomSchema: number | undefined;
|
|
281
|
+
class PageSdk {
|
|
282
|
+
async room(_name?: string, options?: unknown) {
|
|
283
|
+
const requestedSchema =
|
|
284
|
+
(options as { schemaVersion?: number } | undefined)?.schemaVersion ?? 1;
|
|
285
|
+
if (pageRoomSchema !== undefined && requestedSchema !== pageRoomSchema)
|
|
286
|
+
throw new Error("Room already opened with a different schema version");
|
|
287
|
+
pageRoomSchema = requestedSchema;
|
|
288
|
+
roomOptions.push(options);
|
|
289
|
+
return room;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const swarmSdk = new PageSdk();
|
|
293
|
+
const rig = makeRig(swarmSdk, "loading", PageSdk);
|
|
294
|
+
if (!rig.SdkCtor) throw new Error("SDK constructor was not wrapped");
|
|
295
|
+
expect(swarmSdk).toBeInstanceOf(rig.SdkCtor);
|
|
296
|
+
const pageSdk = new rig.SdkCtor();
|
|
297
|
+
|
|
298
|
+
// Page code may set its data before the cursor element connects.
|
|
299
|
+
const board = (await pageSdk.room(undefined, { schemaVersion: 2 })) as typeof room;
|
|
300
|
+
const mismatchedOpens = await Promise.allSettled([
|
|
301
|
+
pageSdk.room(undefined, { schemaVersion: 3 }),
|
|
302
|
+
pageSdk.room(undefined, { schemaVersion: 4 }),
|
|
303
|
+
]);
|
|
304
|
+
expect(mismatchedOpens.map(({ status }) => status)).toEqual(["rejected", "rejected"]);
|
|
305
|
+
board.presence.set({ selectedCard: "card-1" });
|
|
306
|
+
|
|
307
|
+
const cursors = new rig.CursorsCtor();
|
|
308
|
+
rig.setText(cursors, { room: "default" }, "");
|
|
309
|
+
cursors.connectedCallback?.();
|
|
310
|
+
rig.fireDocumentEvent("DOMContentLoaded", undefined);
|
|
311
|
+
await rig.flushMicrotasks();
|
|
312
|
+
rig.fireDocumentEvent("pointermove", { pageX: 250, pageY: 125, clientX: 250, clientY: 125 });
|
|
313
|
+
|
|
314
|
+
expect(sent.at(-1)).toEqual({ selectedCard: "card-1", __cursor: { x: 0.25, y: 0.25 } });
|
|
315
|
+
|
|
316
|
+
// Later page updates also keep the active cursor in the replacement payload.
|
|
317
|
+
board.presence.set({ selectedCard: "card-2", tool: "pen" });
|
|
318
|
+
expect(sent.at(-1)).toEqual({
|
|
319
|
+
selectedCard: "card-2",
|
|
320
|
+
tool: "pen",
|
|
321
|
+
__cursor: { x: 0.25, y: 0.25 },
|
|
322
|
+
});
|
|
323
|
+
rig.fireDocumentEvent("pointerleave", undefined);
|
|
324
|
+
expect(sent.at(-1)).toEqual({ selectedCard: "card-2", tool: "pen" });
|
|
325
|
+
cursors.disconnectedCallback?.();
|
|
326
|
+
await rig.flushMicrotasks();
|
|
327
|
+
expect(roomOptions).toEqual([{ schemaVersion: 2 }]);
|
|
328
|
+
expect(closeCount).toBe(0);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("closes a cursor-only room when the element disconnects", async () => {
|
|
332
|
+
let closeCount = 0;
|
|
333
|
+
const room = {
|
|
334
|
+
me: { userId: "self" },
|
|
335
|
+
presence: { peers: [], set: () => {} },
|
|
336
|
+
on: () => () => {},
|
|
337
|
+
async close() {
|
|
338
|
+
closeCount++;
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const rig = makeRig({ room: async () => room });
|
|
342
|
+
const cursors = new rig.CursorsCtor();
|
|
343
|
+
rig.setText(cursors, { room: "cursor-only" }, "");
|
|
344
|
+
cursors.connectedCallback?.();
|
|
345
|
+
await rig.flushMicrotasks();
|
|
346
|
+
|
|
347
|
+
cursors.disconnectedCallback?.();
|
|
348
|
+
await rig.flushMicrotasks();
|
|
349
|
+
expect(closeCount).toBe(1);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("keeps a shared cursor active while another cursor element remains", async () => {
|
|
353
|
+
const sent: unknown[] = [];
|
|
354
|
+
let closeCount = 0;
|
|
355
|
+
const room = {
|
|
356
|
+
me: { userId: "self" },
|
|
357
|
+
presence: {
|
|
358
|
+
peers: [],
|
|
359
|
+
set(data: unknown) {
|
|
360
|
+
sent.push(structuredClone(data));
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
on: () => () => {},
|
|
364
|
+
async close() {
|
|
365
|
+
closeCount++;
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
const rig = makeRig({ room: async () => room });
|
|
369
|
+
const first = new rig.CursorsCtor();
|
|
370
|
+
const second = new rig.CursorsCtor();
|
|
371
|
+
rig.setText(first, { room: "shared" }, "");
|
|
372
|
+
rig.setText(second, { room: "shared" }, "");
|
|
373
|
+
first.connectedCallback?.();
|
|
374
|
+
await rig.flushMicrotasks();
|
|
375
|
+
|
|
376
|
+
rig.fireDocumentEvent("pointermove", { pageX: 100, pageY: 100, clientX: 100, clientY: 100 });
|
|
377
|
+
second.connectedCallback?.();
|
|
378
|
+
await rig.flushMicrotasks();
|
|
379
|
+
first.disconnectedCallback?.();
|
|
380
|
+
expect(sent.at(-1)).toEqual({ __cursor: { x: 0.1, y: 0.2 } });
|
|
381
|
+
expect(closeCount).toBe(0);
|
|
382
|
+
|
|
383
|
+
second.disconnectedCallback?.();
|
|
384
|
+
await rig.flushMicrotasks();
|
|
385
|
+
expect(sent.at(-1)).toEqual({});
|
|
386
|
+
expect(closeCount).toBe(1);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("uses a page room reopened after close", async () => {
|
|
390
|
+
const firstSent: unknown[] = [];
|
|
391
|
+
const reopenedSent: unknown[] = [];
|
|
392
|
+
let firstCloseCount = 0;
|
|
393
|
+
let reopenedCloseCount = 0;
|
|
394
|
+
const makeRoom = (sent: unknown[], onClose: () => void) => ({
|
|
395
|
+
me: { userId: "self" },
|
|
396
|
+
presence: {
|
|
397
|
+
peers: [],
|
|
398
|
+
set(data: unknown) {
|
|
399
|
+
sent.push(structuredClone(data));
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
on: () => () => {},
|
|
403
|
+
async close() {
|
|
404
|
+
onClose();
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
const firstRoom = makeRoom(firstSent, () => firstCloseCount++);
|
|
408
|
+
const reopenedRoom = makeRoom(reopenedSent, () => reopenedCloseCount++);
|
|
409
|
+
let roomOpens = 0;
|
|
410
|
+
const swarmSdk = {
|
|
411
|
+
room: async (_name?: string) => (roomOpens++ === 0 ? firstRoom : reopenedRoom),
|
|
412
|
+
};
|
|
413
|
+
const rig = makeRig(swarmSdk);
|
|
414
|
+
const board = await swarmSdk.room("board");
|
|
415
|
+
await board.close();
|
|
416
|
+
await swarmSdk.room("board");
|
|
417
|
+
|
|
418
|
+
const cursors = new rig.CursorsCtor();
|
|
419
|
+
rig.setText(cursors, { room: "board" }, "");
|
|
420
|
+
cursors.connectedCallback?.();
|
|
421
|
+
await rig.flushMicrotasks();
|
|
422
|
+
rig.fireDocumentEvent("pointermove", { pageX: 300, pageY: 100, clientX: 300, clientY: 100 });
|
|
423
|
+
|
|
424
|
+
expect(roomOpens).toBe(2);
|
|
425
|
+
expect(firstSent).toHaveLength(0);
|
|
426
|
+
expect(reopenedSent.at(-1)).toEqual({ __cursor: { x: 0.3, y: 0.2 } });
|
|
427
|
+
cursors.disconnectedCallback?.();
|
|
428
|
+
expect(reopenedCloseCount).toBe(0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("renders remote people and removes an unchanged cursor after idle", async () => {
|
|
432
|
+
const roomHandlers = new Map<string, (value: unknown) => void>();
|
|
433
|
+
const room = {
|
|
434
|
+
me: { userId: "self" },
|
|
435
|
+
presence: { peers: [], set: () => {} },
|
|
436
|
+
on(name: string, handler: (value: unknown) => void) {
|
|
437
|
+
roomHandlers.set(name, handler);
|
|
438
|
+
return () => roomHandlers.delete(name);
|
|
439
|
+
},
|
|
440
|
+
async close() {},
|
|
441
|
+
};
|
|
442
|
+
const rig = makeRig({ room: async () => room });
|
|
443
|
+
const cursors = new rig.CursorsCtor();
|
|
444
|
+
rig.setText(cursors, { room: "board" }, "");
|
|
445
|
+
cursors.connectedCallback?.();
|
|
446
|
+
|
|
447
|
+
const realNow = Date.now;
|
|
448
|
+
let now = 1000;
|
|
449
|
+
Date.now = () => now;
|
|
450
|
+
try {
|
|
451
|
+
await rig.flushMicrotasks();
|
|
452
|
+
const peers = [
|
|
453
|
+
{ userId: "self", name: "Self", kind: "human", data: { __cursor: { x: 0.1, y: 0.1 } } },
|
|
454
|
+
{ userId: "agent-1", name: "Agent", kind: "agent", data: { __cursor: { x: 0.9, y: 0.9 } } },
|
|
455
|
+
{
|
|
456
|
+
userId: "person-1",
|
|
457
|
+
name: "Avery",
|
|
458
|
+
kind: "human",
|
|
459
|
+
data: { __cursor: { x: 0.25, y: 0.5 } },
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
roomHandlers.get("presence")?.(peers);
|
|
463
|
+
|
|
464
|
+
const overlay = rig.body.children[0] as StubInstance;
|
|
465
|
+
expect(overlay.children).toHaveLength(1);
|
|
466
|
+
expect(overlay.children[0]?.innerHTML).toContain("Avery");
|
|
467
|
+
expect(overlay.children[0]?.innerHTML).toContain("<svg");
|
|
468
|
+
rig.fireNextFrame();
|
|
469
|
+
expect(overlay.children[0]?.style.transform).toBe("translate3d(250px,250px,0)");
|
|
470
|
+
|
|
471
|
+
now = 5000;
|
|
472
|
+
roomHandlers.get("presence")?.(peers);
|
|
473
|
+
expect(overlay.children).toHaveLength(0);
|
|
474
|
+
} finally {
|
|
475
|
+
Date.now = realNow;
|
|
476
|
+
cursors.disconnectedCallback?.();
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
175
481
|
describe("<swarm-diff> render", () => {
|
|
176
482
|
test("constructs and renders without throwing on the example input", async () => {
|
|
177
483
|
const html = await renderViaStub(JSON.stringify(EXAMPLE_HUNK), {
|
|
@@ -831,7 +831,7 @@ describe("worker task follow-up creation", () => {
|
|
|
831
831
|
expect(rows[0]!.task).not.toContain("post the URL");
|
|
832
832
|
});
|
|
833
833
|
|
|
834
|
-
test("inherits followUpConfig from parent
|
|
834
|
+
test("a continuation inherits followUpConfig from its parent when it has no override", async () => {
|
|
835
835
|
await createAgent({
|
|
836
836
|
name: "follow-up-lead-inheritance",
|
|
837
837
|
isLead: true,
|
|
@@ -851,6 +851,7 @@ describe("worker task follow-up creation", () => {
|
|
|
851
851
|
const child = await createTaskExtended("Child task", {
|
|
852
852
|
agentId: worker.id,
|
|
853
853
|
parentTaskId: parent.id,
|
|
854
|
+
inheritParentFollowUpConfig: true,
|
|
854
855
|
});
|
|
855
856
|
await startTask(child.id);
|
|
856
857
|
|
|
@@ -206,7 +206,9 @@ describe("GET /api/tasks — requestedByUserId route wiring", () => {
|
|
|
206
206
|
requestedByUserId: routeUserId,
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
const res = await listTasks(
|
|
209
|
+
const res = await listTasks(
|
|
210
|
+
"&requestedByUserId=00000000-0000-4000-8000-000000000000&includeTotal=true",
|
|
211
|
+
);
|
|
210
212
|
expect(res.status).toBe(200);
|
|
211
213
|
const body = (await res.json()) as { tasks: unknown[]; total: number };
|
|
212
214
|
expect(body.tasks).toEqual([]);
|
|
@@ -378,7 +378,7 @@ describe("Tool Annotations & Classification", () => {
|
|
|
378
378
|
// Includes 11 skill tools, 7 MCP server tools, reusable script tools, and the
|
|
379
379
|
// native Kapso/WhatsApp tools (register/unregister number + send/reply message).
|
|
380
380
|
expect(count).toBeGreaterThanOrEqual(45);
|
|
381
|
-
expect(count).toBeLessThanOrEqual(
|
|
381
|
+
expect(count).toBeLessThanOrEqual(149);
|
|
382
382
|
});
|
|
383
383
|
|
|
384
384
|
test("core tools are fewer than deferred tools", () => {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { createServer as createHttpServer, type Server } from "node:http";
|
|
4
|
+
import {
|
|
5
|
+
closeDb,
|
|
6
|
+
createApprovalRequest,
|
|
7
|
+
createScheduledTask,
|
|
8
|
+
initDb,
|
|
9
|
+
listApprovalRequestSummaries,
|
|
10
|
+
listApprovalRequests,
|
|
11
|
+
resolveApprovalRequest,
|
|
12
|
+
} from "../be/db";
|
|
13
|
+
import { handleApprovalRequests } from "../http/approval-requests";
|
|
14
|
+
import { handleStats } from "../http/stats";
|
|
15
|
+
import { listenOnFreePort } from "./test-net";
|
|
16
|
+
|
|
17
|
+
const TEST_DB_PATH = "./test-ui-list-slim-projections.sqlite";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `/api/scheduled-tasks` and `/api/approval-requests` serve the UI list pages.
|
|
21
|
+
* `fields=slim` is opt-in: callers that don't pass it keep the full rows.
|
|
22
|
+
*/
|
|
23
|
+
describe("UI list slim projections", () => {
|
|
24
|
+
let server: Server;
|
|
25
|
+
let baseUrl = "";
|
|
26
|
+
let approvalId = "";
|
|
27
|
+
const template = `T${"x".repeat(5000)}`;
|
|
28
|
+
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
31
|
+
try {
|
|
32
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
initDb(TEST_DB_PATH);
|
|
36
|
+
|
|
37
|
+
server = createHttpServer(async (req, res) => {
|
|
38
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
39
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
40
|
+
if (await handleStats(req, res, segments, url.searchParams)) return;
|
|
41
|
+
if (await handleApprovalRequests(req, res, segments, url.searchParams)) return;
|
|
42
|
+
res.writeHead(404);
|
|
43
|
+
res.end();
|
|
44
|
+
});
|
|
45
|
+
baseUrl = `http://127.0.0.1:${await listenOnFreePort(server)}`;
|
|
46
|
+
|
|
47
|
+
await createScheduledTask({
|
|
48
|
+
name: "slim-list-schedule",
|
|
49
|
+
taskTemplate: template,
|
|
50
|
+
cronExpression: "0 9 * * *",
|
|
51
|
+
description: "daily report",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
approvalId = crypto.randomUUID();
|
|
55
|
+
await createApprovalRequest({
|
|
56
|
+
id: approvalId,
|
|
57
|
+
title: "Ship it?",
|
|
58
|
+
questions: [
|
|
59
|
+
{ id: "q1", type: "approval", label: "Approve?", description: "D".repeat(2000) },
|
|
60
|
+
{ id: "q2", type: "text", label: "Why?" },
|
|
61
|
+
],
|
|
62
|
+
approvers: { policy: "any" },
|
|
63
|
+
sourceTaskId: "source-task-1",
|
|
64
|
+
timeoutSeconds: 3600,
|
|
65
|
+
notificationChannels: [{ channel: "slack", target: "C123" }],
|
|
66
|
+
});
|
|
67
|
+
await resolveApprovalRequest(approvalId, {
|
|
68
|
+
status: "approved",
|
|
69
|
+
responses: { q1: { approved: true }, q2: "because" },
|
|
70
|
+
resolvedBy: "user-1",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterAll(async () => {
|
|
75
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
76
|
+
closeDb();
|
|
77
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
78
|
+
try {
|
|
79
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
async function get(path: string) {
|
|
85
|
+
const res = await fetch(`${baseUrl}${path}`);
|
|
86
|
+
expect(res.status).toBe(200);
|
|
87
|
+
return (await res.json()) as Record<string, Array<Record<string, unknown>>>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
test("/api/scheduled-tasks keeps full rows by default", async () => {
|
|
91
|
+
const { scheduledTasks } = await get("/api/scheduled-tasks");
|
|
92
|
+
const row = scheduledTasks.find((s) => s.name === "slim-list-schedule");
|
|
93
|
+
expect(row?.taskTemplate).toBe(template);
|
|
94
|
+
expect(row && "taskTemplatePreview" in row).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("/api/scheduled-tasks?fields=slim swaps taskTemplate for a preview", async () => {
|
|
98
|
+
const { scheduledTasks } = await get("/api/scheduled-tasks?fields=slim");
|
|
99
|
+
const row = scheduledTasks.find((s) => s.name === "slim-list-schedule");
|
|
100
|
+
expect(row).toBeDefined();
|
|
101
|
+
expect(row && "taskTemplate" in row).toBe(false);
|
|
102
|
+
expect(String(row?.taskTemplatePreview).length).toBeLessThan(template.length);
|
|
103
|
+
// Every other field the schedules list page reads is identical to the full row.
|
|
104
|
+
const full = (await get("/api/scheduled-tasks")).scheduledTasks.find(
|
|
105
|
+
(s) => s.name === "slim-list-schedule",
|
|
106
|
+
);
|
|
107
|
+
const { taskTemplate: _omitted, ...fullWithoutTemplate } = full ?? {};
|
|
108
|
+
const { taskTemplatePreview: _preview, ...slimWithoutPreview } = row ?? {};
|
|
109
|
+
expect(slimWithoutPreview).toEqual(fullWithoutTemplate);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("/api/approval-requests keeps full rows by default", async () => {
|
|
113
|
+
const { approvalRequests } = await get("/api/approval-requests");
|
|
114
|
+
const row = approvalRequests.find((r) => r.id === approvalId);
|
|
115
|
+
expect(row?.questions).toHaveLength(2);
|
|
116
|
+
expect(row?.responses).toEqual({ q1: { approved: true }, q2: "because" });
|
|
117
|
+
expect(row && "questionCount" in row).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("/api/approval-requests?fields=slim drops bodies and counts questions", async () => {
|
|
121
|
+
const { approvalRequests } = await get("/api/approval-requests?fields=slim&status=approved");
|
|
122
|
+
const row = approvalRequests.find((r) => r.id === approvalId);
|
|
123
|
+
expect(row).toBeDefined();
|
|
124
|
+
for (const dropped of [
|
|
125
|
+
"questions",
|
|
126
|
+
"approvers",
|
|
127
|
+
"responses",
|
|
128
|
+
"resolutionReason",
|
|
129
|
+
"notificationChannels",
|
|
130
|
+
]) {
|
|
131
|
+
expect(row && dropped in row).toBe(false);
|
|
132
|
+
}
|
|
133
|
+
expect(row?.questionCount).toBe(2);
|
|
134
|
+
expect(row?.title).toBe("Ship it?");
|
|
135
|
+
expect(row?.status).toBe("approved");
|
|
136
|
+
expect(row?.resolvedBy).toBe("user-1");
|
|
137
|
+
expect(row?.sourceTaskId).toBe("source-task-1");
|
|
138
|
+
expect(row?.workflowRunId).toBeNull();
|
|
139
|
+
expect(typeof row?.createdAt).toBe("string");
|
|
140
|
+
expect(typeof row?.expiresAt).toBe("string");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("slim and full DB reads agree on filters, order and limit", async () => {
|
|
144
|
+
for (let i = 0; i < 3; i++) {
|
|
145
|
+
await createApprovalRequest({
|
|
146
|
+
id: crypto.randomUUID(),
|
|
147
|
+
title: `pending ${i}`,
|
|
148
|
+
questions: [{ id: "q1", type: "boolean", label: "Ok?" }],
|
|
149
|
+
approvers: { policy: "any" },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const filters = { status: "pending", limit: 2 };
|
|
153
|
+
const full = await listApprovalRequests(filters);
|
|
154
|
+
const slim = await listApprovalRequestSummaries(filters);
|
|
155
|
+
expect(slim.map((r) => r.id)).toEqual(full.map((r) => r.id));
|
|
156
|
+
expect(slim.map((r) => r.questionCount)).toEqual(full.map((r) => r.questions.length));
|
|
157
|
+
});
|
|
158
|
+
});
|