@desplega.ai/agent-swarm 2.4.4 → 2.5.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 +4 -2
- package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
- package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
- package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
- package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
- package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
- package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
- package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
- package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +31 -29
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
- package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
- package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +793 -6
- package/package.json +11 -11
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/chunking.ts +42 -9
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +54 -5
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +85 -237
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/onboarding.ts +3 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +9 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/fs/provider.ts +13 -1
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/fs.ts +54 -34
- package/src/http/index.ts +4 -0
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/onboarding.ts +1 -0
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +13 -1
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +24 -4
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +138 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +272 -5
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +23 -32
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/context-key.ts +61 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +597 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +23 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +9 -2
- package/src/utils/config-env-export.ts +4 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/utils/transcription.ts +246 -0
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-51y09c6p.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-gdh8jrdm.js +0 -43
- package/dist/index-hg0x2cdv.js +0 -40
- package/dist/index-thm0y6a8.js +0 -41
- package/dist/lead-ee8vazz2.js +0 -77
- package/dist/worker-16s75hm5.js +0 -77
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import { closeDb, createAgent, createTaskExtended, getDbClient, initDb, startTask } from "../be/db";
|
|
4
|
+
import { getEmbeddingProvider, getMemoryStore } from "../be/memory";
|
|
5
|
+
import { EMBEDDING_DIMENSIONS } from "../be/memory/constants";
|
|
6
|
+
import { taskCompletionMemoryKey } from "../be/memory/index-content";
|
|
7
|
+
import { rerank } from "../be/memory/reranker";
|
|
8
|
+
import type { MemoryCandidate } from "../be/memory/types";
|
|
9
|
+
import { runTaskTerminalEffects } from "../tasks/task-terminal-effects";
|
|
10
|
+
import type { Agent } from "../types";
|
|
11
|
+
|
|
12
|
+
const TEST_DB_PATH = "./test-task-completion-memory-chunking.sqlite";
|
|
13
|
+
|
|
14
|
+
let worker: Agent;
|
|
15
|
+
const embedded: string[] = [];
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
initDb(TEST_DB_PATH);
|
|
19
|
+
// Never hit the network: every input gets a valid vector.
|
|
20
|
+
spyOn(getEmbeddingProvider(), "embed").mockImplementation(async (text: string) => {
|
|
21
|
+
embedded.push(text);
|
|
22
|
+
return new Float32Array(EMBEDDING_DIMENSIONS).fill(0.1);
|
|
23
|
+
});
|
|
24
|
+
spyOn(getEmbeddingProvider(), "embedBatch").mockImplementation(async (texts: string[]) => {
|
|
25
|
+
embedded.push(...texts);
|
|
26
|
+
return texts.map(() => new Float32Array(EMBEDDING_DIMENSIONS).fill(0.1));
|
|
27
|
+
});
|
|
28
|
+
await createAgent({ name: "chunk-lead", isLead: true, status: "idle", capabilities: [] });
|
|
29
|
+
worker = await createAgent({
|
|
30
|
+
name: "chunk-worker",
|
|
31
|
+
isLead: false,
|
|
32
|
+
status: "idle",
|
|
33
|
+
capabilities: [],
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(() => {
|
|
38
|
+
closeDb();
|
|
39
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
40
|
+
try {
|
|
41
|
+
unlinkSync(`${TEST_DB_PATH}${suffix}`);
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
type Row = {
|
|
49
|
+
id: string;
|
|
50
|
+
scope: string;
|
|
51
|
+
key: string | null;
|
|
52
|
+
content: string;
|
|
53
|
+
chunkIndex: number;
|
|
54
|
+
totalChunks: number;
|
|
55
|
+
embedded: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
async function rowsFor(taskId: string): Promise<Row[]> {
|
|
59
|
+
return getDbClient().query<Row>(
|
|
60
|
+
`SELECT id, scope, key, content, chunkIndex, totalChunks, embedding IS NOT NULL AS embedded
|
|
61
|
+
FROM agent_memory WHERE sourceTaskId = ? AND source = 'task_completion'
|
|
62
|
+
ORDER BY scope, chunkIndex`,
|
|
63
|
+
[taskId],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function waitForEmbedded(taskId: string, ms = 3000): Promise<Row[]> {
|
|
68
|
+
const deadline = Date.now() + ms;
|
|
69
|
+
while (Date.now() < deadline) {
|
|
70
|
+
const rows = await rowsFor(taskId);
|
|
71
|
+
if (rows.length > 0 && rows.every((r) => r.embedded)) return rows;
|
|
72
|
+
await Bun.sleep(20);
|
|
73
|
+
}
|
|
74
|
+
return rowsFor(taskId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** ~60k chars in paragraphs, the shape of a long dreaming-run output. */
|
|
78
|
+
function longOutput(): string {
|
|
79
|
+
return Array.from(
|
|
80
|
+
{ length: 300 },
|
|
81
|
+
(_, i) => `Paragraph ${i}: ${crypto.randomUUID()} ${"lane finding ".repeat(12)}`,
|
|
82
|
+
).join("\n\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("task_completion memory goes through chunking", () => {
|
|
86
|
+
test("a long output is stored as chunks of one document, each with an embedding", async () => {
|
|
87
|
+
const task = await createTaskExtended("dreaming-v2 run: publish the nightly report", {
|
|
88
|
+
agentId: worker.id,
|
|
89
|
+
taskType: "research",
|
|
90
|
+
});
|
|
91
|
+
await startTask(task.id);
|
|
92
|
+
const output = longOutput();
|
|
93
|
+
|
|
94
|
+
await runTaskTerminalEffects({
|
|
95
|
+
task,
|
|
96
|
+
status: "completed",
|
|
97
|
+
output,
|
|
98
|
+
agentId: worker.id,
|
|
99
|
+
persistMemory: true,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const rows = await waitForEmbedded(task.id);
|
|
103
|
+
const agentRows = rows.filter((r) => r.scope === "agent");
|
|
104
|
+
const swarmRows = rows.filter((r) => r.scope === "swarm");
|
|
105
|
+
|
|
106
|
+
expect(agentRows.length).toBeGreaterThan(1);
|
|
107
|
+
expect(swarmRows.length).toBeGreaterThan(1);
|
|
108
|
+
for (const scoped of [agentRows, swarmRows]) {
|
|
109
|
+
expect(scoped.map((r) => r.chunkIndex)).toEqual(scoped.map((_, i) => i));
|
|
110
|
+
expect(new Set(scoped.map((r) => r.totalChunks))).toEqual(new Set([scoped.length]));
|
|
111
|
+
}
|
|
112
|
+
expect(new Set(rows.map((r) => r.key))).toEqual(new Set([taskCompletionMemoryKey(task.id)]));
|
|
113
|
+
expect(rows.every((r) => r.embedded)).toBe(true);
|
|
114
|
+
for (const row of rows) expect(row.content.length).toBeLessThanOrEqual(2_200);
|
|
115
|
+
expect(agentRows[0]!.content).toContain("Task: dreaming-v2 run");
|
|
116
|
+
expect(Math.max(...embedded.map((t) => t.length))).toBeLessThanOrEqual(2_200);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a short output stays one row, keyed to its task", async () => {
|
|
120
|
+
const task = await createTaskExtended("short task", { agentId: worker.id });
|
|
121
|
+
await startTask(task.id);
|
|
122
|
+
|
|
123
|
+
await runTaskTerminalEffects({
|
|
124
|
+
task,
|
|
125
|
+
status: "completed",
|
|
126
|
+
output: "done, nothing else to report",
|
|
127
|
+
agentId: worker.id,
|
|
128
|
+
persistMemory: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const rows = await waitForEmbedded(task.id);
|
|
132
|
+
expect(rows).toHaveLength(1);
|
|
133
|
+
expect(rows[0]).toMatchObject({
|
|
134
|
+
scope: "agent",
|
|
135
|
+
key: taskCompletionMemoryKey(task.id),
|
|
136
|
+
chunkIndex: 0,
|
|
137
|
+
totalChunks: 1,
|
|
138
|
+
embedded: 1,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("rerank collapses chunks of one task", () => {
|
|
144
|
+
function candidate(overrides: Partial<MemoryCandidate>): MemoryCandidate {
|
|
145
|
+
return {
|
|
146
|
+
id: crypto.randomUUID(),
|
|
147
|
+
agentId: worker.id,
|
|
148
|
+
scope: "agent",
|
|
149
|
+
name: "Task: x",
|
|
150
|
+
content: "x",
|
|
151
|
+
summary: null,
|
|
152
|
+
source: "task_completion",
|
|
153
|
+
sourceTaskId: null,
|
|
154
|
+
sourcePath: null,
|
|
155
|
+
key: null,
|
|
156
|
+
chunkIndex: 0,
|
|
157
|
+
totalChunks: 1,
|
|
158
|
+
tags: [],
|
|
159
|
+
createdAt: new Date().toISOString(),
|
|
160
|
+
accessedAt: new Date().toISOString(),
|
|
161
|
+
accessCount: 0,
|
|
162
|
+
expiresAt: null,
|
|
163
|
+
embeddingModel: null,
|
|
164
|
+
alpha: 1,
|
|
165
|
+
beta: 1,
|
|
166
|
+
similarity: 0.5,
|
|
167
|
+
...overrides,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
test("keeps the best chunk per task document and fills the limit with other memories", () => {
|
|
172
|
+
const keyA = taskCompletionMemoryKey("aaaa");
|
|
173
|
+
const keyB = taskCompletionMemoryKey("bbbb");
|
|
174
|
+
const chunksA = [0.9, 0.85, 0.8, 0.75].map((similarity, chunkIndex) =>
|
|
175
|
+
candidate({ key: keyA, chunkIndex, totalChunks: 4, similarity }),
|
|
176
|
+
);
|
|
177
|
+
const chunkB = candidate({ key: keyB, chunkIndex: 2, totalChunks: 3, similarity: 0.7 });
|
|
178
|
+
const swarmCopyA = candidate({ key: keyA, scope: "swarm", totalChunks: 4, similarity: 0.65 });
|
|
179
|
+
const manual = candidate({ source: "manual", key: "/longterm/facts/x", similarity: 0.3 });
|
|
180
|
+
|
|
181
|
+
const ranked = rerank([...chunksA, chunkB, swarmCopyA, manual], { limit: 4 });
|
|
182
|
+
|
|
183
|
+
expect(new Set(ranked.map((r) => r.id))).toEqual(
|
|
184
|
+
new Set([chunksA[0]!.id, chunkB.id, swarmCopyA.id, manual.id]),
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("manual chunks and single-chunk task rows are left alone", () => {
|
|
189
|
+
const manualChunks = [0.9, 0.8].map((similarity, chunkIndex) =>
|
|
190
|
+
candidate({
|
|
191
|
+
source: "manual",
|
|
192
|
+
key: "/longterm/facts/y",
|
|
193
|
+
chunkIndex,
|
|
194
|
+
totalChunks: 2,
|
|
195
|
+
similarity,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
const legacy = [0.7, 0.6].map((similarity) => candidate({ key: null, similarity }));
|
|
199
|
+
|
|
200
|
+
const ranked = rerank([...manualChunks, ...legacy], { limit: 10 });
|
|
201
|
+
|
|
202
|
+
expect(ranked).toHaveLength(4);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
async function waitForRows(
|
|
207
|
+
taskId: string,
|
|
208
|
+
done: (rows: Row[]) => boolean,
|
|
209
|
+
ms = 3000,
|
|
210
|
+
): Promise<Row[]> {
|
|
211
|
+
const deadline = Date.now() + ms;
|
|
212
|
+
while (Date.now() < deadline) {
|
|
213
|
+
const rows = await rowsFor(taskId);
|
|
214
|
+
if (done(rows)) return rows;
|
|
215
|
+
await Bun.sleep(20);
|
|
216
|
+
}
|
|
217
|
+
return rowsFor(taskId);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function hasTable(name: string): Promise<boolean> {
|
|
221
|
+
const row = await getDbClient().get<{ n: number }>(
|
|
222
|
+
"SELECT COUNT(*) AS n FROM sqlite_master WHERE name = ?",
|
|
223
|
+
[name],
|
|
224
|
+
);
|
|
225
|
+
return (row?.n ?? 0) > 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Rows left behind for memory ids that no longer exist. */
|
|
229
|
+
async function orphanCounts(): Promise<Record<string, number>> {
|
|
230
|
+
const db = getDbClient();
|
|
231
|
+
const count = async (sql: string) => (await db.get<{ n: number }>(sql))?.n ?? 0;
|
|
232
|
+
const counts: Record<string, number> = {
|
|
233
|
+
versions: await count(
|
|
234
|
+
"SELECT COUNT(*) AS n FROM agent_memory_version WHERE memory_id NOT IN (SELECT id FROM agent_memory)",
|
|
235
|
+
),
|
|
236
|
+
};
|
|
237
|
+
if (await hasTable("memory_fts")) {
|
|
238
|
+
counts.fts = await count(
|
|
239
|
+
"SELECT COUNT(*) AS n FROM memory_fts WHERE memory_id NOT IN (SELECT id FROM agent_memory)",
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (await hasTable("memory_vec")) {
|
|
243
|
+
counts.vec = await count(
|
|
244
|
+
"SELECT COUNT(*) AS n FROM memory_vec WHERE memory_id NOT IN (SELECT id FROM agent_memory)",
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return counts;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
describe("re-persisting a task replaces its document", () => {
|
|
251
|
+
test("long -> short -> long replaces each scope's chunks, no duplicates or orphans", async () => {
|
|
252
|
+
const task = await createTaskExtended("research: rewrite the memory report", {
|
|
253
|
+
agentId: worker.id,
|
|
254
|
+
taskType: "research",
|
|
255
|
+
});
|
|
256
|
+
await startTask(task.id);
|
|
257
|
+
const key = taskCompletionMemoryKey(task.id);
|
|
258
|
+
const persist = (output: string) =>
|
|
259
|
+
runTaskTerminalEffects({
|
|
260
|
+
task,
|
|
261
|
+
status: "completed",
|
|
262
|
+
output,
|
|
263
|
+
agentId: worker.id,
|
|
264
|
+
persistMemory: true,
|
|
265
|
+
});
|
|
266
|
+
const scoped = (rows: Row[], scope: string) => rows.filter((r) => r.scope === scope);
|
|
267
|
+
|
|
268
|
+
await persist(longOutput());
|
|
269
|
+
const first = await waitForRows(
|
|
270
|
+
task.id,
|
|
271
|
+
(rows) =>
|
|
272
|
+
scoped(rows, "agent").length > 1 &&
|
|
273
|
+
scoped(rows, "swarm").length > 1 &&
|
|
274
|
+
rows.every((r) => r.embedded),
|
|
275
|
+
);
|
|
276
|
+
expect(scoped(first, "agent").length).toBeGreaterThan(1);
|
|
277
|
+
expect(scoped(first, "swarm").length).toBeGreaterThan(1);
|
|
278
|
+
|
|
279
|
+
// Another owner's document under the same key must survive the replace.
|
|
280
|
+
const foreign = await getMemoryStore().store({
|
|
281
|
+
agentId: crypto.randomUUID(),
|
|
282
|
+
scope: "agent",
|
|
283
|
+
name: "Task: someone else's run",
|
|
284
|
+
content: "another owner's completion of the same task",
|
|
285
|
+
source: "task_completion",
|
|
286
|
+
sourceTaskId: task.id,
|
|
287
|
+
key,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
await persist("short re-run answer");
|
|
291
|
+
const second = await waitForRows(
|
|
292
|
+
task.id,
|
|
293
|
+
(rows) => rows.length === 3 && rows.every((r) => r.embedded || r.id === foreign.id),
|
|
294
|
+
);
|
|
295
|
+
const mine = second.filter((r) => r.id !== foreign.id);
|
|
296
|
+
expect(mine.map((r) => [r.scope, r.chunkIndex, r.totalChunks])).toEqual([
|
|
297
|
+
["agent", 0, 1],
|
|
298
|
+
["swarm", 0, 1],
|
|
299
|
+
]);
|
|
300
|
+
for (const row of mine) expect(row.content).toContain("short re-run answer");
|
|
301
|
+
expect(second.some((r) => r.id === foreign.id)).toBe(true);
|
|
302
|
+
const oldIds = new Set(first.map((r) => r.id));
|
|
303
|
+
expect(second.filter((r) => oldIds.has(r.id))).toEqual([]);
|
|
304
|
+
|
|
305
|
+
await persist(longOutput());
|
|
306
|
+
const third = await waitForRows(
|
|
307
|
+
task.id,
|
|
308
|
+
(rows) =>
|
|
309
|
+
scoped(rows, "agent").length > 2 && rows.every((r) => r.embedded || r.id === foreign.id),
|
|
310
|
+
);
|
|
311
|
+
for (const scope of ["agent", "swarm"]) {
|
|
312
|
+
const rows = scoped(third, scope).filter((r) => r.id !== foreign.id);
|
|
313
|
+
expect(rows.length).toBeGreaterThan(1);
|
|
314
|
+
expect(rows.map((r) => r.chunkIndex)).toEqual(rows.map((_, i) => i));
|
|
315
|
+
expect(new Set(rows.map((r) => r.totalChunks))).toEqual(new Set([rows.length]));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const duplicates = await getDbClient().get<{ n: number }>(
|
|
319
|
+
`SELECT COUNT(*) AS n FROM (
|
|
320
|
+
SELECT 1 FROM agent_memory WHERE key = ?
|
|
321
|
+
GROUP BY scope, COALESCE(agentId, ''), chunkIndex HAVING COUNT(*) > 1)`,
|
|
322
|
+
[key],
|
|
323
|
+
);
|
|
324
|
+
expect(duplicates?.n).toBe(0);
|
|
325
|
+
const orphans = await orphanCounts();
|
|
326
|
+
for (const n of Object.values(orphans)) expect(n).toBe(0);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("an embedding that lands after its row was replaced writes no vec row", async () => {
|
|
330
|
+
const store = getMemoryStore();
|
|
331
|
+
const memory = await store.store({
|
|
332
|
+
agentId: worker.id,
|
|
333
|
+
scope: "agent",
|
|
334
|
+
name: "replaced before embed",
|
|
335
|
+
content: "a chunk whose background embed finishes after a re-persist",
|
|
336
|
+
source: "task_completion",
|
|
337
|
+
});
|
|
338
|
+
await store.delete(memory.id);
|
|
339
|
+
|
|
340
|
+
await store.updateEmbedding(memory.id, new Float32Array(EMBEDDING_DIMENSIONS).fill(0.1), "t");
|
|
341
|
+
|
|
342
|
+
expect(await hasTable("memory_vec")).toBe(true);
|
|
343
|
+
const vec = await getDbClient().get<{ n: number }>(
|
|
344
|
+
"SELECT COUNT(*) AS n FROM memory_vec WHERE memory_id = ?",
|
|
345
|
+
[memory.id],
|
|
346
|
+
);
|
|
347
|
+
expect(vec?.n).toBe(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("an embedding racing a replace leaves no orphan vec row", async () => {
|
|
351
|
+
const store = getMemoryStore();
|
|
352
|
+
const task = await createTaskExtended("embed races replace", { agentId: worker.id });
|
|
353
|
+
const input = {
|
|
354
|
+
agentId: worker.id,
|
|
355
|
+
scope: "agent" as const,
|
|
356
|
+
name: "race",
|
|
357
|
+
content: "original completion",
|
|
358
|
+
source: "task_completion" as const,
|
|
359
|
+
key: taskCompletionMemoryKey(task.id),
|
|
360
|
+
};
|
|
361
|
+
const memory = await store.store(input);
|
|
362
|
+
const vector = new Float32Array(EMBEDDING_DIMENSIONS).fill(0.1);
|
|
363
|
+
await store.updateEmbedding(memory.id, vector, "t");
|
|
364
|
+
|
|
365
|
+
// The background embed of the old row is in flight when the re-persist
|
|
366
|
+
// lands: the replace must not slip between its row UPDATE and vec insert.
|
|
367
|
+
const [, replaced] = await Promise.all([
|
|
368
|
+
store.updateEmbedding(memory.id, vector, "t"),
|
|
369
|
+
store.replaceDocument(input, [{ ...input, content: "new completion" }]),
|
|
370
|
+
]);
|
|
371
|
+
|
|
372
|
+
expect(await store.peek(memory.id)).toBeNull();
|
|
373
|
+
expect(replaced.map((r) => r.content)).toEqual(["new completion"]);
|
|
374
|
+
expect(await hasTable("memory_vec")).toBe(true);
|
|
375
|
+
const orphans = await orphanCounts();
|
|
376
|
+
for (const n of Object.values(orphans)) expect(n).toBe(0);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("a document under the key from another source is not deleted", async () => {
|
|
380
|
+
const task = await createTaskExtended("keyed manual collision", { agentId: worker.id });
|
|
381
|
+
await startTask(task.id);
|
|
382
|
+
const manual = await getMemoryStore().store({
|
|
383
|
+
agentId: worker.id,
|
|
384
|
+
scope: "agent",
|
|
385
|
+
name: "manual note",
|
|
386
|
+
content: "a manual memory that happens to use the task key",
|
|
387
|
+
source: "manual",
|
|
388
|
+
key: taskCompletionMemoryKey(task.id),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await runTaskTerminalEffects({
|
|
392
|
+
task,
|
|
393
|
+
status: "completed",
|
|
394
|
+
output: "the task output that collides with the manual key",
|
|
395
|
+
agentId: worker.id,
|
|
396
|
+
persistMemory: true,
|
|
397
|
+
});
|
|
398
|
+
await Bun.sleep(200);
|
|
399
|
+
|
|
400
|
+
expect((await getMemoryStore().peek(manual.id))?.content).toBe(manual.content);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("structured completion output keeps every section", () => {
|
|
405
|
+
test("100 markdown sections with short findings are all stored", async () => {
|
|
406
|
+
const task = await createTaskExtended(
|
|
407
|
+
"structured findings report: summarize every lane with one line each",
|
|
408
|
+
{ agentId: worker.id },
|
|
409
|
+
);
|
|
410
|
+
await startTask(task.id);
|
|
411
|
+
const output = Array.from({ length: 100 }, (_, i) => `## Lane ${i}\n\nfinding-${i} holds`).join(
|
|
412
|
+
"\n\n",
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
await runTaskTerminalEffects({
|
|
416
|
+
task,
|
|
417
|
+
status: "completed",
|
|
418
|
+
output,
|
|
419
|
+
agentId: worker.id,
|
|
420
|
+
persistMemory: true,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const rows = await waitForRows(task.id, (r) => r.length > 0 && r.every((x) => x.embedded));
|
|
424
|
+
const stored = rows.map((r) => r.content).join("\n");
|
|
425
|
+
const missing = Array.from({ length: 100 }, (_, i) => `finding-${i} holds`).filter(
|
|
426
|
+
(finding) => !stored.includes(finding),
|
|
427
|
+
);
|
|
428
|
+
expect(missing).toEqual([]);
|
|
429
|
+
expect(stored).toContain("## Lane 99");
|
|
430
|
+
for (const row of rows) expect(row.content.length).toBeLessThanOrEqual(2_200);
|
|
431
|
+
// Packed into a few chunks, not one row per three-word section.
|
|
432
|
+
expect(rows.length).toBeLessThan(10);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
@@ -32,6 +32,10 @@ beforeAll(async () => {
|
|
|
32
32
|
embedInputs.push(text);
|
|
33
33
|
return null;
|
|
34
34
|
});
|
|
35
|
+
spyOn(getEmbeddingProvider(), "embedBatch").mockImplementation(async (texts: string[]) => {
|
|
36
|
+
embedInputs.push(...texts);
|
|
37
|
+
return texts.map(() => null);
|
|
38
|
+
});
|
|
35
39
|
await createAgent({ name: "scrub-lead", isLead: true, status: "idle", capabilities: [] });
|
|
36
40
|
worker = await createAgent({
|
|
37
41
|
name: "scrub-worker",
|
|
@@ -185,7 +189,8 @@ describe("runTaskTerminalEffects scrubs completion memory and the Lead follow-up
|
|
|
185
189
|
);
|
|
186
190
|
expect(versions.length).toBeGreaterThan(0);
|
|
187
191
|
for (const v of versions) expectRedacted(v.content);
|
|
188
|
-
|
|
192
|
+
// Embedding runs in the background after the rows land.
|
|
193
|
+
await waitFor(async () => embedInputs.length >= 2 || null);
|
|
189
194
|
for (const input of embedInputs) expectRedacted(input);
|
|
190
195
|
|
|
191
196
|
const followUp = await getDbClient().get<{ task: string }>(
|