@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.
- package/README.md +1 -1
- package/dist/acp-adapter-yzfxn8vj.js +23 -0
- package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
- package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
- package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
- package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
- package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
- package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
- package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
- package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
- package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
- package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
- package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
- package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
- package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
- package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
- package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
- package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
- package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
- package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
- package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
- package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
- package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
- package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
- package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
- package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
- package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
- package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
- package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
- package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
- package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
- package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
- package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
- package/dist/cli-fn697bnt.js +126 -0
- package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
- package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
- package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
- package/dist/cli-j2jbr8af.js +516 -0
- package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
- package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
- package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
- package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
- package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
- package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
- package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
- package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
- package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
- package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
- package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
- package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
- package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
- package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
- package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
- package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
- package/dist/cli-zp67qg5x.js +92 -0
- package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
- package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
- package/dist/cli.js +79 -26
- package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
- package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
- package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
- package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
- package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
- package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
- package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
- package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
- package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
- package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
- package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
- package/dist/grok-adapter-ar9cw9jg.js +29 -0
- package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
- package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
- package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
- package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
- package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
- package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
- package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
- package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
- package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
- package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
- package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
- package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
- package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
- package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
- package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
- package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
- package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
- package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
- package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
- package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
- package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
- package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
- package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
- package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
- package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +457 -31
- package/package.json +14 -6
- package/plugin/commands/create-pr.md +7 -1
- package/plugin/pi-skills/create-pr/SKILL.md +7 -1
- package/src/agentmail/app.ts +5 -4
- package/src/be/automation-preflight.ts +11 -9
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/connector-codes.ts +81 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/197_connector_codes.sql +23 -0
- package/src/be/migrations/198_permission_audit_guest.sql +33 -0
- package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/model-tier-resolution.ts +2 -1
- package/src/be/model-validation.ts +1 -1
- package/src/be/pricing-normalize.ts +3 -0
- package/src/be/rbac-audit.ts +2 -1
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scripts/typecheck.ts +23 -2
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +14 -0
- package/src/be/users.ts +7 -1
- package/src/be/workflow-replay.ts +77 -0
- package/src/cli.tsx +7 -0
- package/src/commands/provider-credentials.ts +46 -8
- package/src/commands/runner.ts +2 -10
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/github/app.ts +4 -4
- package/src/heartbeat/heartbeat.ts +20 -8
- package/src/hooks/pr-body-guard.ts +1 -1
- package/src/http/agents.ts +1 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +3 -0
- package/src/http/auth.ts +4 -1
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/codex-oauth-keep-warm.ts +2 -2
- package/src/http/config-values.ts +59 -0
- package/src/http/config.ts +5 -0
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/extensions.ts +1 -0
- package/src/http/index.ts +23 -6
- package/src/http/integrations.ts +1 -38
- package/src/http/ip-rate-limit.ts +82 -0
- package/src/http/kv.ts +38 -4
- package/src/http/mcp-servers.ts +1 -0
- package/src/http/memory.ts +9 -0
- package/src/http/models-catalog.ts +2 -0
- package/src/http/operator-actor.ts +4 -0
- package/src/http/page-proxy.ts +22 -0
- package/src/http/pages.ts +4 -2
- package/src/http/realtime.ts +1 -1
- package/src/http/request-principal.ts +14 -1
- package/src/http/script-connections.ts +8 -0
- package/src/http/script-runs.ts +12 -4
- package/src/http/scripts.ts +15 -0
- package/src/http/session-cost-recompute.ts +3 -2
- package/src/http/tasks.ts +9 -0
- package/src/http/users.ts +226 -0
- package/src/http/workflows.ts +3 -2
- package/src/http.ts +1 -0
- package/src/integrations/kapso/client.ts +3 -1
- package/src/jira/app.ts +2 -2
- package/src/kv-view.ts +324 -0
- package/src/linear/app.ts +2 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/prompts/memories.ts +42 -1
- package/src/providers/acp-adapter.ts +85 -24
- package/src/providers/acp-target-catalog.ts +54 -1
- package/src/providers/acp-targets.ts +358 -4
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/grok-adapter.ts +186 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/pricing-sources.md +6 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- package/src/rbac/can.ts +4 -3
- package/src/rbac/legacy-policy.ts +3 -3
- package/src/rbac/types.ts +6 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +27 -8
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
- package/src/server-user.ts +2 -20
- package/src/server.ts +3 -1
- package/src/slack/app.ts +4 -0
- package/src/slack/config.ts +3 -1
- package/src/slack/egress-scrub.ts +87 -0
- package/src/tests/acp-adapter.test.ts +191 -2
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/budget-user-scope.test.ts +7 -21
- package/src/tests/check-pr-body.test.ts +149 -6
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/claude-adapter-binary.test.ts +1 -1
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
- package/src/tests/console-scrub.test.ts +141 -0
- package/src/tests/credential-check.test.ts +90 -0
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/explicit-model-validation.test.ts +81 -0
- package/src/tests/extensions-pre-task.test.ts +33 -1
- package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
- package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +647 -0
- package/src/tests/heartbeat-checklist.test.ts +44 -0
- package/src/tests/integration-disable-flags.test.ts +156 -0
- package/src/tests/kv-http.test.ts +34 -0
- package/src/tests/kv-tool.test.ts +274 -1
- package/src/tests/kv-view.test.ts +147 -0
- package/src/tests/mcp-user-route.test.ts +257 -15
- package/src/tests/memory-injection-outcome.test.ts +143 -0
- package/src/tests/model-tier-resolution.test.ts +76 -3
- package/src/tests/page-guest-session.test.ts +424 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-viewer-auth.test.ts +22 -1
- package/src/tests/pr-body-leaks.test.ts +157 -2
- package/src/tests/preload.ts +10 -0
- package/src/tests/provider-registration.test.ts +3 -1
- package/src/tests/rbac-audit.test.ts +32 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +126 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scripts-mcp-e2e.test.ts +42 -6
- package/src/tests/scripts-runtime.test.ts +37 -0
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +317 -1
- package/src/tests/session-costs-golden.test.ts +17 -0
- package/src/tests/slack-egress-scrub.test.ts +216 -0
- package/src/tests/swarm-tool-result-gate.test.ts +143 -30
- package/src/tests/task-tools-ctx.test.ts +3 -0
- package/src/tests/user-token-routes.test.ts +342 -3
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/kv/kv-get.ts +136 -6
- package/src/tools/send-task.ts +61 -17
- package/src/tools/utils.ts +186 -43
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +16 -1
- package/src/utils/console-scrub-install.ts +5 -0
- package/src/utils/console-scrub.ts +57 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/page-session.ts +6 -2
- package/src/utils/pr-body-leaks.ts +149 -10
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +662 -36
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/notify.ts +6 -1
- package/src/workflows/executors/system-one-decision.ts +31 -12
- package/src/workflows/executors/system-one-openai-wire.ts +168 -0
- package/src/workflows/executors/system-one-providers.ts +112 -11
- package/src/workflows/executors/system-one-wire.ts +79 -0
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +5 -5
- package/templates/skills/agent-browser/config.json +1 -1
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/artifacts/content.md +17 -7
- package/templates/skills/jira-interaction/config.json +1 -1
- package/templates/skills/jira-interaction/content.md +56 -205
- package/templates/skills/linear-interaction/config.json +1 -1
- package/templates/skills/linear-interaction/content.md +31 -50
- package/templates/skills/pages/content.md +8 -4
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/workflow-iterate/content.md +4 -4
- package/tsconfig.json +3 -3
- package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
- package/dist/cli-0a7r5331.js +0 -168
- package/dist/cli-93rxw9yd.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-dgnj0zcc.js +0 -40
- package/dist/cli-qt5wzq6v.js +0 -256
- package/dist/cli-v7fqehqd.js +0 -9281
- package/dist/cli-vbr859rz.js +0 -572
- package/src/be/boot-scrub-logs.ts +0 -132
|
@@ -242,6 +242,28 @@ describe("Heartbeat Checklist", () => {
|
|
|
242
242
|
expect(tasks.length).toBe(1);
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
+
test("dedup: superseded heartbeat-checklist does not block a new task", async () => {
|
|
246
|
+
const lead = await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
247
|
+
await updateAgentProfile(lead.id, { heartbeatMd: "- Check tasks\n" });
|
|
248
|
+
await checkHeartbeatChecklist();
|
|
249
|
+
const previous = await getDbClient().get<{ id: string }>(
|
|
250
|
+
"SELECT id FROM agent_tasks WHERE taskType = 'heartbeat-checklist'",
|
|
251
|
+
);
|
|
252
|
+
expect(previous).toBeDefined();
|
|
253
|
+
await getDbClient().run("UPDATE agent_tasks SET status = 'superseded' WHERE id = ?", [
|
|
254
|
+
previous!.id,
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
await checkHeartbeatChecklist();
|
|
258
|
+
|
|
259
|
+
const tasks = await getDbClient().query<{ id: string; status: string }>(
|
|
260
|
+
"SELECT id, status FROM agent_tasks WHERE taskType = 'heartbeat-checklist'",
|
|
261
|
+
);
|
|
262
|
+
expect(tasks).toHaveLength(2);
|
|
263
|
+
expect(tasks.find((task) => task.id === previous!.id)?.status).toBe("superseded");
|
|
264
|
+
expect(tasks.find((task) => task.id !== previous!.id)?.status).toBe("pending");
|
|
265
|
+
});
|
|
266
|
+
|
|
245
267
|
test("created task includes system status with [auto-generated] labels", async () => {
|
|
246
268
|
const lead = await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
247
269
|
await updateAgentProfile(lead.id, {
|
|
@@ -427,6 +449,28 @@ describe("Heartbeat Checklist", () => {
|
|
|
427
449
|
expect(tasks.length).toBe(1);
|
|
428
450
|
});
|
|
429
451
|
|
|
452
|
+
test("dedup: superseded boot-triage does not block a new task", async () => {
|
|
453
|
+
const lead = await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
454
|
+
await updateAgentProfile(lead.id, { heartbeatMd: "- Check tasks\n" });
|
|
455
|
+
await createBootTriageTask();
|
|
456
|
+
const previous = await getDbClient().get<{ id: string }>(
|
|
457
|
+
"SELECT id FROM agent_tasks WHERE taskType = 'boot-triage'",
|
|
458
|
+
);
|
|
459
|
+
expect(previous).toBeDefined();
|
|
460
|
+
await getDbClient().run("UPDATE agent_tasks SET status = 'superseded' WHERE id = ?", [
|
|
461
|
+
previous!.id,
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
await createBootTriageTask();
|
|
465
|
+
|
|
466
|
+
const tasks = await getDbClient().query<{ id: string; status: string }>(
|
|
467
|
+
"SELECT id, status FROM agent_tasks WHERE taskType = 'boot-triage'",
|
|
468
|
+
);
|
|
469
|
+
expect(tasks).toHaveLength(2);
|
|
470
|
+
expect(tasks.find((task) => task.id === previous!.id)?.status).toBe("superseded");
|
|
471
|
+
expect(tasks.find((task) => task.id !== previous!.id)?.status).toBe("pending");
|
|
472
|
+
});
|
|
473
|
+
|
|
430
474
|
test("boot-triage has correct tags", async () => {
|
|
431
475
|
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
432
476
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration `*_DISABLE` flags parse identically via the shared env-flag
|
|
3
|
+
* parser (issue #1939).
|
|
4
|
+
*
|
|
5
|
+
* GITHUB_DISABLE, JIRA_DISABLE, LINEAR_DISABLE, AGENTMAIL_DISABLE and
|
|
6
|
+
* SLACK_DISABLE used to hand-roll `value === "true" || value === "1"`,
|
|
7
|
+
* silently ignoring "TRUE", "True" or " 1 " — while the config validator
|
|
8
|
+
* and `isEnvFlagEnabled` (already used by GITLAB_DISABLE /
|
|
9
|
+
* AZURE_DEVOPS_DISABLE) accept any case. Every read now goes through
|
|
10
|
+
* `isEnvFlagEnabled`, so these tests pin the shared-parser behavior at
|
|
11
|
+
* each integration's entry point.
|
|
12
|
+
*/
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
import { initAgentMail, isAgentMailEnabled, resetAgentMail } from "../agentmail/app";
|
|
15
|
+
import { initGitHub, isGitHubEnabled, resetGitHub } from "../github/app";
|
|
16
|
+
import { isJiraEnabled, resetJira } from "../jira/app";
|
|
17
|
+
import { isLinearEnabled, resetLinear } from "../linear/app";
|
|
18
|
+
import { getSlackConfiguration } from "../slack/config";
|
|
19
|
+
|
|
20
|
+
const ENV_KEYS = [
|
|
21
|
+
"GITHUB_DISABLE",
|
|
22
|
+
"GITHUB_WEBHOOK_SECRET",
|
|
23
|
+
"JIRA_DISABLE",
|
|
24
|
+
"JIRA_CLIENT_ID",
|
|
25
|
+
"LINEAR_DISABLE",
|
|
26
|
+
"LINEAR_CLIENT_ID",
|
|
27
|
+
"AGENTMAIL_DISABLE",
|
|
28
|
+
"AGENTMAIL_WEBHOOK_SECRET",
|
|
29
|
+
] as const;
|
|
30
|
+
const originalEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
|
|
31
|
+
|
|
32
|
+
// Spellings the hand-rolled `=== "true" || === "1"` checks used to ignore.
|
|
33
|
+
const TRUTHY_SPELLINGS = ["true", "TRUE", "True", "1", " 1 ", " True "];
|
|
34
|
+
// Everything that must NOT disable the integration.
|
|
35
|
+
const NON_DISABLING = ["false", "FALSE", "0", " 0 ", "", undefined];
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
for (const key of ENV_KEYS) delete process.env[key];
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
resetGitHub();
|
|
43
|
+
resetAgentMail();
|
|
44
|
+
resetJira();
|
|
45
|
+
resetLinear();
|
|
46
|
+
for (const key of ENV_KEYS) {
|
|
47
|
+
const value = originalEnv.get(key);
|
|
48
|
+
if (value === undefined) delete process.env[key];
|
|
49
|
+
else process.env[key] = value;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("GitHub GITHUB_DISABLE", () => {
|
|
54
|
+
test("every truthy spelling disables the integration", () => {
|
|
55
|
+
process.env.GITHUB_WEBHOOK_SECRET = "secret";
|
|
56
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
57
|
+
process.env.GITHUB_DISABLE = spelling;
|
|
58
|
+
expect(isGitHubEnabled()).toBe(false);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("non-disabling values keep the integration enabled", () => {
|
|
63
|
+
process.env.GITHUB_WEBHOOK_SECRET = "secret";
|
|
64
|
+
for (const value of NON_DISABLING) {
|
|
65
|
+
if (value === undefined) delete process.env.GITHUB_DISABLE;
|
|
66
|
+
else process.env.GITHUB_DISABLE = value;
|
|
67
|
+
expect(isGitHubEnabled()).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("initGitHub honors TRUE even with credentials configured", () => {
|
|
72
|
+
process.env.GITHUB_WEBHOOK_SECRET = "secret";
|
|
73
|
+
process.env.GITHUB_DISABLE = "TRUE";
|
|
74
|
+
resetGitHub();
|
|
75
|
+
expect(initGitHub()).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("Jira JIRA_DISABLE", () => {
|
|
80
|
+
test("every truthy spelling disables the integration", () => {
|
|
81
|
+
process.env.JIRA_CLIENT_ID = "client-id";
|
|
82
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
83
|
+
process.env.JIRA_DISABLE = spelling;
|
|
84
|
+
expect(isJiraEnabled()).toBe(false);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("non-disabling values keep the integration enabled", () => {
|
|
89
|
+
process.env.JIRA_CLIENT_ID = "client-id";
|
|
90
|
+
for (const value of NON_DISABLING) {
|
|
91
|
+
if (value === undefined) delete process.env.JIRA_DISABLE;
|
|
92
|
+
else process.env.JIRA_DISABLE = value;
|
|
93
|
+
expect(isJiraEnabled()).toBe(true);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("Linear LINEAR_DISABLE", () => {
|
|
99
|
+
test("every truthy spelling disables the integration", () => {
|
|
100
|
+
process.env.LINEAR_CLIENT_ID = "client-id";
|
|
101
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
102
|
+
process.env.LINEAR_DISABLE = spelling;
|
|
103
|
+
expect(isLinearEnabled()).toBe(false);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("non-disabling values keep the integration enabled", () => {
|
|
108
|
+
process.env.LINEAR_CLIENT_ID = "client-id";
|
|
109
|
+
for (const value of NON_DISABLING) {
|
|
110
|
+
if (value === undefined) delete process.env.LINEAR_DISABLE;
|
|
111
|
+
else process.env.LINEAR_DISABLE = value;
|
|
112
|
+
expect(isLinearEnabled()).toBe(true);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("AgentMail AGENTMAIL_DISABLE", () => {
|
|
118
|
+
test("every truthy spelling disables the integration", () => {
|
|
119
|
+
process.env.AGENTMAIL_WEBHOOK_SECRET = "secret";
|
|
120
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
121
|
+
process.env.AGENTMAIL_DISABLE = spelling;
|
|
122
|
+
expect(isAgentMailEnabled()).toBe(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("non-disabling values keep the integration enabled", () => {
|
|
127
|
+
process.env.AGENTMAIL_WEBHOOK_SECRET = "secret";
|
|
128
|
+
for (const value of NON_DISABLING) {
|
|
129
|
+
if (value === undefined) delete process.env.AGENTMAIL_DISABLE;
|
|
130
|
+
else process.env.AGENTMAIL_DISABLE = value;
|
|
131
|
+
expect(isAgentMailEnabled()).toBe(true);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("initAgentMail honors TRUE even with credentials configured", () => {
|
|
136
|
+
process.env.AGENTMAIL_WEBHOOK_SECRET = "secret";
|
|
137
|
+
process.env.AGENTMAIL_DISABLE = "TRUE";
|
|
138
|
+
resetAgentMail();
|
|
139
|
+
expect(initAgentMail()).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("Slack SLACK_DISABLE", () => {
|
|
144
|
+
test("every truthy spelling reports disabled", () => {
|
|
145
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
146
|
+
expect(getSlackConfiguration({ SLACK_DISABLE: spelling }).disabled).toBe(true);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("non-disabling values report enabled", () => {
|
|
151
|
+
for (const value of NON_DISABLING) {
|
|
152
|
+
const env = value === undefined ? {} : { SLACK_DISABLE: value };
|
|
153
|
+
expect(getSlackConfiguration(env).disabled).toBe(false);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -246,6 +246,40 @@ describe("/api/kv REST — explicit namespace shape", () => {
|
|
|
246
246
|
expect((await get.json()).value).toBe("hi");
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
+
test("GET with path/offset/limit returns a view of a JSON string value", async () => {
|
|
250
|
+
const ns = "swarm:test:view";
|
|
251
|
+
const stored = JSON.stringify({ outcome: { data: { rows: [10, 11, 12, 13, 14] } } });
|
|
252
|
+
const put = await authedFetch(`/api/kv/_/${encodeURIComponent(ns)}/spill`, {
|
|
253
|
+
method: "PUT",
|
|
254
|
+
body: JSON.stringify({ value: stored, valueType: "string" }),
|
|
255
|
+
agentId,
|
|
256
|
+
});
|
|
257
|
+
expect(put.status).toBe(200);
|
|
258
|
+
const base = `/api/kv/_/${encodeURIComponent(ns)}/spill`;
|
|
259
|
+
|
|
260
|
+
const page = await authedFetch(`${base}?path=outcome.data.rows&offset=1&limit=2`, { agentId });
|
|
261
|
+
expect(page.status).toBe(200);
|
|
262
|
+
const body = await page.json();
|
|
263
|
+
expect(body.value).toEqual([11, 12]);
|
|
264
|
+
expect(body.view).toEqual({
|
|
265
|
+
path: "outcome.data.rows",
|
|
266
|
+
type: "array",
|
|
267
|
+
total: 5,
|
|
268
|
+
offset: 1,
|
|
269
|
+
returned: 2,
|
|
270
|
+
nextOffset: 3,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const whole = await authedFetch(base, { agentId });
|
|
274
|
+
const wholeBody = await whole.json();
|
|
275
|
+
expect(wholeBody.value).toBe(stored);
|
|
276
|
+
expect(wholeBody).not.toHaveProperty("view");
|
|
277
|
+
|
|
278
|
+
const missing = await authedFetch(`${base}?path=outcome.nope`, { agentId });
|
|
279
|
+
expect(missing.status).toBe(400);
|
|
280
|
+
expect((await missing.json()).error).toContain('no key "nope"');
|
|
281
|
+
});
|
|
282
|
+
|
|
249
283
|
test("list with explicit namespace", async () => {
|
|
250
284
|
const ns = "swarm:test:explicit-list";
|
|
251
285
|
await authedFetch(`/api/kv/_/${encodeURIComponent(ns)}/k`, {
|
|
@@ -15,6 +15,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:tes
|
|
|
15
15
|
import { unlink } from "node:fs/promises";
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
17
|
import { closeDb, createAgent, getDbClient, initDb } from "../be/db";
|
|
18
|
+
import { summarizeKvShape } from "../kv-view";
|
|
18
19
|
import {
|
|
19
20
|
registerKvDeleteTool,
|
|
20
21
|
registerKvGetTool,
|
|
@@ -22,7 +23,12 @@ import {
|
|
|
22
23
|
registerKvListTool,
|
|
23
24
|
registerKvSetTool,
|
|
24
25
|
} from "../tools/kv";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
finalizeSwarmToolResult,
|
|
28
|
+
MCP_RESULT_WIRE_LIMIT_BYTES,
|
|
29
|
+
mcpOverflowNamespace,
|
|
30
|
+
wireChannelBytes,
|
|
31
|
+
} from "../tools/utils";
|
|
26
32
|
|
|
27
33
|
const TEST_DB_PATH = "./test-kv-tool.sqlite";
|
|
28
34
|
|
|
@@ -172,6 +178,273 @@ describe("kv MCP tools", () => {
|
|
|
172
178
|
expect(intruderList.structuredContent.entries).toBeUndefined();
|
|
173
179
|
});
|
|
174
180
|
|
|
181
|
+
type ViewResult = {
|
|
182
|
+
content: Array<{ type: string; text: string }>;
|
|
183
|
+
structuredContent: {
|
|
184
|
+
success: boolean;
|
|
185
|
+
message: string;
|
|
186
|
+
details?: string;
|
|
187
|
+
entry?: { value?: unknown; key?: string };
|
|
188
|
+
view?: {
|
|
189
|
+
path: string;
|
|
190
|
+
type: string;
|
|
191
|
+
total?: number;
|
|
192
|
+
offset?: number;
|
|
193
|
+
returned?: number;
|
|
194
|
+
nextOffset?: number | null;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
test("kv-get path/offset/limit follows the spill hint to a bounded, pageable slice", async () => {
|
|
200
|
+
const tools = buildServer();
|
|
201
|
+
const rows = Array.from({ length: 400 }, (_, id) => ({ id, note: "r".repeat(100) }));
|
|
202
|
+
const spill = await finalizeSwarmToolResult(
|
|
203
|
+
"script-run",
|
|
204
|
+
{
|
|
205
|
+
ok: true,
|
|
206
|
+
message: "Script run completed.",
|
|
207
|
+
details: `result:\n${JSON.stringify({ rows }, null, 2)}`,
|
|
208
|
+
data: { status: 200, data: { result: { rows } } },
|
|
209
|
+
},
|
|
210
|
+
{ agentId: agentA },
|
|
211
|
+
);
|
|
212
|
+
const truncation = (
|
|
213
|
+
spill.structuredContent as {
|
|
214
|
+
truncation: { retrieval: string; shape: Array<{ path: string; items?: number }> };
|
|
215
|
+
}
|
|
216
|
+
).truncation;
|
|
217
|
+
expect(truncation.shape).toContainEqual(
|
|
218
|
+
expect.objectContaining({ path: "outcome.data.data.result.rows", type: "array", items: 400 }),
|
|
219
|
+
);
|
|
220
|
+
// The hint is a literal, copy-pasteable call.
|
|
221
|
+
const hinted = JSON.parse(
|
|
222
|
+
truncation.retrieval.slice("kv-get(".length, truncation.retrieval.indexOf(") returns")),
|
|
223
|
+
) as { namespace: string; key: string; path: string; offset: number };
|
|
224
|
+
expect(hinted.path).toBe("outcome.data.data.result.rows");
|
|
225
|
+
|
|
226
|
+
const first = (await tools.get.handler(hinted, meta(agentA))) as ViewResult;
|
|
227
|
+
const view = first.structuredContent.view!;
|
|
228
|
+
expect(first.structuredContent.success).toBe(true);
|
|
229
|
+
expect(view).toMatchObject({
|
|
230
|
+
path: "outcome.data.data.result.rows",
|
|
231
|
+
type: "array",
|
|
232
|
+
total: 400,
|
|
233
|
+
});
|
|
234
|
+
expect(view.returned).toBeGreaterThan(0);
|
|
235
|
+
expect(view.returned).toBeLessThan(400);
|
|
236
|
+
expect(view.nextOffset).toBe(view.returned);
|
|
237
|
+
expect(JSON.parse(first.structuredContent.details!)).toEqual(rows.slice(0, view.returned));
|
|
238
|
+
expect(first.structuredContent.entry).not.toHaveProperty("value");
|
|
239
|
+
expect(first.structuredContent.message).toContain("continue at view.nextOffset");
|
|
240
|
+
expect(wireChannelBytes(first)).toBeLessThanOrEqual(MCP_RESULT_WIRE_LIMIT_BYTES);
|
|
241
|
+
|
|
242
|
+
const next = (await tools.get.handler(
|
|
243
|
+
{ ...hinted, offset: view.nextOffset!, limit: 5 },
|
|
244
|
+
meta(agentA),
|
|
245
|
+
)) as ViewResult;
|
|
246
|
+
expect(next.structuredContent.view).toMatchObject({ offset: view.nextOffset, returned: 5 });
|
|
247
|
+
expect(JSON.parse(next.structuredContent.details!)).toEqual(
|
|
248
|
+
rows.slice(view.nextOffset!, view.nextOffset! + 5),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const pastEnd = (await tools.get.handler(
|
|
252
|
+
{ ...hinted, offset: 10_000 },
|
|
253
|
+
meta(agentA),
|
|
254
|
+
)) as ViewResult;
|
|
255
|
+
expect(pastEnd.structuredContent.success).toBe(true);
|
|
256
|
+
expect(pastEnd.structuredContent.view).toMatchObject({
|
|
257
|
+
total: 400,
|
|
258
|
+
returned: 0,
|
|
259
|
+
nextOffset: null,
|
|
260
|
+
});
|
|
261
|
+
expect(pastEnd.structuredContent.details).toBe("[]");
|
|
262
|
+
|
|
263
|
+
const missing = (await tools.get.handler(
|
|
264
|
+
{ ...hinted, path: "outcome.data.nope" },
|
|
265
|
+
meta(agentA),
|
|
266
|
+
)) as ViewResult;
|
|
267
|
+
expect(missing.structuredContent.success).toBe(false);
|
|
268
|
+
expect(missing.structuredContent.message).toContain('no key "nope" under "outcome.data"');
|
|
269
|
+
|
|
270
|
+
const badIndex = (await tools.get.handler(
|
|
271
|
+
{ ...hinted, path: "outcome.data.data.result.rows.400" },
|
|
272
|
+
meta(agentA),
|
|
273
|
+
)) as ViewResult;
|
|
274
|
+
expect(badIndex.structuredContent.success).toBe(false);
|
|
275
|
+
expect(badIndex.structuredContent.message).toContain("array of 400");
|
|
276
|
+
|
|
277
|
+
const oneRow = (await tools.get.handler(
|
|
278
|
+
{ ...hinted, path: "outcome.data.data.result.rows.7" },
|
|
279
|
+
meta(agentA),
|
|
280
|
+
)) as ViewResult;
|
|
281
|
+
expect(JSON.parse(oneRow.structuredContent.details!)).toEqual(rows[7]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("a wide-object spill hint pages the parent of its scalar leaves", async () => {
|
|
285
|
+
const tools = buildServer();
|
|
286
|
+
const wide = Object.fromEntries(
|
|
287
|
+
Array.from({ length: 2_000 }, (_, i) => [`k${i}`, 1_000_000_000.5 + i]),
|
|
288
|
+
);
|
|
289
|
+
const spill = await finalizeSwarmToolResult(
|
|
290
|
+
"wide-tool",
|
|
291
|
+
{ ok: true, message: "Done.", data: wide },
|
|
292
|
+
{ agentId: agentA },
|
|
293
|
+
);
|
|
294
|
+
const { retrieval, shape } = (
|
|
295
|
+
spill.structuredContent as {
|
|
296
|
+
truncation: { retrieval: string; shape: Array<{ path: string; type: string }> };
|
|
297
|
+
}
|
|
298
|
+
).truncation;
|
|
299
|
+
expect(shape[0]).toMatchObject({ type: "number" });
|
|
300
|
+
const hinted = JSON.parse(retrieval.slice("kv-get(".length, retrieval.indexOf(") returns")));
|
|
301
|
+
expect(hinted).toMatchObject({ path: "outcome.data", offset: 0 });
|
|
302
|
+
|
|
303
|
+
const page = (await tools.get.handler(hinted, meta(agentA))) as ViewResult;
|
|
304
|
+
expect(page.structuredContent.success).toBe(true);
|
|
305
|
+
expect(page.structuredContent.view).toMatchObject({ type: "object", total: 2_000 });
|
|
306
|
+
expect(page.structuredContent.view!.returned).toBeGreaterThan(0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("kv-get view on a plain string pages characters and rejects a JSON path", async () => {
|
|
310
|
+
const tools = buildServer();
|
|
311
|
+
await tools.set.handler(
|
|
312
|
+
{ key: "plain", value: "hello world, not json", valueType: "string" },
|
|
313
|
+
meta(agentA),
|
|
314
|
+
);
|
|
315
|
+
const chars = (await tools.get.handler(
|
|
316
|
+
{ key: "plain", offset: 6, limit: 5 },
|
|
317
|
+
meta(agentA),
|
|
318
|
+
)) as ViewResult;
|
|
319
|
+
expect(chars.structuredContent.success).toBe(true);
|
|
320
|
+
expect(chars.structuredContent.details).toBe('"world"');
|
|
321
|
+
expect(chars.structuredContent.view).toMatchObject({
|
|
322
|
+
type: "string",
|
|
323
|
+
total: 21,
|
|
324
|
+
returned: 5,
|
|
325
|
+
nextOffset: 11,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
await tools.set.handler(
|
|
329
|
+
{ key: "spaced", value: "a \n b", valueType: "string" },
|
|
330
|
+
meta(agentA),
|
|
331
|
+
);
|
|
332
|
+
for (const [offset, slice] of [
|
|
333
|
+
[1, " \n "],
|
|
334
|
+
[0, "a "],
|
|
335
|
+
] as const) {
|
|
336
|
+
const exact = (await tools.get.handler(
|
|
337
|
+
{ key: "spaced", offset, limit: slice.length },
|
|
338
|
+
meta(agentA),
|
|
339
|
+
)) as ViewResult;
|
|
340
|
+
expect(JSON.parse(exact.structuredContent.details!)).toBe(slice);
|
|
341
|
+
expect(JSON.parse(exact.content[0]!.text.split("\n\n").at(-1)!)).toBe(slice);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const pathed = (await tools.get.handler(
|
|
345
|
+
{ key: "plain", path: "a" },
|
|
346
|
+
meta(agentA),
|
|
347
|
+
)) as ViewResult;
|
|
348
|
+
expect(pathed.structuredContent.success).toBe(false);
|
|
349
|
+
expect(pathed.structuredContent.message).toContain("plain string");
|
|
350
|
+
|
|
351
|
+
await tools.set.handler({ key: "num", value: 42 }, meta(agentA));
|
|
352
|
+
const scalar = (await tools.get.handler({ key: "num", offset: 1 }, meta(agentA))) as ViewResult;
|
|
353
|
+
expect(scalar.structuredContent.success).toBe(false);
|
|
354
|
+
expect(scalar.structuredContent.message).toContain("is a number");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("kv-get view names a narrower path when one item alone exceeds the cap", async () => {
|
|
358
|
+
const tools = buildServer();
|
|
359
|
+
await tools.set.handler(
|
|
360
|
+
{ key: "fat-items", value: { items: [{ blob: "f".repeat(20_000) }, { id: 2 }] } },
|
|
361
|
+
meta(agentA),
|
|
362
|
+
);
|
|
363
|
+
const res = (await tools.get.handler(
|
|
364
|
+
{ key: "fat-items", path: "items" },
|
|
365
|
+
meta(agentA),
|
|
366
|
+
)) as ViewResult;
|
|
367
|
+
expect(res.structuredContent.success).toBe(true);
|
|
368
|
+
expect(res.structuredContent.view).toMatchObject({ returned: 0, nextOffset: 0, total: 2 });
|
|
369
|
+
expect(res.structuredContent.message).toContain('narrow the path to "items.0"');
|
|
370
|
+
expect(wireChannelBytes(res)).toBeLessThanOrEqual(MCP_RESULT_WIRE_LIMIT_BYTES);
|
|
371
|
+
|
|
372
|
+
const narrowed = (await tools.get.handler(
|
|
373
|
+
{ key: "fat-items", path: "items.0.blob", limit: 100 },
|
|
374
|
+
meta(agentA),
|
|
375
|
+
)) as ViewResult;
|
|
376
|
+
expect(JSON.parse(narrowed.structuredContent.details!)).toBe("f".repeat(100));
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("a shrunk kv-get page counts its note, so both channels stay within the cap", async () => {
|
|
380
|
+
const tools = buildServer();
|
|
381
|
+
const channelBytes = (res: ViewResult) => ({
|
|
382
|
+
text: Buffer.byteLength(res.content.map((part) => part.text).join(""), "utf8"),
|
|
383
|
+
structured: Buffer.byteLength(JSON.stringify(res.structuredContent), "utf8"),
|
|
384
|
+
});
|
|
385
|
+
const longKey = "k".repeat(900);
|
|
386
|
+
await tools.set.handler(
|
|
387
|
+
{
|
|
388
|
+
key: "near-cap",
|
|
389
|
+
value: {
|
|
390
|
+
text: "a".repeat(20_000),
|
|
391
|
+
rows: Array.from({ length: 400 }, (_, id) => ({ id, note: "r".repeat(100) })),
|
|
392
|
+
[longKey]: [{ blob: "f".repeat(20_000) }],
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
meta(agentA),
|
|
396
|
+
);
|
|
397
|
+
await tools.set.handler(
|
|
398
|
+
{ key: "dense", value: "x".repeat(20_000), valueType: "string" },
|
|
399
|
+
meta(agentA),
|
|
400
|
+
);
|
|
401
|
+
const cases = [
|
|
402
|
+
{ key: "dense", args: { offset: 0 }, note: "Page shrunk" },
|
|
403
|
+
{ args: { path: "text" }, note: "Page shrunk" },
|
|
404
|
+
{ args: { path: "text", offset: 3 }, note: "Page shrunk" },
|
|
405
|
+
{ args: { path: "rows" }, note: "Page shrunk" },
|
|
406
|
+
{ args: { path: longKey }, note: `narrow the path to "${longKey}.0"` },
|
|
407
|
+
];
|
|
408
|
+
for (const { key = "near-cap", args, note } of cases) {
|
|
409
|
+
const res = (await tools.get.handler({ key, ...args }, meta(agentA))) as ViewResult;
|
|
410
|
+
expect(res.structuredContent.success).toBe(true);
|
|
411
|
+
expect(res.structuredContent.message).toContain(note);
|
|
412
|
+
const bytes = channelBytes(res);
|
|
413
|
+
expect(bytes.text).toBeLessThanOrEqual(MCP_RESULT_WIRE_LIMIT_BYTES);
|
|
414
|
+
expect(bytes.structured).toBeLessThanOrEqual(MCP_RESULT_WIRE_LIMIT_BYTES);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("every spill shape path, long or empty-keyed, works as a kv-get path", async () => {
|
|
419
|
+
const tools = buildServer();
|
|
420
|
+
const rows = (n: number) =>
|
|
421
|
+
Array.from({ length: n }, (_, id) => ({ id, note: "r".repeat(60) }));
|
|
422
|
+
const longKey = "L".repeat(200);
|
|
423
|
+
const values = {
|
|
424
|
+
long: { [longKey]: rows(80), small: 1 },
|
|
425
|
+
emptyRoot: { "": rows(80), small: 1 },
|
|
426
|
+
emptyNested: { outer: { "": rows(80), side: rows(40) }, small: 1 },
|
|
427
|
+
overMax: { ["X".repeat(1_100)]: rows(80), small: 1 },
|
|
428
|
+
};
|
|
429
|
+
for (const [key, value] of Object.entries(values)) {
|
|
430
|
+
await tools.set.handler({ key, value: JSON.stringify(value) }, meta(agentA));
|
|
431
|
+
const shape = summarizeKvShape(value);
|
|
432
|
+
expect(shape.length).toBeGreaterThan(0);
|
|
433
|
+
for (const entry of shape) {
|
|
434
|
+
const res = (await tools.get.handler(
|
|
435
|
+
{ key, path: entry.path, ...(entry.items === undefined ? {} : { offset: 0 }) },
|
|
436
|
+
meta(agentA),
|
|
437
|
+
)) as ViewResult;
|
|
438
|
+
expect(res.structuredContent.success).toBe(true);
|
|
439
|
+
expect(res.structuredContent.view).toMatchObject({ path: entry.path, type: entry.type });
|
|
440
|
+
if (entry.items !== undefined) {
|
|
441
|
+
expect(res.structuredContent.view!.total).toBe(entry.items);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (key === "long") expect(shape[0]!.path).toBe(longKey);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
175
448
|
test("kv-incr creates + increments + reports value", async () => {
|
|
176
449
|
const tools = buildServer();
|
|
177
450
|
const r1 = (await tools.incr.handler({ key: "ctr", by: 5 }, meta(agentA))) as StructuredResult<{
|