@desplega.ai/agent-swarm 2.4.3 → 2.4.5
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-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
- package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
- package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
- package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
- package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
- package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
- package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
- package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
- package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
- package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
- package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
- package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
- package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
- package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
- package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
- package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
- package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
- package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
- package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
- package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
- package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
- package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
- package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
- package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
- package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
- package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
- package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
- package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
- package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
- package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
- package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
- package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
- package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
- package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
- package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
- package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
- package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
- package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
- package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
- package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
- package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
- package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
- package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
- package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
- package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
- package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
- package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
- package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
- package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
- package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
- package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
- package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
- package/dist/cli.js +45 -20
- package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
- package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
- package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
- package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
- package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
- package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
- package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
- package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
- package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
- package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
- package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
- package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
- package/dist/handlers-7gw64dxw.js +39 -0
- package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
- package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
- package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
- package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
- package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
- package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
- package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
- package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
- package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
- package/dist/index-tfb26zhy.js +41 -0
- package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
- package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
- package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
- package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
- package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
- package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
- package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
- package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
- package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
- package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
- package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
- package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
- package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
- package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
- package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
- package/dist/worker-token-xf11yyds.js +136 -0
- package/openapi.json +303 -13
- package/package.json +8 -8
- package/src/apps/action-runner.ts +4 -0
- package/src/apps/catalog.generated.json +209 -4
- package/src/apps/page-validator.ts +21 -3
- package/src/be/chunking.ts +42 -9
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +14 -1
- package/src/be/db.ts +5 -1
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/232_agent_session_tokens.sql +41 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/swarm-config-guard.ts +40 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/be/users.ts +169 -12
- package/src/cli.tsx +24 -0
- package/src/commands/worker-token.ts +164 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +7 -0
- package/src/fs/provider.ts +13 -1
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/apps.ts +43 -5
- package/src/http/codex-oauth-keep-warm.ts +27 -9
- package/src/http/config.ts +5 -3
- package/src/http/core.ts +2 -0
- package/src/http/fs.ts +54 -34
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/session-access.ts +51 -2
- package/src/http/sessions.ts +249 -10
- package/src/http/tasks.ts +4 -32
- package/src/http/users.ts +12 -1
- package/src/integrations/chat-sdk/adapters.ts +7 -3
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +120 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +234 -4
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-adapter.ts +33 -13
- package/src/providers/acp-swarm-events.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/codex-oauth/storage.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/rbac/legacy-policy.ts +3 -1
- package/src/rbac/permissions.ts +4 -0
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.ts +4 -4
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/acp-adapter.test.ts +10 -23
- package/src/tests/acp-fake-agent.ts +36 -0
- package/src/tests/acp-swarm-events.test.ts +1 -1
- package/src/tests/agent-session-tokens.test.ts +452 -0
- package/src/tests/apps-action-parent-task.test.ts +295 -0
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +558 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/config-session-auth.test.ts +14 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/grok-adapter.test.ts +3 -3
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +265 -1
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/session-route-posture.test.ts +2 -0
- package/src/tests/swarm-config-export-to-env.test.ts +22 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/worker-token-cli.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-system-one-extraction.test.ts +805 -0
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/types.ts +8 -1
- package/src/utils/config-env-export.ts +3 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/request-auth-context.ts +2 -1
- package/src/utils/transcription.ts +246 -0
- package/src/workflows/definition.ts +7 -1
- package/src/workflows/engine.ts +18 -12
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +84 -262
- package/src/workflows/executors/system-one-extract-wire.ts +57 -0
- package/src/workflows/executors/system-one-extraction.ts +830 -0
- package/src/workflows/executors/system-one-providers.ts +316 -114
- package/src/workflows/executors/system-one-transport.ts +264 -0
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/official/lead/HEARTBEAT.md +1 -1
- package/templates/skills/apps/SKILL.md +31 -7
- package/templates/skills/apps/content.md +31 -7
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +8 -8
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/dist/cli-v1sjhn52.js +0 -7
- package/dist/handlers-2qjw434w.js +0 -39
- package/dist/index-d0g18bax.js +0 -41
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import * as db from "../be/db";
|
|
4
5
|
import {
|
|
5
6
|
closeDb,
|
|
6
7
|
createAgent,
|
|
@@ -8,9 +9,16 @@ import {
|
|
|
8
9
|
getApprovalRequestById,
|
|
9
10
|
initDb,
|
|
10
11
|
startTask,
|
|
12
|
+
upsertSwarmConfig,
|
|
11
13
|
} from "../be/db";
|
|
12
14
|
import gate from "../be/seed-scripts/catalog/heartbeat-jev-gate";
|
|
13
15
|
import { registerRequestHumanInputTool } from "../tools/request-human-input";
|
|
16
|
+
import { registerSystemOneDecideTool } from "../tools/system-one-decide";
|
|
17
|
+
import type { ExecutorDependencies } from "../workflows/executors/base";
|
|
18
|
+
import {
|
|
19
|
+
SystemOneDecisionConfigSchema,
|
|
20
|
+
SystemOneDecisionExecutor,
|
|
21
|
+
} from "../workflows/executors/system-one-decision";
|
|
14
22
|
|
|
15
23
|
const LEAD = "11111111-2222-4333-8444-555555555555";
|
|
16
24
|
const NOW = Date.parse("2026-10-15T14:00:00.000Z");
|
|
@@ -176,6 +184,51 @@ const jevFails = (message: string) => async () => ({
|
|
|
176
184
|
status: 200,
|
|
177
185
|
data: { success: false, message },
|
|
178
186
|
});
|
|
187
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Stands in for `ctx.swarm.systemOne_decide` with the real executor behind it, as
|
|
191
|
+
* the tool runs it: Workers AI answers in its REST envelope, and the executor
|
|
192
|
+
* unwraps it. `urls` records where each request went.
|
|
193
|
+
*/
|
|
194
|
+
function clefDecide(p: number) {
|
|
195
|
+
const urls: string[] = [];
|
|
196
|
+
const executor = new SystemOneDecisionExecutor({ db } as unknown as ExecutorDependencies, {
|
|
197
|
+
env: {},
|
|
198
|
+
getApiKey: async (id) => (id === "cloudflare" ? "cf-example-token" : null),
|
|
199
|
+
getServerUrl: async (id) => (id === "cloudflare" ? CF_ACCOUNT : null),
|
|
200
|
+
fetch: (async (input: string | URL | Request) => {
|
|
201
|
+
urls.push(String(input));
|
|
202
|
+
return Response.json({
|
|
203
|
+
result: {
|
|
204
|
+
model: "clef",
|
|
205
|
+
answers: { act: { type: "noul", noul: p } },
|
|
206
|
+
usage: { input_tokens: 739, output_tokens: 0 },
|
|
207
|
+
},
|
|
208
|
+
success: true,
|
|
209
|
+
errors: [],
|
|
210
|
+
messages: [],
|
|
211
|
+
});
|
|
212
|
+
}) as typeof fetch,
|
|
213
|
+
});
|
|
214
|
+
const decide = async (args: Record<string, any>) => {
|
|
215
|
+
const returns = Object.fromEntries(
|
|
216
|
+
Object.entries(args.questions).map(([id, q]: [string, any]) => [id, { type: q.type }]),
|
|
217
|
+
);
|
|
218
|
+
const decided = await executor.decide(
|
|
219
|
+
SystemOneDecisionConfigSchema.parse({ ...args, returns }),
|
|
220
|
+
);
|
|
221
|
+
return {
|
|
222
|
+
success: true,
|
|
223
|
+
status: 200,
|
|
224
|
+
data: decided.ok
|
|
225
|
+
? { success: true, ...decided.output }
|
|
226
|
+
: { success: false, message: decided.error },
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
return { urls, decide };
|
|
230
|
+
}
|
|
231
|
+
|
|
179
232
|
const run = (ctx: unknown, args: Record<string, unknown> = {}) =>
|
|
180
233
|
gate({ leadAgentId: LEAD, ...args }, ctx) as Promise<any>;
|
|
181
234
|
|
|
@@ -261,6 +314,29 @@ describe("heartbeat-jev-gate with a provider", () => {
|
|
|
261
314
|
});
|
|
262
315
|
});
|
|
263
316
|
|
|
317
|
+
test("only a bare MM-DD counts as a due tracked item", async () => {
|
|
318
|
+
const mocked = Date.now;
|
|
319
|
+
Date.now = () => Date.parse("2026-10-20T09:30:00.000Z");
|
|
320
|
+
try {
|
|
321
|
+
const { ctx } = fakeCtx({
|
|
322
|
+
configs: { TYPESAFE_API_KEY: "x" },
|
|
323
|
+
heartbeatMd: [
|
|
324
|
+
"- **2026-10-20 ~10:45Z (heartbeat 2026-10-20):** re-verified, nothing pending",
|
|
325
|
+
"- Watch: 10-20s retry window closed, nothing pending",
|
|
326
|
+
"- Watch: session 4a12-10-20-9e2f finished cleanly, nothing pending",
|
|
327
|
+
"- Watch: release on 10-20 — lift after ship",
|
|
328
|
+
].join("\n"),
|
|
329
|
+
jev: jevSays(0.9),
|
|
330
|
+
});
|
|
331
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
332
|
+
expect(verdict.state.tracked_items_mentioning_today_or_past_delete_date).toEqual([
|
|
333
|
+
"- Watch: release on 10-20 — lift after ship",
|
|
334
|
+
]);
|
|
335
|
+
} finally {
|
|
336
|
+
Date.now = mocked;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
264
340
|
for (const ordersFirst of [true, false]) {
|
|
265
341
|
test(`previous failures are not new with standing orders ${ordersFirst ? "before" : "after"} status`, async () => {
|
|
266
342
|
const failures =
|
|
@@ -654,6 +730,101 @@ describe("heartbeat-jev-gate with a provider", () => {
|
|
|
654
730
|
});
|
|
655
731
|
});
|
|
656
732
|
|
|
733
|
+
describe("heartbeat-jev-gate on Clef", () => {
|
|
734
|
+
const cloudflareOnly = { CLOUDFLARE_API_TOKEN: "x", CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT };
|
|
735
|
+
|
|
736
|
+
test("runs on Clef when only the Cloudflare token and account id are set", async () => {
|
|
737
|
+
const clef = clefDecide(0.15);
|
|
738
|
+
const { ctx, calls } = fakeCtx({ configs: cloudflareOnly, jev: clef.decide });
|
|
739
|
+
const verdict = await run(ctx);
|
|
740
|
+
// 0.15 is quiet only under Clef's own measured threshold, not Jev's 0.5 neighbourhood.
|
|
741
|
+
expect(verdict).toEqual({
|
|
742
|
+
quiet: true,
|
|
743
|
+
reason: "clef p(needs action)=0.150 < 0.2 via cloudflare",
|
|
744
|
+
});
|
|
745
|
+
expect(calls.decides[0]).toMatchObject({ provider: "cloudflare", model: "clef" });
|
|
746
|
+
expect(clef.urls).toEqual([
|
|
747
|
+
`https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`,
|
|
748
|
+
]);
|
|
749
|
+
expect(calls.prompts).toHaveLength(0);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test("at or above 0.2 Clef wakes the Lead, and the summary names Clef", async () => {
|
|
753
|
+
const { ctx } = fakeCtx({ configs: cloudflareOnly, jev: clefDecide(0.25).decide });
|
|
754
|
+
const verdict = await run(ctx);
|
|
755
|
+
expect(verdict.quiet).toBe(false);
|
|
756
|
+
expect(verdict.reason).toBe("clef p(needs action)=0.250 >= 0.2 via cloudflare");
|
|
757
|
+
expect(verdict.summary).toStartWith(
|
|
758
|
+
"Clef p(needs action) = 0.250 (threshold 0.2, cloudflare).",
|
|
759
|
+
);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("CLOUDFLARE_API_KEY is accepted in place of CLOUDFLARE_API_TOKEN", async () => {
|
|
763
|
+
const { ctx } = fakeCtx({
|
|
764
|
+
configs: { CLOUDFLARE_API_KEY: "x", CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT },
|
|
765
|
+
jev: clefDecide(0.1).decide,
|
|
766
|
+
});
|
|
767
|
+
expect((await run(ctx)).reason).toContain("via cloudflare");
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("auto prefers Jev when a Jev key is also set", async () => {
|
|
771
|
+
const { ctx, calls } = fakeCtx({
|
|
772
|
+
configs: { ...cloudflareOnly, OPENROUTER_API_KEY: "x" },
|
|
773
|
+
jev: jevSays(0.1),
|
|
774
|
+
});
|
|
775
|
+
expect((await run(ctx)).reason).toContain("via openrouter");
|
|
776
|
+
expect(calls.decides[0]!.provider).toBe("openrouter");
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("an explicit cloudflare provider wins over a Jev key", async () => {
|
|
780
|
+
const { ctx, calls } = fakeCtx({
|
|
781
|
+
configs: {
|
|
782
|
+
...cloudflareOnly,
|
|
783
|
+
TYPESAFE_API_KEY: "x",
|
|
784
|
+
HEARTBEAT_JEV_GATE_PROVIDER: "cloudflare",
|
|
785
|
+
},
|
|
786
|
+
jev: clefDecide(0.1).decide,
|
|
787
|
+
});
|
|
788
|
+
expect((await run(ctx)).reason).toContain("via cloudflare");
|
|
789
|
+
expect(calls.decides[0]!.provider).toBe("cloudflare");
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("an unknown provider value falls back to auto, even an Object prototype name", async () => {
|
|
793
|
+
for (const pref of ["constructor", "toString", "jev"]) {
|
|
794
|
+
const { ctx } = fakeCtx({
|
|
795
|
+
configs: { TYPESAFE_API_KEY: "x", HEARTBEAT_JEV_GATE_PROVIDER: pref },
|
|
796
|
+
jev: jevSays(0.1),
|
|
797
|
+
});
|
|
798
|
+
expect((await run(ctx)).reason).toContain("via typesafe");
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test("a configured threshold applies to Clef too", async () => {
|
|
803
|
+
const { ctx } = fakeCtx({
|
|
804
|
+
configs: { ...cloudflareOnly, HEARTBEAT_JEV_GATE_THRESHOLD: "0.5" },
|
|
805
|
+
jev: clefDecide(0.3).decide,
|
|
806
|
+
});
|
|
807
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
808
|
+
expect(verdict.quiet).toBe(true);
|
|
809
|
+
expect(verdict.threshold).toBe(0.5);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("a token without an account id is not a usable provider", async () => {
|
|
813
|
+
const { ctx, calls } = fakeCtx({ configs: { CLOUDFLARE_API_TOKEN: "x" } });
|
|
814
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
815
|
+
expect(verdict.reason).toStartWith("jev-gate inactive:");
|
|
816
|
+
expect(calls.decides).toHaveLength(0);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test("a Clef error throws with the provider named, so the heartbeat fails open", async () => {
|
|
820
|
+
const { ctx } = fakeCtx({
|
|
821
|
+
configs: cloudflareOnly,
|
|
822
|
+
jev: jevFails("CLOUDFLARE_API_TOKEN was rejected by Cloudflare Workers AI (HTTP 401)."),
|
|
823
|
+
});
|
|
824
|
+
await expect(run(ctx)).rejects.toThrow("clef cloudflare: CLOUDFLARE_API_TOKEN was rejected");
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
|
|
657
828
|
describe("heartbeat-jev-gate without a provider", () => {
|
|
658
829
|
const inactive = (verdict: any) => {
|
|
659
830
|
expect(verdict.quiet).toBe(false);
|
|
@@ -674,6 +845,8 @@ describe("heartbeat-jev-gate without a provider", () => {
|
|
|
674
845
|
expect(prompt.questions[0].type).toBe("approval");
|
|
675
846
|
expect(prompt.questions[0].description).toContain("TYPESAFE_API_KEY");
|
|
676
847
|
expect(prompt.questions[0].description).toContain("OPENROUTER_API_KEY");
|
|
848
|
+
expect(prompt.questions[0].description).toContain("CLOUDFLARE_API_TOKEN");
|
|
849
|
+
expect(prompt.questions[0].description).toContain("CLOUDFLARE_ACCOUNT_ID");
|
|
677
850
|
expect(prompt.questions[0].description).toContain("$0.00004");
|
|
678
851
|
expect(kv.get("setup")).toMatchObject({ status: "asked", requestId: "req-1" });
|
|
679
852
|
|
|
@@ -732,13 +905,104 @@ describe("heartbeat-jev-gate without a provider", () => {
|
|
|
732
905
|
const verdict = await run(ctx, { dryRun: true });
|
|
733
906
|
inactive(verdict);
|
|
734
907
|
expect(verdict.dryRun).toBe(true);
|
|
735
|
-
expect(verdict.prompt.title).toContain("
|
|
908
|
+
expect(verdict.prompt.title).toContain("decision model");
|
|
736
909
|
expect(verdict.state.agents).toBe("- Online: 6 (4 idle, 2 busy), Offline: 5");
|
|
737
910
|
expect(calls.prompts).toHaveLength(0);
|
|
738
911
|
expect(calls.kvSets).toBe(0);
|
|
739
912
|
});
|
|
740
913
|
});
|
|
741
914
|
|
|
915
|
+
describe("heartbeat-jev-gate on the real system-one-decide tool", () => {
|
|
916
|
+
let agentId: string;
|
|
917
|
+
let prevBase: string | undefined;
|
|
918
|
+
const realFetch = globalThis.fetch;
|
|
919
|
+
beforeAll(async () => {
|
|
920
|
+
initDb(":memory:");
|
|
921
|
+
agentId = (await createAgent({ name: "jev-gate-decide", isLead: true, status: "idle" })).id;
|
|
922
|
+
await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: CF_ACCOUNT });
|
|
923
|
+
await upsertSwarmConfig({
|
|
924
|
+
scope: "global",
|
|
925
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
926
|
+
value: "cf-example-token",
|
|
927
|
+
isSecret: true,
|
|
928
|
+
});
|
|
929
|
+
prevBase = process.env.OPENROUTER_BASE_URL;
|
|
930
|
+
});
|
|
931
|
+
afterAll(() => {
|
|
932
|
+
globalThis.fetch = realFetch;
|
|
933
|
+
if (prevBase === undefined) delete process.env.OPENROUTER_BASE_URL;
|
|
934
|
+
else process.env.OPENROUTER_BASE_URL = prevBase;
|
|
935
|
+
closeDb();
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
/** `ctx.swarm.systemOne_decide` as the MCP bridge serves it, backed by the registered tool. */
|
|
939
|
+
const viaTool = () => {
|
|
940
|
+
const server = new McpServer({ name: "jev-gate-decide", version: "1" });
|
|
941
|
+
registerSystemOneDecideTool(server);
|
|
942
|
+
const tool = (server as any)._registeredTools["system-one-decide"];
|
|
943
|
+
return async (args: Record<string, any>) => {
|
|
944
|
+
const result = await tool.handler(args, {
|
|
945
|
+
sessionId: "script",
|
|
946
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
947
|
+
});
|
|
948
|
+
return { success: true, status: 200, data: result.structuredContent };
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
test("auto skips OpenRouter behind a gateway, which the server refuses, and runs on Clef", async () => {
|
|
953
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
954
|
+
const urls: string[] = [];
|
|
955
|
+
globalThis.fetch = (async (input: string | URL | Request) => {
|
|
956
|
+
urls.push(String(input));
|
|
957
|
+
return Response.json({
|
|
958
|
+
result: {
|
|
959
|
+
model: "clef",
|
|
960
|
+
answers: { act: { type: "noul", noul: 0.1 } },
|
|
961
|
+
usage: { input_tokens: 739, output_tokens: 0 },
|
|
962
|
+
},
|
|
963
|
+
success: true,
|
|
964
|
+
errors: [],
|
|
965
|
+
messages: [],
|
|
966
|
+
});
|
|
967
|
+
}) as typeof fetch;
|
|
968
|
+
const { ctx, calls } = fakeCtx({
|
|
969
|
+
configs: {
|
|
970
|
+
OPENROUTER_API_KEY: "x",
|
|
971
|
+
CLOUDFLARE_API_TOKEN: "x",
|
|
972
|
+
CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT,
|
|
973
|
+
},
|
|
974
|
+
jev: viaTool(),
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
978
|
+
expect(verdict).toMatchObject({ quiet: true, provider: "cloudflare", threshold: 0.2 });
|
|
979
|
+
expect(verdict.skipped).toEqual([
|
|
980
|
+
expect.stringContaining("openrouter: OPENROUTER_BASE_URL points at a gateway"),
|
|
981
|
+
]);
|
|
982
|
+
expect(calls.decides.map((d) => d.provider)).toEqual(["openrouter", "cloudflare"]);
|
|
983
|
+
expect(urls).toEqual([
|
|
984
|
+
`https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`,
|
|
985
|
+
]);
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
test("an explicit openrouter choice behind a gateway still throws", async () => {
|
|
989
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
990
|
+
const { ctx, calls } = fakeCtx({
|
|
991
|
+
configs: {
|
|
992
|
+
OPENROUTER_API_KEY: "x",
|
|
993
|
+
CLOUDFLARE_API_TOKEN: "x",
|
|
994
|
+
CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT,
|
|
995
|
+
HEARTBEAT_JEV_GATE_PROVIDER: "openrouter",
|
|
996
|
+
},
|
|
997
|
+
jev: viaTool(),
|
|
998
|
+
});
|
|
999
|
+
await expect(run(ctx)).rejects.toThrow(
|
|
1000
|
+
"jev openrouter: OPENROUTER_BASE_URL points at a gateway",
|
|
1001
|
+
);
|
|
1002
|
+
expect(calls.decides).toHaveLength(1);
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
742
1006
|
describe("request-human-input detached", () => {
|
|
743
1007
|
beforeAll(() => initDb(":memory:"));
|
|
744
1008
|
afterAll(() => closeDb());
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* 3. Reading from the agent's `task:agent:*` namespace via the cookie is
|
|
12
12
|
* ALSO forced to the page namespace (the page can't see the agent's
|
|
13
13
|
* scratchpad).
|
|
14
|
+
* 4. Per-page cookies: two pages open in one browser each proxy as
|
|
15
|
+
* themselves, a header naming another page is refused, and the legacy
|
|
16
|
+
* `page_session` cookie still works for its own page.
|
|
14
17
|
*/
|
|
15
18
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
16
19
|
import { randomUUID } from "node:crypto";
|
|
@@ -123,11 +126,94 @@ async function launchPage(pageId: string): Promise<string> {
|
|
|
123
126
|
expect(res.status).toBe(204);
|
|
124
127
|
const setCookie = res.headers.get("set-cookie");
|
|
125
128
|
expect(setCookie).toBeTruthy();
|
|
126
|
-
const cookieValue = /
|
|
129
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!)?.[1];
|
|
127
130
|
expect(cookieValue).toBeTruthy();
|
|
128
131
|
return cookieValue!;
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
/** What the browser SDK sends from `/p/<id>`: its own cookie plus its page id. */
|
|
135
|
+
function pageSessionHeaders(id: string, cookie: string): Record<string, string> {
|
|
136
|
+
return { Cookie: `page_session_${id}=${cookie}`, "X-Swarm-Page-Id": id };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function putClick(headers: Record<string, string>): Promise<Response> {
|
|
140
|
+
return fetch(`${BASE}/@swarm/api/kv/clicks`, {
|
|
141
|
+
method: "PUT",
|
|
142
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
143
|
+
body: JSON.stringify({ value: 1, valueType: "integer" }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function namespaceOf(res: Response): Promise<string> {
|
|
148
|
+
expect(res.status).toBe(200);
|
|
149
|
+
return ((await res.json()) as { namespace: string }).namespace;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
describe("page proxy → kv with several pages open in one browser", () => {
|
|
153
|
+
test("two pages launched in one cookie jar each proxy as themselves, concurrently", async () => {
|
|
154
|
+
const idA = await createPage();
|
|
155
|
+
const idB = await createPage();
|
|
156
|
+
const jar = `page_session_${idA}=${await launchPage(idA)}; page_session_${idB}=${await launchPage(idB)}`;
|
|
157
|
+
|
|
158
|
+
const [resA, resB] = await Promise.all([
|
|
159
|
+
putClick({ Cookie: jar, "X-Swarm-Page-Id": idA }),
|
|
160
|
+
putClick({ Cookie: jar, "X-Swarm-Page-Id": idB }),
|
|
161
|
+
]);
|
|
162
|
+
expect(await namespaceOf(resA)).toBe(`task:page:${idA}`);
|
|
163
|
+
expect(await namespaceOf(resB)).toBe(`task:page:${idB}`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a header naming page B with only page A's session is rejected", async () => {
|
|
167
|
+
const idA = await createPage();
|
|
168
|
+
const idB = await createPage();
|
|
169
|
+
const cookieA = await launchPage(idA);
|
|
170
|
+
|
|
171
|
+
for (const cookie of [
|
|
172
|
+
`page_session_${idA}=${cookieA}`,
|
|
173
|
+
`page_session_${idB}=${cookieA}`,
|
|
174
|
+
`page_session=${cookieA}`,
|
|
175
|
+
]) {
|
|
176
|
+
const res = await putClick({ Cookie: cookie, "X-Swarm-Page-Id": idB });
|
|
177
|
+
expect(res.status).toBe(401);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a header that cannot be a page id is rejected", async () => {
|
|
182
|
+
const id = await createPage();
|
|
183
|
+
const cookie = await launchPage(id);
|
|
184
|
+
const res = await putClick({
|
|
185
|
+
Cookie: `page_session_${id}=${cookie}; page_session=${cookie}`,
|
|
186
|
+
"X-Swarm-Page-Id": "../x",
|
|
187
|
+
});
|
|
188
|
+
expect(res.status).toBe(401);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("the legacy page_session cookie still works for its own page", async () => {
|
|
192
|
+
const id = await createPage();
|
|
193
|
+
const cookie = await launchPage(id);
|
|
194
|
+
// With the page named, and from a client that names no page at all.
|
|
195
|
+
const named = await putClick({ Cookie: `page_session=${cookie}`, "X-Swarm-Page-Id": id });
|
|
196
|
+
expect(await namespaceOf(named)).toBe(`task:page:${id}`);
|
|
197
|
+
const unnamed = await putClick({ Cookie: `page_session=${cookie}` });
|
|
198
|
+
expect(await namespaceOf(unnamed)).toBe(`task:page:${id}`);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("a same-host /p/<id> Referer selects the page when no header is sent", async () => {
|
|
202
|
+
const idA = await createPage();
|
|
203
|
+
const idB = await createPage();
|
|
204
|
+
const jar = `page_session_${idA}=${await launchPage(idA)}; page_session_${idB}=${await launchPage(idB)}`;
|
|
205
|
+
|
|
206
|
+
const res = await putClick({ Cookie: jar, Referer: `${BASE}/p/${idB}` });
|
|
207
|
+
expect(await namespaceOf(res)).toBe(`task:page:${idB}`);
|
|
208
|
+
|
|
209
|
+
// A cross-site Referer picks nothing, and no per-page cookie is used unnamed.
|
|
210
|
+
const crossSite = await putClick({ Cookie: jar, Referer: `https://evil.example/p/${idB}` });
|
|
211
|
+
expect(crossSite.status).toBe(401);
|
|
212
|
+
const unnamed = await putClick({ Cookie: jar });
|
|
213
|
+
expect(unnamed.status).toBe(401);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
131
217
|
describe("page proxy → kv", () => {
|
|
132
218
|
test("writes via the proxy land in task:page:<id> automatically", async () => {
|
|
133
219
|
const id = await createPage();
|
|
@@ -137,7 +223,7 @@ describe("page proxy → kv", () => {
|
|
|
137
223
|
method: "PUT",
|
|
138
224
|
headers: {
|
|
139
225
|
"Content-Type": "application/json",
|
|
140
|
-
|
|
226
|
+
...pageSessionHeaders(id, cookie),
|
|
141
227
|
},
|
|
142
228
|
body: JSON.stringify({ value: 1, valueType: "integer" }),
|
|
143
229
|
});
|
|
@@ -161,13 +247,13 @@ describe("page proxy → kv", () => {
|
|
|
161
247
|
const cookie = await launchPage(id);
|
|
162
248
|
const r1 = await fetch(`${BASE}/@swarm/api/kv/votes/incr`, {
|
|
163
249
|
method: "POST",
|
|
164
|
-
headers: { "Content-Type": "application/json",
|
|
250
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
165
251
|
body: JSON.stringify({ by: 2 }),
|
|
166
252
|
});
|
|
167
253
|
expect(r1.status).toBe(200);
|
|
168
254
|
const r2 = await fetch(`${BASE}/@swarm/api/kv/votes/incr`, {
|
|
169
255
|
method: "POST",
|
|
170
|
-
headers: { "Content-Type": "application/json",
|
|
256
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
171
257
|
body: JSON.stringify({}),
|
|
172
258
|
});
|
|
173
259
|
const v = (await r2.json()) as { value: number; namespace: string };
|
|
@@ -184,7 +270,7 @@ describe("page proxy → kv", () => {
|
|
|
184
270
|
const fakeNs = `task:agent:${agentId}`;
|
|
185
271
|
const put = await fetch(`${BASE}/@swarm/api/kv/_/${encodeURIComponent(fakeNs)}/escape`, {
|
|
186
272
|
method: "PUT",
|
|
187
|
-
headers: { "Content-Type": "application/json",
|
|
273
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
188
274
|
body: JSON.stringify({ value: "leaked", valueType: "string" }),
|
|
189
275
|
});
|
|
190
276
|
// The kv handler treats X-Page-Id as the highest-priority namespace
|
|
@@ -112,7 +112,7 @@ describe("POST /api/pages/:id/launch — password mode rejection (step-4)", () =
|
|
|
112
112
|
headers: { "X-Agent-ID": agentId },
|
|
113
113
|
});
|
|
114
114
|
expect(launch.status).toBe(204);
|
|
115
|
-
expect(launch.headers.get("set-cookie")).toContain("
|
|
115
|
+
expect(launch.headers.get("set-cookie")).toContain("page_session_");
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
test("public page → launch still issues a cookie (uniform path)", async () => {
|
|
@@ -135,6 +135,6 @@ describe("POST /api/pages/:id/launch — password mode rejection (step-4)", () =
|
|
|
135
135
|
headers: { "X-Agent-ID": agentId },
|
|
136
136
|
});
|
|
137
137
|
expect(launch.status).toBe(204);
|
|
138
|
-
expect(launch.headers.get("set-cookie")).toContain("
|
|
138
|
+
expect(launch.headers.get("set-cookie")).toContain("page_session_");
|
|
139
139
|
});
|
|
140
140
|
});
|