@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.
- package/README.md +2 -1
- package/dist/{acp-adapter-74m73ae9.js → acp-adapter-8t22d18g.js} +2 -2
- package/dist/{actions-4sr14gnt.js → actions-49xrhww7.js} +32 -29
- package/dist/amp-adapter-p230tajt.js +34 -0
- package/dist/{anthropic-messages-8xxrzky9.js → anthropic-messages-hst62ws9.js} +3 -3
- package/dist/{app-pt68fbwp.js → app-tgzw3f3d.js} +11 -10
- package/dist/{assistant-cv3jytks.js → assistant-dgfjm07j.js} +33 -30
- package/dist/{azure-openai-responses-f5cqsyj8.js → azure-openai-responses-mh8tvf78.js} +9 -68
- package/dist/{boot-reembed-4ht4gqth.js → boot-reembed-0qzs614r.js} +13 -12
- package/dist/{boot-reembed-nxpcvpj9.js → boot-reembed-dgp6peyx.js} +12 -11
- package/dist/{boot-scrub-logs-k93kyw8k.js → boot-scrub-logs-fxnk021a.js} +10 -9
- package/dist/{boot-seeds-1skcrs0e.js → boot-seeds-xe7adjvm.js} +14 -13
- package/dist/{claude-adapter-r5g0tzge.js → claude-adapter-hx0n8a31.js} +22 -20
- package/dist/{claude-managed-adapter-yxkfxg8j.js → claude-managed-adapter-hskapeen.js} +3 -3
- package/dist/{claude-managed-setup-pkyrgshb.js → claude-managed-setup-p7mt6dfq.js} +1 -1
- package/dist/{claude-sdk-session-jxyxnrwd.js → claude-sdk-session-d61a9c8x.js} +22 -20
- package/dist/{cli-twrh04ap.js → cli-0y30kqz4.js} +3 -3
- package/dist/{cli-5xhpb3kv.js → cli-0z9dj97t.js} +9 -9
- package/dist/{cli-t845n9xk.js → cli-1nzb8ftr.js} +2 -2
- package/dist/{cli-kdkwd3yr.js → cli-1wd1n2ya.js} +38 -10
- package/dist/{cli-vxe6qa4h.js → cli-23az412e.js} +1 -1
- package/dist/{cli-bsky8g7w.js → cli-26x4d89j.js} +1 -1
- package/dist/{cli-swfmp1fx.js → cli-2ar0x7cz.js} +9 -9
- package/dist/cli-2ksgp01n.js +933 -0
- package/dist/{cli-rnyzn44p.js → cli-2zhhnz2h.js} +1 -1
- package/dist/{cli-vc9bp061.js → cli-3qaw32we.js} +5 -5
- package/dist/{cli-vy3ed88x.js → cli-402m8btc.js} +31 -3
- package/dist/{cli-5bcd7sfy.js → cli-43fh8fzw.js} +3 -3
- package/dist/{cli-5k6k8ta3.js → cli-4f7s6k3q.js} +13 -5
- package/dist/{cli-sqvfa090.js → cli-4ntg6tkp.js} +6 -5
- package/dist/{cli-kh04eqwe.js → cli-4y9bnpvy.js} +1 -1
- package/dist/{cli-qjjvzwzd.js → cli-5c2j5yvx.js} +1 -1
- package/dist/{cli-2zy95vhm.js → cli-6891ft0y.js} +2 -2
- package/dist/{cli-tre4bdgq.js → cli-6behs2bt.js} +1 -1
- package/dist/cli-7mm2r1kp.js +17 -0
- package/dist/cli-7xcgj20z.js +68 -0
- package/dist/{cli-4cwxn7gp.js → cli-812vt3ht.js} +2 -2
- package/dist/{cli-mbssg1xv.js → cli-8kt0bmgx.js} +1 -1
- package/dist/{cli-wjgs5wxe.js → cli-8rpq35c0.js} +7 -7
- package/dist/{cli-jxs2ygaw.js → cli-9m8fk2v7.js} +74 -29
- package/dist/{cli-pkd6s2wy.js → cli-9q3wt23f.js} +2 -2
- package/dist/{cli-s3hwpt9v.js → cli-9s6fsg07.js} +30 -10
- package/dist/{cli-xqpk3rp7.js → cli-9x3bwfd3.js} +28 -5
- package/dist/{cli-qrgt5bqf.js → cli-agamgm0p.js} +1 -1
- package/dist/{cli-13hw4z91.js → cli-b0f70z24.js} +1 -1
- package/dist/{cli-gpqpmns7.js → cli-cgrvsz2t.js} +2 -2
- package/dist/{cli-2hhfhfq5.js → cli-cn53e7vc.js} +8 -1
- package/dist/{cli-p0r8sxj5.js → cli-crjazzsg.js} +222 -4
- package/dist/{cli-tcen9p5w.js → cli-d37n8b7c.js} +5 -5
- package/dist/{cli-wxkkd0e0.js → cli-dgh781z9.js} +3 -3
- package/dist/cli-dsdg2wrk.js +516 -0
- package/dist/{cli-q24re23n.js → cli-e2xnc4m5.js} +4 -4
- package/dist/{cli-f7kvq46p.js → cli-erxeg64j.js} +20 -0
- package/dist/{cli-8ev9xqnx.js → cli-fajbc7sa.js} +17 -219
- package/dist/{cli-g739zqzt.js → cli-fj86xr3c.js} +2 -2
- package/dist/{cli-zvhd3f64.js → cli-fpbvcf55.js} +3 -3
- package/dist/{cli-kp42yek0.js → cli-fyra5y8k.js} +424 -71
- package/dist/{cli-jyy6pm2f.js → cli-g3qgrzck.js} +15 -3
- package/dist/{cli-eg0b3p0k.js → cli-g5erf0zt.js} +23 -14
- package/dist/{cli-cvatsam7.js → cli-gcvzvq3e.js} +4 -4
- package/dist/{cli-89s4ngw3.js → cli-gvcgmx69.js} +1 -1
- package/dist/{cli-7tre70zp.js → cli-j261c831.js} +138 -128
- package/dist/{cli-sptrj5f5.js → cli-j3me0jw2.js} +2 -2
- package/dist/{cli-zxqx25nz.js → cli-k6w44yvq.js} +6 -6
- package/dist/cli-ndx5yz5n.js +3299 -0
- package/dist/{cli-ykk5jaax.js → cli-nexwh8zg.js} +34 -29
- package/dist/{cli-rfjw9jw2.js → cli-ng97fjq7.js} +2 -2
- package/dist/{cli-4x0csdjq.js → cli-ngne1dng.js} +2 -2
- package/dist/{cli-4dcpaefp.js → cli-pfzrhb8n.js} +2 -2
- package/dist/{cli-hb5c2x5y.js → cli-q38n4bq0.js} +2 -2
- package/dist/{cli-cebmgwsc.js → cli-r109nq7n.js} +4 -4
- package/dist/{cli-nq7yqw7b.js → cli-t7g0vmtc.js} +4 -4
- package/dist/{cli-jf7wmv1r.js → cli-tsvj33y0.js} +58 -19
- package/dist/{cli-7af8yhav.js → cli-vbr859rz.js} +7 -4
- package/dist/{cli-nt8hecg9.js → cli-wk3v6h55.js} +67 -38
- package/dist/{cli-vyqs9ya8.js → cli-wwvk0tsy.js} +7 -7
- package/dist/{cli-rj0gqwwp.js → cli-y2de3bzp.js} +1 -1
- package/dist/{cli-hmsw1gpy.js → cli-y74170pb.js} +4 -4
- package/dist/cli-ydt3zkwv.js +70 -0
- package/dist/{cli-xk79303e.js → cli-yj790e19.js} +9 -1
- package/dist/cli-zt3w1cn1.js +137 -0
- package/dist/{cli-trc24wk2.js → cli-zxg80zs9.js} +1 -1
- package/dist/cli.js +23 -22
- package/dist/{cloudflare-workers-ai-system-one-k8ddqjkq.js → cloudflare-workers-ai-system-one-tt484t61.js} +2 -2
- package/dist/{codex-adapter-esvmnhn7.js → codex-adapter-e3pky8aj.js} +14 -13
- package/dist/{codex-hook-m73jmqgx.js → codex-hook-b1fb7c2k.js} +4 -3
- package/dist/{codex-session-runner-ban9excr.js → codex-session-runner-1gq27w2t.js} +14 -13
- package/dist/{commands-75413p2n.js → commands-q07ezjzn.js} +10 -9
- package/dist/cursor-adapter-50cz7yba.js +32 -0
- package/dist/{db-7f5pecwg.js → db-wwt0sq9q.js} +18 -9
- package/dist/{dispatcher-fh9at8nz.js → dispatcher-wrg35vdd.js} +31 -28
- package/dist/{dsh-adapter-wpze65d4.js → dsh-adapter-sa6rxfpc.js} +6 -6
- package/dist/{e2b-gjgdjp91.js → e2b-v5np73yj.js} +4 -4
- package/dist/{easter-egg-3d-3w5c2n4c.js → easter-egg-3d-h2hx9b64.js} +2 -2
- package/dist/{event-bus-sfx8dx3t.js → event-bus-vkbhzt4n.js} +1 -1
- package/dist/{execute-f50wg9nh.js → execute-6gczxy8v.js} +42 -13
- package/dist/{google-generative-ai-jxxpnegy.js → google-generative-ai-d6nphg3q.js} +3 -3
- package/dist/{google-vertex-x7q1angm.js → google-vertex-cpshkref.js} +3 -3
- package/dist/{handlers-w13787bb.js → handlers-rv3gxvsp.js} +34 -31
- package/dist/{hook-46g1jrvx.js → hook-bcf5m80y.js} +15 -13
- package/dist/{hook-4tpts2ry.js → hook-z8b1jm2d.js} +16 -14
- package/dist/{http-19pzbfrt.js → http-56f7yt97.js} +932 -134
- package/dist/{index-scptvdxg.js → index-0kncjnrm.js} +30 -23
- package/dist/{index-dqmp15bq.js → index-1e6cg96n.js} +31 -28
- package/dist/{index-qb4q1y3e.js → index-8xgdb3k8.js} +31 -28
- package/dist/index-adw4zr9j.js +1020 -0
- package/dist/{index-h5gs261z.js → index-etdrp7yp.js} +140 -140
- package/dist/{index-8fd4dm4h.js → index-f9zscmft.js} +32 -29
- package/dist/index-fdk1t4d6.js +91297 -0
- package/dist/{index-s75wc4wd.js → index-jvb37h77.js} +12 -11
- package/dist/{keepalive-xa5ts9cd.js → keepalive-rp8daj4s.js} +12 -11
- package/dist/{lead-qp4xkvjq.js → lead-dfp77erc.js} +45 -40
- package/dist/{maintenance-rm5885hm.js → maintenance-wtmw3jqv.js} +13 -12
- package/dist/{mistral-conversations-rw7fbt22.js → mistral-conversations-8445ddjn.js} +2 -2
- package/dist/{oauth-refresh-sweep-1csdfebf.js → oauth-refresh-sweep-yhzqt95v.js} +11 -10
- package/dist/{onboard-r6b78cs4.js → onboard-9z8rwbcy.js} +2 -2
- package/dist/{openai-codex-responses-efvrgrq3.js → openai-codex-responses-9ynm88zy.js} +3 -3
- package/dist/{openai-completions-bbbrrz4z.js → openai-completions-nhqwjmaw.js} +2 -2
- package/dist/{openai-responses-9jjyrcm1.js → openai-responses-xx1c4kgm.js} +3 -3
- package/dist/{opencode-adapter-kb3g6pkf.js → opencode-adapter-1nxb6555.js} +5 -5
- package/dist/{otel-impl-0y9knpky.js → otel-impl-b83gaxw2.js} +1 -1
- package/dist/{pi-mono-adapter-z6s1bme2.js → pi-mono-adapter-ddxz8pjp.js} +18 -17
- package/dist/{pricing-refresh-mth26tgp.js → pricing-refresh-tmjvazb6.js} +12 -11
- package/dist/{rbac-roles-kwntb23q.js → rbac-roles-pa0gzjws.js} +11 -10
- package/dist/{rbac-roles-vpk4ebm8.js → rbac-roles-rhexa9s2.js} +12 -11
- package/dist/{render-v2-hcm7h00y.js → render-v2-ask84n8y.js} +11 -10
- package/dist/{runtime-vfjsfsc5.js → runtime-ckjnj3vf.js} +4 -4
- package/dist/{setup-hbyzd65h.js → setup-5ga5gdsy.js} +2 -2
- package/dist/{typesafe-system-one-2ef30pec.js → typesafe-system-one-datfr82p.js} +2 -2
- package/dist/{virtual-modules-9bv7ry91.js → virtual-modules-bmp3zwjf.js} +12 -11
- package/dist/{work-objects-m09sxnhe.js → work-objects-snnh0t7j.js} +34 -31
- package/dist/{worker-0h35rvk4.js → worker-rxyymrhb.js} +45 -40
- package/openapi.json +997 -39
- package/package.json +6 -5
- package/src/be/db/active-sessions.ts +30 -0
- package/src/be/db/approvals.ts +54 -0
- package/src/be/db.ts +25 -4
- package/src/be/mcp-server-checked-update.ts +34 -0
- package/src/be/mcp-server-stdio-gate.ts +34 -0
- package/src/be/memory/key-browser.ts +304 -0
- package/src/be/memory/providers/sqlite-store.ts +16 -3
- package/src/be/migrations/194_memory_orphan_chunks.sql +15 -0
- package/src/be/migrations/195_approval_request_approvals.sql +6 -0
- package/src/be/model-validation.ts +12 -2
- package/src/be/multi-runtime.ts +4 -3
- package/src/be/pricing-normalize.ts +11 -0
- package/src/be/repo-checked-update.ts +37 -0
- package/src/be/repo-merge-policy.ts +15 -0
- package/src/be/seed-pricing.ts +48 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/provider-credentials.ts +44 -2
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +100 -0
- package/src/extensions/dispatcher.ts +19 -0
- package/src/extensions/post-bridge.ts +191 -0
- package/src/github/handlers.ts +128 -2
- package/src/gitlab/workflow-action-name.ts +15 -0
- package/src/heartbeat/heartbeat.ts +4 -3
- package/src/http/active-sessions.ts +6 -5
- package/src/http/agents.ts +10 -1
- package/src/http/approval-requests.ts +271 -35
- package/src/http/index.ts +1 -1
- package/src/http/mcp-servers.ts +74 -50
- package/src/http/memory.ts +275 -6
- package/src/http/repos.ts +58 -18
- package/src/http/request-principal.ts +19 -0
- package/src/http/session-cost-recompute.ts +64 -0
- package/src/http/session-data.ts +11 -0
- package/src/http/tasks.ts +9 -1
- package/src/http/webhooks.ts +17 -5
- package/src/prompts/base-prompt.ts +9 -4
- package/src/prompts/resolver.ts +1 -1
- package/src/prompts/session-templates.ts +28 -0
- package/src/providers/amp-adapter.ts +1232 -0
- package/src/providers/cursor-adapter.ts +638 -0
- package/src/providers/index.ts +9 -1
- package/src/providers/pricing-sources.md +39 -0
- package/src/providers/reasoning-effort.ts +15 -1
- package/src/providers/types.ts +11 -1
- package/src/rbac/legacy-policy.ts +23 -8
- package/src/rbac/permissions.ts +27 -1
- package/src/scripts-runtime/types/swarm-extension.d.ts +119 -0
- package/src/tests/amp-adapter.test.ts +822 -0
- package/src/tests/amp-budget-export-failed.test.ts +133 -0
- package/src/tests/amp-pricing-helpers.ts +16 -0
- package/src/tests/amp-pricing.test.ts +218 -0
- package/src/tests/api-drain-worker-e2e.test.ts +18 -7
- package/src/tests/approval-respond-auth.test.ts +371 -0
- package/src/tests/base-prompt.test.ts +13 -0
- package/src/tests/claude-adapter-binary.test.ts +24 -0
- package/src/tests/claude-adapter.test.ts +8 -0
- package/src/tests/cursor-adapter.test.ts +690 -0
- package/src/tests/db-task-read-contract.test.ts +5 -0
- package/src/tests/db-task-write-contract.test.ts +5 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-post-bridge.test.ts +669 -0
- package/src/tests/extensions-run-context.test.ts +61 -0
- package/src/tests/fixtures/fake-amp.ts +304 -0
- package/src/tests/github-handlers-inline-comments.test.ts +147 -1
- package/src/tests/gitlab-workflow-event-names.test.ts +136 -0
- package/src/tests/mcp-input-ergonomics.test.ts +15 -10
- package/src/tests/mcp-server-stdio-gate.test.ts +438 -0
- package/src/tests/memory-http-access.test.ts +221 -0
- package/src/tests/memory-keys-endpoint.test.ts +406 -0
- package/src/tests/memory-store.test.ts +166 -0
- package/src/tests/model-tier-resolution.test.ts +3 -3
- package/src/tests/pricing-refresh.test.ts +5 -4
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/provider-process-group.test.ts +41 -0
- package/src/tests/provider-registration.test.ts +3 -1
- package/src/tests/rbac-charact-misc-tools.test.ts +18 -3
- package/src/tests/rbac-engine.test.ts +15 -11
- package/src/tests/rbac-wire-e2e.test.ts +4 -9
- package/src/tests/repo-allow-merge-gate.test.ts +382 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/send-task-requester-gate.test.ts +260 -0
- package/src/tests/session-activity-liveness.test.ts +248 -0
- package/src/tests/session-costs-golden.test.ts +28 -0
- package/src/tests/webhooks-task-blocked.test.ts +4 -4
- package/src/tests/write-gate.ts +68 -0
- package/src/tests/x402-client.test.ts +124 -0
- package/src/tests/x402-spending-tracker.test.ts +66 -0
- package/src/tools/mcp-servers/mcp-server-create.ts +23 -1
- package/src/tools/mcp-servers/mcp-server-update.ts +46 -28
- package/src/tools/memory-delete.ts +3 -3
- package/src/tools/repos/update-repo.ts +26 -5
- package/src/tools/send-task.ts +24 -1
- package/src/tools/steer-task.ts +1 -1
- package/src/types.ts +37 -2
- package/src/utils/amp-models.ts +114 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/process-group.ts +79 -0
- package/src/workflows/event-bus.ts +28 -0
- package/src/x402/client.ts +36 -14
- package/src/x402/spending-tracker.ts +64 -3
- package/templates/skills/agent-browser/SKILL.md +5 -0
- package/templates/skills/agent-browser/content.md +5 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -0
- package/templates/skills/swarm-extensions/content.md +4 -0
- 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(
|
|
512
|
-
expect(rows).toHaveLength(
|
|
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(
|
|
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(
|
|
110
|
-
expect(first.inserted).toBe(
|
|
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(
|
|
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=
|
|
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
|
|
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
|
-
//
|
|
121
|
-
expect(sessionSystem.length).toBe(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
537
|
-
test("swarm-scoped memory:
|
|
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:
|
|
545
|
+
ownerWorker: false,
|
|
542
546
|
creatorWorker: false,
|
|
543
|
-
userRequester:
|
|
544
|
-
foreignUser:
|
|
545
|
-
operator:
|
|
547
|
+
userRequester: true,
|
|
548
|
+
foreignUser: true,
|
|
549
|
+
operator: true,
|
|
546
550
|
});
|
|
547
551
|
});
|
|
548
552
|
|
|
549
|
-
test("agent-scoped memory:
|
|
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:
|
|
556
|
-
foreignUser:
|
|
557
|
-
operator:
|
|
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
|
-
|
|
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.
|
|
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,
|