@desplega.ai/agent-swarm 1.164.1 → 1.166.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-yzfxn8vj.js +23 -0
  3. package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
  4. package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
  5. package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
  6. package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
  7. package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
  8. package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
  9. package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
  10. package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
  11. package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
  12. package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
  13. package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
  14. package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
  15. package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
  16. package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
  17. package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
  18. package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
  19. package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
  20. package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
  21. package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
  22. package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
  23. package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
  24. package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
  25. package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
  26. package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
  27. package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
  28. package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
  29. package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
  30. package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
  31. package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
  32. package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
  33. package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
  34. package/dist/cli-fn697bnt.js +126 -0
  35. package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
  36. package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
  37. package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
  38. package/dist/cli-j2jbr8af.js +516 -0
  39. package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
  40. package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
  41. package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
  42. package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
  43. package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
  44. package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
  45. package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
  46. package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
  47. package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
  48. package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
  49. package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
  50. package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
  51. package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
  52. package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
  53. package/dist/cli-wt7ccy4r.js +2654 -0
  54. package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
  55. package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
  56. package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
  57. package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
  58. package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
  59. package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
  60. package/dist/cli-zp67qg5x.js +92 -0
  61. package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
  62. package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
  63. package/dist/cli.js +79 -26
  64. package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
  65. package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
  66. package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
  67. package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
  68. package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
  69. package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
  70. package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
  71. package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
  72. package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
  73. package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
  74. package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
  75. package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
  76. package/dist/grok-adapter-ar9cw9jg.js +29 -0
  77. package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
  78. package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
  79. package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
  80. package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
  81. package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
  82. package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
  83. package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
  84. package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
  85. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  86. package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
  87. package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
  88. package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
  89. package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
  90. package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
  91. package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
  92. package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
  93. package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
  94. package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
  95. package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
  96. package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
  97. package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
  98. package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
  99. package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
  100. package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
  101. package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
  102. package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
  103. package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
  104. package/openapi.json +457 -31
  105. package/package.json +14 -6
  106. package/plugin/commands/create-pr.md +7 -1
  107. package/plugin/pi-skills/create-pr/SKILL.md +7 -1
  108. package/src/agentmail/app.ts +5 -4
  109. package/src/be/automation-preflight.ts +11 -9
  110. package/src/be/boot-scrub-sweep.ts +302 -0
  111. package/src/be/connector-codes.ts +81 -0
  112. package/src/be/crypto/key-bootstrap.ts +1 -1
  113. package/src/be/db/approvals.ts +15 -1
  114. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  115. package/src/be/db/config-secrets.ts +31 -0
  116. package/src/be/db/runtime.ts +4 -0
  117. package/src/be/db/script-runs.ts +15 -0
  118. package/src/be/db/tasks/write.ts +1 -1
  119. package/src/be/db-queries/mcp-oauth.ts +5 -0
  120. package/src/be/db-queries/oauth.ts +77 -0
  121. package/src/be/db.ts +162 -59
  122. package/src/be/events.ts +17 -1
  123. package/src/be/memory/index-content.ts +2 -2
  124. package/src/be/memory/providers/sqlite-store.ts +63 -11
  125. package/src/be/memory/types.ts +14 -0
  126. package/src/be/migrations/197_connector_codes.sql +23 -0
  127. package/src/be/migrations/198_permission_audit_guest.sql +33 -0
  128. package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
  129. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  130. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  131. package/src/be/model-tier-resolution.ts +2 -1
  132. package/src/be/model-validation.ts +1 -1
  133. package/src/be/pricing-normalize.ts +3 -0
  134. package/src/be/rbac-audit.ts +2 -1
  135. package/src/be/scripts/db.ts +28 -4
  136. package/src/be/scripts/source-secrets.ts +37 -0
  137. package/src/be/scripts/typecheck.ts +23 -2
  138. package/src/be/scrub-json.ts +32 -0
  139. package/src/be/sealed-json.ts +59 -0
  140. package/src/be/secret-registry.ts +131 -0
  141. package/src/be/seed-pricing.ts +14 -0
  142. package/src/be/users.ts +7 -1
  143. package/src/be/workflow-replay.ts +77 -0
  144. package/src/cli.tsx +7 -0
  145. package/src/commands/provider-credentials.ts +46 -8
  146. package/src/commands/runner.ts +2 -10
  147. package/src/extensions/contract-types.generated.ts +1 -1
  148. package/src/github/app.ts +4 -4
  149. package/src/heartbeat/heartbeat.ts +20 -8
  150. package/src/hooks/pr-body-guard.ts +1 -1
  151. package/src/http/agents.ts +1 -0
  152. package/src/http/approval-requests.ts +1 -0
  153. package/src/http/apps.ts +3 -0
  154. package/src/http/auth.ts +4 -1
  155. package/src/http/codex-oauth-device.ts +8 -10
  156. package/src/http/codex-oauth-keep-warm.ts +2 -2
  157. package/src/http/config-values.ts +59 -0
  158. package/src/http/config.ts +5 -0
  159. package/src/http/db-query-bounded.ts +3 -0
  160. package/src/http/db-query-shared.ts +107 -15
  161. package/src/http/db-query.ts +10 -2
  162. package/src/http/extensions.ts +1 -0
  163. package/src/http/index.ts +23 -6
  164. package/src/http/integrations.ts +1 -38
  165. package/src/http/ip-rate-limit.ts +82 -0
  166. package/src/http/kv.ts +38 -4
  167. package/src/http/mcp-servers.ts +1 -0
  168. package/src/http/memory.ts +9 -0
  169. package/src/http/models-catalog.ts +2 -0
  170. package/src/http/operator-actor.ts +4 -0
  171. package/src/http/page-proxy.ts +22 -0
  172. package/src/http/pages.ts +4 -2
  173. package/src/http/realtime.ts +1 -1
  174. package/src/http/request-principal.ts +14 -1
  175. package/src/http/script-connections.ts +8 -0
  176. package/src/http/script-runs.ts +12 -4
  177. package/src/http/scripts.ts +15 -0
  178. package/src/http/session-cost-recompute.ts +3 -2
  179. package/src/http/tasks.ts +9 -0
  180. package/src/http/users.ts +226 -0
  181. package/src/http/workflows.ts +3 -2
  182. package/src/http.ts +1 -0
  183. package/src/integrations/kapso/client.ts +3 -1
  184. package/src/jira/app.ts +2 -2
  185. package/src/kv-view.ts +324 -0
  186. package/src/linear/app.ts +2 -2
  187. package/src/oauth/mcp-wrapper.ts +1 -1
  188. package/src/prompts/memories.ts +42 -1
  189. package/src/providers/acp-adapter.ts +85 -24
  190. package/src/providers/acp-target-catalog.ts +54 -1
  191. package/src/providers/acp-targets.ts +358 -4
  192. package/src/providers/amp-adapter.ts +1 -1
  193. package/src/providers/cursor-adapter.ts +1 -1
  194. package/src/providers/grok-adapter.ts +186 -0
  195. package/src/providers/index.ts +5 -1
  196. package/src/providers/pricing-sources.md +6 -0
  197. package/src/providers/reasoning-effort.ts +17 -1
  198. package/src/providers/types.ts +2 -1
  199. package/src/rbac/can.ts +4 -3
  200. package/src/rbac/legacy-policy.ts +3 -3
  201. package/src/rbac/types.ts +6 -0
  202. package/src/script-workflows/supervisor.ts +11 -2
  203. package/src/scripts-runtime/eval-harness.ts +2 -12
  204. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  205. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  206. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  207. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  208. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  209. package/src/scripts-runtime/executors/registry.ts +2 -0
  210. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  211. package/src/scripts-runtime/script-error-hints.ts +14 -0
  212. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  213. package/src/scripts-runtime/types/stdlib.d.ts +27 -8
  214. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  215. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
  216. package/src/server-user.ts +2 -20
  217. package/src/server.ts +3 -1
  218. package/src/slack/app.ts +4 -0
  219. package/src/slack/config.ts +3 -1
  220. package/src/slack/egress-scrub.ts +87 -0
  221. package/src/tests/acp-adapter.test.ts +191 -2
  222. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  223. package/src/tests/budget-user-scope.test.ts +7 -21
  224. package/src/tests/check-pr-body.test.ts +149 -6
  225. package/src/tests/check-text-columns.test.ts +145 -0
  226. package/src/tests/claude-adapter-binary.test.ts +1 -1
  227. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  228. package/src/tests/codex-oauth-device.test.ts +23 -37
  229. package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
  230. package/src/tests/console-scrub.test.ts +141 -0
  231. package/src/tests/credential-check.test.ts +90 -0
  232. package/src/tests/db-query-denylist.test.ts +202 -0
  233. package/src/tests/db-task-read-contract.test.ts +1 -0
  234. package/src/tests/db-task-write-contract.test.ts +1 -0
  235. package/src/tests/docs-index-footprint.test.js +3 -3
  236. package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
  237. package/src/tests/events-scrub.test.ts +88 -0
  238. package/src/tests/explicit-model-validation.test.ts +81 -0
  239. package/src/tests/extensions-pre-task.test.ts +33 -1
  240. package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
  241. package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
  242. package/src/tests/fixtures/secret-corpus.ts +320 -0
  243. package/src/tests/grok-adapter.test.ts +647 -0
  244. package/src/tests/heartbeat-checklist.test.ts +44 -0
  245. package/src/tests/integration-disable-flags.test.ts +156 -0
  246. package/src/tests/kv-http.test.ts +34 -0
  247. package/src/tests/kv-tool.test.ts +274 -1
  248. package/src/tests/kv-view.test.ts +147 -0
  249. package/src/tests/mcp-user-route.test.ts +257 -15
  250. package/src/tests/memory-injection-outcome.test.ts +143 -0
  251. package/src/tests/model-tier-resolution.test.ts +76 -3
  252. package/src/tests/page-guest-session.test.ts +424 -0
  253. package/src/tests/page-proxy.test.ts +1 -1
  254. package/src/tests/page-viewer-auth.test.ts +22 -1
  255. package/src/tests/pr-body-leaks.test.ts +157 -2
  256. package/src/tests/preload.ts +10 -0
  257. package/src/tests/provider-registration.test.ts +3 -1
  258. package/src/tests/rbac-audit.test.ts +32 -0
  259. package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
  260. package/src/tests/script-connections.test.ts +9 -0
  261. package/src/tests/script-executor-conformance.test.ts +3 -0
  262. package/src/tests/script-executor-quickjs.test.ts +442 -0
  263. package/src/tests/script-executor-registry.test.ts +6 -1
  264. package/src/tests/script-runs-http.test.ts +126 -1
  265. package/src/tests/script-runs-scrub.test.ts +275 -0
  266. package/src/tests/scripts-http.test.ts +82 -1
  267. package/src/tests/scripts-mcp-e2e.test.ts +42 -6
  268. package/src/tests/scripts-runtime.test.ts +37 -0
  269. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  270. package/src/tests/sealed-key-guard.test.ts +166 -0
  271. package/src/tests/secret-canary-sweep.test.ts +958 -0
  272. package/src/tests/secret-registry.test.ts +355 -0
  273. package/src/tests/secret-rules.test.ts +230 -0
  274. package/src/tests/secret-scrubber.test.ts +317 -1
  275. package/src/tests/session-costs-golden.test.ts +17 -0
  276. package/src/tests/slack-egress-scrub.test.ts +216 -0
  277. package/src/tests/swarm-tool-result-gate.test.ts +143 -30
  278. package/src/tests/task-tools-ctx.test.ts +3 -0
  279. package/src/tests/user-token-routes.test.ts +342 -3
  280. package/src/tests/workflow-replay.test.ts +518 -0
  281. package/src/tests/workflow-scrub.test.ts +242 -0
  282. package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
  283. package/src/tools/db-query.ts +1 -1
  284. package/src/tools/inject-learning.ts +1 -1
  285. package/src/tools/kv/kv-get.ts +136 -6
  286. package/src/tools/send-task.ts +61 -17
  287. package/src/tools/utils.ts +186 -43
  288. package/src/tools/workflows/get-workflow-run.ts +3 -2
  289. package/src/types.ts +16 -1
  290. package/src/utils/console-scrub-install.ts +5 -0
  291. package/src/utils/console-scrub.ts +57 -0
  292. package/src/utils/credentials.ts +8 -0
  293. package/src/utils/page-session.ts +6 -2
  294. package/src/utils/pr-body-leaks.ts +149 -10
  295. package/src/utils/request-auth-context.ts +2 -0
  296. package/src/utils/secret-rules.generated.ts +2055 -0
  297. package/src/utils/secret-scrubber.ts +662 -36
  298. package/src/workflows/engine.ts +39 -4
  299. package/src/workflows/executors/notify.ts +6 -1
  300. package/src/workflows/executors/system-one-decision.ts +31 -12
  301. package/src/workflows/executors/system-one-openai-wire.ts +168 -0
  302. package/src/workflows/executors/system-one-providers.ts +112 -11
  303. package/src/workflows/executors/system-one-wire.ts +79 -0
  304. package/src/workflows/recovery.ts +10 -1
  305. package/src/workflows/resume.ts +62 -3
  306. package/src/workflows/retry-poller.ts +3 -0
  307. package/templates/ai-toolbox.manifest.json +2 -2
  308. package/templates/skills/agent-browser/SKILL.md +5 -5
  309. package/templates/skills/agent-browser/config.json +1 -1
  310. package/templates/skills/agent-browser/content.md +4 -4
  311. package/templates/skills/artifacts/content.md +17 -7
  312. package/templates/skills/jira-interaction/config.json +1 -1
  313. package/templates/skills/jira-interaction/content.md +56 -205
  314. package/templates/skills/linear-interaction/config.json +1 -1
  315. package/templates/skills/linear-interaction/content.md +31 -50
  316. package/templates/skills/pages/content.md +8 -4
  317. package/templates/skills/qa/content.md +2 -2
  318. package/templates/skills/workflow-iterate/content.md +4 -4
  319. package/tsconfig.json +3 -3
  320. package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
  321. package/dist/cli-0a7r5331.js +0 -168
  322. package/dist/cli-93rxw9yd.js +0 -951
  323. package/dist/cli-cvk6yzw5.js +0 -40
  324. package/dist/cli-dgnj0zcc.js +0 -40
  325. package/dist/cli-qt5wzq6v.js +0 -256
  326. package/dist/cli-v7fqehqd.js +0 -9281
  327. package/dist/cli-vbr859rz.js +0 -572
  328. package/src/be/boot-scrub-logs.ts +0 -132
