@desplega.ai/agent-swarm 1.162.0 → 1.164.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 (240) hide show
  1. package/README.md +2 -1
  2. package/dist/{acp-adapter-74m73ae9.js → acp-adapter-8t22d18g.js} +2 -2
  3. package/dist/{actions-4sr14gnt.js → actions-49xrhww7.js} +32 -29
  4. package/dist/amp-adapter-p230tajt.js +34 -0
  5. package/dist/{anthropic-messages-8xxrzky9.js → anthropic-messages-hst62ws9.js} +3 -3
  6. package/dist/{app-pt68fbwp.js → app-tgzw3f3d.js} +11 -10
  7. package/dist/{assistant-cv3jytks.js → assistant-dgfjm07j.js} +33 -30
  8. package/dist/{azure-openai-responses-f5cqsyj8.js → azure-openai-responses-mh8tvf78.js} +9 -68
  9. package/dist/{boot-reembed-4ht4gqth.js → boot-reembed-0qzs614r.js} +13 -12
  10. package/dist/{boot-reembed-nxpcvpj9.js → boot-reembed-dgp6peyx.js} +12 -11
  11. package/dist/{boot-scrub-logs-k93kyw8k.js → boot-scrub-logs-fxnk021a.js} +10 -9
  12. package/dist/{boot-seeds-1skcrs0e.js → boot-seeds-xe7adjvm.js} +14 -13
  13. package/dist/{claude-adapter-r5g0tzge.js → claude-adapter-hx0n8a31.js} +22 -20
  14. package/dist/{claude-managed-adapter-yxkfxg8j.js → claude-managed-adapter-hskapeen.js} +3 -3
  15. package/dist/{claude-managed-setup-pkyrgshb.js → claude-managed-setup-p7mt6dfq.js} +1 -1
  16. package/dist/{claude-sdk-session-jxyxnrwd.js → claude-sdk-session-d61a9c8x.js} +22 -20
  17. package/dist/{cli-twrh04ap.js → cli-0y30kqz4.js} +3 -3
  18. package/dist/{cli-5xhpb3kv.js → cli-0z9dj97t.js} +9 -9
  19. package/dist/{cli-t845n9xk.js → cli-1nzb8ftr.js} +2 -2
  20. package/dist/{cli-kdkwd3yr.js → cli-1wd1n2ya.js} +38 -10
  21. package/dist/{cli-vxe6qa4h.js → cli-23az412e.js} +1 -1
  22. package/dist/{cli-bsky8g7w.js → cli-26x4d89j.js} +1 -1
  23. package/dist/{cli-swfmp1fx.js → cli-2ar0x7cz.js} +9 -9
  24. package/dist/cli-2ksgp01n.js +933 -0
  25. package/dist/{cli-rnyzn44p.js → cli-2zhhnz2h.js} +1 -1
  26. package/dist/{cli-vc9bp061.js → cli-3qaw32we.js} +5 -5
  27. package/dist/{cli-vy3ed88x.js → cli-402m8btc.js} +31 -3
  28. package/dist/{cli-5bcd7sfy.js → cli-43fh8fzw.js} +3 -3
  29. package/dist/{cli-5k6k8ta3.js → cli-4f7s6k3q.js} +13 -5
  30. package/dist/{cli-sqvfa090.js → cli-4ntg6tkp.js} +6 -5
  31. package/dist/{cli-kh04eqwe.js → cli-4y9bnpvy.js} +1 -1
  32. package/dist/{cli-qjjvzwzd.js → cli-5c2j5yvx.js} +1 -1
  33. package/dist/{cli-2zy95vhm.js → cli-6891ft0y.js} +2 -2
  34. package/dist/{cli-tre4bdgq.js → cli-6behs2bt.js} +1 -1
  35. package/dist/cli-7mm2r1kp.js +17 -0
  36. package/dist/cli-7xcgj20z.js +68 -0
  37. package/dist/{cli-4cwxn7gp.js → cli-812vt3ht.js} +2 -2
  38. package/dist/{cli-mbssg1xv.js → cli-8kt0bmgx.js} +1 -1
  39. package/dist/{cli-wjgs5wxe.js → cli-8rpq35c0.js} +7 -7
  40. package/dist/{cli-jxs2ygaw.js → cli-9m8fk2v7.js} +74 -29
  41. package/dist/{cli-pkd6s2wy.js → cli-9q3wt23f.js} +2 -2
  42. package/dist/{cli-s3hwpt9v.js → cli-9s6fsg07.js} +30 -10
  43. package/dist/{cli-xqpk3rp7.js → cli-9x3bwfd3.js} +28 -5
  44. package/dist/{cli-qrgt5bqf.js → cli-agamgm0p.js} +1 -1
  45. package/dist/{cli-13hw4z91.js → cli-b0f70z24.js} +1 -1
  46. package/dist/{cli-gpqpmns7.js → cli-cgrvsz2t.js} +2 -2
  47. package/dist/{cli-2hhfhfq5.js → cli-cn53e7vc.js} +8 -1
  48. package/dist/{cli-p0r8sxj5.js → cli-crjazzsg.js} +222 -4
  49. package/dist/{cli-tcen9p5w.js → cli-d37n8b7c.js} +5 -5
  50. package/dist/{cli-wxkkd0e0.js → cli-dgh781z9.js} +3 -3
  51. package/dist/cli-dsdg2wrk.js +516 -0
  52. package/dist/{cli-q24re23n.js → cli-e2xnc4m5.js} +4 -4
  53. package/dist/{cli-f7kvq46p.js → cli-erxeg64j.js} +20 -0
  54. package/dist/{cli-8ev9xqnx.js → cli-fajbc7sa.js} +17 -219
  55. package/dist/{cli-g739zqzt.js → cli-fj86xr3c.js} +2 -2
  56. package/dist/{cli-zvhd3f64.js → cli-fpbvcf55.js} +3 -3
  57. package/dist/{cli-kp42yek0.js → cli-fyra5y8k.js} +424 -71
  58. package/dist/{cli-jyy6pm2f.js → cli-g3qgrzck.js} +15 -3
  59. package/dist/{cli-eg0b3p0k.js → cli-g5erf0zt.js} +23 -14
  60. package/dist/{cli-cvatsam7.js → cli-gcvzvq3e.js} +4 -4
  61. package/dist/{cli-89s4ngw3.js → cli-gvcgmx69.js} +1 -1
  62. package/dist/{cli-7tre70zp.js → cli-j261c831.js} +138 -128
  63. package/dist/{cli-sptrj5f5.js → cli-j3me0jw2.js} +2 -2
  64. package/dist/{cli-zxqx25nz.js → cli-k6w44yvq.js} +6 -6
  65. package/dist/cli-ndx5yz5n.js +3299 -0
  66. package/dist/{cli-ykk5jaax.js → cli-nexwh8zg.js} +34 -29
  67. package/dist/{cli-rfjw9jw2.js → cli-ng97fjq7.js} +2 -2
  68. package/dist/{cli-4x0csdjq.js → cli-ngne1dng.js} +2 -2
  69. package/dist/{cli-4dcpaefp.js → cli-pfzrhb8n.js} +2 -2
  70. package/dist/{cli-hb5c2x5y.js → cli-q38n4bq0.js} +2 -2
  71. package/dist/{cli-cebmgwsc.js → cli-r109nq7n.js} +4 -4
  72. package/dist/{cli-nq7yqw7b.js → cli-t7g0vmtc.js} +4 -4
  73. package/dist/{cli-jf7wmv1r.js → cli-tsvj33y0.js} +58 -19
  74. package/dist/{cli-7af8yhav.js → cli-vbr859rz.js} +7 -4
  75. package/dist/{cli-nt8hecg9.js → cli-wk3v6h55.js} +67 -38
  76. package/dist/{cli-vyqs9ya8.js → cli-wwvk0tsy.js} +7 -7
  77. package/dist/{cli-rj0gqwwp.js → cli-y2de3bzp.js} +1 -1
  78. package/dist/{cli-hmsw1gpy.js → cli-y74170pb.js} +4 -4
  79. package/dist/cli-ydt3zkwv.js +70 -0
  80. package/dist/{cli-xk79303e.js → cli-yj790e19.js} +9 -1
  81. package/dist/cli-zt3w1cn1.js +137 -0
  82. package/dist/{cli-trc24wk2.js → cli-zxg80zs9.js} +1 -1
  83. package/dist/cli.js +23 -22
  84. package/dist/{cloudflare-workers-ai-system-one-k8ddqjkq.js → cloudflare-workers-ai-system-one-tt484t61.js} +2 -2
  85. package/dist/{codex-adapter-esvmnhn7.js → codex-adapter-e3pky8aj.js} +14 -13
  86. package/dist/{codex-hook-m73jmqgx.js → codex-hook-b1fb7c2k.js} +4 -3
  87. package/dist/{codex-session-runner-ban9excr.js → codex-session-runner-1gq27w2t.js} +14 -13
  88. package/dist/{commands-75413p2n.js → commands-q07ezjzn.js} +10 -9
  89. package/dist/cursor-adapter-50cz7yba.js +32 -0
  90. package/dist/{db-7f5pecwg.js → db-wwt0sq9q.js} +18 -9
  91. package/dist/{dispatcher-fh9at8nz.js → dispatcher-wrg35vdd.js} +31 -28
  92. package/dist/{dsh-adapter-wpze65d4.js → dsh-adapter-sa6rxfpc.js} +6 -6
  93. package/dist/{e2b-gjgdjp91.js → e2b-v5np73yj.js} +4 -4
  94. package/dist/{easter-egg-3d-3w5c2n4c.js → easter-egg-3d-h2hx9b64.js} +2 -2
  95. package/dist/{event-bus-sfx8dx3t.js → event-bus-vkbhzt4n.js} +1 -1
  96. package/dist/{execute-f50wg9nh.js → execute-6gczxy8v.js} +42 -13
  97. package/dist/{google-generative-ai-jxxpnegy.js → google-generative-ai-d6nphg3q.js} +3 -3
  98. package/dist/{google-vertex-x7q1angm.js → google-vertex-cpshkref.js} +3 -3
  99. package/dist/{handlers-w13787bb.js → handlers-rv3gxvsp.js} +34 -31
  100. package/dist/{hook-46g1jrvx.js → hook-bcf5m80y.js} +15 -13
  101. package/dist/{hook-4tpts2ry.js → hook-z8b1jm2d.js} +16 -14
  102. package/dist/{http-19pzbfrt.js → http-56f7yt97.js} +932 -134
  103. package/dist/{index-scptvdxg.js → index-0kncjnrm.js} +30 -23
  104. package/dist/{index-dqmp15bq.js → index-1e6cg96n.js} +31 -28
  105. package/dist/{index-qb4q1y3e.js → index-8xgdb3k8.js} +31 -28
  106. package/dist/index-adw4zr9j.js +1020 -0
  107. package/dist/{index-h5gs261z.js → index-etdrp7yp.js} +140 -140
  108. package/dist/{index-8fd4dm4h.js → index-f9zscmft.js} +32 -29
  109. package/dist/index-fdk1t4d6.js +91297 -0
  110. package/dist/{index-s75wc4wd.js → index-jvb37h77.js} +12 -11
  111. package/dist/{keepalive-xa5ts9cd.js → keepalive-rp8daj4s.js} +12 -11
  112. package/dist/{lead-qp4xkvjq.js → lead-dfp77erc.js} +45 -40
  113. package/dist/{maintenance-rm5885hm.js → maintenance-wtmw3jqv.js} +13 -12
  114. package/dist/{mistral-conversations-rw7fbt22.js → mistral-conversations-8445ddjn.js} +2 -2
  115. package/dist/{oauth-refresh-sweep-1csdfebf.js → oauth-refresh-sweep-yhzqt95v.js} +11 -10
  116. package/dist/{onboard-r6b78cs4.js → onboard-9z8rwbcy.js} +2 -2
  117. package/dist/{openai-codex-responses-efvrgrq3.js → openai-codex-responses-9ynm88zy.js} +3 -3
  118. package/dist/{openai-completions-bbbrrz4z.js → openai-completions-nhqwjmaw.js} +2 -2
  119. package/dist/{openai-responses-9jjyrcm1.js → openai-responses-xx1c4kgm.js} +3 -3
  120. package/dist/{opencode-adapter-kb3g6pkf.js → opencode-adapter-1nxb6555.js} +5 -5
  121. package/dist/{otel-impl-0y9knpky.js → otel-impl-b83gaxw2.js} +1 -1
  122. package/dist/{pi-mono-adapter-z6s1bme2.js → pi-mono-adapter-ddxz8pjp.js} +18 -17
  123. package/dist/{pricing-refresh-mth26tgp.js → pricing-refresh-tmjvazb6.js} +12 -11
  124. package/dist/{rbac-roles-kwntb23q.js → rbac-roles-pa0gzjws.js} +11 -10
  125. package/dist/{rbac-roles-vpk4ebm8.js → rbac-roles-rhexa9s2.js} +12 -11
  126. package/dist/{render-v2-hcm7h00y.js → render-v2-ask84n8y.js} +11 -10
  127. package/dist/{runtime-vfjsfsc5.js → runtime-ckjnj3vf.js} +4 -4
  128. package/dist/{setup-hbyzd65h.js → setup-5ga5gdsy.js} +2 -2
  129. package/dist/{typesafe-system-one-2ef30pec.js → typesafe-system-one-datfr82p.js} +2 -2
  130. package/dist/{virtual-modules-9bv7ry91.js → virtual-modules-bmp3zwjf.js} +12 -11
  131. package/dist/{work-objects-m09sxnhe.js → work-objects-snnh0t7j.js} +34 -31
  132. package/dist/{worker-0h35rvk4.js → worker-rxyymrhb.js} +45 -40
  133. package/openapi.json +997 -39
  134. package/package.json +6 -5
  135. package/src/be/db/active-sessions.ts +30 -0
  136. package/src/be/db/approvals.ts +54 -0
  137. package/src/be/db.ts +25 -4
  138. package/src/be/mcp-server-checked-update.ts +34 -0
  139. package/src/be/mcp-server-stdio-gate.ts +34 -0
  140. package/src/be/memory/key-browser.ts +304 -0
  141. package/src/be/memory/providers/sqlite-store.ts +16 -3
  142. package/src/be/migrations/194_memory_orphan_chunks.sql +15 -0
  143. package/src/be/migrations/195_approval_request_approvals.sql +6 -0
  144. package/src/be/model-validation.ts +12 -2
  145. package/src/be/multi-runtime.ts +4 -3
  146. package/src/be/pricing-normalize.ts +11 -0
  147. package/src/be/repo-checked-update.ts +37 -0
  148. package/src/be/repo-merge-policy.ts +15 -0
  149. package/src/be/seed-pricing.ts +48 -1
  150. package/src/be/swarm-config-guard.ts +1 -0
  151. package/src/commands/provider-credentials.ts +44 -2
  152. package/src/extensions/contract-types.generated.ts +1 -1
  153. package/src/extensions/contract.ts +100 -0
  154. package/src/extensions/dispatcher.ts +19 -0
  155. package/src/extensions/post-bridge.ts +191 -0
  156. package/src/github/handlers.ts +128 -2
  157. package/src/gitlab/workflow-action-name.ts +15 -0
  158. package/src/heartbeat/heartbeat.ts +4 -3
  159. package/src/http/active-sessions.ts +6 -5
  160. package/src/http/agents.ts +10 -1
  161. package/src/http/approval-requests.ts +271 -35
  162. package/src/http/index.ts +1 -1
  163. package/src/http/mcp-servers.ts +74 -50
  164. package/src/http/memory.ts +275 -6
  165. package/src/http/repos.ts +58 -18
  166. package/src/http/request-principal.ts +19 -0
  167. package/src/http/session-cost-recompute.ts +64 -0
  168. package/src/http/session-data.ts +11 -0
  169. package/src/http/tasks.ts +9 -1
  170. package/src/http/webhooks.ts +17 -5
  171. package/src/prompts/base-prompt.ts +9 -4
  172. package/src/prompts/resolver.ts +1 -1
  173. package/src/prompts/session-templates.ts +28 -0
  174. package/src/providers/amp-adapter.ts +1232 -0
  175. package/src/providers/cursor-adapter.ts +638 -0
  176. package/src/providers/index.ts +9 -1
  177. package/src/providers/pricing-sources.md +39 -0
  178. package/src/providers/reasoning-effort.ts +15 -1
  179. package/src/providers/types.ts +11 -1
  180. package/src/rbac/legacy-policy.ts +23 -8
  181. package/src/rbac/permissions.ts +27 -1
  182. package/src/scripts-runtime/types/swarm-extension.d.ts +119 -0
  183. package/src/tests/amp-adapter.test.ts +822 -0
  184. package/src/tests/amp-budget-export-failed.test.ts +133 -0
  185. package/src/tests/amp-pricing-helpers.ts +16 -0
  186. package/src/tests/amp-pricing.test.ts +218 -0
  187. package/src/tests/api-drain-worker-e2e.test.ts +18 -7
  188. package/src/tests/approval-respond-auth.test.ts +371 -0
  189. package/src/tests/base-prompt.test.ts +13 -0
  190. package/src/tests/claude-adapter-binary.test.ts +24 -0
  191. package/src/tests/claude-adapter.test.ts +8 -0
  192. package/src/tests/cursor-adapter.test.ts +690 -0
  193. package/src/tests/db-task-read-contract.test.ts +5 -0
  194. package/src/tests/db-task-write-contract.test.ts +5 -0
  195. package/src/tests/docs-index-footprint.test.js +3 -3
  196. package/src/tests/extensions-post-bridge.test.ts +669 -0
  197. package/src/tests/extensions-run-context.test.ts +61 -0
  198. package/src/tests/fixtures/fake-amp.ts +304 -0
  199. package/src/tests/github-handlers-inline-comments.test.ts +147 -1
  200. package/src/tests/gitlab-workflow-event-names.test.ts +136 -0
  201. package/src/tests/mcp-input-ergonomics.test.ts +15 -10
  202. package/src/tests/mcp-server-stdio-gate.test.ts +438 -0
  203. package/src/tests/memory-http-access.test.ts +221 -0
  204. package/src/tests/memory-keys-endpoint.test.ts +406 -0
  205. package/src/tests/memory-store.test.ts +166 -0
  206. package/src/tests/model-tier-resolution.test.ts +3 -3
  207. package/src/tests/pricing-refresh.test.ts +5 -4
  208. package/src/tests/prompt-template-session.test.ts +5 -3
  209. package/src/tests/provider-process-group.test.ts +41 -0
  210. package/src/tests/provider-registration.test.ts +3 -1
  211. package/src/tests/rbac-charact-misc-tools.test.ts +18 -3
  212. package/src/tests/rbac-engine.test.ts +15 -11
  213. package/src/tests/rbac-wire-e2e.test.ts +4 -9
  214. package/src/tests/repo-allow-merge-gate.test.ts +382 -0
  215. package/src/tests/script-connections.test.ts +4 -0
  216. package/src/tests/send-task-requester-gate.test.ts +260 -0
  217. package/src/tests/session-activity-liveness.test.ts +248 -0
  218. package/src/tests/session-costs-golden.test.ts +28 -0
  219. package/src/tests/webhooks-task-blocked.test.ts +4 -4
  220. package/src/tests/write-gate.ts +68 -0
  221. package/src/tests/x402-client.test.ts +124 -0
  222. package/src/tests/x402-spending-tracker.test.ts +66 -0
  223. package/src/tools/mcp-servers/mcp-server-create.ts +23 -1
  224. package/src/tools/mcp-servers/mcp-server-update.ts +46 -28
  225. package/src/tools/memory-delete.ts +3 -3
  226. package/src/tools/repos/update-repo.ts +26 -5
  227. package/src/tools/send-task.ts +24 -1
  228. package/src/tools/steer-task.ts +1 -1
  229. package/src/types.ts +37 -2
  230. package/src/utils/amp-models.ts +114 -0
  231. package/src/utils/credentials.ts +8 -0
  232. package/src/utils/process-group.ts +79 -0
  233. package/src/workflows/event-bus.ts +28 -0
  234. package/src/x402/client.ts +36 -14
  235. package/src/x402/spending-tracker.ts +64 -3
  236. package/templates/skills/agent-browser/SKILL.md +5 -0
  237. package/templates/skills/agent-browser/content.md +5 -0
  238. package/templates/skills/heartbeat-runbook/content.md +1 -0
  239. package/templates/skills/swarm-extensions/content.md +4 -0
  240. package/dist/cli-y89t4nca.js +0 -67
