@desplega.ai/agent-swarm 1.158.0 → 1.160.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-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
- package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
- package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
- package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
- package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
- package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
- package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
- package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
- package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
- package/dist/boot-seeds-gsetbs5y.js +28 -0
- package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
- package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
- package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
- package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
- package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
- package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
- package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
- package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
- package/dist/cli-0x01kwrd.js +491 -0
- package/dist/cli-0xqde53b.js +176 -0
- package/dist/cli-10nhj6xq.js +8355 -0
- package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
- package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
- package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
- package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
- package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
- package/dist/cli-3qhyh31j.js +166 -0
- package/dist/cli-447dt1h3.js +3828 -0
- package/dist/cli-4derpgyk.js +115 -0
- package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
- package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
- package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
- package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
- package/dist/cli-741yb0xx.js +1027 -0
- package/dist/cli-74h9hscv.js +69 -0
- package/dist/cli-7cxw8b60.js +14910 -0
- package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
- package/dist/cli-7kyk9jr3.js +22 -0
- package/dist/cli-89s4ngw3.js +183 -0
- package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
- package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
- package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
- package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
- package/dist/cli-br0qdrnp.js +2150 -0
- package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
- package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
- package/dist/cli-cpvsaqsw.js +333 -0
- package/dist/cli-d5czme6m.js +17805 -0
- package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
- package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
- package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
- package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
- package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
- package/dist/cli-gt4hxb8p.js +99 -0
- package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
- package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
- package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
- package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
- package/dist/cli-kmcpfwn2.js +333856 -0
- package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
- package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
- package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
- package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
- package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
- package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
- package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
- package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
- package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
- package/dist/cli-rb9m6rpe.js +4980 -0
- package/dist/cli-rykfmgwg.js +102 -0
- package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
- package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
- package/dist/cli-tfky2838.js +756 -0
- package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
- package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
- package/dist/cli-trc24wk2.js +143 -0
- package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
- package/dist/cli-v7fn5gms.js +1425 -0
- package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
- package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
- package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
- package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
- package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
- package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
- package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
- package/dist/cli-zpep9q5e.js +491 -0
- package/dist/cli.js +41 -32
- package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
- package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
- package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
- package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
- package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
- package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
- package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
- package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
- package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
- package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
- package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
- package/dist/execute-6e7qeyk4.js +1017 -0
- package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
- package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
- package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
- package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
- package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
- package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
- package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
- package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
- package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
- package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
- package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
- package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
- package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
- package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
- package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
- package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
- package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
- package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
- package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
- package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
- package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
- package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
- package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
- package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
- package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
- package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
- package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
- package/dist/pricing-refresh-wx0tp7cb.js +39 -0
- package/dist/quickjs-r1pq2107.wasm +0 -0
- package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
- package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
- package/dist/runtime-zyssgse0.js +24 -0
- package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
- package/dist/typesafe-system-one-2ef30pec.js +27 -0
- package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
- package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
- package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
- package/openapi.json +1314 -91
- package/package.json +12 -11
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/api-keys.ts +70 -0
- package/src/be/db/tasks/human-free.ts +442 -0
- package/src/be/db/tasks/read.ts +56 -1
- package/src/be/db/tasks/write.ts +107 -11
- package/src/be/db.ts +274 -188
- package/src/be/harness-model-support.ts +182 -0
- package/src/be/human-free-drain.ts +67 -0
- package/src/be/memory/providers/sqlite-store.ts +51 -15
- package/src/be/migrations/182_task_human_free_flag.sql +63 -0
- package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
- package/src/be/migrations/184_model_catalog.sql +120 -0
- package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
- package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
- package/src/be/model-catalog-store.ts +644 -0
- package/src/be/model-tier-keys.ts +46 -0
- package/src/be/model-tier-resolution.ts +534 -0
- package/src/be/model-validation.ts +188 -0
- package/src/be/models-catalog.ts +61 -7
- package/src/be/modelsdev-cache.json +82 -0
- package/src/be/modelsdev-cache.ts +2 -0
- package/src/be/pricing-refresh.ts +154 -12
- package/src/be/scripts/typecheck.ts +15 -0
- package/src/be/seed/runner.ts +18 -12
- package/src/be/seed/types.ts +1 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
- package/src/be/swarm-config-guard.ts +8 -0
- package/src/cli.tsx +10 -0
- package/src/commands/claude-managed-setup.ts +3 -1
- package/src/commands/codex-login.ts +67 -0
- package/src/commands/credential-outcome-report.ts +191 -0
- package/src/commands/rate-limit-outcome.ts +34 -4
- package/src/commands/runner.ts +357 -186
- package/src/commands/seat-mismatch-report.ts +46 -0
- package/src/extensions/apply-task-create.ts +27 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +79 -17
- package/src/heartbeat/templates.ts +2 -2
- package/src/http/agents.ts +62 -1
- package/src/http/api-keys.ts +140 -2
- package/src/http/codex-oauth-keep-warm.ts +3 -5
- package/src/http/index.ts +8 -0
- package/src/http/memory.ts +12 -5
- package/src/http/models-catalog.ts +357 -8
- package/src/http/poll.ts +54 -3
- package/src/http/schedules.ts +51 -6
- package/src/http/script-runs.ts +12 -0
- package/src/http/session-data.ts +2 -5
- package/src/http/stats.ts +6 -8
- package/src/http/status.ts +15 -1
- package/src/http/tasks.ts +90 -7
- package/src/http/usage-cache.ts +56 -15
- package/src/http/workflows.ts +46 -8
- package/src/mcp-client/http-client.ts +5 -0
- package/src/memory/recall-query.ts +74 -0
- package/src/prompts/base-prompt.ts +15 -4
- package/src/prompts/session-templates.ts +23 -1
- package/src/providers/acp-adapter.ts +1 -0
- package/src/providers/claude-adapter.ts +14 -5
- package/src/providers/claude-managed-adapter.ts +1 -0
- package/src/providers/claude-managed-models.ts +47 -161
- package/src/providers/claude-sdk-session.ts +3 -1
- package/src/providers/codex-adapter.ts +14 -14
- package/src/providers/codex-models.ts +65 -198
- package/src/providers/devin-adapter.ts +1 -0
- package/src/providers/dsh-adapter.ts +2 -1
- package/src/providers/harness-version.ts +83 -7
- package/src/providers/opencode-adapter.ts +53 -1
- package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
- package/src/providers/pi-codemode-runtime.ts +17 -0
- package/src/providers/pi-mono-adapter.ts +361 -83
- package/src/providers/pi-mono-extension.ts +16 -1
- package/src/providers/reasoning-effort.ts +44 -140
- package/src/providers/types.ts +8 -0
- package/src/rbac/legacy-policy.ts +43 -0
- package/src/rbac/permissions.ts +15 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +15 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
- package/src/server.ts +6 -0
- package/src/slack/assistant.ts +13 -0
- package/src/slack/message-text.ts +56 -2
- package/src/slack/render-v2.ts +87 -37
- package/src/slack/session-status.ts +541 -0
- package/src/slack/thread-buffer.ts +10 -2
- package/src/tasks/sibling-awareness.ts +3 -2
- package/src/telemetry.ts +5 -4
- package/src/templates.d.ts +6 -0
- package/src/tests/agents-list-model-display.test.ts +4 -6
- package/src/tests/api-key-auth-failure-route.test.ts +142 -0
- package/src/tests/api-key-tracking.test.ts +368 -1
- package/src/tests/base-prompt.test.ts +43 -2
- package/src/tests/claude-adapter.test.ts +68 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -18
- package/src/tests/codex-adapter.test.ts +2 -1
- package/src/tests/codex-auth-failure.test.ts +52 -0
- package/src/tests/codex-gpt6-release.test.ts +66 -5
- package/src/tests/codex-login.test.ts +116 -0
- package/src/tests/context-window.test.ts +7 -5
- package/src/tests/credential-exhaustion-policy.test.ts +118 -3
- package/src/tests/credential-outcome-report.test.ts +171 -0
- package/src/tests/credential-status-api.test.ts +103 -2
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +10 -1
- package/src/tests/db-task-write-contract.test.ts +535 -2
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dokploy-deploy.test.ts +315 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/explicit-model-validation.test.ts +583 -0
- package/src/tests/extensions-pre-task.test.ts +73 -0
- package/src/tests/fetch-resolved-env.test.ts +46 -0
- package/src/tests/hang-watchdog-worker.ts +47 -0
- package/src/tests/hang-watchdog.test.ts +45 -0
- package/src/tests/hang-watchdog.ts +30 -0
- package/src/tests/harness-model-guard.test.ts +123 -0
- package/src/tests/harness-model-support.test.ts +374 -0
- package/src/tests/harness-version.test.ts +70 -3
- package/src/tests/heartbeat-checklist-config.test.ts +71 -0
- package/src/tests/heartbeat-checklist.test.ts +4 -2
- package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
- package/src/tests/http-task-write-ownership.test.ts +172 -0
- package/src/tests/human-free-flag.test.ts +279 -0
- package/src/tests/human-free-oracle.ts +57 -0
- package/src/tests/human-free-reclassify.test.ts +669 -0
- package/src/tests/memory-fts-populate.test.ts +139 -0
- package/src/tests/memory-http-recall-gating.test.ts +125 -1
- package/src/tests/migration-046-budgets.test.ts +3 -3
- package/src/tests/model-catalog-migration.test.ts +134 -0
- package/src/tests/model-catalog-table.test.ts +249 -0
- package/src/tests/model-catalog-write-access.test.ts +387 -0
- package/src/tests/model-rate-limit-windows.test.ts +19 -0
- package/src/tests/model-tier-resolution.test.ts +611 -0
- package/src/tests/models-catalog.test.ts +1 -1
- package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
- package/src/tests/opencode-adapter.test.ts +157 -2
- package/src/tests/package-publish.test.ts +137 -36
- package/src/tests/pi-codemode.test.ts +246 -0
- package/src/tests/pi-mono-adapter.test.ts +3 -0
- package/src/tests/pi-native-mcp.test.ts +193 -0
- package/src/tests/pi-tool-exposure.test.ts +374 -0
- package/src/tests/preload.ts +3 -0
- package/src/tests/pricing-routes.test.ts +2 -2
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/rate-limit-event.test.ts +65 -0
- package/src/tests/rate-limit-outcome.test.ts +90 -0
- package/src/tests/rbac-engine.test.ts +77 -3
- package/src/tests/reasoning-effort.test.ts +109 -0
- package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
- package/src/tests/runner-key-completion-outcome.test.ts +112 -0
- package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
- package/src/tests/runner-registration-retry.test.ts +149 -0
- package/src/tests/runtime-model-catalog.test.ts +86 -0
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/seat-mismatch-report.test.ts +85 -0
- package/src/tests/seed-scripts.test.ts +138 -0
- package/src/tests/seed.test.ts +25 -0
- package/src/tests/session-costs.test.ts +14 -11
- package/src/tests/slack-message-text.test.ts +101 -0
- package/src/tests/slack-render-v2.test.ts +96 -0
- package/src/tests/slack-session-status.test.ts +626 -0
- package/src/tests/slack-thread-buffer-context.test.ts +93 -0
- package/src/tests/status.test.ts +29 -0
- package/src/tests/steering-promotion.test.ts +51 -0
- package/src/tests/store-progress-ownership.test.ts +136 -0
- package/src/tests/subscription-plans.test.ts +28 -0
- package/src/tests/task-cascade-fail.test.ts +148 -2
- package/src/tests/task-citations.test.ts +3 -2
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/usage-cache.test.ts +166 -0
- package/src/tests/workflow-agent-task.test.ts +30 -0
- package/src/tests/workflow-executors.test.ts +5 -2
- package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
- package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
- package/src/tests/workflow-system-one-decision.test.ts +2063 -0
- package/src/tests/workflow-triggers-v2.test.ts +320 -0
- package/src/tools/model-catalog/index.ts +137 -0
- package/src/tools/schedules/create-schedule.ts +17 -2
- package/src/tools/schedules/patch-schedule.ts +31 -1
- package/src/tools/schedules/update-schedule.ts +31 -1
- package/src/tools/send-task.ts +18 -1
- package/src/tools/store-progress.ts +49 -4
- package/src/tools/task-action.ts +14 -1
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/workflows/create-workflow.ts +17 -4
- package/src/tools/workflows/patch-workflow-node.ts +17 -7
- package/src/tools/workflows/patch-workflow.ts +9 -2
- package/src/tools/workflows/update-workflow.ts +17 -4
- package/src/types.ts +41 -1
- package/src/utils/codex-auth-failure.ts +34 -0
- package/src/utils/context-window.ts +47 -53
- package/src/utils/credentials.ts +72 -30
- package/src/utils/error-tracker.ts +80 -0
- package/src/utils/harness-cli-version.ts +115 -0
- package/src/utils/harness-model-error.ts +33 -0
- package/src/utils/model-rate-limit-windows.ts +12 -0
- package/src/utils/runtime-model-catalog.ts +152 -0
- package/src/utils/spawn-model-guard.ts +43 -0
- package/src/utils/subscription-plans.ts +22 -0
- package/src/workflows/approval-resolution.ts +39 -0
- package/src/workflows/definition.ts +7 -0
- package/src/workflows/engine.ts +69 -1
- package/src/workflows/executors/agent-task.ts +16 -6
- package/src/workflows/executors/base.ts +53 -0
- package/src/workflows/executors/human-in-the-loop.ts +8 -8
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +1232 -0
- package/src/workflows/executors/system-one-providers.ts +301 -0
- package/src/workflows/executors/workflow-llm.ts +13 -1
- package/src/workflows/model-validation.ts +45 -0
- package/src/workflows/patch-definition.ts +5 -0
- package/src/workflows/readiness.ts +86 -0
- package/src/workflows/recovery.ts +12 -12
- package/src/workflows/resume.ts +23 -9
- package/src/workflows/triggers.ts +85 -0
- package/templates/schedules/daily-blocker-digest/content.md +4 -5
- package/templates/skills/agent-browser/SKILL.md +6 -5
- package/templates/skills/agent-browser/content.md +6 -5
- package/templates/skills/heartbeat-runbook/config.json +1 -1
- package/templates/skills/heartbeat-runbook/content.md +2 -2
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
- package/templates/skills/swarm-scripts/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +82 -1
- package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
- package/templates/workflows/pr-review-status-sweep/content.md +2 -0
- package/dist/boot-seeds-s3q01xj3.js +0 -26
- package/dist/claude-sdk-session-t442qz6f.js +0 -39108
- package/dist/cli-0xjkj1j7.js +0 -61
- package/dist/cli-20cj37vt.js +0 -335561
- package/dist/cli-2djtr51b.js +0 -48
- package/dist/cli-65hjmev5.js +0 -1141
- package/dist/cli-aw1jwp1r.js +0 -14075
- package/dist/cli-h967nb6r.js +0 -6603
- package/dist/cli-vapyk8sw.js +0 -20
- package/dist/cli-ws6rqycm.js +0 -247
- package/dist/pricing-refresh-v1cbr9te.js +0 -27
- package/src/providers/modelsdev-reasoning.json +0 -6453
- package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
|
@@ -10,6 +10,7 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
10
10
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import type { Event as OpencodeEvent } from "@opencode-ai/sdk";
|
|
13
|
+
import { resolveProviderOutput, trackAssistantText } from "../commands/runner";
|
|
13
14
|
import type {
|
|
14
15
|
ProviderEvent,
|
|
15
16
|
ProviderResult,
|
|
@@ -927,6 +928,122 @@ describe("OpencodeSession — context_usage emission (phase 9 fix)", () => {
|
|
|
927
928
|
|
|
928
929
|
// ── DES-300: per-task isolation ────────────────────────────────────────────────
|
|
929
930
|
|
|
931
|
+
describe("OpencodeSession — assistant text for final-message validation", () => {
|
|
932
|
+
beforeEach(() => {
|
|
933
|
+
mock.restore();
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
const SESSION = "sess-abc-123";
|
|
937
|
+
|
|
938
|
+
function messageUpdated(
|
|
939
|
+
id: string,
|
|
940
|
+
role: "assistant" | "user",
|
|
941
|
+
completed: boolean,
|
|
942
|
+
): OpencodeEvent {
|
|
943
|
+
const now = Date.now();
|
|
944
|
+
return {
|
|
945
|
+
type: "message.updated",
|
|
946
|
+
properties: {
|
|
947
|
+
info: {
|
|
948
|
+
id,
|
|
949
|
+
sessionID: SESSION,
|
|
950
|
+
role,
|
|
951
|
+
cost: 0,
|
|
952
|
+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
|
953
|
+
time: completed ? { created: now, completed: now + 1 } : { created: now },
|
|
954
|
+
parentID: "",
|
|
955
|
+
modelID: "m",
|
|
956
|
+
providerID: "p",
|
|
957
|
+
mode: "live",
|
|
958
|
+
path: { cwd: "/", root: "/" },
|
|
959
|
+
} as never,
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function textPart(
|
|
965
|
+
messageID: string,
|
|
966
|
+
id: string,
|
|
967
|
+
text: string,
|
|
968
|
+
extra: { synthetic?: boolean; ignored?: boolean } = {},
|
|
969
|
+
): OpencodeEvent {
|
|
970
|
+
return {
|
|
971
|
+
type: "message.part.updated",
|
|
972
|
+
properties: {
|
|
973
|
+
part: { id, sessionID: SESSION, messageID, type: "text", text, ...extra } as never,
|
|
974
|
+
},
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const idle: OpencodeEvent = { type: "session.idle", properties: { sessionID: SESSION } };
|
|
979
|
+
|
|
980
|
+
function assistantTexts(emitted: ProviderEvent[]): string[] {
|
|
981
|
+
return emitted.flatMap((e) =>
|
|
982
|
+
e.type === "message" && e.role === "assistant" ? [e.content] : [],
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
test("the runner's final-message buffer holds the last step's text, ready for outputSchema validation", async () => {
|
|
987
|
+
const final = JSON.stringify({ verdict: "pass", score: 3 });
|
|
988
|
+
const { emitted } = await driveSession([
|
|
989
|
+
messageUpdated("m1", "assistant", false),
|
|
990
|
+
textPart("m1", "p1", "Checking the page first."),
|
|
991
|
+
messageUpdated("m1", "assistant", true),
|
|
992
|
+
// Step 2 is tool-free and carries the answer.
|
|
993
|
+
messageUpdated("m2", "assistant", false),
|
|
994
|
+
textPart("m2", "p2", final),
|
|
995
|
+
messageUpdated("m2", "assistant", true),
|
|
996
|
+
idle,
|
|
997
|
+
]);
|
|
998
|
+
|
|
999
|
+
expect(assistantTexts(emitted)).toEqual(["Checking the page first.", final]);
|
|
1000
|
+
|
|
1001
|
+
const holder: { value?: string } = {};
|
|
1002
|
+
for (const e of emitted) trackAssistantText(holder, e);
|
|
1003
|
+
expect(resolveProviderOutput({ output: undefined }, holder)).toBe(final);
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
test("user-message text and synthetic or ignored parts are never emitted as assistant text", async () => {
|
|
1007
|
+
const { emitted } = await driveSession([
|
|
1008
|
+
messageUpdated("u1", "user", true),
|
|
1009
|
+
textPart("u1", "pu", "the task prompt"),
|
|
1010
|
+
messageUpdated("m1", "assistant", false),
|
|
1011
|
+
textPart("m1", "p-synth", "injected reminder", { synthetic: true }),
|
|
1012
|
+
textPart("m1", "p-ign", "ignored text", { ignored: true }),
|
|
1013
|
+
textPart("m1", "p1", "real answer"),
|
|
1014
|
+
messageUpdated("m1", "assistant", true),
|
|
1015
|
+
idle,
|
|
1016
|
+
]);
|
|
1017
|
+
|
|
1018
|
+
expect(assistantTexts(emitted)).toEqual(["real answer"]);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
test("streamed snapshots and replayed finalized updates emit the message once, with its full text", async () => {
|
|
1022
|
+
const { emitted } = await driveSession([
|
|
1023
|
+
messageUpdated("m1", "assistant", false),
|
|
1024
|
+
textPart("m1", "p1", '{"a"'),
|
|
1025
|
+
textPart("m1", "p1", '{"a": 1}'),
|
|
1026
|
+
messageUpdated("m1", "assistant", true),
|
|
1027
|
+
messageUpdated("m1", "assistant", true),
|
|
1028
|
+
idle,
|
|
1029
|
+
]);
|
|
1030
|
+
|
|
1031
|
+
expect(assistantTexts(emitted)).toEqual(['{"a": 1}']);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test("a text part that lands after its message finalized re-emits the fuller text", async () => {
|
|
1035
|
+
const { emitted } = await driveSession([
|
|
1036
|
+
messageUpdated("m1", "assistant", false),
|
|
1037
|
+
textPart("m1", "p1", "first half. "),
|
|
1038
|
+
messageUpdated("m1", "assistant", true),
|
|
1039
|
+
textPart("m1", "p2", "second half."),
|
|
1040
|
+
idle,
|
|
1041
|
+
]);
|
|
1042
|
+
|
|
1043
|
+
expect(assistantTexts(emitted)).toEqual(["first half.", "first half. second half."]);
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
|
|
930
1047
|
describe("OpencodeAdapter — per-task isolation (DES-300)", () => {
|
|
931
1048
|
let prevOpencodeSkillsDir: string | undefined;
|
|
932
1049
|
|
|
@@ -1038,6 +1155,37 @@ describe("OpencodeAdapter — per-task isolation (DES-300)", () => {
|
|
|
1038
1155
|
}
|
|
1039
1156
|
});
|
|
1040
1157
|
|
|
1158
|
+
test("swarm MCP headers carry the task id and context key", async () => {
|
|
1159
|
+
const events: OpencodeEvent[] = [
|
|
1160
|
+
{ type: "session.idle", properties: { sessionID: "sess-abc-123" } },
|
|
1161
|
+
];
|
|
1162
|
+
await driveSession(
|
|
1163
|
+
events,
|
|
1164
|
+
testConfig({ taskId: "task-1", contextKey: "task:slack:C1:1700000000.000100" }),
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
const opts = lastCreateOpencodeConfig as {
|
|
1168
|
+
config?: { mcp?: Record<string, { headers?: Record<string, string> }> };
|
|
1169
|
+
};
|
|
1170
|
+
const headers = opts.config?.mcp?.swarm?.headers;
|
|
1171
|
+
expect(headers?.["X-Source-Task-Id"]).toBe("task-1");
|
|
1172
|
+
expect(headers?.["X-Context-Key"]).toBe("task:slack:C1:1700000000.000100");
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
test("swarm MCP headers omit the context key when the task has none", async () => {
|
|
1176
|
+
const events: OpencodeEvent[] = [
|
|
1177
|
+
{ type: "session.idle", properties: { sessionID: "sess-abc-123" } },
|
|
1178
|
+
];
|
|
1179
|
+
await driveSession(events, testConfig({ taskId: "task-1" }));
|
|
1180
|
+
|
|
1181
|
+
const opts = lastCreateOpencodeConfig as {
|
|
1182
|
+
config?: { mcp?: Record<string, { headers?: Record<string, string> }> };
|
|
1183
|
+
};
|
|
1184
|
+
const headers = opts.config?.mcp?.swarm?.headers;
|
|
1185
|
+
expect(headers?.["X-Source-Task-Id"]).toBe("task-1");
|
|
1186
|
+
expect(headers?.["X-Context-Key"]).toBeUndefined();
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1041
1189
|
test("swarm MCP headers omit the runtime identity when unset", async () => {
|
|
1042
1190
|
const prev = process.env.SWARM_RUNTIME_INSTANCE_ID;
|
|
1043
1191
|
delete process.env.SWARM_RUNTIME_INSTANCE_ID;
|
|
@@ -1076,7 +1224,7 @@ describe("OpencodeAdapter — per-task isolation (DES-300)", () => {
|
|
|
1076
1224
|
});
|
|
1077
1225
|
|
|
1078
1226
|
test("per-task config file is written as valid JSON", async () => {
|
|
1079
|
-
const cfg = testConfig({ taskId: "task-cfg-json" });
|
|
1227
|
+
const cfg = testConfig({ taskId: "task-cfg-json", contextKey: "task:api:ctx-1" });
|
|
1080
1228
|
await inspectSessionBeforeIdle(cfg, async () => {
|
|
1081
1229
|
const configFile = Bun.file("/tmp/opencode-task-cfg-json.json");
|
|
1082
1230
|
const exists = await configFile.exists();
|
|
@@ -1084,9 +1232,16 @@ describe("OpencodeAdapter — per-task isolation (DES-300)", () => {
|
|
|
1084
1232
|
if (exists) {
|
|
1085
1233
|
const text = await configFile.text();
|
|
1086
1234
|
expect(() => JSON.parse(text)).not.toThrow();
|
|
1087
|
-
const parsed = JSON.parse(text) as {
|
|
1235
|
+
const parsed = JSON.parse(text) as {
|
|
1236
|
+
mcp?: { swarm?: { headers?: Record<string, string> } };
|
|
1237
|
+
permission?: unknown;
|
|
1238
|
+
};
|
|
1088
1239
|
expect(parsed.mcp).toBeDefined();
|
|
1089
1240
|
expect(parsed.permission).toBeDefined();
|
|
1241
|
+
// The per-task file must carry the task identity too: `opencode serve`
|
|
1242
|
+
// reads this file (via OPENCODE_CONFIG), not the in-process object.
|
|
1243
|
+
expect(parsed.mcp?.swarm?.headers?.["X-Source-Task-Id"]).toBe("task-cfg-json");
|
|
1244
|
+
expect(parsed.mcp?.swarm?.headers?.["X-Context-Key"]).toBe("task:api:ctx-1");
|
|
1090
1245
|
}
|
|
1091
1246
|
});
|
|
1092
1247
|
|
|
@@ -1,47 +1,148 @@
|
|
|
1
|
-
import { afterAll, describe, expect,
|
|
2
|
-
import {
|
|
3
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, realpath, rm, symlink } from "node:fs/promises";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
5
4
|
import { join } from "node:path";
|
|
5
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc.ts";
|
|
6
6
|
|
|
7
7
|
const REPO_ROOT = join(import.meta.dir, "../..");
|
|
8
|
-
const tempDir =
|
|
8
|
+
const tempDir = await realpath(await mkdtemp(join(tmpdir(), "agent-swarm-pack-")));
|
|
9
|
+
const unpackDir = join(tempDir, "unpacked");
|
|
10
|
+
const cliPath = join(unpackDir, "package", "dist", "cli.js");
|
|
11
|
+
const nodePath = Bun.which("node")!;
|
|
12
|
+
const emptyPath = join(tempDir, "empty-path");
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
const tarballPath = join(tempDir, "agent-swarm.tgz");
|
|
16
|
+
expectChildOk(
|
|
17
|
+
await runChild(["bun", "pm", "pack", "--filename", tarballPath], { cwd: REPO_ROOT }),
|
|
18
|
+
"pack CLI",
|
|
19
|
+
);
|
|
20
|
+
await mkdir(unpackDir);
|
|
21
|
+
await mkdir(emptyPath);
|
|
22
|
+
expectChildOk(await runChild(["tar", "-xzf", tarballPath, "-C", unpackDir]), "unpack CLI");
|
|
23
|
+
// Reuse installed dependencies without a network install.
|
|
24
|
+
await symlink(join(REPO_ROOT, "node_modules"), join(unpackDir, "package", "node_modules"));
|
|
25
|
+
}, CHILD_PROCESS_TEST_BUDGET_MS);
|
|
11
26
|
|
|
12
|
-
afterAll(() => {
|
|
13
|
-
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await rm(tempDir, { force: true, recursive: true });
|
|
14
29
|
});
|
|
15
30
|
|
|
16
31
|
describe("published package", () => {
|
|
17
|
-
test("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(output).toContain("agent-swarm v");
|
|
32
|
+
test("runtime dependencies never reference a private workspace package", async () => {
|
|
33
|
+
// The release step runs plain `npm publish`, which does not rewrite `workspace:*`, and a
|
|
34
|
+
// private workspace package is never on the registry: either one breaks `npm i` / `npx`.
|
|
35
|
+
// Workspace code the CLI needs is bundled into dist/cli.js, so it belongs in devDependencies.
|
|
36
|
+
const workspaceNames = new Set<string>();
|
|
37
|
+
for (const pattern of ["packages/*/package.json", "apps/*/package.json"]) {
|
|
38
|
+
for await (const file of new Bun.Glob(pattern).scan({ cwd: REPO_ROOT })) {
|
|
39
|
+
workspaceNames.add((await Bun.file(join(REPO_ROOT, file)).json()).name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
expect(workspaceNames.has("@desplega/model-catalog")).toBe(true);
|
|
43
|
+
|
|
44
|
+
const offenders: string[] = [];
|
|
45
|
+
for (const manifestPath of [
|
|
46
|
+
join(REPO_ROOT, "package.json"),
|
|
47
|
+
join(unpackDir, "package", "package.json"),
|
|
48
|
+
]) {
|
|
49
|
+
const manifest = await Bun.file(manifestPath).json();
|
|
50
|
+
for (const field of ["dependencies", "optionalDependencies", "peerDependencies"]) {
|
|
51
|
+
for (const [name, spec] of Object.entries<string>(manifest[field] ?? {})) {
|
|
52
|
+
if (spec.startsWith("workspace:") || workspaceNames.has(name)) {
|
|
53
|
+
offenders.push(`${manifestPath} ${field} ${name}@${spec}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
expect(offenders).toEqual([]);
|
|
46
59
|
});
|
|
60
|
+
|
|
61
|
+
test(
|
|
62
|
+
"version and help work without Bun installed",
|
|
63
|
+
async () => {
|
|
64
|
+
for (const args of [["version"], ["onboard", "--help"], ["codex-login", "--help"]]) {
|
|
65
|
+
const result = expectChildOk(
|
|
66
|
+
await runChild([nodePath, cliPath, ...args], {
|
|
67
|
+
cwd: unpackDir,
|
|
68
|
+
env: { ...process.env, PATH: emptyPath },
|
|
69
|
+
}),
|
|
70
|
+
args.join(" "),
|
|
71
|
+
);
|
|
72
|
+
expect(result.stdout).toContain("agent-swarm");
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
test(
|
|
79
|
+
"onboard restarts with Bun and preserves arguments, environment, streams, and exit status",
|
|
80
|
+
async () => {
|
|
81
|
+
const binDir = join(tempDir, "fake-bin");
|
|
82
|
+
await mkdir(binDir);
|
|
83
|
+
const bunPath = join(binDir, "bun");
|
|
84
|
+
await Bun.write(
|
|
85
|
+
bunPath,
|
|
86
|
+
`#!${process.execPath}
|
|
87
|
+
console.log(JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd(), marker: process.env.ONBOARD_TEST_MARKER, bun: !!process.versions.bun }));
|
|
88
|
+
console.error("test stderr");
|
|
89
|
+
process.exit(37);
|
|
90
|
+
`,
|
|
91
|
+
);
|
|
92
|
+
await chmod(bunPath, 0o755);
|
|
93
|
+
const args = ["onboard", "--dry-run", "--preset", "value with spaces"];
|
|
94
|
+
const result = await runChild([nodePath, cliPath, ...args], {
|
|
95
|
+
cwd: unpackDir,
|
|
96
|
+
env: { ...process.env, PATH: binDir, ONBOARD_TEST_MARKER: "preserved" },
|
|
97
|
+
});
|
|
98
|
+
expect(result.exitCode).toBe(37);
|
|
99
|
+
expect(JSON.parse(result.stdout)).toEqual({
|
|
100
|
+
args: [cliPath, ...args],
|
|
101
|
+
cwd: unpackDir,
|
|
102
|
+
marker: "preserved",
|
|
103
|
+
bun: true,
|
|
104
|
+
});
|
|
105
|
+
expect(result.stderr).toContain("test stderr");
|
|
106
|
+
},
|
|
107
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
test(
|
|
111
|
+
"onboard reports a Bun installation requirement when Bun is unavailable",
|
|
112
|
+
async () => {
|
|
113
|
+
const result = await runChild([nodePath, cliPath, "onboard"], {
|
|
114
|
+
cwd: unpackDir,
|
|
115
|
+
env: { ...process.env, PATH: emptyPath },
|
|
116
|
+
});
|
|
117
|
+
expect(result.exitCode).toBe(1);
|
|
118
|
+
expect(result.stderr).toContain("Onboarding requires Bun.");
|
|
119
|
+
expect(result.stderr).toContain("https://bun.sh");
|
|
120
|
+
expect(result.stdout + result.stderr).not.toContain("Bun is not defined");
|
|
121
|
+
},
|
|
122
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
test(
|
|
126
|
+
"onboard completes a dry run under Node and Bun",
|
|
127
|
+
async () => {
|
|
128
|
+
for (const runtime of [nodePath, process.execPath]) {
|
|
129
|
+
const result = expectChildOk(
|
|
130
|
+
await runChild([runtime, cliPath, "onboard", "--yes", "--preset=solo", "--dry-run"], {
|
|
131
|
+
cwd: unpackDir,
|
|
132
|
+
env: {
|
|
133
|
+
...process.env,
|
|
134
|
+
HARNESS_PROVIDER: "claude",
|
|
135
|
+
ANTHROPIC_API_KEY: "test-onboard-key",
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
"onboard dry run",
|
|
139
|
+
);
|
|
140
|
+
expect(result.stdout).toContain("DRY-RUN MODE");
|
|
141
|
+
expect(result.stdout + result.stderr).not.toContain("Bun is not defined");
|
|
142
|
+
}
|
|
143
|
+
expect(await Bun.file(join(unpackDir, "docker-compose.yml")).exists()).toBe(false);
|
|
144
|
+
expect(await Bun.file(join(unpackDir, ".env")).exists()).toBe(false);
|
|
145
|
+
},
|
|
146
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
147
|
+
);
|
|
47
148
|
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type {
|
|
6
|
+
AgentSession,
|
|
7
|
+
ExtensionAPI,
|
|
8
|
+
ExtensionFactory,
|
|
9
|
+
ExtensionToolContext,
|
|
10
|
+
ToolDefinition,
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import {
|
|
13
|
+
type CodemodeLimits,
|
|
14
|
+
createBoundedCodemodeExtension,
|
|
15
|
+
isPiCodemodeEnabled,
|
|
16
|
+
PiMonoAdapter,
|
|
17
|
+
piDefaultToolAdditions,
|
|
18
|
+
piExtensionFactories,
|
|
19
|
+
} from "../providers/pi-mono-adapter";
|
|
20
|
+
|
|
21
|
+
describe("PI_CODEMODE flag", () => {
|
|
22
|
+
test("off by default and for false values", () => {
|
|
23
|
+
expect(isPiCodemodeEnabled({})).toBe(false);
|
|
24
|
+
expect(isPiCodemodeEnabled({ PI_CODEMODE: "false" })).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("on when true", () => {
|
|
28
|
+
expect(isPiCodemodeEnabled({ PI_CODEMODE: "true" })).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("pi extension factories and default tools — codemode", () => {
|
|
33
|
+
const swarm: ExtensionFactory = () => {};
|
|
34
|
+
|
|
35
|
+
test("codemode adds its extension and +codemode", () => {
|
|
36
|
+
expect(piExtensionFactories(swarm, { toolDeferral: false, codemode: true })).toHaveLength(2);
|
|
37
|
+
expect(piDefaultToolAdditions({ toolDeferral: false, codemode: true })).toEqual(["+codemode"]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("all features together", () => {
|
|
41
|
+
const features = { toolDeferral: true, installedMcp: true, codemode: true };
|
|
42
|
+
expect(piExtensionFactories(swarm, features)).toHaveLength(4);
|
|
43
|
+
expect(piDefaultToolAdditions(features)).toEqual(["+tool_search", "+codemode"]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("PiMonoAdapter.createSession — codemode", () => {
|
|
48
|
+
const envKeys = [
|
|
49
|
+
"PI_CODEMODE",
|
|
50
|
+
"PI_TOOL_DEFERRAL",
|
|
51
|
+
"PI_CODING_AGENT_DIR",
|
|
52
|
+
"OPENROUTER_API_KEY",
|
|
53
|
+
"OPENROUTER_BASE_URL",
|
|
54
|
+
];
|
|
55
|
+
const saved: Record<string, string | undefined> = {};
|
|
56
|
+
let dir = "";
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
for (const key of envKeys) saved[key] = process.env[key];
|
|
60
|
+
dir = mkdtempSync(join(tmpdir(), "pi-codemode-"));
|
|
61
|
+
process.env.PI_CODING_AGENT_DIR = join(dir, "agent");
|
|
62
|
+
process.env.OPENROUTER_API_KEY = "example-test-key";
|
|
63
|
+
// The session prompts on creation; keep that request off the network.
|
|
64
|
+
process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
|
|
65
|
+
delete process.env.PI_CODEMODE;
|
|
66
|
+
delete process.env.PI_TOOL_DEFERRAL;
|
|
67
|
+
});
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
for (const key of envKeys) {
|
|
70
|
+
if (saved[key] === undefined) delete process.env[key];
|
|
71
|
+
else process.env[key] = saved[key];
|
|
72
|
+
}
|
|
73
|
+
rmSync(dir, { recursive: true, force: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
async function activeTools(): Promise<string[]> {
|
|
77
|
+
const server = Bun.serve({
|
|
78
|
+
port: 0,
|
|
79
|
+
async fetch(req) {
|
|
80
|
+
const url = new URL(req.url);
|
|
81
|
+
if (url.pathname !== "/mcp") return Response.json({ servers: [] });
|
|
82
|
+
const body = (await req.json()) as { id?: number; method?: string };
|
|
83
|
+
const result =
|
|
84
|
+
body.method === "tools/list"
|
|
85
|
+
? {
|
|
86
|
+
tools: [
|
|
87
|
+
{ name: "store-progress", inputSchema: { type: "object" } },
|
|
88
|
+
{ name: "create-page", inputSchema: { type: "object" } },
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
: { protocolVersion: "2025-03-26", capabilities: {} };
|
|
92
|
+
return Response.json({ jsonrpc: "2.0", id: body.id ?? 1, result });
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
let session: AgentSession | undefined;
|
|
96
|
+
try {
|
|
97
|
+
const provider = await new PiMonoAdapter().createSession({
|
|
98
|
+
prompt: "hello",
|
|
99
|
+
systemPrompt: "",
|
|
100
|
+
model: "openrouter/google/gemini-3-flash-preview",
|
|
101
|
+
role: "worker",
|
|
102
|
+
agentId: "test-agent",
|
|
103
|
+
taskId: "test-task",
|
|
104
|
+
apiUrl: `http://localhost:${server.port}`,
|
|
105
|
+
apiKey: "example-test-key",
|
|
106
|
+
cwd: dir,
|
|
107
|
+
logFile: join(dir, "session.log"),
|
|
108
|
+
});
|
|
109
|
+
session = (provider as unknown as { agentSession: AgentSession }).agentSession;
|
|
110
|
+
return session.getActiveToolNames();
|
|
111
|
+
} finally {
|
|
112
|
+
session?.dispose();
|
|
113
|
+
server.stop(true);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
test("flag on: codemode is added and swarm tools stay declared", async () => {
|
|
118
|
+
process.env.PI_CODEMODE = "true";
|
|
119
|
+
const active = await activeTools();
|
|
120
|
+
expect(active).toContain("codemode");
|
|
121
|
+
expect(active).toContain("store-progress");
|
|
122
|
+
expect(active).toContain("create-page");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("flag off: no codemode", async () => {
|
|
126
|
+
expect(await activeTools()).not.toContain("codemode");
|
|
127
|
+
process.env.PI_CODEMODE = "false";
|
|
128
|
+
expect(await activeTools()).not.toContain("codemode");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("bounded codemode", () => {
|
|
133
|
+
type Registered = ToolDefinition;
|
|
134
|
+
|
|
135
|
+
function registerCodemode(limits: CodemodeLimits): Registered {
|
|
136
|
+
let registered: Registered | undefined;
|
|
137
|
+
const pi = {
|
|
138
|
+
registerTool: (tool: Registered) => {
|
|
139
|
+
registered = tool;
|
|
140
|
+
},
|
|
141
|
+
getSettings: () => ({}),
|
|
142
|
+
getAllTools: () => [],
|
|
143
|
+
appendEntry: () => {},
|
|
144
|
+
} as unknown as ExtensionAPI;
|
|
145
|
+
createBoundedCodemodeExtension(limits)(pi);
|
|
146
|
+
if (!registered) throw new Error("codemode tool was not registered");
|
|
147
|
+
return registered;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function fakeCtx(onCall: () => Promise<void> = async () => {}) {
|
|
151
|
+
const calls: string[] = [];
|
|
152
|
+
let running = 0;
|
|
153
|
+
let peak = 0;
|
|
154
|
+
// Same shape as pi's createToolContext(): executeTool is defined
|
|
155
|
+
// non-writable and non-configurable, so a Proxy cannot swap it.
|
|
156
|
+
const ctx = Object.defineProperties(
|
|
157
|
+
{
|
|
158
|
+
tools: [{ name: "noop", description: "does nothing", parameters: { type: "object" } }],
|
|
159
|
+
sessionManager: { getBranch: () => [] },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
executeTool: {
|
|
163
|
+
value: async (name: string) => {
|
|
164
|
+
calls.push(name);
|
|
165
|
+
running++;
|
|
166
|
+
peak = Math.max(peak, running);
|
|
167
|
+
try {
|
|
168
|
+
await onCall();
|
|
169
|
+
} finally {
|
|
170
|
+
running--;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
toolCall: { id: `call-${calls.length}` },
|
|
174
|
+
result: { content: [{ type: "text", text: "ok" }] },
|
|
175
|
+
isError: false,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
) as unknown as ExtensionToolContext;
|
|
181
|
+
return { ctx, calls, peak: () => peak };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function textOf(result: { content: Array<{ type: string; text?: string }> }): string {
|
|
185
|
+
return result.content.map((item) => item.text ?? "").join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const limits = { timeoutMs: 30_000, maxNestedCalls: 32, maxConcurrentCalls: 4 };
|
|
189
|
+
|
|
190
|
+
test("a non-terminating script is interrupted at the deadline", async () => {
|
|
191
|
+
const tool = registerCodemode({ ...limits, timeoutMs: 300 });
|
|
192
|
+
const started = Date.now();
|
|
193
|
+
const result = await tool.execute(
|
|
194
|
+
"t1",
|
|
195
|
+
{ code: "while (true) {}" },
|
|
196
|
+
undefined,
|
|
197
|
+
undefined,
|
|
198
|
+
fakeCtx().ctx,
|
|
199
|
+
);
|
|
200
|
+
expect(Date.now() - started).toBeLessThan(10_000);
|
|
201
|
+
expect(textOf(result)).toContain("300 ms deadline");
|
|
202
|
+
}, 20_000);
|
|
203
|
+
|
|
204
|
+
test("the deadline also caps a script that asks for a longer timeout_ms", async () => {
|
|
205
|
+
const tool = registerCodemode({ ...limits, timeoutMs: 300 });
|
|
206
|
+
const code = '// @options: {"timeout_ms": 600000}\nwhile (true) {}';
|
|
207
|
+
const result = await tool.execute("t2", { code }, undefined, undefined, fakeCtx().ctx);
|
|
208
|
+
expect(textOf(result)).toContain("300 ms deadline");
|
|
209
|
+
}, 20_000);
|
|
210
|
+
|
|
211
|
+
test("nested calls past the budget fail the script", async () => {
|
|
212
|
+
const tool = registerCodemode({ ...limits, maxNestedCalls: 3 });
|
|
213
|
+
const fake = fakeCtx();
|
|
214
|
+
const code =
|
|
215
|
+
"for (let i = 0; i < 10; i++) { try { await tools.noop({}); } catch {} }\nreturn 'done';";
|
|
216
|
+
const result = await tool.execute("t3", { code }, undefined, undefined, fake.ctx);
|
|
217
|
+
expect(fake.calls).toHaveLength(3);
|
|
218
|
+
expect(textOf(result)).toStartWith("Script failed");
|
|
219
|
+
expect(textOf(result)).toContain("budget of 3 nested tool calls");
|
|
220
|
+
}, 20_000);
|
|
221
|
+
|
|
222
|
+
test("nested calls run at most maxConcurrentCalls at once", async () => {
|
|
223
|
+
const tool = registerCodemode({ ...limits, maxConcurrentCalls: 2 });
|
|
224
|
+
const fake = fakeCtx(() => new Promise((resolve) => setTimeout(resolve, 20)));
|
|
225
|
+
const code =
|
|
226
|
+
"await Promise.all(Array.from({ length: 6 }, () => tools.noop({})));\nreturn 'done';";
|
|
227
|
+
const result = await tool.execute("t4", { code }, undefined, undefined, fake.ctx);
|
|
228
|
+
expect(textOf(result)).toStartWith("Script completed");
|
|
229
|
+
expect(fake.calls).toHaveLength(6);
|
|
230
|
+
expect(fake.peak()).toBe(2);
|
|
231
|
+
}, 20_000);
|
|
232
|
+
|
|
233
|
+
test("cancelling the task aborts a running script", async () => {
|
|
234
|
+
const tool = registerCodemode(limits);
|
|
235
|
+
const controller = new AbortController();
|
|
236
|
+
setTimeout(() => controller.abort(new Error("task cancelled")), 200);
|
|
237
|
+
const result = await tool.execute(
|
|
238
|
+
"t5",
|
|
239
|
+
{ code: "while (true) {}" },
|
|
240
|
+
controller.signal,
|
|
241
|
+
undefined,
|
|
242
|
+
fakeCtx().ctx,
|
|
243
|
+
);
|
|
244
|
+
expect(textOf(result)).toContain("task cancelled");
|
|
245
|
+
}, 20_000);
|
|
246
|
+
});
|
|
@@ -46,6 +46,7 @@ describe("PiMonoAdapter.createSession — reasoning_effort", () => {
|
|
|
46
46
|
model: undefined,
|
|
47
47
|
subscribe: () => () => {},
|
|
48
48
|
dispose: () => {},
|
|
49
|
+
bindExtensions: async () => {},
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -136,6 +137,7 @@ describe("PiMonoAdapter.createSession — OPENROUTER_BASE_URL gateway", () => {
|
|
|
136
137
|
model: undefined,
|
|
137
138
|
subscribe: () => () => {},
|
|
138
139
|
dispose: () => {},
|
|
140
|
+
bindExtensions: async () => {},
|
|
139
141
|
},
|
|
140
142
|
};
|
|
141
143
|
}) as typeof piCodingAgent.createAgentSession);
|
|
@@ -191,6 +193,7 @@ describe("PiMonoAdapter.createSession — MCP runtime identity header", () => {
|
|
|
191
193
|
model: undefined,
|
|
192
194
|
subscribe: () => () => {},
|
|
193
195
|
dispose: () => {},
|
|
196
|
+
bindExtensions: async () => {},
|
|
194
197
|
},
|
|
195
198
|
};
|
|
196
199
|
}) as typeof piCodingAgent.createAgentSession);
|