@@ -359,6 +359,79 @@ describe("claim-time harness guard", () => {
359
359
  });
360
360
  });
361
361
 
362
+ describe("claim-time harness guard on grok", () => {
363
+ async function grokWorker(name: string) {
364
+ return createAgent({
365
+ name,
366
+ isLead: false,
367
+ status: "idle",
368
+ maxTasks: 1,
369
+ harnessProvider: "grok",
370
+ });
371
+ }
372
+
373
+ test("a directed task pinning another vendor's model or alias fails fast", async () => {
374
+ for (const model of ["claude-opus-5-5", "anthropic/claude-opus-5-5", "latest:anthropic/opus"]) {
375
+ const worker = await grokWorker(`w-grok-mismatch-${model}`);
376
+ const task = await createTaskExtended("review", { agentId: worker.id, model });
377
+ const trigger = await callPoll(worker.id, header({}));
378
+ expect(trigger?.taskId).toBe(task.id);
379
+ expect(String(trigger?.task.modelUnsupported)).toStartWith(
380
+ `[model-harness-mismatch] Model "${model}" does not run on the grok harness of this worker.`,
381
+ );
382
+ expect(trigger?.task.resolvedModel).toBeUndefined();
383
+ }
384
+ });
385
+
386
+ test("a directed xAI or OpenRouter model resolves as pinned", async () => {
387
+ await seedCatalog([
388
+ entry("anthropic", "claude-opus-5-5", daysAgo(30)),
389
+ entry("openrouter", "deepseek/deepseek-v4.1-flash", daysAgo(30)),
390
+ ]);
391
+ for (const [model, resolved] of [
392
+ ["grok-4.6", "grok-4.6"],
393
+ ["openrouter/deepseek/deepseek-v4.1-flash", "openrouter/deepseek/deepseek-v4.1-flash"],
394
+ ["latest:openrouter/deepseek/deepseek-v4.1-*", "openrouter/deepseek/deepseek-v4.1-flash"],
395
+ ]) {
396
+ const worker = await grokWorker(`w-grok-ok-${model}`);
397
+ const task = await createTaskExtended("review", { agentId: worker.id, model });
398
+ const trigger = await callPoll(worker.id, header({}));
399
+ expect(trigger?.taskId).toBe(task.id);
400
+ expect(trigger?.task.modelUnsupported).toBeUndefined();
401
+ expect(trigger?.task.resolvedModel).toBe(resolved);
402
+ }
403
+ });
404
+
405
+ test("an Anthropic worker-env tier value is skipped for the grok tier default", async () => {
406
+ const worker = await grokWorker("w-grok-env");
407
+ const task = await createTaskExtended("smart", { agentId: worker.id, modelTier: "smart" });
408
+ await callPoll(worker.id, header({ grok: { smart: "claude-opus-5-5" } }));
409
+ expect(await taskRow(task.id)).toEqual({
410
+ resolvedModel: "grok-4.6",
411
+ modelSource: "tier-default",
412
+ modelAlias: null,
413
+ });
414
+ });
415
+
416
+ test("pool: Grok skips an Anthropic task and claims the xAI and OpenRouter ones", async () => {
417
+ const vendor = await createTaskExtended("opus pool", {
418
+ model: "claude-opus-5-5",
419
+ priority: 90,
420
+ });
421
+ const xai = await createTaskExtended("xai pool", { model: "grok-4.6", priority: 50 });
422
+ const openrouter = await createTaskExtended("or pool", {
423
+ model: "openrouter/deepseek/deepseek-v4.1-flash",
424
+ priority: 10,
425
+ });
426
+ const first = await callPoll((await grokWorker("w-grok-pool-1")).id, header({}));
427
+ expect(first?.taskId).toBe(xai.id);
428
+ const second = await callPoll((await grokWorker("w-grok-pool-2")).id, header({}));
429
+ expect(second?.taskId).toBe(openrouter.id);
430
+ const third = await callPoll((await grokWorker("w-grok-pool-3")).id, header({}));
431
+ expect(third?.type === "task_assigned" && third.taskId === vendor.id).toBe(false);
432
+ });
433
+ });
434
+
362
435
  describe("task list summaries", () => {
363
436
  test("carry the claim-time resolution the dashboard table shows", async () => {
364
437
  const worker = await createAgent({
@@ -508,8 +581,8 @@ describe("previewModelTiers", () => {
508
581
 
509
582
  // acp has no portable tier mapping, so it has nothing to preview.
510
583
  expect(rows.some((r) => r.provider === "acp")).toBe(false);
511
- expect(new Set(rows.map((r) => r.provider)).size).toBe(9);
512
- expect(rows).toHaveLength(36);
584
+ expect(new Set(rows.map((r) => r.provider)).size).toBe(10);
585
+ expect(rows).toHaveLength(40);
513
586
  });
514
587
 
515
588
  test("an alias that resolves to nothing reports the default it falls back to", async () => {
@@ -564,7 +637,7 @@ describe("GET /api/models-catalog/tiers", () => {
564
637
  expect(handled).toBe(true);
565
638
  expect(status).toBe(200);
566
639
  const body = JSON.parse(bodyStr) as { tiers: { provider: string; tier: string }[] };
567
- expect(body.tiers).toHaveLength(36);
640
+ expect(body.tiers).toHaveLength(40);
568
641
  expect(body.tiers.find((t) => t.provider === "claude" && t.tier === "smart")).toMatchObject({
569
642
  key: "MODEL_TIER_CLAUDE_SMART",
570
643
  source: "tier-config",
@@ -0,0 +1,424 @@
1
+ /**
2
+ * A page session with no signed-in user and no operator launch is a guest. A guest
3
+ * reaches only its own page record and its own KV through the proxy, and every
4
+ * server-side gate denies it.
5
+ * A user session and an operator-launched session keep working.
6
+ */
7
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8
+ import { randomUUID } from "node:crypto";
9
+ import { unlink } from "node:fs/promises";
10
+ import { type IncomingMessage, ServerResponse } from "node:http";
11
+ import type { Subprocess } from "bun";
12
+ import { getOperatorActor } from "../http/operator-actor";
13
+ import { isGuestAllowedProxyPath } from "../http/page-proxy";
14
+ import { agentFirstPrincipal, requestPrincipal } from "../http/request-principal";
15
+ import { can } from "../rbac";
16
+ import { signPageSession } from "../utils/page-session";
17
+ import { setRequestAuth } from "../utils/request-auth-context";
18
+ import { getFreePort, SERVER_BOOT_HOOK_TIMEOUT_MS, waitForServer } from "./test-net";
19
+
20
+ let BASE = "";
21
+ const TEST_DB_PATH = `/tmp/test-page-guest-${Date.now()}.sqlite`;
22
+ const API_KEY = "example-test-page-guest-key-12345";
23
+ const PAGE_SECRET = "example-test-page-guest-secret-67890";
24
+ let serverProc: Subprocess;
25
+ const agentId = randomUUID();
26
+
27
+ const exp = () => Math.floor(Date.now() / 1000) + 3600;
28
+
29
+ async function api(path: string, init: RequestInit & { headers?: Record<string, string> } = {}) {
30
+ return fetch(`${BASE}${path}`, {
31
+ ...init,
32
+ headers: {
33
+ Authorization: `Bearer ${API_KEY}`,
34
+ "Content-Type": "application/json",
35
+ ...init.headers,
36
+ },
37
+ });
38
+ }
39
+
40
+ async function createPage(): Promise<string> {
41
+ const res = await api("/api/pages", {
42
+ method: "POST",
43
+ headers: { "X-Agent-ID": agentId },
44
+ body: JSON.stringify({
45
+ slug: `g-${randomUUID().slice(0, 8)}`,
46
+ title: "Guest Test",
47
+ contentType: "text/html",
48
+ authMode: "public",
49
+ body: "<h1>guest</h1>",
50
+ }),
51
+ });
52
+ expect(res.status).toBe(201);
53
+ return ((await res.json()) as { id: string }).id;
54
+ }
55
+
56
+ /** Call the API the way the page proxy does: shared key plus the signed page session. */
57
+ function asSession(cookie: string, pageId: string, path: string, init: RequestInit = {}) {
58
+ return api(path, {
59
+ ...init,
60
+ headers: { "X-Page-Session": cookie, "X-Page-Id": pageId },
61
+ });
62
+ }
63
+
64
+ let pageId = "";
65
+ let guestCookie = "";
66
+ let operatorCookie = "";
67
+ let userCookie = "";
68
+
69
+ beforeAll(async () => {
70
+ const port = await getFreePort();
71
+ BASE = `http://localhost:${port}`;
72
+ process.env.PAGE_SESSION_SECRET = PAGE_SECRET;
73
+ serverProc = Bun.spawn(["bun", "src/http.ts"], {
74
+ cwd: `${import.meta.dir}/../..`,
75
+ env: {
76
+ ...process.env,
77
+ PORT: String(port),
78
+ DATABASE_PATH: TEST_DB_PATH,
79
+ API_KEY,
80
+ AGENT_SWARM_API_KEY: API_KEY,
81
+ PAGE_SESSION_SECRET: PAGE_SECRET,
82
+ MCP_BASE_URL: `http://127.0.0.1:${port}`,
83
+ CAPABILITIES: "core,task-pool,messaging,profiles,services,scheduling,memory",
84
+ EMBEDDING_API_KEY: "",
85
+ OPENAI_API_KEY: "",
86
+ SLACK_BOT_TOKEN: "",
87
+ GITHUB_WEBHOOK_SECRET: "",
88
+ AGENTMAIL_API_KEY: "",
89
+ },
90
+ stdout: "ignore",
91
+ stderr: "ignore",
92
+ });
93
+ await waitForServer(`${BASE}/health`);
94
+ await api("/api/agents", {
95
+ method: "POST",
96
+ headers: { "X-Agent-ID": agentId },
97
+ body: JSON.stringify({
98
+ name: "Owner",
99
+ isLead: false,
100
+ role: "worker",
101
+ capabilities: ["core"],
102
+ maxTasks: 1,
103
+ }),
104
+ });
105
+ pageId = await createPage();
106
+
107
+ const created = await api("/api/users", {
108
+ method: "POST",
109
+ body: JSON.stringify({ name: "Viewer" }),
110
+ });
111
+ const userId = ((await created.json()) as { user: { id: string } }).user.id;
112
+ userCookie = await signPageSession({ pageId, exp: exp(), uid: userId, name: "Viewer" });
113
+ guestCookie = await signPageSession({ pageId, exp: exp() });
114
+ operatorCookie = await signPageSession({ pageId, exp: exp(), name: "guest-op", op: true });
115
+
116
+ await api("/api/config", {
117
+ method: "PUT",
118
+ body: JSON.stringify({
119
+ scope: "global",
120
+ key: "GUEST_PROBE_SECRET",
121
+ value: "s3cret-value",
122
+ isSecret: true,
123
+ }),
124
+ });
125
+ }, SERVER_BOOT_HOOK_TIMEOUT_MS);
126
+
127
+ afterAll(async () => {
128
+ serverProc?.kill();
129
+ await serverProc?.exited.catch(() => {});
130
+ for (const suffix of ["", "-wal", "-shm"])
131
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
132
+ });
133
+
134
+ describe("proxy allowlist for a guest session", () => {
135
+ test("allows the page's own record and its KV", async () => {
136
+ const own = await fetch(`${BASE}/@swarm/api/pages/${pageId}`, {
137
+ headers: { Cookie: `page_session=${guestCookie}` },
138
+ });
139
+ expect(own.status).toBe(200);
140
+ const put = await fetch(`${BASE}/@swarm/api/kv/probe`, {
141
+ method: "PUT",
142
+ headers: { Cookie: `page_session=${guestCookie}`, "Content-Type": "application/json" },
143
+ body: JSON.stringify({ value: "v" }),
144
+ });
145
+ expect(put.status).toBe(200);
146
+ const get = await fetch(`${BASE}/@swarm/api/kv/probe`, {
147
+ headers: { Cookie: `page_session=${guestCookie}` },
148
+ });
149
+ expect(get.status).toBe(200);
150
+ });
151
+
152
+ for (const [method, path] of [
153
+ ["GET", "config?includeSecrets=true"],
154
+ ["GET", "tasks"],
155
+ ["GET", `agents/${agentId}`],
156
+ ["GET", "whoami"],
157
+ ["POST", "memory/search"],
158
+ ["GET", "pages/not-this-page"],
159
+ ["GET", "pages"],
160
+ ["DELETE", "pages/not-this-page"],
161
+ ] as const) {
162
+ test(`refuses ${method} ${path} with 403`, async () => {
163
+ const res = await fetch(`${BASE}/@swarm/api/${path}`, {
164
+ method,
165
+ headers: { Cookie: `page_session=${guestCookie}`, "Content-Type": "application/json" },
166
+ body: method === "POST" ? "{}" : undefined,
167
+ });
168
+ expect(res.status).toBe(403);
169
+ });
170
+ }
171
+
172
+ test("allowlist helper", () => {
173
+ expect(isGuestAllowedProxyPath("GET", `pages/${pageId}`, pageId)).toBe(true);
174
+ expect(isGuestAllowedProxyPath("GET", "kv/a/incr", pageId)).toBe(true);
175
+ expect(isGuestAllowedProxyPath("PUT", `pages/${pageId}`, pageId)).toBe(false);
176
+ expect(isGuestAllowedProxyPath("GET", `pages/${pageId}/versions`, pageId)).toBe(false);
177
+ expect(isGuestAllowedProxyPath("GET", "kvx", pageId)).toBe(false);
178
+ });
179
+ });
180
+
181
+ describe("a password-page guest is isolated from another page", () => {
182
+ test("cannot read page B's record or KV, and gets its own KV without passwordHash", async () => {
183
+ const created = await api("/api/pages", {
184
+ method: "POST",
185
+ headers: { "X-Agent-ID": agentId },
186
+ body: JSON.stringify({
187
+ slug: `pw-${randomUUID().slice(0, 8)}`,
188
+ title: "Password Page",
189
+ contentType: "text/html",
190
+ authMode: "password",
191
+ password: "open-sesame",
192
+ body: "<h1>a</h1>",
193
+ }),
194
+ });
195
+ expect(created.status).toBe(201);
196
+ const pageA = ((await created.json()) as { id: string }).id;
197
+ const pageB = await createPage();
198
+ const nsB = `task:page:${pageB}`;
199
+
200
+ const seedB = await api("/api/kv/shared", {
201
+ method: "PUT",
202
+ headers: { "X-Page-Id": pageB },
203
+ body: JSON.stringify({ value: "b-value" }),
204
+ });
205
+ expect(seedB.status).toBe(200);
206
+
207
+ const unlock = await fetch(`${BASE}/p/${pageA}?key=open-sesame`);
208
+ expect(unlock.status).toBe(200);
209
+ const cookieA = /page_session=([^;]+)/.exec(unlock.headers.get("set-cookie") ?? "")?.[1];
210
+ expect(cookieA).toBeTruthy();
211
+ const asGuest = (path: string, init: RequestInit = {}) =>
212
+ fetch(`${BASE}/@swarm/api/${path}`, {
213
+ ...init,
214
+ headers: {
215
+ Cookie: `page_session=${cookieA}`,
216
+ "Content-Type": "application/json",
217
+ "X-Page-Id": pageB,
218
+ "X-Agent-ID": agentId,
219
+ },
220
+ });
221
+
222
+ const putA = await asGuest("kv/shared", {
223
+ method: "PUT",
224
+ body: JSON.stringify({ value: "a-value" }),
225
+ });
226
+ expect(putA.status).toBe(200);
227
+
228
+ expect((await asGuest(`pages/${pageB}`)).status).toBe(403);
229
+
230
+ for (const path of ["kv/shared", `kv/_/${encodeURIComponent(nsB)}/shared`]) {
231
+ const res = await asGuest(path);
232
+ expect(res.status).toBe(200);
233
+ expect(await res.json()).toMatchObject({
234
+ namespace: `task:page:${pageA}`,
235
+ key: "shared",
236
+ value: "a-value",
237
+ });
238
+ }
239
+
240
+ const list = await asGuest(`kv/_/${encodeURIComponent(nsB)}`);
241
+ expect(list.status).toBe(200);
242
+ expect(JSON.stringify(await list.json())).not.toContain("b-value");
243
+
244
+ const own = await asGuest(`pages/${pageA}`);
245
+ expect(own.status).toBe(200);
246
+ expect(await own.json()).not.toHaveProperty("passwordHash");
247
+
248
+ const stillB = await api("/api/kv/shared", { headers: { "X-Page-Id": pageB } });
249
+ expect(await stillB.json()).toMatchObject({ namespace: nsB, value: "b-value" });
250
+ });
251
+ });
252
+
253
+ describe("server-side gates deny a guest and keep the operator and user", () => {
254
+ test("config reads by session kind", async () => {
255
+ const guest = await (
256
+ await asSession(guestCookie, pageId, "/api/config?includeSecrets=true&key=GUEST_PROBE_SECRET")
257
+ ).json();
258
+ expect(JSON.stringify(guest)).not.toContain("s3cret-value");
259
+ const op = await (
260
+ await asSession(
261
+ operatorCookie,
262
+ pageId,
263
+ "/api/config?includeSecrets=true&key=GUEST_PROBE_SECRET",
264
+ )
265
+ ).json();
266
+ expect(JSON.stringify(op)).toContain("s3cret-value");
267
+ });
268
+
269
+ test("config resolved: guest masked", async () => {
270
+ const guest = await (
271
+ await asSession(
272
+ guestCookie,
273
+ pageId,
274
+ "/api/config/resolved?includeSecrets=true&key=GUEST_PROBE_SECRET",
275
+ )
276
+ ).json();
277
+ expect(JSON.stringify(guest)).not.toContain("s3cret-value");
278
+ });
279
+
280
+ test("config write (ensureConfigAdmin)", async () => {
281
+ const body = JSON.stringify({ scope: "global", key: "GUEST_WRITE", value: "x" });
282
+ expect(
283
+ (await asSession(guestCookie, pageId, "/api/config", { method: "PUT", body })).status,
284
+ ).toBe(403);
285
+ expect(
286
+ (await asSession(operatorCookie, pageId, "/api/config", { method: "PUT", body })).status,
287
+ ).toBe(200);
288
+ expect(
289
+ (await asSession(userCookie, pageId, "/api/config", { method: "PUT", body })).status,
290
+ ).toBe(200);
291
+ });
292
+
293
+ test("tasks (resolveTaskWritePrincipal, canSteerTask, canActOnOwnTask)", async () => {
294
+ const id = randomUUID();
295
+ for (const path of [
296
+ `/api/tasks/${id}/cancel`,
297
+ `/api/tasks/${id}/progress`,
298
+ `/api/tasks/${id}/steer`,
299
+ `/api/tasks/${id}/promote-draft`,
300
+ ]) {
301
+ expect(
302
+ (await asSession(guestCookie, pageId, path, { method: "POST", body: "{}" })).status,
303
+ ).toBe(403);
304
+ }
305
+ expect(
306
+ (
307
+ await asSession(operatorCookie, pageId, `/api/tasks/${id}/cancel`, {
308
+ method: "POST",
309
+ body: "{}",
310
+ })
311
+ ).status,
312
+ ).toBe(404);
313
+ expect(
314
+ (
315
+ await asSession(userCookie, pageId, `/api/tasks/${id}/cancel`, {
316
+ method: "POST",
317
+ body: "{}",
318
+ })
319
+ ).status,
320
+ ).toBe(404);
321
+ });
322
+
323
+ test("memory (ingestPrincipal)", async () => {
324
+ const body = JSON.stringify({ content: "c", name: "n", scope: "swarm", source: "manual" });
325
+ expect(
326
+ (await asSession(guestCookie, pageId, "/api/memory/index", { method: "POST", body })).status,
327
+ ).toBe(403);
328
+ expect(
329
+ (await asSession(guestCookie, pageId, "/api/memory/list", { method: "POST", body: "{}" }))
330
+ .status,
331
+ ).toBe(403);
332
+ expect(
333
+ (await asSession(operatorCookie, pageId, "/api/memory/index", { method: "POST", body }))
334
+ .status,
335
+ ).not.toBe(403);
336
+ });
337
+
338
+ test("admin surfaces: script-connections, mcp-servers, extensions, apps", async () => {
339
+ const probes: [string, string, string][] = [
340
+ ["POST", `/api/script-connections/${randomUUID()}/refresh`, "{}"],
341
+ [
342
+ "POST",
343
+ "/api/mcp-servers",
344
+ JSON.stringify({ name: "m", transport: "http", url: "http://127.0.0.1:1" }),
345
+ ],
346
+ ["POST", `/api/extensions/${randomUUID()}/enable`, "{}"],
347
+ ["POST", "/api/apps", JSON.stringify({ name: "a", definition: {} })],
348
+ ];
349
+ for (const [method, path, body] of probes) {
350
+ const guest = await asSession(guestCookie, pageId, path, { method, body });
351
+ expect([method, path, guest.status]).toEqual([method, path, 403]);
352
+ const op = await asSession(operatorCookie, pageId, path, { method, body });
353
+ expect([method, path, op.status === 403]).toEqual([method, path, false]);
354
+ }
355
+ });
356
+
357
+ test("approval cancel (approvalCancelPrincipal)", async () => {
358
+ const created = await api("/api/approval-requests", {
359
+ method: "POST",
360
+ body: JSON.stringify({
361
+ title: "t",
362
+ questions: [{ id: "q", type: "approval", label: "ok" }],
363
+ approvers: { policy: "any" },
364
+ }),
365
+ });
366
+ expect(created.status).toBe(201);
367
+ const { approvalRequest } = (await created.json()) as { approvalRequest: { id: string } };
368
+ const path = `/api/approval-requests/${approvalRequest.id}/cancel`;
369
+ expect(
370
+ (await asSession(guestCookie, pageId, path, { method: "POST", body: "{}" })).status,
371
+ ).toBe(403);
372
+ expect(
373
+ (await asSession(operatorCookie, pageId, path, { method: "POST", body: "{}" })).status,
374
+ ).toBe(200);
375
+ });
376
+
377
+ test("a user session through the proxy and an operator-launched session still work", async () => {
378
+ for (const cookie of [userCookie, operatorCookie]) {
379
+ const res = await fetch(`${BASE}/@swarm/api/tasks`, {
380
+ headers: { Cookie: `page_session=${cookie}` },
381
+ });
382
+ expect(res.status).toBe(200);
383
+ }
384
+ const whoami = await fetch(`${BASE}/@swarm/api/whoami`, {
385
+ headers: { Cookie: `page_session=${userCookie}` },
386
+ });
387
+ expect(((await whoami.json()) as { kind: string }).kind).toBe("user");
388
+ });
389
+ });
390
+
391
+ describe("in-process principal helpers", () => {
392
+ const fakeReq = () => ({ headers: {} }) as unknown as IncomingMessage;
393
+
394
+ test("requestPrincipal and agentFirstPrincipal map a guest to guest", async () => {
395
+ const req = fakeReq();
396
+ setRequestAuth(req, { kind: "guest" });
397
+ expect(await requestPrincipal(req, undefined)).toEqual({ kind: "guest" });
398
+ expect(await agentFirstPrincipal(req, undefined)).toEqual({ kind: "guest" });
399
+ const op = fakeReq();
400
+ setRequestAuth(op, { kind: "operator", fingerprint: "fp" });
401
+ expect(await requestPrincipal(op, undefined)).toEqual({ kind: "operator" });
402
+ expect(await agentFirstPrincipal(op, undefined)).toEqual({ kind: "operator" });
403
+ });
404
+
405
+ test("getOperatorActor refuses a guest", () => {
406
+ const req = fakeReq();
407
+ setRequestAuth(req, { kind: "guest" });
408
+ const res = new ServerResponse(req);
409
+ expect(getOperatorActor(req, res)).toBeNull();
410
+ expect(res.statusCode).toBe(403);
411
+ });
412
+
413
+ test("can() denies every verb for a guest and still allows the operator", () => {
414
+ expect(
415
+ can({ principal: { kind: "guest" }, verb: "config.read.secrets", source: "http" }).allow,
416
+ ).toBe(false);
417
+ expect(
418
+ can({ principal: { kind: "guest" }, verb: "memory.read.any", source: "http" }).allow,
419
+ ).toBe(false);
420
+ expect(
421
+ can({ principal: { kind: "operator" }, verb: "memory.read.any", source: "http" }).allow,
422
+ ).toBe(true);
423
+ });
424
+ });
@@ -389,7 +389,7 @@ describe("/@swarm/api/* proxy", () => {
389
389
  test("proxy does NOT require a bearer header (cookie is the auth)", async () => {
390
390
  const id = await createPage();
391
391
  const exp = Math.floor(Date.now() / 1000) + 3600;
392
- const token = await signPageSession({ pageId: id, exp });
392
+ const token = await signPageSession({ pageId: id, exp, op: true });
393
393
  // Send WITHOUT Authorization header — pure cookie auth.
394
394
  const res = await fetch(`${BASE}/@swarm/api/agents/${agentId}`, {
395
395
  headers: { Cookie: `page_session=${token}` },
@@ -84,7 +84,7 @@ describe("page-session viewer auth", () => {
84
84
  expect(forged?.page).toBeUndefined();
85
85
  });
86
86
 
87
- test("guest sessions retain separate page execution context", async () => {
87
+ test("guest sessions resolve to guest", async () => {
88
88
  const session = await signPageSession({
89
89
  pageId: PAGE_ID,
90
90
  exp: Math.floor(Date.now() / 1000) + 3600,
@@ -98,6 +98,27 @@ describe("page-session viewer auth", () => {
98
98
  }),
99
99
  API_KEY,
100
100
  );
101
+ expect(auth).toMatchObject({
102
+ kind: "guest",
103
+ page: { id: PAGE_ID, executionAgentId: PAGE_AGENT_ID },
104
+ });
105
+ });
106
+
107
+ test("operator-launched sessions stay operator", async () => {
108
+ const session = await signPageSession({
109
+ pageId: PAGE_ID,
110
+ exp: Math.floor(Date.now() / 1000) + 3600,
111
+ name: "guest-test",
112
+ op: true,
113
+ });
114
+ const auth = await resolveHttpRequestAuth(
115
+ request({
116
+ authorization: `Bearer ${API_KEY}`,
117
+ "x-page-session": session,
118
+ "x-page-id": PAGE_ID,
119
+ }),
120
+ API_KEY,
121
+ );
101
122
  expect(auth).toMatchObject({
102
123
  kind: "operator",
103
124
  page: { id: PAGE_ID, executionAgentId: PAGE_AGENT_ID },