@@ -51,6 +51,172 @@ describe("SqliteMemoryStore", () => {
51
51
  }
52
52
  });
53
53
 
54
+ test("deleting either keyed chunk removes the document before a shorter replacement", async () => {
55
+ for (const deletedIndex of [0, 1]) {
56
+ const key = `/test/delete-chunks/${deletedIndex}`;
57
+ const inputs = [0, 1].map((chunkIndex) => ({
58
+ agentId: agentA,
59
+ scope: "agent" as const,
60
+ source: "manual" as const,
61
+ name: "old document",
62
+ content: `chunk ${chunkIndex}`,
63
+ key,
64
+ chunkIndex,
65
+ totalChunks: 2,
66
+ }));
67
+ const chunks = await store.storeBatch(inputs);
68
+ const otherOwner = await store.store({ ...inputs[0]!, agentId: agentB });
69
+ const otherScope = await store.store({ ...inputs[0]!, scope: "swarm" });
70
+ await store.whenFtsPopulated();
71
+ for (const chunk of chunks) await store.updateEmbedding(chunk.id, vector({ 0: 1 }), "test");
72
+ expect(await store.delete(chunks[deletedIndex]!.id)).toBe(true);
73
+ for (const chunk of chunks) {
74
+ expect(await store.peek(chunk.id)).toBeNull();
75
+ expect(
76
+ await getDbClient().get("SELECT memory_id FROM memory_fts WHERE memory_id = ?", [
77
+ chunk.id,
78
+ ]),
79
+ ).toBeNull();
80
+ if (isSqliteVecAvailable()) {
81
+ expect(
82
+ await getDbClient().get("SELECT memory_id FROM memory_vec WHERE memory_id = ?", [
83
+ chunk.id,
84
+ ]),
85
+ ).toBeNull();
86
+ }
87
+ }
88
+ expect(await store.peek(otherOwner.id)).not.toBeNull();
89
+ expect(await store.peek(otherScope.id)).not.toBeNull();
90
+ await store.store({ ...inputs[0]!, name: "replacement", totalChunks: 1 });
91
+ const rows = await getDbClient().query<{ chunkIndex: number }>(
92
+ "SELECT chunkIndex FROM agent_memory WHERE key = ? AND scope = 'agent' AND agentId = ?",
93
+ [key, agentA],
94
+ );
95
+ expect(rows).toEqual([{ chunkIndex: 0 }]);
96
+ expect(await store.delete(chunks[deletedIndex]!.id)).toBe(false);
97
+ }
98
+ });
99
+
100
+ test("deletion groups unowned swarm chunks without touching another owner", async () => {
101
+ const input = {
102
+ agentId: null,
103
+ scope: "swarm" as const,
104
+ source: "manual" as const,
105
+ name: "unowned",
106
+ content: "chunk",
107
+ key: "/test/delete-unowned",
108
+ };
109
+ const chunks = await store.storeBatch(
110
+ [0, 1].map((chunkIndex) => ({
111
+ ...input,
112
+ chunkIndex,
113
+ totalChunks: 2,
114
+ })),
115
+ );
116
+ const owned = await store.store({ ...input, agentId: agentA });
117
+ expect(await store.delete(chunks[1]!.id)).toBe(true);
118
+ for (const chunk of chunks) expect(await store.peek(chunk.id)).toBeNull();
119
+ expect(await store.peek(owned.id)).not.toBeNull();
120
+ });
121
+
122
+ test("source-path rewrite removes old chunks and duplicate-key writes roll back", async () => {
123
+ const key = "/test/rewrite-chunks";
124
+ const input = {
125
+ agentId: agentA,
126
+ scope: "agent" as const,
127
+ source: "manual" as const,
128
+ name: "rewrite",
129
+ content: "old",
130
+ key,
131
+ sourcePath: key,
132
+ };
133
+ const chunks = await store.storeBatch(
134
+ [0, 1].map((chunkIndex) => ({
135
+ ...input,
136
+ chunkIndex,
137
+ totalChunks: 2,
138
+ })),
139
+ );
140
+ await expect(store.storeBatch([{ ...input, content: "new" }])).rejects.toThrow();
141
+ for (const chunk of chunks) expect(await store.peek(chunk.id)).not.toBeNull();
142
+ expect(await store.deleteBySourcePath(key, agentA)).toBe(2);
143
+ const replacement = await store.storeBatch([{ ...input, content: "new" }]);
144
+ expect(replacement).toHaveLength(1);
145
+ for (const chunk of chunks) expect(await store.peek(chunk.id)).toBeNull();
146
+ });
147
+
148
+ test("orphan migration preserves valid and legacy chunks and is idempotent", async () => {
149
+ const input = {
150
+ agentId: agentA,
151
+ scope: "agent" as const,
152
+ source: "manual" as const,
153
+ name: "migration",
154
+ content: "chunk",
155
+ };
156
+ const valid = await store.storeBatch(
157
+ [0, 1].map((chunkIndex) => ({
158
+ ...input,
159
+ key: "/test/migration-valid",
160
+ chunkIndex,
161
+ totalChunks: 2,
162
+ })),
163
+ );
164
+ const head = await store.store({ ...input, key: "/test/migration-short" });
165
+ const tail = await store.store({ ...input, key: head.key, chunkIndex: 1, totalChunks: 2 });
166
+ const missingHead = await store.store({
167
+ ...input,
168
+ key: "/test/migration-missing",
169
+ chunkIndex: 1,
170
+ totalChunks: 2,
171
+ });
172
+ const legacy = await store.store({ ...input, chunkIndex: 1, totalChunks: 2 });
173
+ const nullOwnerHead = await store.store({
174
+ ...input,
175
+ agentId: null,
176
+ scope: "swarm",
177
+ key: "/test/migration-null-owner",
178
+ totalChunks: 2,
179
+ });
180
+ const nullOwnerTail = await store.store({
181
+ ...input,
182
+ agentId: null,
183
+ scope: "swarm",
184
+ key: nullOwnerHead.key,
185
+ chunkIndex: 1,
186
+ totalChunks: 2,
187
+ });
188
+ const isolatedOwner = await store.store({
189
+ ...input,
190
+ agentId: agentB,
191
+ key: valid[0]!.key,
192
+ chunkIndex: 1,
193
+ totalChunks: 2,
194
+ });
195
+ const isolatedScope = await store.store({
196
+ ...input,
197
+ scope: "swarm",
198
+ key: valid[0]!.key,
199
+ chunkIndex: 1,
200
+ totalChunks: 2,
201
+ });
202
+ const sql = await Bun.file(
203
+ new URL("../be/migrations/194_memory_orphan_chunks.sql", import.meta.url),
204
+ ).text();
205
+ await getDbClient().run(sql);
206
+ for (const row of [tail, missingHead, isolatedOwner, isolatedScope]) {
207
+ expect(await store.peek(row.id)).toBeNull();
208
+ expect(
209
+ await getDbClient().get("SELECT id FROM agent_memory_version WHERE memory_id = ?", [
210
+ row.id,
211
+ ]),
212
+ ).toBeNull();
213
+ }
214
+ for (const row of [...valid, head, legacy, nullOwnerHead, nullOwnerTail]) {
215
+ expect(await store.peek(row.id)).not.toBeNull();
216
+ }
217
+ expect((await getDbClient().run(sql)).changes).toBe(0);
218
+ });
219
+
54
220
  describe("store()", () => {
55
221
  test("creates memory with correct fields", async () => {
56
222
  const memory = await store.store({
@@ -508,8 +508,8 @@ describe("previewModelTiers", () => {
508
508
 
509
509
  // acp has no portable tier mapping, so it has nothing to preview.
510
510
  expect(rows.some((r) => r.provider === "acp")).toBe(false);
511
- expect(new Set(rows.map((r) => r.provider)).size).toBe(7);
512
- expect(rows).toHaveLength(28);
511
+ expect(new Set(rows.map((r) => r.provider)).size).toBe(9);
512
+ expect(rows).toHaveLength(36);
513
513
  });
514
514
 
515
515
  test("an alias that resolves to nothing reports the default it falls back to", async () => {
@@ -564,7 +564,7 @@ describe("GET /api/models-catalog/tiers", () => {
564
564
  expect(handled).toBe(true);
565
565
  expect(status).toBe(200);
566
566
  const body = JSON.parse(bodyStr) as { tiers: { provider: string; tier: string }[] };
567
- expect(body.tiers).toHaveLength(28);
567
+ expect(body.tiers).toHaveLength(36);
568
568
  expect(body.tiers.find((t) => t.provider === "claude" && t.tier === "smart")).toMatchObject({
569
569
  key: "MODEL_TIER_CLAUDE_SMART",
570
570
  source: "tier-config",
@@ -101,13 +101,14 @@ describe("models.dev runtime pricing refresh", () => {
101
101
  VALUES ('codex', 'gpt-refresh-test', 'input', 0, 1, 0, 0)`,
102
102
  );
103
103
 
104
+ // codex, pi, cursor and amp each project the OpenAI rows: 4 providers x 2 token classes.
104
105
  const first = await refreshPricingFromModelsDev({
105
106
  now: 1_000,
106
107
  fetchImpl: async () => responseFor(openAiCache(2, 8), '"etag-1"'),
107
108
  });
108
109
  expect(first.status).toBe("refreshed");
109
- expect(first.candidateRows).toBe(4);
110
- expect(first.inserted).toBe(4);
110
+ expect(first.candidateRows).toBe(8);
111
+ expect(first.inserted).toBe(8);
111
112
  expect(first.unchanged).toBe(0);
112
113
 
113
114
  const activeChanged = await getActivePricingRow("codex", "gpt-refresh-test", "input", 1_000);
@@ -119,7 +120,7 @@ describe("models.dev runtime pricing refresh", () => {
119
120
  fetchImpl: async () => responseFor(openAiCache(2, 8), '"etag-2"'),
120
121
  });
121
122
  expect(second.inserted).toBe(0);
122
- expect(second.unchanged).toBe(4);
123
+ expect(second.unchanged).toBe(8);
123
124
 
124
125
  const rows = await getDbClient().query<{ effective_from: number }>(
125
126
  `SELECT effective_from FROM pricing
@@ -208,7 +209,7 @@ describe("models.dev runtime pricing refresh", () => {
208
209
 
209
210
  const logs = await getLogsByEventType("pricing.refresh");
210
211
  expect(logs).toHaveLength(1);
211
- expect(logs[0]?.newValue).toContain("inserted=4");
212
+ expect(logs[0]?.newValue).toContain("inserted=8");
212
213
  expect(logs[0]?.metadata).toContain('"etag":"\\"etag-log\\""');
213
214
  });
214
215
  });
@@ -21,6 +21,7 @@ const EM_DASH = "\u2014";
21
21
  const SYSTEM_TEMPLATES = [
22
22
  "system.agent.communication",
23
23
  "system.agent.communication.remote",
24
+ "system.agent.cursor.first_message",
24
25
  "system.agent.lead",
25
26
  "system.agent.memory",
26
27
  "system.agent.memory.remote",
@@ -36,6 +37,7 @@ const SYSTEM_TEMPLATES = [
36
37
  "system.agent.steering.delivery",
37
38
  "system.agent.tool_preload",
38
39
  "system.agent.tool_discovery.search",
40
+ "system.agent.tool_discovery.amp",
39
41
  "system.agent.tool_discovery.direct",
40
42
  "system.agent.tools_skills",
41
43
  "system.agent.worker",
@@ -114,11 +116,11 @@ describe("Session templates: registration", () => {
114
116
  expect(sessionSystemEventTypes("session")).toEqual([...SESSION_TEMPLATES].sort());
115
117
  });
116
118
 
117
- test("registers 28 system and session templates in total", () => {
119
+ test("registers 30 system and session templates in total", () => {
118
120
  const all = getAllTemplateDefinitions();
119
121
  const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
120
- // 23 system blocks + 5 session composites.
121
- expect(sessionSystem.length).toBe(28);
122
+ // 25 system blocks + 5 session composites.
123
+ expect(sessionSystem.length).toBe(30);
122
124
  });
123
125
 
124
126
  test("drops the v1 blocks that prompt v2 deleted", () => {
@@ -4,6 +4,11 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { ClaudeAdapter } from "../providers/claude-adapter";
6
6
  import type { ProviderSession, ProviderSessionConfig } from "../providers/types";
7
+ import {
8
+ detachedProcessGroup,
9
+ listDescendantPids,
10
+ terminateProcessTree,
11
+ } from "../utils/process-group";
7
12
  import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
8
13
 
9
14
  function processExists(pid: number): boolean {
@@ -193,4 +198,40 @@ describe("provider process groups", () => {
193
198
  },
194
199
  CHILD_PROCESS_TEST_BUDGET_MS,
195
200
  );
201
+
202
+ test.skipIf(process.platform !== "linux")(
203
+ "terminateProcessTree also kills a descendant that left the process group",
204
+ async () => {
205
+ // `setsid` gives the sleep its own session, as Amp's shell tool does, so a
206
+ // group kill cannot reach it.
207
+ const proc = Bun.spawn(["sh", "-c", "setsid sleep 120 & wait"], {
208
+ stdin: "ignore",
209
+ stdout: "ignore",
210
+ stderr: "ignore",
211
+ detached: detachedProcessGroup,
212
+ });
213
+ let descendants: number[] = [];
214
+ try {
215
+ for (let i = 0; i < 100 && descendants.length === 0; i++) {
216
+ descendants = await listDescendantPids(proc.pid);
217
+ if (descendants.length === 0) await Bun.sleep(30);
218
+ }
219
+ expect(descendants.length).toBeGreaterThan(0);
220
+ expect(descendants).not.toContain(proc.pid);
221
+ await terminateProcessTree(proc.pid);
222
+ for (const pid of descendants) {
223
+ expect(await waitForProcessGone(pid, PROCESS_GONE_TIMEOUT_MS)).toBe(true);
224
+ }
225
+ } finally {
226
+ for (const pid of descendants) {
227
+ try {
228
+ process.kill(pid, "SIGKILL");
229
+ } catch {
230
+ // Already gone.
231
+ }
232
+ }
233
+ }
234
+ },
235
+ CHILD_PROCESS_TEST_BUDGET_MS,
236
+ );
196
237
  });
@@ -231,6 +231,8 @@ if (isolatedBootstrapBlock === bootstrapBlock) {
231
231
  const BOOTSTRAP: Record<ProviderName, { expect: string } | { exempt: string }> = {
232
232
  claude: { expect: "Warning: claude provider has no credentials yet" },
233
233
  dsh: { expect: "Warning: dsh provider has no credentials yet" },
234
+ amp: { expect: "Warning: amp provider has no credentials yet" },
235
+ cursor: { expect: "Warning: cursor provider has no credentials yet" },
234
236
  pi: { expect: "Warning: pi provider has no credentials yet" },
235
237
  opencode: { expect: "Warning: opencode provider has no credentials yet" },
236
238
  "claude-managed": { expect: "Warning: claude-managed provider missing:" },
@@ -280,7 +282,7 @@ function extractMarkedBlock(name: string): string {
280
282
  /** Resolvable no-op stubs for every binary the verify chain may look up. */
281
283
  function makeBinaryStubDir(): string {
282
284
  const dir = mkdtempSync(join(tmpdir(), "provider-registration-bin-"));
283
- for (const name of ["claude", "codex", "opencode", "dsh"]) {
285
+ for (const name of ["claude", "codex", "opencode", "dsh", "amp"]) {
284
286
  const file = join(dir, name);
285
287
  writeFileSync(file, "#!/bin/sh\nexit 0\n");
286
288
  chmodSync(file, 0o755);
@@ -281,7 +281,7 @@ describe("context-history / context-diff gates (characterization)", () => {
281
281
  });
282
282
 
283
283
  describe("memory-delete gate (characterization)", () => {
284
- // memory-delete.ts:54,56 — owner OR (lead AND scope=swarm)
284
+ // memory.delete.any for agents: owner of an agent-scoped memory, or lead for own/swarm-scoped
285
285
  test("worker cannot delete another agent's memory", async () => {
286
286
  const memory = await getMemoryStore().store({
287
287
  agentId: LEAD_ID,
@@ -295,7 +295,7 @@ describe("memory-delete gate (characterization)", () => {
295
295
 
296
296
  expect(result.structuredContent.success).toBe(false);
297
297
  expect(result.structuredContent.message).toBe(
298
- "Permission denied. You can only delete your own memories, or swarm memories if you are the lead.",
298
+ "Permission denied. You can only delete your own agent-scoped memories; only the lead can delete swarm memories.",
299
299
  );
300
300
  // DB not mutated
301
301
  expect(await getMemoryStore().peek(memory.id)).not.toBeNull();
@@ -316,6 +316,21 @@ describe("memory-delete gate (characterization)", () => {
316
316
  expect(await getMemoryStore().peek(memory.id)).toBeNull();
317
317
  });
318
318
 
319
+ test("owner worker cannot delete its own swarm-scoped memory", async () => {
320
+ const memory = await getMemoryStore().store({
321
+ agentId: WORKER_ID,
322
+ scope: "swarm",
323
+ name: "charact worker swarm memory",
324
+ content: "shared by worker",
325
+ source: "manual",
326
+ });
327
+
328
+ const result = await callTool("memory-delete", WORKER_ID, { memoryId: memory.id });
329
+
330
+ expect(result.structuredContent.success).toBe(false);
331
+ expect(await getMemoryStore().peek(memory.id)).not.toBeNull();
332
+ });
333
+
319
334
  test("lead can delete another agent's swarm-scoped memory", async () => {
320
335
  const memory = await getMemoryStore().store({
321
336
  agentId: WORKER_ID,
@@ -345,7 +360,7 @@ describe("memory-delete gate (characterization)", () => {
345
360
 
346
361
  expect(result.structuredContent.success).toBe(false);
347
362
  expect(result.structuredContent.message).toBe(
348
- "Permission denied. You can only delete your own memories, or swarm memories if you are the lead.",
363
+ "Permission denied. You can only delete your own agent-scoped memories; only the lead can delete swarm memories.",
349
364
  );
350
365
  expect(await getMemoryStore().peek(memory.id)).not.toBeNull();
351
366
  });
@@ -84,11 +84,15 @@ const LEAD_ONLY_VERBS: PermissionVerb[] = [
84
84
 
85
85
  const OPERATOR_ONLY_VERBS: PermissionVerb[] = [];
86
86
  const LEAD_OR_OPERATOR_OR_USER_VERBS: PermissionVerb[] = [
87
+ "mcp-server.stdio.write",
88
+ "memory.read.any",
87
89
  "extension.write",
90
+ "task.requester.assign",
88
91
  "extension.activate",
89
92
  "extension.install.inline",
93
+ "repo.merge-policy.write",
90
94
  ];
91
- const OPERATOR_OR_USER_VERBS: PermissionVerb[] = ["comb.presence"];
95
+ const OPERATOR_OR_USER_VERBS: PermissionVerb[] = ["comb.presence", "approval.respond"];
92
96
 
93
97
  const LEAD_OR_OPERATOR_VERBS: PermissionVerb[] = ["models.catalog.write"];
94
98
 
@@ -533,28 +537,28 @@ describe("any-authenticated verbs", () => {
533
537
  }
534
538
  });
535
539
 
536
- describe("memory.delete.any composite (owner OR (lead AND scope=swarm))", () => {
537
- test("swarm-scoped memory: owner or lead allowed", () => {
540
+ describe("memory.delete.any composite (human, lead for swarm, owner for agent scope)", () => {
541
+ test("swarm-scoped memory: lead and humans allowed, owner worker denied", () => {
538
542
  expectDecisions("memory.delete.any", SWARM_MEMORY_RESOURCE, {
539
543
  lead: true,
540
544
  worker: false,
541
- ownerWorker: true,
545
+ ownerWorker: false,
542
546
  creatorWorker: false,
543
- userRequester: false,
544
- foreignUser: false,
545
- operator: false,
547
+ userRequester: true,
548
+ foreignUser: true,
549
+ operator: true,
546
550
  });
547
551
  });
548
552
 
549
- test("agent-scoped memory: only owner allowed — lead is denied (deny edge)", () => {
553
+ test("agent-scoped memory: owner and humans allowed — lead is denied (deny edge)", () => {
550
554
  expectDecisions("memory.delete.any", AGENT_MEMORY_RESOURCE, {
551
555
  lead: false,
552
556
  worker: false,
553
557
  ownerWorker: true,
554
558
  creatorWorker: false,
555
- userRequester: false,
556
- foreignUser: false,
557
- operator: false,
559
+ userRequester: true,
560
+ foreignUser: true,
561
+ operator: true,
558
562
  });
559
563
  });
560
564
  });
@@ -82,7 +82,8 @@ const DENY = {
82
82
  leadOrTaskCreator: "requires lead agent or task creator",
83
83
  leadOrResourceOwner: "requires lead agent or resource owner",
84
84
  leadOrOwnNamespace: "requires lead agent or your own task:agent: namespace",
85
- memoryOwnerOrLeadSwarm: "requires memory owner, or lead agent for swarm-scoped memories",
85
+ memoryDelete:
86
+ "requires operator or user, the lead for swarm-scoped memories, or the owner for agent-scoped memories",
86
87
  } as const;
87
88
 
88
89
  const skillMd = (name: string, description: string) =>
@@ -329,7 +330,7 @@ describe("MCP gate matrix", () => {
329
330
  { type: "agent", id: WORKER_B },
330
331
  "memory.delete.any",
331
332
  "deny",
332
- DENY.memoryOwnerOrLeadSwarm,
333
+ DENY.memoryDelete,
333
334
  "mcp",
334
335
  );
335
336
 
@@ -342,13 +343,7 @@ describe("MCP gate matrix", () => {
342
343
  memoryId: m3,
343
344
  });
344
345
  expect(leadOnAgentScoped.structuredContent.success).toBe(false);
345
- expectRow(
346
- { type: "agent", id: LEAD },
347
- "memory.delete.any",
348
- "deny",
349
- DENY.memoryOwnerOrLeadSwarm,
350
- "mcp",
351
- );
346
+ expectRow({ type: "agent", id: LEAD }, "memory.delete.any", "deny", DENY.memoryDelete, "mcp");
352
347
 
353
348
  const leadOnSwarmScoped = await mcpCall(base, LEAD, sidLead, "memory-delete", {
354
349
  memoryId: m2,