@desplega.ai/agent-swarm 1.172.0 → 2.0.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-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
- package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
- package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
- package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
- package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
- package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
- package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
- package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
- package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
- package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
- package/dist/{claude-sdk-session-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
- package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-kb4ehzx5.js → cli-1e4b6bzh.js} +7 -7
- package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-zxm9p3x4.js → cli-3fbmmawm.js} +5 -5
- package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
- package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
- package/dist/{cli-6jb2rf77.js → cli-5t4f29ez.js} +3 -3
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-bf7485pm.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
- package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
- package/dist/{cli-1hgat6pa.js → cli-d5f9ed5c.js} +5 -5
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
- package/dist/{cli-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
- package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
- package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
- package/dist/{cli-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
- package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
- package/dist/cli-k3dp5tpf.js +263 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
- package/dist/cli-p0sg24zp.js +16 -0
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-r8smj8b8.js → cli-r62a35ea.js} +19 -3
- package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-xczpdp9e.js → cli-se8w6wny.js} +6 -6
- package/dist/cli-skxvvpap.js +10140 -0
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
- package/dist/{cli-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
- package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
- package/dist/{cli-04emfe2q.js → cli-vdach5a9.js} +5 -5
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/{cli-hwwvy36w.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +32 -48
- package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
- package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
- package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-2h8m4cab.js → dsh-adapter-3epjm3af.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
- package/dist/external-channel-h3359cwn.js +52 -0
- package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
- package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
- package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
- package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
- package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
- package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
- package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
- package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
- package/dist/index-9gw58n4k.js +193 -0
- package/dist/index-9j87mcm8.js +56305 -0
- package/dist/index-bsmcresk.js +79424 -0
- package/dist/index-mh85qhec.js +475 -0
- package/dist/{index-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
- package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
- package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
- package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
- package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
- package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
- package/dist/node-c9we95xf.js +1 -1
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
- package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
- package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
- package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
- package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
- package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
- package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
- package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
- package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
- package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
- package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +951 -356
- package/package.json +12 -13
- package/src/apps/catalog.generated.json +1309 -53
- package/src/apps/page-validator.ts +93 -31
- package/src/azure-devops/api.ts +94 -18
- package/src/azure-devops/auth.ts +32 -0
- package/src/azure-devops/boards-sync.ts +708 -0
- package/src/azure-devops/handlers.ts +52 -8
- package/src/azure-devops/html.ts +26 -0
- package/src/azure-devops/index.ts +15 -0
- package/src/azure-devops/templates.ts +86 -0
- package/src/azure-devops/types.ts +65 -1
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/page-versions.ts +15 -0
- package/src/be/db/runtime.ts +35 -8
- package/src/be/db/skill-name-resolution.ts +36 -0
- package/src/be/db/tasks/read.ts +230 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +74 -13
- package/src/be/migrations/213_script_connections_sql.sql +79 -0
- package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
- package/src/be/migrations/216_task_resolved_effort.sql +5 -0
- package/src/be/migrations/217_chat_sdk_state.sql +104 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +12 -1
- package/src/be/model-validation.ts +5 -2
- package/src/be/models-catalog.ts +26 -1
- package/src/be/modelsdev-cache.json +198 -63
- package/src/be/modelsdev-cache.ts +9 -1
- package/src/be/script-connections.ts +47 -8
- package/src/be/script-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -8
- package/src/be/seed/runner.ts +13 -1
- package/src/be/seed/state-db.ts +5 -0
- package/src/be/seed/types.ts +6 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-skills/bundled-files.generated.json +10 -18
- package/src/be/seed-skills/index.ts +107 -3
- package/src/be/sql-connections.ts +118 -0
- package/src/be/swarm-config-guard.ts +53 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/profile-sync.ts +5 -3
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +196 -56
- package/src/e2b/env.ts +5 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +45 -0
- package/src/extensions/dispatcher.ts +7 -0
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/all-routes.ts +0 -1
- package/src/http/config.ts +36 -17
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +14 -2
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp-oauth.ts +6 -13
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +6 -5
- package/src/http/pages.ts +5 -14
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/route-def.ts +7 -6
- package/src/http/script-connection-proxy.ts +51 -4
- package/src/http/script-connections.ts +5 -2
- package/src/http/scripts.ts +1 -0
- package/src/http/session-cost-recompute.ts +12 -2
- package/src/http/session-data.ts +4 -0
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +61 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +183 -2
- package/src/http/x.ts +1 -0
- package/src/integrations/chat-sdk/adapters.ts +129 -0
- package/src/integrations/chat-sdk/attachments.ts +232 -0
- package/src/integrations/chat-sdk/background.ts +36 -0
- package/src/integrations/chat-sdk/commands.ts +194 -0
- package/src/integrations/chat-sdk/config.ts +198 -0
- package/src/integrations/chat-sdk/discord-api.ts +63 -0
- package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
- package/src/integrations/chat-sdk/handlers.ts +28 -0
- package/src/integrations/chat-sdk/inbound.ts +426 -0
- package/src/integrations/chat-sdk/limits.ts +48 -0
- package/src/integrations/chat-sdk/logger.ts +81 -0
- package/src/integrations/chat-sdk/outbound.ts +123 -0
- package/src/integrations/chat-sdk/relay.ts +172 -0
- package/src/integrations/chat-sdk/router.ts +169 -0
- package/src/integrations/chat-sdk/runtime.ts +351 -0
- package/src/integrations/chat-sdk/schema.ts +245 -0
- package/src/integrations/chat-sdk/state.ts +243 -0
- package/src/integrations/chat-sdk/status.ts +244 -0
- package/src/integrations/chat-sdk/steering.ts +58 -0
- package/src/integrations/chat-sdk/teams-origin.ts +77 -0
- package/src/integrations/chat-sdk/telegram-api.ts +56 -0
- package/src/integrations/chat-sdk/templates.ts +16 -0
- package/src/integrations/chat-sdk/test-hooks.ts +37 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
- package/src/integrations/chat-sdk/typing.ts +65 -0
- package/src/jira/adf.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/oauth/mcp-wrapper.ts +10 -6
- package/src/otel-impl.ts +2 -1
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +56 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +27 -2
- package/src/providers/claude-flag-settings.ts +133 -0
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-sdk-session.ts +7 -0
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +268 -37
- package/src/providers/opencode-billing.ts +86 -0
- package/src/providers/opencode-swarm-plugin.ts +51 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +37 -3
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +11 -2
- package/src/rbac/legacy-policy.ts +8 -2
- package/src/rbac/permissions.ts +21 -0
- package/src/scheduler/scheduler.ts +4 -2
- package/src/scripts-runtime/api-client.ts +15 -1
- package/src/scripts-runtime/api-types.ts +9 -1
- package/src/scripts-runtime/ctx.ts +1 -1
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/mcp-client.ts +30 -19
- package/src/scripts-runtime/sdk-allowlist.ts +8 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +27 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
- package/src/server.ts +52 -18
- package/src/slack/actions.ts +29 -1
- package/src/slack/app.ts +14 -2
- package/src/slack/assistant.ts +36 -2
- package/src/slack/channel-join.ts +3 -16
- package/src/slack/commands.ts +9 -1
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/external-channel-egress.ts +113 -0
- package/src/slack/external-channel.ts +309 -0
- package/src/slack/handlers.ts +61 -0
- package/src/slack/render-v2.ts +40 -47
- package/src/slack/work-objects.ts +5 -0
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +94 -2
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/telemetry.ts +2 -1
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-catalog-feedback.test.ts +310 -0
- package/src/tests/apps-catalog-inputs.test.ts +70 -0
- package/src/tests/apps-catalog-table.test.ts +113 -0
- package/src/tests/azure-devops-boards-sync.test.ts +584 -0
- package/src/tests/azure-devops-html.test.ts +13 -0
- package/src/tests/azure-devops-webhook-malformed.test.ts +4 -0
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +144 -0
- package/src/tests/claude-flag-settings.test.ts +114 -0
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
- package/src/tests/claude-sdk-process.fixture.ts +7 -0
- package/src/tests/claude-thinking-tokens.test.ts +57 -0
- package/src/tests/claude-worker-parity.test.ts +2 -7
- package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
- package/src/tests/config-refs.test.ts +66 -0
- package/src/tests/context-key.test.ts +51 -0
- package/src/tests/cors-allowlist.test.ts +25 -0
- package/src/tests/create-page-tool.test.ts +18 -0
- package/src/tests/credential-check.test.ts +139 -0
- package/src/tests/credentials.test.ts +21 -0
- package/src/tests/db-runtime.test.ts +61 -0
- package/src/tests/db-task-read-contract.test.ts +20 -0
- package/src/tests/db-task-write-contract.test.ts +10 -0
- package/src/tests/deferred-task-wake.test.ts +52 -4
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/enabled-capabilities.test.ts +20 -0
- package/src/tests/entrypoint-api-readiness.test.ts +14 -8
- package/src/tests/entrypoint-config-env-export.test.ts +5 -2
- package/src/tests/entrypoint-export-to-env.test.ts +78 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-post-bridge.test.ts +51 -0
- package/src/tests/fetch-resolved-env.test.ts +128 -0
- package/src/tests/fixtures/azure-boards/workitem.commented.json +89 -0
- package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
- package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
- package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
- package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
- package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
- package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
- package/src/tests/fixtures/catalog-table-definition.json.txt +468 -0
- package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
- package/src/tests/fixtures/runner-identity-refresh.ts +3 -0
- package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
- package/src/tests/github-handlers.test.ts +35 -0
- package/src/tests/harness-provider-resolution.test.ts +12 -0
- package/src/tests/heartbeat-checklist.test.ts +3 -2
- package/src/tests/hook-runtime-close.test.ts +0 -2
- package/src/tests/http-api-integration.test.ts +0 -25
- package/src/tests/migration-046-budgets.test.ts +17 -16
- package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- package/src/tests/node-env-runtime.test.ts +98 -0
- package/src/tests/openai-compatible-providers.test.ts +134 -0
- package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
- package/src/tests/opencode-adapter.test.ts +348 -3
- package/src/tests/opencode-billing.test.ts +28 -0
- package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
- package/src/tests/opencode-shell-env.test.ts +145 -0
- package/src/tests/openrouter-base-url.test.ts +101 -151
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/pages-browser-sdk.test.ts +76 -0
- package/src/tests/pages-feedback.test.ts +1 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
- package/src/tests/pi-mono-adapter.test.ts +385 -5
- package/src/tests/pi-shell-env.test.ts +170 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/rbac-engine.test.ts +11 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-identity-refresh.test.ts +3 -0
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections-sql.test.ts +675 -0
- package/src/tests/script-connections.test.ts +7 -1
- package/src/tests/scripts-http.test.ts +201 -1
- package/src/tests/scripts-only-gating.test.ts +2 -3
- package/src/tests/scripts-runtime.test.ts +58 -40
- package/src/tests/secret-scrubber.test.ts +86 -0
- package/src/tests/seed-skills-bundled-files.test.ts +45 -53
- package/src/tests/seed-skills-retire.test.ts +182 -0
- package/src/tests/send-task-requester-gate.test.ts +58 -0
- package/src/tests/session-cost-recompute-unit.test.ts +94 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/slack-channel-join.test.ts +4 -1
- package/src/tests/slack-external-channel-config.test.ts +65 -0
- package/src/tests/slack-external-channel-egress.test.ts +237 -0
- package/src/tests/slack-external-channel-guard.test.ts +280 -0
- package/src/tests/slack-external-channel-tools.test.ts +173 -0
- package/src/tests/slack-external-channel.test.ts +370 -0
- package/src/tests/slack-render-v2.test.ts +86 -10
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/spec-base-url.test.ts +16 -1
- package/src/tests/swarm-config-export-to-env.test.ts +267 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
- package/src/tests/swarm-diff.test.ts +1 -1
- package/src/tests/task-model-effort-rerun.test.ts +164 -0
- package/src/tests/task-session-prompts.test.ts +132 -0
- package/src/tests/task-tool-manifest.test.ts +14 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-annotations.test.ts +6 -6
- package/src/tests/ui-logs-parser.test.ts +43 -0
- package/src/tests/used-by-route.test.ts +6 -0
- package/src/tests/vendored-openapi.test.ts +16 -1
- package/src/tests/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-swarm-script.test.ts +38 -0
- package/src/tests/workflow-wait-filter.test.ts +5 -3
- package/src/tools/chat-access.ts +41 -0
- package/src/tools/chat-open-dm.ts +59 -0
- package/src/tools/chat-post.ts +74 -0
- package/src/tools/chat-read-thread.ts +100 -0
- package/src/tools/chat-reply.ts +75 -0
- package/src/tools/create-page.ts +3 -13
- package/src/tools/defer-task.ts +17 -0
- package/src/tools/model-catalog/index.ts +2 -1
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/send-task.ts +24 -0
- package/src/tools/skills/skill-get.ts +17 -9
- package/src/tools/slack-post.ts +6 -0
- package/src/tools/slack-read.ts +8 -0
- package/src/tools/slack-reply.ts +6 -0
- package/src/tools/slack-start-thread.ts +6 -0
- package/src/tools/slack-update.ts +6 -0
- package/src/tools/slack-upload-file.ts +6 -0
- package/src/tools/swarm-config/get-config.ts +2 -1
- package/src/tools/swarm-config/list-config.ts +2 -1
- package/src/tools/swarm-config/set-config.ts +16 -3
- package/src/tools/tool-config.ts +8 -6
- package/src/types.ts +12 -2
- package/src/utils/config-env-export.ts +74 -0
- package/src/utils/credentials.test.ts +92 -0
- package/src/utils/credentials.ts +52 -4
- package/src/utils/harness-provider.ts +15 -6
- package/src/utils/node-env.ts +24 -0
- package/src/utils/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +71 -2
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/templates/skills/agent-browser/SKILL.md +1 -1
- package/templates/skills/agent-browser/content.md +1 -1
- package/templates/skills/apps/SKILL.md +749 -0
- package/templates/skills/apps/content.md +60 -22
- package/templates/skills/chat-sdk/config.json +14 -0
- package/templates/skills/chat-sdk/content.md +84 -0
- package/templates/skills/composio-gmail/content.md +7 -0
- package/templates/skills/composio-google-docs/content.md +1 -1
- package/templates/skills/email-domain-health/config.json +14 -0
- package/templates/skills/email-domain-health/content.md +131 -0
- package/templates/skills/kv-storage/content.md +2 -2
- package/templates/skills/pages/content.md +8 -10
- package/templates/skills/qa-loom/config.json +14 -0
- package/templates/skills/qa-loom/content.md +187 -0
- package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
- package/templates/skills/qa-loom/files/run-qa.sh +57 -0
- package/templates/skills/slack-interaction/content.md +4 -0
- package/templates/skills/swarm-extensions/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +4 -2
- package/templates/skills/swarm-scripts/config.json +1 -1
- package/templates/skills/swarm-scripts/content.md +4 -2
- package/templates/skills/workflow-iterate/content.md +38 -0
- package/dist/artifact-eczsknc3.js +0 -3843
- package/dist/cli-9xsmdpdt.js +0 -56377
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/index-9ch1n761.js +0 -1102
- package/src/artifact-sdk/index.ts +0 -2
- package/src/artifact-sdk/localtunnel.d.ts +0 -20
- package/src/artifact-sdk/port.ts +0 -12
- package/src/artifact-sdk/server.ts +0 -183
- package/src/artifact-sdk/tunnel.ts +0 -19
- package/src/commands/artifact.ts +0 -249
- package/src/http/ecosystem.ts +0 -77
- package/src/tests/artifact-commands.test.ts +0 -92
- package/src/tests/artifact-sdk.test.ts +0 -793
- package/src/tests/register-service-security.test.ts +0 -194
- package/src/tools/list-services.ts +0 -107
- package/src/tools/register-service.ts +0 -177
- package/src/tools/unregister-service.ts +0 -73
- package/src/tools/update-service-status.ts +0 -96
- package/templates/skills/artifacts/config.json +0 -14
- package/templates/skills/artifacts/content.md +0 -344
- package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
- package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
- package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
- package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
- package/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
- package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
- package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
- package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
- package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
- package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
symlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
3
12
|
import { join } from "node:path";
|
|
13
|
+
import { OPENAI_COMPATIBLE_PROVIDERS } from "@desplega/model-catalog";
|
|
4
14
|
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
5
15
|
import { configureHttpResolver, resetHttpResolver } from "../prompts/resolver";
|
|
6
16
|
import {
|
|
17
|
+
checkPiMonoCredentials,
|
|
7
18
|
createPiRuntimeAuth,
|
|
8
19
|
extractPiAssistantText,
|
|
9
20
|
PiMonoAdapter,
|
|
10
21
|
resolveModel,
|
|
22
|
+
type SessionModelsJson,
|
|
23
|
+
writeSessionModelsJson,
|
|
11
24
|
} from "../providers/pi-mono-adapter";
|
|
12
25
|
import type { ProviderSessionConfig } from "../providers/types";
|
|
13
26
|
|
|
@@ -151,8 +164,8 @@ describe("PiMonoAdapter.createSession — OPENROUTER_BASE_URL gateway", () => {
|
|
|
151
164
|
const model = capturedOptions?.model as { baseUrl?: string; id?: string } | undefined;
|
|
152
165
|
expect(model?.id).toBe("google/gemini-3-flash-preview");
|
|
153
166
|
expect(model?.baseUrl).toBe(GATEWAY);
|
|
154
|
-
// The override
|
|
155
|
-
expect(existsSync(join(agentDir, "models.json"))).toBe(
|
|
167
|
+
// The override lives in the session's own copy, never the shared file.
|
|
168
|
+
expect(existsSync(join(agentDir, "models.json"))).toBe(false);
|
|
156
169
|
});
|
|
157
170
|
|
|
158
171
|
test("session model keeps the default openrouter.ai baseUrl when env is unset", async () => {
|
|
@@ -310,7 +323,7 @@ describe("PiMonoAdapter.createSession — MCP setup", () => {
|
|
|
310
323
|
const adapter = new PiMonoAdapter();
|
|
311
324
|
await adapter.createSession(makeMcpConfig(`http://localhost:${server.port}`));
|
|
312
325
|
const options = createAgentSessionSpy.mock.calls[0]?.[0];
|
|
313
|
-
expect(options?.customTools?.map((tool) => tool.name)).toEqual(["get-tasks"]);
|
|
326
|
+
expect(options?.customTools?.map((tool) => tool.name)).toEqual(["get-tasks", "bash"]);
|
|
314
327
|
expect(initializeSessions).toEqual([null, null]);
|
|
315
328
|
expect(new Set(seen)).toEqual(new Set(["pi-retry-runtime"]));
|
|
316
329
|
} finally {
|
|
@@ -330,7 +343,9 @@ describe("PiMonoAdapter.createSession — MCP setup", () => {
|
|
|
330
343
|
await adapter.createSession(makeMcpConfig(`http://localhost:${server.port}`));
|
|
331
344
|
expect(initializeSessions).toEqual([null, null, null]);
|
|
332
345
|
expect(failures).toEqual(["initialize"]);
|
|
333
|
-
expect(
|
|
346
|
+
expect(
|
|
347
|
+
createAgentSessionSpy.mock.calls[0]?.[0]?.customTools?.map((tool) => tool.name),
|
|
348
|
+
).toEqual(["bash"]);
|
|
334
349
|
} finally {
|
|
335
350
|
server.stop(true);
|
|
336
351
|
}
|
|
@@ -542,6 +557,371 @@ describe("createPiRuntimeAuth", () => {
|
|
|
542
557
|
});
|
|
543
558
|
});
|
|
544
559
|
|
|
560
|
+
// ─── NaN / Helmcode custom providers (models.json, runtime keys, resolve) ───
|
|
561
|
+
|
|
562
|
+
const NAN_ENV = { NAN_API_KEY: "example-nan-picked" };
|
|
563
|
+
const HELMCODE_ENV = { HELMCODE_API_KEY: "example-helmcode-picked" };
|
|
564
|
+
async function readModelsJson(path: string | undefined): Promise<Record<string, any>> {
|
|
565
|
+
if (!path) throw new Error("expected a per-session models.json");
|
|
566
|
+
return JSON.parse(await Bun.file(path).text());
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function withTempDir(prefix: string, fn: (dir: string) => Promise<void>): () => Promise<void> {
|
|
570
|
+
return async () => {
|
|
571
|
+
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
572
|
+
try {
|
|
573
|
+
await fn(dir);
|
|
574
|
+
} finally {
|
|
575
|
+
rmSync(dir, { recursive: true, force: true });
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Compose a session's models.json and always remove it afterwards. */
|
|
581
|
+
async function withSessionModels(
|
|
582
|
+
dir: string,
|
|
583
|
+
env: Record<string, string | undefined>,
|
|
584
|
+
model: string | undefined,
|
|
585
|
+
fn: (session: SessionModelsJson) => Promise<void>,
|
|
586
|
+
): Promise<void> {
|
|
587
|
+
const session = await writeSessionModelsJson(dir, env, model);
|
|
588
|
+
try {
|
|
589
|
+
await fn(session);
|
|
590
|
+
} finally {
|
|
591
|
+
await session.dispose();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
describe("writeSessionModelsJson (pi)", () => {
|
|
596
|
+
test(
|
|
597
|
+
"writes both blocks with $VAR apiKeys to a private file, never the key value or the shared file",
|
|
598
|
+
withTempDir("pi-custom-fresh-", async (dir) => {
|
|
599
|
+
await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
|
|
600
|
+
expect(s.modelsPath).toBeDefined();
|
|
601
|
+
expect(s.modelsPath?.startsWith(dir)).toBe(false);
|
|
602
|
+
expect(existsSync(join(dir, "models.json"))).toBe(false);
|
|
603
|
+
const raw = await Bun.file(s.modelsPath as string).text();
|
|
604
|
+
expect(raw).not.toContain("example-nan-picked");
|
|
605
|
+
expect(raw).not.toContain("example-helmcode-picked");
|
|
606
|
+
const written = JSON.parse(raw);
|
|
607
|
+
expect(written.providers.nan).toMatchObject({
|
|
608
|
+
baseUrl: OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl,
|
|
609
|
+
api: "openai-completions",
|
|
610
|
+
apiKey: "$NAN_API_KEY",
|
|
611
|
+
});
|
|
612
|
+
expect(written.providers.helmcode).toMatchObject({
|
|
613
|
+
baseUrl: OPENAI_COMPATIBLE_PROVIDERS.helmcode.baseUrl,
|
|
614
|
+
api: "openai-completions",
|
|
615
|
+
apiKey: "$HELMCODE_API_KEY",
|
|
616
|
+
});
|
|
617
|
+
expect(written.providers.helmcode.models.map((m: { id: string }) => m.id)).toEqual(
|
|
618
|
+
OPENAI_COMPATIBLE_PROVIDERS.helmcode.models.map((m) => m.id),
|
|
619
|
+
);
|
|
620
|
+
const glm = written.providers.nan.models.find((m: { id: string }) => m.id === "glm5.3");
|
|
621
|
+
expect(glm).toMatchObject({
|
|
622
|
+
contextWindow: 1000000,
|
|
623
|
+
maxTokens: 131072,
|
|
624
|
+
reasoning: true,
|
|
625
|
+
compat: { supportsDeveloperRole: true },
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}),
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
test(
|
|
632
|
+
"dispose removes the private file",
|
|
633
|
+
withTempDir("pi-custom-dispose-", async (dir) => {
|
|
634
|
+
const s = await writeSessionModelsJson(dir, NAN_ENV);
|
|
635
|
+
expect(existsSync(s.modelsPath as string)).toBe(true);
|
|
636
|
+
await s.dispose();
|
|
637
|
+
await s.dispose();
|
|
638
|
+
expect(existsSync(s.modelsPath as string)).toBe(false);
|
|
639
|
+
}),
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
test(
|
|
643
|
+
"only the HELMCODE_API_NAN alias set: nan block still uses $NAN_API_KEY",
|
|
644
|
+
withTempDir("pi-custom-alias-", async (dir) => {
|
|
645
|
+
await withSessionModels(dir, { HELMCODE_API_NAN: "example-alias" }, undefined, async (s) => {
|
|
646
|
+
const written = await readModelsJson(s.modelsPath);
|
|
647
|
+
expect(written.providers.nan.apiKey).toBe("$NAN_API_KEY");
|
|
648
|
+
expect(written.providers.helmcode).toBeUndefined();
|
|
649
|
+
expect(JSON.stringify(written)).not.toContain("example-alias");
|
|
650
|
+
});
|
|
651
|
+
}),
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
test(
|
|
655
|
+
"no key, default gateway, no marker: the session loads the shared file",
|
|
656
|
+
withTempDir("pi-custom-noop-", async (dir) => {
|
|
657
|
+
const userConfig = { providers: { nan: { baseUrl: "https://mine.example/v1" } } };
|
|
658
|
+
await Bun.write(join(dir, "models.json"), JSON.stringify(userConfig));
|
|
659
|
+
const s = await writeSessionModelsJson(dir, {});
|
|
660
|
+
expect(s.modelsPath).toBeUndefined();
|
|
661
|
+
expect(JSON.parse(readFileSync(join(dir, "models.json"), "utf-8"))).toEqual(userConfig);
|
|
662
|
+
}),
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
test(
|
|
666
|
+
"a user-authored providers.nan in the shared file wins; helmcode is still added",
|
|
667
|
+
withTempDir("pi-custom-user-", async (dir) => {
|
|
668
|
+
const userNan = { baseUrl: "https://mine.example/v1", apiKey: "$MY_VAR", models: [] };
|
|
669
|
+
const shared = JSON.stringify({ providers: { nan: userNan } });
|
|
670
|
+
await Bun.write(join(dir, "models.json"), shared);
|
|
671
|
+
await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
|
|
672
|
+
const written = await readModelsJson(s.modelsPath);
|
|
673
|
+
expect(written.providers.nan).toEqual(userNan);
|
|
674
|
+
expect(written.providers.helmcode.apiKey).toBe("$HELMCODE_API_KEY");
|
|
675
|
+
});
|
|
676
|
+
expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe(shared);
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
test(
|
|
681
|
+
"keeps other providers and top-level keys; the gateway comes from this session's env",
|
|
682
|
+
withTempDir("pi-custom-merge-", async (dir) => {
|
|
683
|
+
const gateway = "https://control-plane.example/proxy/v1";
|
|
684
|
+
await Bun.write(
|
|
685
|
+
join(dir, "models.json"),
|
|
686
|
+
JSON.stringify({
|
|
687
|
+
$schema: "https://pi.dev/schemas/models.schema.json",
|
|
688
|
+
providers: {
|
|
689
|
+
custom: { baseUrl: "https://custom.example/v1", api: "openai-completions" },
|
|
690
|
+
openrouter: { baseUrl: gateway },
|
|
691
|
+
},
|
|
692
|
+
}),
|
|
693
|
+
);
|
|
694
|
+
// An earlier version wrote that gateway into the shared file.
|
|
695
|
+
await Bun.write(
|
|
696
|
+
join(dir, ".agent-swarm-openrouter-override.json"),
|
|
697
|
+
JSON.stringify({ baseUrl: gateway }),
|
|
698
|
+
);
|
|
699
|
+
const sharedBefore = readFileSync(join(dir, "models.json"), "utf-8");
|
|
700
|
+
await withSessionModels(dir, NAN_ENV, undefined, async (s) => {
|
|
701
|
+
// This session uses the default gateway: the leftover override
|
|
702
|
+
// recorded in the legacy marker is reverted in its copy.
|
|
703
|
+
const written = await readModelsJson(s.modelsPath);
|
|
704
|
+
expect(written.$schema).toBe("https://pi.dev/schemas/models.schema.json");
|
|
705
|
+
expect(written.providers.openrouter).toBeUndefined();
|
|
706
|
+
expect(written.providers.custom.baseUrl).toBe("https://custom.example/v1");
|
|
707
|
+
expect(written.providers.nan.apiKey).toBe("$NAN_API_KEY");
|
|
708
|
+
});
|
|
709
|
+
const other = "https://other-gateway.example/v1";
|
|
710
|
+
await withSessionModels(dir, { OPENROUTER_BASE_URL: other }, undefined, async (s) => {
|
|
711
|
+
expect((await readModelsJson(s.modelsPath)).providers.openrouter).toEqual({
|
|
712
|
+
baseUrl: other,
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
// The shared file is never written by the composer.
|
|
716
|
+
expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe(sharedBefore);
|
|
717
|
+
}),
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
test(
|
|
721
|
+
"sibling sessions get their own endpoints, providers and model ids",
|
|
722
|
+
withTempDir("pi-custom-siblings-", async (dir) => {
|
|
723
|
+
const a = await writeSessionModelsJson(
|
|
724
|
+
dir,
|
|
725
|
+
{ HELMCODE_API_KEY: "a", HELMCODE_BASE_URL: "http://127.0.0.1:1/v1", ...NAN_ENV },
|
|
726
|
+
"nan/glm5.2",
|
|
727
|
+
);
|
|
728
|
+
const b = await writeSessionModelsJson(
|
|
729
|
+
dir,
|
|
730
|
+
{ HELMCODE_API_KEY: "b", HELMCODE_BASE_URL: "http://127.0.0.1:2/v1" },
|
|
731
|
+
"helmcode/glm5.3",
|
|
732
|
+
);
|
|
733
|
+
try {
|
|
734
|
+
expect(a.modelsPath).not.toBe(b.modelsPath);
|
|
735
|
+
const first = await readModelsJson(a.modelsPath);
|
|
736
|
+
const second = await readModelsJson(b.modelsPath);
|
|
737
|
+
expect(first.providers.helmcode.baseUrl).toBe("http://127.0.0.1:1/v1");
|
|
738
|
+
expect(second.providers.helmcode.baseUrl).toBe("http://127.0.0.1:2/v1");
|
|
739
|
+
// B has no NaN key: no nan block, even while A's session holds one.
|
|
740
|
+
expect(first.providers.nan.models.map((m: { id: string }) => m.id)).toContain("glm5.2");
|
|
741
|
+
expect(second.providers.nan).toBeUndefined();
|
|
742
|
+
} finally {
|
|
743
|
+
await a.dispose();
|
|
744
|
+
await b.dispose();
|
|
745
|
+
}
|
|
746
|
+
}),
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
test(
|
|
750
|
+
"corrupt shared models.json: the copy holds our blocks only, the shared file is untouched",
|
|
751
|
+
withTempDir("pi-custom-corrupt-", async (dir) => {
|
|
752
|
+
await Bun.write(join(dir, "models.json"), "{not json");
|
|
753
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
754
|
+
try {
|
|
755
|
+
await withSessionModels(dir, NAN_ENV, undefined, async (s) => {
|
|
756
|
+
expect((await readModelsJson(s.modelsPath)).providers.nan.apiKey).toBe("$NAN_API_KEY");
|
|
757
|
+
});
|
|
758
|
+
} finally {
|
|
759
|
+
warn.mockRestore();
|
|
760
|
+
}
|
|
761
|
+
expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe("{not json");
|
|
762
|
+
}),
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
test(
|
|
766
|
+
"ModelRuntime resolves the written blocks as nan/<id> and helmcode/<id>",
|
|
767
|
+
withTempDir("pi-custom-runtime-", async (dir) => {
|
|
768
|
+
await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
|
|
769
|
+
const runtime = await piCodingAgent.ModelRuntime.create({
|
|
770
|
+
authPath: join(dir, "auth.json"),
|
|
771
|
+
modelsPath: s.modelsPath,
|
|
772
|
+
});
|
|
773
|
+
expect(runtime.getError()).toBeUndefined();
|
|
774
|
+
expect(runtime.getModel("nan", "qwen3.8-flash")?.baseUrl).toBe(
|
|
775
|
+
OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl,
|
|
776
|
+
);
|
|
777
|
+
expect(runtime.getModel("helmcode", "glm5.3")?.contextWindow).toBe(1048576);
|
|
778
|
+
});
|
|
779
|
+
}),
|
|
780
|
+
);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
describe("createPiRuntimeAuth — NaN and Helmcode", () => {
|
|
784
|
+
test(
|
|
785
|
+
"HELMCODE_API_NAN alone sets the nan runtime key; NAN_API_KEY wins when both are set",
|
|
786
|
+
withTempDir("pi-custom-auth-", async (dir) => {
|
|
787
|
+
const models = await writeSessionModelsJson(dir, { NAN_API_KEY: "x", HELMCODE_API_KEY: "y" });
|
|
788
|
+
// pi's own agent-dir env var points auth.json at the temp dir.
|
|
789
|
+
const origAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
790
|
+
process.env.PI_CODING_AGENT_DIR = dir;
|
|
791
|
+
try {
|
|
792
|
+
const path = models.modelsPath;
|
|
793
|
+
const aliasOnly = await createPiRuntimeAuth({ HELMCODE_API_NAN: "a" }, path);
|
|
794
|
+
await expect(aliasOnly.getAuth("nan")).resolves.toMatchObject({ auth: { apiKey: "a" } });
|
|
795
|
+
|
|
796
|
+
const both = await createPiRuntimeAuth({ HELMCODE_API_NAN: "a", NAN_API_KEY: "b" }, path);
|
|
797
|
+
await expect(both.getAuth("nan")).resolves.toMatchObject({ auth: { apiKey: "b" } });
|
|
798
|
+
|
|
799
|
+
const helm = await createPiRuntimeAuth({ HELMCODE_API_KEY: "h" }, path);
|
|
800
|
+
await expect(helm.getAuth("helmcode")).resolves.toMatchObject({ auth: { apiKey: "h" } });
|
|
801
|
+
} finally {
|
|
802
|
+
await models.dispose();
|
|
803
|
+
if (origAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
|
804
|
+
else process.env.PI_CODING_AGENT_DIR = origAgentDir;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Runtime keys never land in auth.json.
|
|
808
|
+
const auth = (await Bun.file(join(dir, "auth.json")).exists())
|
|
809
|
+
? await Bun.file(join(dir, "auth.json")).text()
|
|
810
|
+
: "";
|
|
811
|
+
expect(auth).not.toContain('"a"');
|
|
812
|
+
expect(auth).not.toContain('"b"');
|
|
813
|
+
expect(auth).not.toContain('"h"');
|
|
814
|
+
}),
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
describe("resolveModel — models.json custom providers", () => {
|
|
819
|
+
test(
|
|
820
|
+
"falls back to the runtime for nan/qwen3.8-flash and helmcode/glm5.3",
|
|
821
|
+
withTempDir("pi-custom-resolve-", async (dir) => {
|
|
822
|
+
const models = await writeSessionModelsJson(dir, { ...NAN_ENV, ...HELMCODE_ENV });
|
|
823
|
+
const runtime = await piCodingAgent.ModelRuntime.create({
|
|
824
|
+
authPath: join(dir, "auth.json"),
|
|
825
|
+
modelsPath: models.modelsPath,
|
|
826
|
+
});
|
|
827
|
+
await models.dispose();
|
|
828
|
+
const nan = resolveModel("nan/qwen3.8-flash", NAN_ENV, runtime);
|
|
829
|
+
expect(nan?.provider).toBe("nan");
|
|
830
|
+
expect(nan?.id).toBe("qwen3.8-flash");
|
|
831
|
+
const helm = resolveModel("helmcode/glm5.3", HELMCODE_ENV, runtime);
|
|
832
|
+
expect(helm?.provider).toBe("helmcode");
|
|
833
|
+
expect(helm?.id).toBe("glm5.3");
|
|
834
|
+
expect(helm?.baseUrl).toBe(OPENAI_COMPATIBLE_PROVIDERS.helmcode.baseUrl);
|
|
835
|
+
}),
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
test(
|
|
839
|
+
"a nan/<id> outside the table (nan/glm5.2) gets a bare entry and resolves",
|
|
840
|
+
withTempDir("pi-custom-bare-", async (dir) => {
|
|
841
|
+
await withSessionModels(dir, NAN_ENV, "nan/glm5.2", async (s) => {
|
|
842
|
+
const written = await readModelsJson(s.modelsPath);
|
|
843
|
+
const ids = written.providers.nan.models.map((m: { id: string }) => m.id);
|
|
844
|
+
expect(ids).toContain("glm5.2");
|
|
845
|
+
expect(ids).toContain("glm5.3");
|
|
846
|
+
const runtime = await piCodingAgent.ModelRuntime.create({
|
|
847
|
+
authPath: join(dir, "auth.json"),
|
|
848
|
+
modelsPath: s.modelsPath,
|
|
849
|
+
});
|
|
850
|
+
const model = resolveModel("nan/glm5.2", NAN_ENV, runtime);
|
|
851
|
+
expect(model?.provider).toBe("nan");
|
|
852
|
+
expect(model?.id).toBe("glm5.2");
|
|
853
|
+
expect(model?.baseUrl).toBe(OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// The bare entry belongs to that session: the next task's model never sees it.
|
|
857
|
+
await withSessionModels(dir, NAN_ENV, "nan/glm5.3", async (s) => {
|
|
858
|
+
const next = await readModelsJson(s.modelsPath);
|
|
859
|
+
expect(next.providers.nan.models.map((m: { id: string }) => m.id)).not.toContain("glm5.2");
|
|
860
|
+
});
|
|
861
|
+
}),
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
test("builtin hits are unchanged when a runtime is passed", async () => {
|
|
865
|
+
const runtime = { getModel: () => undefined };
|
|
866
|
+
const model = resolveModel("anthropic/claude-sonnet-5", {}, runtime);
|
|
867
|
+
expect(model?.provider).toBe("anthropic");
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
test("an unknown provider returns undefined and logs a scrubbed warning", () => {
|
|
871
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
872
|
+
try {
|
|
873
|
+
const runtime = { getModel: () => undefined };
|
|
874
|
+
expect(resolveModel("nope/model-x", {}, runtime)).toBeUndefined();
|
|
875
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
876
|
+
expect(String(warn.mock.calls[0]?.[0])).toContain('"nope/model-x"');
|
|
877
|
+
expect(String(warn.mock.calls[0]?.[0])).toContain("default model");
|
|
878
|
+
} finally {
|
|
879
|
+
warn.mockRestore();
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("without a runtime, nan/<id> does not resolve (pi-ai has no builtin nan)", () => {
|
|
884
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
885
|
+
try {
|
|
886
|
+
expect(resolveModel("nan/glm5.3", NAN_ENV)).toBeUndefined();
|
|
887
|
+
} finally {
|
|
888
|
+
warn.mockRestore();
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
describe("checkPiMonoCredentials — NaN and Helmcode", () => {
|
|
894
|
+
const opts = { homeDir: "/home/worker", fs: { existsSync: () => false } };
|
|
895
|
+
|
|
896
|
+
test("MODEL_OVERRIDE=nan/* accepts NAN_API_KEY or the HELMCODE_API_NAN alias", async () => {
|
|
897
|
+
const env = { MODEL_OVERRIDE: "nan/glm5.3" };
|
|
898
|
+
expect((await checkPiMonoCredentials(env, opts)).ready).toBe(false);
|
|
899
|
+
expect((await checkPiMonoCredentials({ ...env, NAN_API_KEY: "x" }, opts)).ready).toBe(true);
|
|
900
|
+
expect((await checkPiMonoCredentials({ ...env, HELMCODE_API_NAN: "x" }, opts)).ready).toBe(
|
|
901
|
+
true,
|
|
902
|
+
);
|
|
903
|
+
// An OpenRouter key does not satisfy a nan/* model.
|
|
904
|
+
expect((await checkPiMonoCredentials({ ...env, OPENROUTER_API_KEY: "x" }, opts)).ready).toBe(
|
|
905
|
+
false,
|
|
906
|
+
);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
test("MODEL_OVERRIDE=helmcode/* requires HELMCODE_API_KEY", async () => {
|
|
910
|
+
const env = { MODEL_OVERRIDE: "helmcode/glm5.3" };
|
|
911
|
+
const missing = await checkPiMonoCredentials({ ...env, NAN_API_KEY: "x" }, opts);
|
|
912
|
+
expect(missing.ready).toBe(false);
|
|
913
|
+
expect(missing.missing).toContain("HELMCODE_API_KEY");
|
|
914
|
+
expect((await checkPiMonoCredentials({ ...env, HELMCODE_API_KEY: "x" }, opts)).ready).toBe(
|
|
915
|
+
true,
|
|
916
|
+
);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test("permissive case accepts a NaN-only or Helmcode-only worker", async () => {
|
|
920
|
+
expect((await checkPiMonoCredentials({ HELMCODE_API_NAN: "x" }, opts)).ready).toBe(true);
|
|
921
|
+
expect((await checkPiMonoCredentials({ HELMCODE_API_KEY: "x" }, opts)).ready).toBe(true);
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
|
|
545
925
|
describe("Pi-mono event normalization", () => {
|
|
546
926
|
test("extractPiAssistantText ignores user messages", () => {
|
|
547
927
|
const text = extractPiAssistantText({
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
11
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { fetchResolvedEnv } from "../commands/runner";
|
|
16
|
+
import { buildPiShellEnv, PiMonoAdapter } from "../providers/pi-mono-adapter";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* pi's Bash tool must run with the env the runner resolved for this task,
|
|
20
|
+
* not the worker's process.env: a row admitted after boot (exportToEnv
|
|
21
|
+
* flipped on) must reach the shell, and a row revoked after boot (flipped off)
|
|
22
|
+
* must leave it, even though process.env still holds the boot value.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
type ConfigRow = { key: string; value: string; isSecret: boolean; exportToEnv: boolean };
|
|
26
|
+
|
|
27
|
+
let server: ReturnType<typeof Bun.serve>;
|
|
28
|
+
let apiUrl = "";
|
|
29
|
+
let rows: ConfigRow[] = [];
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
server = Bun.serve({
|
|
33
|
+
port: 0,
|
|
34
|
+
async fetch(req) {
|
|
35
|
+
const url = new URL(req.url);
|
|
36
|
+
if (url.pathname === "/api/config/resolved") return Response.json({ configs: rows });
|
|
37
|
+
if (url.pathname !== "/mcp") return Response.json({ servers: [] });
|
|
38
|
+
const body = (await req.json()) as { id?: number; method?: string };
|
|
39
|
+
const result =
|
|
40
|
+
body.method === "tools/list"
|
|
41
|
+
? { tools: [] }
|
|
42
|
+
: { protocolVersion: "2025-03-26", capabilities: {} };
|
|
43
|
+
return Response.json({ jsonrpc: "2.0", id: body.id ?? 1, result });
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
apiUrl = `http://localhost:${server.port}`;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterAll(() => {
|
|
50
|
+
server.stop(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("pi Bash tool env", () => {
|
|
54
|
+
const envKeys = [
|
|
55
|
+
"PI_CODING_AGENT_DIR",
|
|
56
|
+
"OPENROUTER_API_KEY",
|
|
57
|
+
"OPENROUTER_BASE_URL",
|
|
58
|
+
"SHELL_REVOKED_CANARY",
|
|
59
|
+
"SHELL_ADMITTED_CANARY",
|
|
60
|
+
] as const;
|
|
61
|
+
const saved: Record<string, string | undefined> = {};
|
|
62
|
+
let agentDir = "";
|
|
63
|
+
let log: ReturnType<typeof spyOn>;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
for (const key of envKeys) saved[key] = process.env[key];
|
|
67
|
+
agentDir = mkdtempSync(join(tmpdir(), "pi-shell-env-"));
|
|
68
|
+
process.env.PI_CODING_AGENT_DIR = agentDir;
|
|
69
|
+
process.env.OPENROUTER_API_KEY = "example-test-key";
|
|
70
|
+
process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
|
|
71
|
+
log = spyOn(console, "log").mockImplementation(() => {});
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
log.mockRestore();
|
|
75
|
+
for (const key of envKeys) {
|
|
76
|
+
if (saved[key] === undefined) delete process.env[key];
|
|
77
|
+
else process.env[key] = saved[key];
|
|
78
|
+
}
|
|
79
|
+
rmSync(agentDir, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
async function sessionWithEnv(env: Record<string, string | undefined>): Promise<AgentSession> {
|
|
83
|
+
const session = await new PiMonoAdapter().createSession({
|
|
84
|
+
prompt: "hello",
|
|
85
|
+
systemPrompt: "test",
|
|
86
|
+
model: "openrouter/google/gemini-3-flash-preview",
|
|
87
|
+
role: "worker",
|
|
88
|
+
agentId: "test-agent",
|
|
89
|
+
taskId: "test-task",
|
|
90
|
+
apiUrl,
|
|
91
|
+
apiKey: "example-test-key",
|
|
92
|
+
cwd: agentDir,
|
|
93
|
+
logFile: join(agentDir, `session-${crypto.randomUUID()}.log`),
|
|
94
|
+
env,
|
|
95
|
+
});
|
|
96
|
+
return (session as unknown as { agentSession: AgentSession }).agentSession;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function shell(session: AgentSession, command: string): Promise<string> {
|
|
100
|
+
const bash = session.getToolDefinition("bash");
|
|
101
|
+
if (!bash) throw new Error("bash tool missing");
|
|
102
|
+
const result = await bash.execute(
|
|
103
|
+
"call-1",
|
|
104
|
+
{ command },
|
|
105
|
+
undefined,
|
|
106
|
+
undefined,
|
|
107
|
+
undefined as never,
|
|
108
|
+
);
|
|
109
|
+
return result.content
|
|
110
|
+
.map((part) => ("text" in part ? part.text : ""))
|
|
111
|
+
.join("")
|
|
112
|
+
.trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
test("admission and revocation after boot reach the shell", async () => {
|
|
116
|
+
// Boot exported SHELL_REVOKED_CANARY and withheld SHELL_ADMITTED_CANARY; the
|
|
117
|
+
// operator then flipped both rows.
|
|
118
|
+
process.env.SHELL_REVOKED_CANARY = "boot-revoked";
|
|
119
|
+
delete process.env.SHELL_ADMITTED_CANARY;
|
|
120
|
+
rows = [
|
|
121
|
+
{ key: "SHELL_REVOKED_CANARY", value: "row-revoked", isSecret: true, exportToEnv: false },
|
|
122
|
+
{ key: "SHELL_ADMITTED_CANARY", value: "row-admitted", isSecret: true, exportToEnv: true },
|
|
123
|
+
];
|
|
124
|
+
const { env } = await fetchResolvedEnv(apiUrl, "key", "agent-1", process.env);
|
|
125
|
+
const session = await sessionWithEnv(env);
|
|
126
|
+
try {
|
|
127
|
+
const out = await shell(
|
|
128
|
+
session,
|
|
129
|
+
// The shell's own environment: /proc on Linux, `env` on macOS (no procfs).
|
|
130
|
+
'echo "admitted=$SHELL_ADMITTED_CANARY revoked=$SHELL_REVOKED_CANARY."; { if [ -r /proc/$$/environ ]; then tr "\\0" "\\n" < /proc/$$/environ; else env; fi; } | grep -c "^SHELL_REVOKED_CANARY=" || true',
|
|
131
|
+
);
|
|
132
|
+
expect(out).toContain("admitted=row-admitted revoked=.");
|
|
133
|
+
expect(out.split("\n").at(-1)).toBe("0");
|
|
134
|
+
} finally {
|
|
135
|
+
session.dispose();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("concurrent sessions keep their own env", async () => {
|
|
140
|
+
const first = await sessionWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: "first" });
|
|
141
|
+
const second = await sessionWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: undefined });
|
|
142
|
+
try {
|
|
143
|
+
const [a, b] = await Promise.all([
|
|
144
|
+
shell(first, 'echo "v=$SHELL_ADMITTED_CANARY"'),
|
|
145
|
+
shell(second, 'echo "v=$SHELL_ADMITTED_CANARY"'),
|
|
146
|
+
]);
|
|
147
|
+
expect(a).toBe("v=first");
|
|
148
|
+
expect(b).toBe("v=");
|
|
149
|
+
expect(process.env.SHELL_ADMITTED_CANARY).toBeUndefined();
|
|
150
|
+
} finally {
|
|
151
|
+
first.dispose();
|
|
152
|
+
second.dispose();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("buildPiShellEnv", () => {
|
|
158
|
+
test("keeps the session env and takes only PATH and pi's session keys from its spawn env", () => {
|
|
159
|
+
const env = buildPiShellEnv(
|
|
160
|
+
{ HOME: "/home/worker", PATH: "/usr/bin", TASK_ONLY: "yes" },
|
|
161
|
+
{ PATH: "/pi/bin:/usr/bin", PI_SESSION_ID: "s1", PI_OTHER: "leak", PROCESS_ONLY: "leak" },
|
|
162
|
+
);
|
|
163
|
+
expect(env).toEqual({
|
|
164
|
+
HOME: "/home/worker",
|
|
165
|
+
PATH: "/pi/bin:/usr/bin",
|
|
166
|
+
TASK_ONLY: "yes",
|
|
167
|
+
PI_SESSION_ID: "s1",
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -12,6 +12,7 @@ import "../heartbeat/templates";
|
|
|
12
12
|
import "../tools/templates";
|
|
13
13
|
import "../commands/templates";
|
|
14
14
|
import "../slack/templates";
|
|
15
|
+
import "../integrations/chat-sdk/templates";
|
|
15
16
|
import "../prompts/session-templates";
|
|
16
17
|
|
|
17
18
|
const TEST_DB_PATH = "./test-prompt-remaining.sqlite";
|
|
@@ -33,6 +34,7 @@ async function ensureTemplatesRegistered(): Promise<void> {
|
|
|
33
34
|
await import(`../tools/templates?t=${ts}`);
|
|
34
35
|
await import(`../commands/templates?t=${ts}`);
|
|
35
36
|
await import(`../slack/templates?t=${ts}`);
|
|
37
|
+
await import(`../integrations/chat-sdk/templates?t=${ts}`);
|
|
36
38
|
await import(`../prompts/session-templates?t=${ts}`);
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -115,6 +117,22 @@ describe("template registration — all sources", () => {
|
|
|
115
117
|
expect(eventTypes).toContain("heartbeat.checklist");
|
|
116
118
|
});
|
|
117
119
|
|
|
120
|
+
test("Heartbeat checklist puts the live status after the standing orders and instructions", () => {
|
|
121
|
+
// The status changes every tick; the orders and instructions usually do
|
|
122
|
+
// not, so they lead the text and the status goes last.
|
|
123
|
+
const { text } = resolveTemplate("heartbeat.checklist", {
|
|
124
|
+
system_status: "STATUS_MARKER",
|
|
125
|
+
heartbeat_content: "ORDERS_MARKER",
|
|
126
|
+
});
|
|
127
|
+
const orders = text.indexOf("ORDERS_MARKER");
|
|
128
|
+
const instructions = text.indexOf("## Instructions");
|
|
129
|
+
const status = text.indexOf("## Current System Status");
|
|
130
|
+
expect(orders).toBeGreaterThan(-1);
|
|
131
|
+
expect(instructions).toBeGreaterThan(orders);
|
|
132
|
+
expect(status).toBeGreaterThan(instructions);
|
|
133
|
+
expect(text.trimEnd()).toEndWith("STATUS_MARKER");
|
|
134
|
+
});
|
|
135
|
+
|
|
118
136
|
test("Task lifecycle templates are registered (3 task_lifecycle)", () => {
|
|
119
137
|
const all = getAllTemplateDefinitions();
|
|
120
138
|
const eventTypes = all.map((d) => d.eventType);
|
|
@@ -143,6 +161,11 @@ describe("template registration — all sources", () => {
|
|
|
143
161
|
expect(eventTypes).toContain("slack.assistant.offline");
|
|
144
162
|
expect(eventTypes).toContain("slack.message.thread_context");
|
|
145
163
|
});
|
|
164
|
+
|
|
165
|
+
test("Chat SDK templates are registered (1 system)", () => {
|
|
166
|
+
const eventTypes = getAllTemplateDefinitions().map((d) => d.eventType);
|
|
167
|
+
expect(eventTypes).toContain("chat.message.thread_context");
|
|
168
|
+
});
|
|
146
169
|
});
|
|
147
170
|
|
|
148
171
|
// ============================================================================
|
|
@@ -608,4 +631,16 @@ Alice: Hello
|
|
|
608
631
|
|
|
609
632
|
expect(result.text).toBe(expected);
|
|
610
633
|
});
|
|
634
|
+
|
|
635
|
+
test("chat.message.thread_context produces the same block as Slack", () => {
|
|
636
|
+
const result = resolveTemplate("chat.message.thread_context", {
|
|
637
|
+
thread_messages: "Alice: Hello\n[Agent]: Hi there!",
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
expect(result.skipped).toBe(false);
|
|
641
|
+
expect(result.text).toBe(`<thread_context>
|
|
642
|
+
Alice: Hello
|
|
643
|
+
[Agent]: Hi there!
|
|
644
|
+
</thread_context>`);
|
|
645
|
+
});
|
|
611
646
|
});
|