@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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
+
import {
|
|
5
|
+
startTelegramBotApiMock,
|
|
6
|
+
type TelegramBotApiMock,
|
|
7
|
+
} from "../../scripts/dev/telegram-bot-api-mock";
|
|
8
|
+
import { closeDb, createAgent, getDbClient, initDb } from "../be/db";
|
|
9
|
+
import { handleWebhooks } from "../http/webhooks";
|
|
10
|
+
import { drainBackground } from "../integrations/chat-sdk/background";
|
|
11
|
+
import { getChatSdkHealth, startChatSdk, stopChatSdk } from "../integrations/chat-sdk/runtime";
|
|
12
|
+
import { flushAllChatBuffers } from "../integrations/chat-sdk/thread-buffer";
|
|
13
|
+
import { getEnabledCapabilities } from "../server";
|
|
14
|
+
import privateText from "./fixtures/chat-sdk/telegram-private-text.json";
|
|
15
|
+
|
|
16
|
+
const TEST_DB_PATH = "./test-chat-sdk-telegram.sqlite";
|
|
17
|
+
const SECRET = "s";
|
|
18
|
+
const ENV_KEYS = ["CHAT_SDK_ENABLED", "CHAT_SDK_ADAPTERS", "CHAT_SDK_API_URL_OVERRIDES"];
|
|
19
|
+
const savedEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
|
|
20
|
+
|
|
21
|
+
let mock: TelegramBotApiMock;
|
|
22
|
+
let leadId: string;
|
|
23
|
+
|
|
24
|
+
async function removeDbFiles(): Promise<void> {
|
|
25
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
26
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Minimal fake req/res to drive handleWebhooks without a live server. */
|
|
31
|
+
function fakeReqRes(path: string, rawBody: string, headers: Record<string, string>) {
|
|
32
|
+
const rawHeaders = Object.entries(headers).flat();
|
|
33
|
+
const req = {
|
|
34
|
+
method: "POST",
|
|
35
|
+
url: path,
|
|
36
|
+
headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])),
|
|
37
|
+
rawHeaders,
|
|
38
|
+
async *[Symbol.asyncIterator]() {
|
|
39
|
+
yield Buffer.from(rawBody);
|
|
40
|
+
},
|
|
41
|
+
} as unknown as IncomingMessage;
|
|
42
|
+
|
|
43
|
+
const captured = { status: 0, body: "" };
|
|
44
|
+
const res = {
|
|
45
|
+
writeHead(status: number) {
|
|
46
|
+
captured.status = status;
|
|
47
|
+
return this;
|
|
48
|
+
},
|
|
49
|
+
end(chunk?: string | Buffer) {
|
|
50
|
+
if (chunk) captured.body = chunk.toString();
|
|
51
|
+
return this;
|
|
52
|
+
},
|
|
53
|
+
} as unknown as ServerResponse;
|
|
54
|
+
|
|
55
|
+
return { req, res, captured };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function post(adapter: string, body: unknown, headers: Record<string, string>) {
|
|
59
|
+
const path = `/api/integrations/chat/${adapter}/webhook`;
|
|
60
|
+
const { req, res, captured } = fakeReqRes(path, JSON.stringify(body), {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
...headers,
|
|
63
|
+
});
|
|
64
|
+
const handled = await handleWebhooks(req, res, path.split("/").filter(Boolean));
|
|
65
|
+
expect(handled).toBe(true);
|
|
66
|
+
return captured;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
beforeAll(async () => {
|
|
70
|
+
await removeDbFiles();
|
|
71
|
+
initDb(TEST_DB_PATH);
|
|
72
|
+
const lead = await createAgent({
|
|
73
|
+
id: crypto.randomUUID(),
|
|
74
|
+
name: "ChatLead",
|
|
75
|
+
isLead: true,
|
|
76
|
+
status: "idle",
|
|
77
|
+
});
|
|
78
|
+
leadId = lead.id;
|
|
79
|
+
mock = startTelegramBotApiMock();
|
|
80
|
+
process.env.CHAT_SDK_ENABLED = "true";
|
|
81
|
+
process.env.CHAT_SDK_ADAPTERS = JSON.stringify({
|
|
82
|
+
telegram: { botToken: "t", secretToken: SECRET, mode: "webhook" },
|
|
83
|
+
});
|
|
84
|
+
process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({ telegram: mock.url });
|
|
85
|
+
await startChatSdk();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
afterAll(async () => {
|
|
89
|
+
await stopChatSdk();
|
|
90
|
+
mock.stop();
|
|
91
|
+
for (const [key, value] of savedEnv) {
|
|
92
|
+
if (value === undefined) delete process.env[key];
|
|
93
|
+
else process.env[key] = value;
|
|
94
|
+
}
|
|
95
|
+
closeDb();
|
|
96
|
+
await removeDbFiles();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("Chat SDK Telegram webhook", () => {
|
|
100
|
+
test("the adapter starts healthy against the mock Bot API", () => {
|
|
101
|
+
expect(getChatSdkHealth().telegram?.status).toBe("ok");
|
|
102
|
+
expect(mock.calls.some((call) => call.method === "getMe")).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a wrong secret token is rejected with 401", async () => {
|
|
106
|
+
const captured = await post("telegram", privateText, {
|
|
107
|
+
"X-Telegram-Bot-Api-Secret-Token": "wrong",
|
|
108
|
+
});
|
|
109
|
+
expect(captured.status).toBe(401);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("a body above 1 MiB is rejected with 413 before the adapter runs", async () => {
|
|
113
|
+
const path = "/api/integrations/chat/telegram/webhook";
|
|
114
|
+
const segments = path.split("/").filter(Boolean);
|
|
115
|
+
const oversized = "x".repeat(1024 * 1024 + 1);
|
|
116
|
+
const secretHeader = { "X-Telegram-Bot-Api-Secret-Token": SECRET };
|
|
117
|
+
const sentBefore = mock.calls.length;
|
|
118
|
+
|
|
119
|
+
// Declared up front: rejected before any chunk is read.
|
|
120
|
+
const declared = fakeReqRes(path, "{}", {
|
|
121
|
+
...secretHeader,
|
|
122
|
+
"Content-Length": String(oversized.length),
|
|
123
|
+
});
|
|
124
|
+
expect(await handleWebhooks(declared.req, declared.res, segments)).toBe(true);
|
|
125
|
+
expect(declared.captured.status).toBe(413);
|
|
126
|
+
|
|
127
|
+
// Streamed without Content-Length: rejected once the cap is crossed.
|
|
128
|
+
const streamed = fakeReqRes(path, oversized, secretHeader);
|
|
129
|
+
expect(await handleWebhooks(streamed.req, streamed.res, segments)).toBe(true);
|
|
130
|
+
expect(streamed.captured.status).toBe(413);
|
|
131
|
+
expect(JSON.parse(streamed.captured.body).error).toContain("Payload too large");
|
|
132
|
+
expect(mock.calls.length).toBe(sentBefore);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("an unconfigured adapter answers 404", async () => {
|
|
136
|
+
const captured = await post("discord", {}, {});
|
|
137
|
+
expect(captured.status).toBe(404);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("a private-chat text update creates one Lead task with a chat-sdk contextKey", async () => {
|
|
141
|
+
const captured = await post("telegram", privateText, {
|
|
142
|
+
"X-Telegram-Bot-Api-Secret-Token": SECRET,
|
|
143
|
+
});
|
|
144
|
+
expect(captured.status).toBe(200);
|
|
145
|
+
expect(await drainBackground(5_000)).toBe(true);
|
|
146
|
+
// Messages wait in the thread buffer; flush it instead of waiting 10 s.
|
|
147
|
+
await flushAllChatBuffers();
|
|
148
|
+
expect(await drainBackground(5_000)).toBe(true);
|
|
149
|
+
|
|
150
|
+
const tasks = await getDbClient().query<{
|
|
151
|
+
id: string;
|
|
152
|
+
source: string;
|
|
153
|
+
contextKey: string;
|
|
154
|
+
agentId: string | null;
|
|
155
|
+
task: string;
|
|
156
|
+
taskType: string;
|
|
157
|
+
}>(
|
|
158
|
+
"SELECT id, source, contextKey, agentId, task, taskType FROM agent_tasks WHERE source = 'chat'",
|
|
159
|
+
);
|
|
160
|
+
expect(tasks).toHaveLength(1);
|
|
161
|
+
const [task] = tasks;
|
|
162
|
+
expect(task!.contextKey.startsWith("task:chat-sdk:telegram:")).toBe(true);
|
|
163
|
+
expect(task!.agentId).toBe(leadId);
|
|
164
|
+
expect(task!.taskType).toBe("chat-inbound");
|
|
165
|
+
expect(task!.task).toContain("summarize the open tasks");
|
|
166
|
+
|
|
167
|
+
const subscriptions = await getDbClient().query<{ thread_id: string }>(
|
|
168
|
+
"SELECT thread_id FROM chat_subscriptions",
|
|
169
|
+
);
|
|
170
|
+
expect(subscriptions.map((row) => row.thread_id)).toContain("telegram:555001");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("a redelivered update is deduplicated", async () => {
|
|
174
|
+
const captured = await post("telegram", privateText, {
|
|
175
|
+
"X-Telegram-Bot-Api-Secret-Token": SECRET,
|
|
176
|
+
});
|
|
177
|
+
expect(captured.status).toBe(200);
|
|
178
|
+
expect(await drainBackground(5_000)).toBe(true);
|
|
179
|
+
await flushAllChatBuffers();
|
|
180
|
+
const row = await getDbClient().get<{ n: number }>(
|
|
181
|
+
"SELECT COUNT(*) AS n FROM agent_tasks WHERE source = 'chat'",
|
|
182
|
+
);
|
|
183
|
+
expect(row?.n).toBe(1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("chat-sdk capability", () => {
|
|
188
|
+
test("is advertised only when CHAT_SDK_ENABLED is true", () => {
|
|
189
|
+
const previous = process.env.CHAT_SDK_ENABLED;
|
|
190
|
+
try {
|
|
191
|
+
process.env.CHAT_SDK_ENABLED = "true";
|
|
192
|
+
expect(getEnabledCapabilities()).toContain("chat-sdk");
|
|
193
|
+
process.env.CHAT_SDK_ENABLED = "false";
|
|
194
|
+
expect(getEnabledCapabilities()).not.toContain("chat-sdk");
|
|
195
|
+
} finally {
|
|
196
|
+
if (previous === undefined) delete process.env.CHAT_SDK_ENABLED;
|
|
197
|
+
else process.env.CHAT_SDK_ENABLED = previous;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
} from "node:fs/promises";
|
|
39
39
|
import { tmpdir } from "node:os";
|
|
40
40
|
import { join } from "node:path";
|
|
41
|
+
import { WORKSPACE_CLAUDE_MD_PATH } from "../commands/profile-sync";
|
|
41
42
|
import {
|
|
42
43
|
ClaudeAdapter,
|
|
43
44
|
parseClaudeBinary,
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
resolveClaudeBridgeEnabled,
|
|
49
50
|
resolveClaudeTrustDirs,
|
|
50
51
|
} from "../providers/claude-adapter";
|
|
52
|
+
import { ClaudeSettingsConfigError, userClaudeMdExclude } from "../providers/claude-flag-settings";
|
|
51
53
|
import type { ProviderSessionConfig } from "../providers/types";
|
|
52
54
|
import { setFlockForTests } from "../utils/file-lock";
|
|
53
55
|
import { holdFileLock } from "./fixtures/hold-file-lock";
|
|
@@ -694,6 +696,8 @@ describe("CLAUDE_BINARY env override", () => {
|
|
|
694
696
|
let spawnSpy: ReturnType<typeof spyOn>;
|
|
695
697
|
let whichSpy: ReturnType<typeof spyOn>;
|
|
696
698
|
let spawnedArgs: Array<readonly string[]>;
|
|
699
|
+
/** Every non-git spawn, including the `--version` probe. */
|
|
700
|
+
let claudeSpawns: Array<readonly string[]>;
|
|
697
701
|
let spawnedEnvs: Array<Record<string, string> | undefined>;
|
|
698
702
|
|
|
699
703
|
beforeEach(async () => {
|
|
@@ -712,9 +716,11 @@ describe("CLAUDE_BINARY env override", () => {
|
|
|
712
716
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = "example-test-token";
|
|
713
717
|
|
|
714
718
|
spawnedArgs = [];
|
|
719
|
+
claudeSpawns = [];
|
|
715
720
|
spawnedEnvs = [];
|
|
716
721
|
spawnSpy = spyOn(Bun, "spawn").mockImplementation(((cmd: readonly string[], opts?: unknown) => {
|
|
717
722
|
if (isGit(cmd)) return (realSpawn as (...a: unknown[]) => unknown)(cmd, opts);
|
|
723
|
+
claudeSpawns.push(cmd);
|
|
718
724
|
if (cmd.at(-1) !== "--version") {
|
|
719
725
|
spawnedArgs.push(cmd);
|
|
720
726
|
spawnedEnvs.push((opts as { env?: Record<string, string> } | undefined)?.env);
|
|
@@ -769,6 +775,144 @@ describe("CLAUDE_BINARY env override", () => {
|
|
|
769
775
|
expect(argv[0]).toBe("claude");
|
|
770
776
|
});
|
|
771
777
|
|
|
778
|
+
test("excludes both on-disk agent CLAUDE.md copies from Claude's memory load", async () => {
|
|
779
|
+
const adapter = new ClaudeAdapter();
|
|
780
|
+
await createCompletedSession(adapter, makeConfig());
|
|
781
|
+
|
|
782
|
+
const argv = spawnedArgs[0];
|
|
783
|
+
const at = argv.indexOf("--settings");
|
|
784
|
+
expect(at).toBeGreaterThan(0);
|
|
785
|
+
expect(JSON.parse(argv[at + 1] ?? "")).toEqual({
|
|
786
|
+
claudeMdExcludes: [WORKSPACE_CLAUDE_MD_PATH, userClaudeMdExclude()],
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("merges caller --settings from additionalArgs and CLAUDE_BINARY into one flag", async () => {
|
|
791
|
+
const settingsDir = await mkdtemp(join(tmpdir(), "claude-settings-merge-"));
|
|
792
|
+
try {
|
|
793
|
+
await writeFile(
|
|
794
|
+
join(settingsDir, "caller.json"),
|
|
795
|
+
JSON.stringify({ model: "from-file", claudeMdExcludes: ["/repo/CLAUDE.md"] }),
|
|
796
|
+
);
|
|
797
|
+
process.env.CLAUDE_BINARY = `claude --settings=${join(settingsDir, "caller.json")}`;
|
|
798
|
+
const adapter = new ClaudeAdapter();
|
|
799
|
+
await createCompletedSession(
|
|
800
|
+
adapter,
|
|
801
|
+
makeConfig({
|
|
802
|
+
cwd: settingsDir,
|
|
803
|
+
additionalArgs: [
|
|
804
|
+
"--settings",
|
|
805
|
+
JSON.stringify({
|
|
806
|
+
env: { X: "kept" },
|
|
807
|
+
claudeMdExcludes: [WORKSPACE_CLAUDE_MD_PATH, "/other/CLAUDE.md"],
|
|
808
|
+
}),
|
|
809
|
+
"--max-turns",
|
|
810
|
+
"3",
|
|
811
|
+
],
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
const argv = spawnedArgs[0];
|
|
816
|
+
const settingsFlags = argv.filter(
|
|
817
|
+
(arg) => arg === "--settings" || arg.startsWith("--settings="),
|
|
818
|
+
);
|
|
819
|
+
expect(settingsFlags).toEqual(["--settings"]);
|
|
820
|
+
expect(JSON.parse(argv[argv.indexOf("--settings") + 1] ?? "")).toEqual({
|
|
821
|
+
model: "from-file",
|
|
822
|
+
env: { X: "kept" },
|
|
823
|
+
claudeMdExcludes: [
|
|
824
|
+
"/repo/CLAUDE.md",
|
|
825
|
+
WORKSPACE_CLAUDE_MD_PATH,
|
|
826
|
+
"/other/CLAUDE.md",
|
|
827
|
+
userClaudeMdExclude(),
|
|
828
|
+
],
|
|
829
|
+
});
|
|
830
|
+
expect(argv[0]).toBe("claude");
|
|
831
|
+
expect(argv.slice(argv.indexOf("--max-turns"), argv.indexOf("--max-turns") + 2)).toEqual([
|
|
832
|
+
"--max-turns",
|
|
833
|
+
"3",
|
|
834
|
+
]);
|
|
835
|
+
// The version probe runs with the same stripped prefix and merged settings.
|
|
836
|
+
const probe = claudeSpawns.find((cmd) => cmd.at(-1) === "--version") ?? [];
|
|
837
|
+
expect(probe).toEqual([
|
|
838
|
+
"claude",
|
|
839
|
+
"--settings",
|
|
840
|
+
argv[argv.indexOf("--settings") + 1] ?? "",
|
|
841
|
+
"--version",
|
|
842
|
+
]);
|
|
843
|
+
} finally {
|
|
844
|
+
await rm(settingsDir, { recursive: true, force: true });
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("invalid caller --settings refuses to start: no Claude child spawns", async () => {
|
|
849
|
+
const settingsDir = await mkdtemp(join(tmpdir(), "claude-settings-invalid-"));
|
|
850
|
+
// Built at runtime so no single source literal looks like a credential.
|
|
851
|
+
const sentinel = ["settings", "sentinel", crypto.randomUUID()].join("-");
|
|
852
|
+
try {
|
|
853
|
+
await writeFile(join(settingsDir, "broken.json"), "{nope");
|
|
854
|
+
await writeFile(join(settingsDir, "array.json"), "[1]");
|
|
855
|
+
const cases: [string[], string][] = [
|
|
856
|
+
[["--settings", "missing.json"], "cannot be read (ENOENT)"],
|
|
857
|
+
[["--settings", "broken.json"], "is not valid JSON"],
|
|
858
|
+
[["--settings", `{"env":{"TOKEN":"${sentinel}"`], "is not valid JSON"],
|
|
859
|
+
[["--settings=array.json"], "is not a JSON object"],
|
|
860
|
+
[["--settings", "--max-turns", "3"], "--settings has no value"],
|
|
861
|
+
[["--settings", '--settings={"model":"x"}'], "--settings has no value"],
|
|
862
|
+
];
|
|
863
|
+
for (const [additionalArgs, message] of cases) {
|
|
864
|
+
claudeSpawns = [];
|
|
865
|
+
const adapter = new ClaudeAdapter();
|
|
866
|
+
const error = await adapter
|
|
867
|
+
.createSession(makeConfig({ cwd: settingsDir, additionalArgs }))
|
|
868
|
+
.then(
|
|
869
|
+
() => undefined,
|
|
870
|
+
(err: unknown) => err,
|
|
871
|
+
);
|
|
872
|
+
expect(error).toBeInstanceOf(ClaudeSettingsConfigError);
|
|
873
|
+
expect((error as Error).message).toContain(message);
|
|
874
|
+
expect((error as Error).message).not.toContain(sentinel);
|
|
875
|
+
// Counts the `--version` probe too: validation runs before every spawn.
|
|
876
|
+
expect(claudeSpawns).toEqual([]);
|
|
877
|
+
// ...and before the trust pre-seed writes ~/.claude.json.
|
|
878
|
+
expect(await Bun.file(join(homeDir, ".claude.json")).exists()).toBeFalse();
|
|
879
|
+
}
|
|
880
|
+
} finally {
|
|
881
|
+
await rm(settingsDir, { recursive: true, force: true });
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test("invalid CLAUDE_BINARY prefix --settings never reaches logs, errors, or a spawn", async () => {
|
|
886
|
+
const sentinel = ["prefix", "sentinel", crypto.randomUUID()].join("-");
|
|
887
|
+
const inline = `{"env":{"TOKEN":"${sentinel}"`;
|
|
888
|
+
const logged: string[] = [];
|
|
889
|
+
const capture = (...args: unknown[]) => {
|
|
890
|
+
logged.push(args.map(String).join(" "));
|
|
891
|
+
};
|
|
892
|
+
const logSpy = spyOn(console, "log").mockImplementation(capture);
|
|
893
|
+
const warnSpy = spyOn(console, "warn").mockImplementation(capture);
|
|
894
|
+
const errorSpy = spyOn(console, "error").mockImplementation(capture);
|
|
895
|
+
try {
|
|
896
|
+
for (const binary of [`claude --settings=${inline}`, `claude --settings ${inline}`]) {
|
|
897
|
+
process.env.CLAUDE_BINARY = binary;
|
|
898
|
+
claudeSpawns = [];
|
|
899
|
+
const error = await new ClaudeAdapter().createSession(makeConfig()).then(
|
|
900
|
+
() => undefined,
|
|
901
|
+
(err: unknown) => err,
|
|
902
|
+
);
|
|
903
|
+
expect(error).toBeInstanceOf(ClaudeSettingsConfigError);
|
|
904
|
+
expect((error as Error).message).toContain("inline JSON is not valid JSON");
|
|
905
|
+
expect((error as Error).message).not.toContain(sentinel);
|
|
906
|
+
expect(claudeSpawns).toEqual([]);
|
|
907
|
+
}
|
|
908
|
+
} finally {
|
|
909
|
+
logSpy.mockRestore();
|
|
910
|
+
warnSpy.mockRestore();
|
|
911
|
+
errorSpy.mockRestore();
|
|
912
|
+
}
|
|
913
|
+
expect(logged.filter((line) => line.includes(sentinel))).toEqual([]);
|
|
914
|
+
});
|
|
915
|
+
|
|
772
916
|
test("legacy bridge override: argv[0] comes from CLAUDE_BINARY", async () => {
|
|
773
917
|
process.env.CLAUDE_BINARY = LEGACY_BRIDGE_COMPAT_BINARY;
|
|
774
918
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
ClaudeSettingsConfigError,
|
|
7
|
+
mergeClaudeSettingsArgs,
|
|
8
|
+
userClaudeMdExclude,
|
|
9
|
+
WORKSPACE_CLAUDE_MD_EXCLUDE,
|
|
10
|
+
} from "../providers/claude-flag-settings";
|
|
11
|
+
|
|
12
|
+
let directory: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
directory = await mkdtemp(join(tmpdir(), "claude-flag-settings-"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await rm(directory, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("mergeClaudeSettingsArgs", () => {
|
|
23
|
+
test("no caller settings: args pass through, only the workspace exclusion", () => {
|
|
24
|
+
const merged = mergeClaudeSettingsArgs([["--max-turns", "3"]], directory);
|
|
25
|
+
expect(merged.argLists).toEqual([["--max-turns", "3"]]);
|
|
26
|
+
expect(merged.settings).toEqual({
|
|
27
|
+
claudeMdExcludes: [WORKSPACE_CLAUDE_MD_EXCLUDE, userClaudeMdExclude()],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("strips both flag forms, keeps caller keys, unions and dedupes claudeMdExcludes", async () => {
|
|
32
|
+
await writeFile(
|
|
33
|
+
join(directory, "relative.json"),
|
|
34
|
+
JSON.stringify({ model: "file", claudeMdExcludes: ["/a/CLAUDE.md", "/a/CLAUDE.md"] }),
|
|
35
|
+
);
|
|
36
|
+
const merged = mergeClaudeSettingsArgs(
|
|
37
|
+
[
|
|
38
|
+
["--settings", "relative.json"],
|
|
39
|
+
[
|
|
40
|
+
"--verbose",
|
|
41
|
+
`--settings=${JSON.stringify({ env: { X: "kept" }, claudeMdExcludes: [WORKSPACE_CLAUDE_MD_EXCLUDE, "/b/CLAUDE.md"] })}`,
|
|
42
|
+
],
|
|
43
|
+
],
|
|
44
|
+
directory,
|
|
45
|
+
);
|
|
46
|
+
expect(merged.argLists).toEqual([[], ["--verbose"]]);
|
|
47
|
+
expect(merged.settings).toEqual({
|
|
48
|
+
model: "file",
|
|
49
|
+
env: { X: "kept" },
|
|
50
|
+
claudeMdExcludes: [
|
|
51
|
+
"/a/CLAUDE.md",
|
|
52
|
+
WORKSPACE_CLAUDE_MD_EXCLUDE,
|
|
53
|
+
"/b/CLAUDE.md",
|
|
54
|
+
userClaudeMdExclude(),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("missing file, malformed JSON and non-object values refuse to start", async () => {
|
|
60
|
+
await writeFile(join(directory, "array.json"), "[1]");
|
|
61
|
+
await writeFile(join(directory, "broken.json"), "{nope");
|
|
62
|
+
const cases: [string[], string][] = [
|
|
63
|
+
[
|
|
64
|
+
["--settings=missing.json"],
|
|
65
|
+
`file ${join(directory, "missing.json")} cannot be read (ENOENT)`,
|
|
66
|
+
],
|
|
67
|
+
[["--settings", "broken.json"], `file ${join(directory, "broken.json")} is not valid JSON`],
|
|
68
|
+
[["--settings", "array.json"], "is not a JSON object"],
|
|
69
|
+
];
|
|
70
|
+
for (const [args, message] of cases) {
|
|
71
|
+
expect(() => mergeClaudeSettingsArgs([args], directory)).toThrow(message);
|
|
72
|
+
expect(() => mergeClaudeSettingsArgs([args], directory)).toThrow(ClaudeSettingsConfigError);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("malformed inline JSON error never echoes the value", () => {
|
|
77
|
+
let error: unknown;
|
|
78
|
+
try {
|
|
79
|
+
mergeClaudeSettingsArgs(
|
|
80
|
+
[["--settings", '{"env":{"TOKEN":"sk-ant-oat01-should-not-leak"', "--verbose"]],
|
|
81
|
+
directory,
|
|
82
|
+
);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
error = err;
|
|
85
|
+
}
|
|
86
|
+
expect(error).toBeInstanceOf(ClaudeSettingsConfigError);
|
|
87
|
+
expect((error as Error).message).toContain("inline JSON is not valid JSON");
|
|
88
|
+
expect((error as Error).message).not.toContain("should-not-leak");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("a missing value never swallows the next flag", () => {
|
|
92
|
+
for (const args of [
|
|
93
|
+
["--settings"],
|
|
94
|
+
["--settings="],
|
|
95
|
+
["--settings", "--max-turns", "3"],
|
|
96
|
+
["--settings", `--settings=${JSON.stringify({ model: "x" })}`],
|
|
97
|
+
]) {
|
|
98
|
+
expect(() => mergeClaudeSettingsArgs([args], directory)).toThrow("--settings has no value");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("consecutive valid settings flags merge in order", () => {
|
|
103
|
+
const merged = mergeClaudeSettingsArgs(
|
|
104
|
+
[["--settings", '{"model":"a"}', '--settings={"model":"b","env":{}}', "--max-turns", "3"]],
|
|
105
|
+
directory,
|
|
106
|
+
);
|
|
107
|
+
expect(merged.argLists).toEqual([["--max-turns", "3"]]);
|
|
108
|
+
expect(merged.settings).toEqual({
|
|
109
|
+
model: "b",
|
|
110
|
+
env: {},
|
|
111
|
+
claudeMdExcludes: [WORKSPACE_CLAUDE_MD_EXCLUDE, userClaudeMdExclude()],
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -966,7 +966,7 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
|
|
|
966
966
|
expect(getClaudeManagedModelPricing("claude-sonnet-5-5")).toEqual({
|
|
967
967
|
inputPerMillion: 2.0,
|
|
968
968
|
outputPerMillion: 10.0,
|
|
969
|
-
cacheReadPerMillion: 0.
|
|
969
|
+
cacheReadPerMillion: 0.1,
|
|
970
970
|
cacheWritePerMillion: 2.5,
|
|
971
971
|
});
|
|
972
972
|
expect(getClaudeManagedModelPricing("claude-sonnet-5")).toEqual({
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { mkdtemp, readFile, realpath, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { ClaudeAdapter, getTaskFilePath } from "../providers/claude-adapter";
|
|
6
|
+
import { userClaudeMdExclude } from "../providers/claude-flag-settings";
|
|
6
7
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
7
8
|
import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
|
|
8
9
|
|
|
@@ -148,6 +149,152 @@ describe("Claude SDK production adapter lifecycle", () => {
|
|
|
148
149
|
argument === "--system-prompt" && fixtureState.argv[index + 1] === "",
|
|
149
150
|
),
|
|
150
151
|
).toBeFalse();
|
|
152
|
+
// The SDK forwards `settings` as `--settings`: Claude skips both agent CLAUDE.md copies.
|
|
153
|
+
const settingsAt = fixtureState.argv.indexOf("--settings");
|
|
154
|
+
expect(settingsAt).toBeGreaterThan(-1);
|
|
155
|
+
expect(JSON.parse(fixtureState.argv[settingsAt + 1] ?? "")).toMatchObject({
|
|
156
|
+
claudeMdExcludes: ["/workspace/CLAUDE.md", userClaudeMdExclude()],
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
{ timeout: CHILD_PROCESS_TEST_BUDGET_MS },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
test(
|
|
163
|
+
"merges caller --settings from additionalArgs and CLAUDE_BINARY into the SDK's one flag",
|
|
164
|
+
async () => {
|
|
165
|
+
const directory = await temporaryDirectory();
|
|
166
|
+
const stateFile = join(directory, "settings-state.json");
|
|
167
|
+
const settingsFile = join(directory, "caller-settings.json");
|
|
168
|
+
await Bun.write(
|
|
169
|
+
settingsFile,
|
|
170
|
+
JSON.stringify({ model: "from-file", claudeMdExcludes: ["/repo/CLAUDE.md"] }),
|
|
171
|
+
);
|
|
172
|
+
const sessionConfig = config(directory, "queue", stateFile);
|
|
173
|
+
sessionConfig.env = {
|
|
174
|
+
...sessionConfig.env,
|
|
175
|
+
CLAUDE_BINARY: `${process.execPath} ${fixturePath} --settings ${settingsFile}`,
|
|
176
|
+
};
|
|
177
|
+
sessionConfig.additionalArgs = [
|
|
178
|
+
`--settings=${JSON.stringify({ env: { X: "kept" }, claudeMdExcludes: ["/other/CLAUDE.md"] })}`,
|
|
179
|
+
];
|
|
180
|
+
const session = await new ClaudeAdapter(async () => {}).createSession(sessionConfig);
|
|
181
|
+
// Queue mode records argv once it has the initial plus two queued messages.
|
|
182
|
+
await session.deliverSteering?.({ mode: "queue", text: "queued-message-a" });
|
|
183
|
+
await session.deliverSteering?.({ mode: "queue", text: "queued-message-b" });
|
|
184
|
+
await session.waitForCompletion();
|
|
185
|
+
|
|
186
|
+
await waitForFile(stateFile);
|
|
187
|
+
const { argv } = JSON.parse(await Bun.file(stateFile).text()) as { argv: string[] };
|
|
188
|
+
const settingsFlags = argv.filter(
|
|
189
|
+
(arg) => arg === "--settings" || arg.startsWith("--settings="),
|
|
190
|
+
);
|
|
191
|
+
expect(settingsFlags).toEqual(["--settings"]);
|
|
192
|
+
expect(JSON.parse(argv[argv.indexOf("--settings") + 1] ?? "")).toEqual({
|
|
193
|
+
model: "from-file",
|
|
194
|
+
env: { X: "kept" },
|
|
195
|
+
claudeMdExcludes: [
|
|
196
|
+
"/repo/CLAUDE.md",
|
|
197
|
+
"/other/CLAUDE.md",
|
|
198
|
+
"/workspace/CLAUDE.md",
|
|
199
|
+
userClaudeMdExclude(),
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
expect(argv).not.toContain(settingsFile);
|
|
203
|
+
// The version probe carries the same merged settings value.
|
|
204
|
+
const spawns = (await Bun.file(`${stateFile}.spawns`).text())
|
|
205
|
+
.trim()
|
|
206
|
+
.split("\n")
|
|
207
|
+
.map((line) => JSON.parse(line) as string[]);
|
|
208
|
+
const probe = spawns.find((args) => args.at(-1) === "--version") ?? [];
|
|
209
|
+
expect(probe).toEqual([
|
|
210
|
+
"--settings",
|
|
211
|
+
argv[argv.indexOf("--settings") + 1] ?? "",
|
|
212
|
+
"--version",
|
|
213
|
+
]);
|
|
214
|
+
},
|
|
215
|
+
{ timeout: CHILD_PROCESS_TEST_BUDGET_MS },
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
test(
|
|
219
|
+
"invalid caller --settings refuses to start: no Claude child spawns",
|
|
220
|
+
async () => {
|
|
221
|
+
const directory = await temporaryDirectory();
|
|
222
|
+
// Built at runtime so no single source literal looks like a credential.
|
|
223
|
+
const sentinel = ["settings", "sentinel", crypto.randomUUID()].join("-");
|
|
224
|
+
await Bun.write(join(directory, "broken.json"), "{nope");
|
|
225
|
+
await Bun.write(join(directory, "array.json"), "[1]");
|
|
226
|
+
const cases: [string[], string][] = [
|
|
227
|
+
[["--settings", "missing.json"], "cannot be read (ENOENT)"],
|
|
228
|
+
[["--settings", "broken.json"], "is not valid JSON"],
|
|
229
|
+
[["--settings", `{"env":{"TOKEN":"${sentinel}"`], "is not valid JSON"],
|
|
230
|
+
[["--settings=array.json"], "is not a JSON object"],
|
|
231
|
+
[["--settings", "--max-turns", "3"], "--settings has no value"],
|
|
232
|
+
[["--settings", '--settings={"model":"x"}'], "--settings has no value"],
|
|
233
|
+
];
|
|
234
|
+
for (const [index, [additionalArgs, message]] of cases.entries()) {
|
|
235
|
+
const stateFile = join(directory, `invalid-${index}.json`);
|
|
236
|
+
const sessionConfig = { ...config(directory, "cancel", stateFile), additionalArgs };
|
|
237
|
+
let summaries = 0;
|
|
238
|
+
const error = await new ClaudeAdapter(async () => {
|
|
239
|
+
summaries++;
|
|
240
|
+
})
|
|
241
|
+
.createSession(sessionConfig)
|
|
242
|
+
.then(
|
|
243
|
+
() => undefined,
|
|
244
|
+
(err: unknown) => err,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect((error as Error).name).toBe("ClaudeSettingsConfigError");
|
|
248
|
+
expect((error as Error).message).toContain(message);
|
|
249
|
+
expect((error as Error).message).not.toContain(sentinel);
|
|
250
|
+
// The fixture records every start, the `--version` probe included.
|
|
251
|
+
expect(await Bun.file(`${stateFile}.spawns`).exists()).toBeFalse();
|
|
252
|
+
expect(summaries).toBe(0);
|
|
253
|
+
expect(
|
|
254
|
+
await Bun.file(getTaskFilePath(`${process.pid}-${sessionConfig.taskId}`)).exists(),
|
|
255
|
+
).toBeFalse();
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{ timeout: CHILD_PROCESS_TEST_BUDGET_MS },
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
test(
|
|
262
|
+
"invalid CLAUDE_BINARY prefix --settings never reaches logs, errors, or a spawn",
|
|
263
|
+
async () => {
|
|
264
|
+
const directory = await temporaryDirectory();
|
|
265
|
+
const sentinel = ["prefix", "sentinel", crypto.randomUUID()].join("-");
|
|
266
|
+
const inline = `{"env":{"TOKEN":"${sentinel}"`;
|
|
267
|
+
const logged: string[] = [];
|
|
268
|
+
const capture = (...args: unknown[]) => {
|
|
269
|
+
logged.push(args.map(String).join(" "));
|
|
270
|
+
};
|
|
271
|
+
const logSpy = spyOn(console, "log").mockImplementation(capture);
|
|
272
|
+
const warnSpy = spyOn(console, "warn").mockImplementation(capture);
|
|
273
|
+
const errorSpy = spyOn(console, "error").mockImplementation(capture);
|
|
274
|
+
try {
|
|
275
|
+
for (const [index, flag] of [`--settings=${inline}`, `--settings ${inline}`].entries()) {
|
|
276
|
+
const stateFile = join(directory, `prefix-${index}.json`);
|
|
277
|
+
const sessionConfig = config(directory, "cancel", stateFile);
|
|
278
|
+
sessionConfig.env = {
|
|
279
|
+
...sessionConfig.env,
|
|
280
|
+
CLAUDE_BINARY: `${process.execPath} ${fixturePath} ${flag}`,
|
|
281
|
+
};
|
|
282
|
+
const error = await new ClaudeAdapter(async () => {}).createSession(sessionConfig).then(
|
|
283
|
+
() => undefined,
|
|
284
|
+
(err: unknown) => err,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect((error as Error).name).toBe("ClaudeSettingsConfigError");
|
|
288
|
+
expect((error as Error).message).toContain("inline JSON is not valid JSON");
|
|
289
|
+
expect((error as Error).message).not.toContain(sentinel);
|
|
290
|
+
expect(await Bun.file(`${stateFile}.spawns`).exists()).toBeFalse();
|
|
291
|
+
}
|
|
292
|
+
} finally {
|
|
293
|
+
logSpy.mockRestore();
|
|
294
|
+
warnSpy.mockRestore();
|
|
295
|
+
errorSpy.mockRestore();
|
|
296
|
+
}
|
|
297
|
+
expect(logged.filter((line) => line.includes(sentinel))).toEqual([]);
|
|
151
298
|
},
|
|
152
299
|
{ timeout: CHILD_PROCESS_TEST_BUDGET_MS },
|
|
153
300
|
);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
1
2
|
import { rename } from "node:fs/promises";
|
|
2
3
|
|
|
3
4
|
const mode = process.env.CLAUDE_SDK_FIXTURE_MODE;
|
|
4
5
|
const stateFile = process.env.CLAUDE_SDK_FIXTURE_STATE_FILE;
|
|
5
6
|
const sessionId = "11111111-1111-4111-8111-111111111111";
|
|
6
7
|
|
|
8
|
+
// Record every invocation, the `--version` probe included, before any exit, so
|
|
9
|
+
// a test can assert that no Claude child started at all.
|
|
10
|
+
if (stateFile) {
|
|
11
|
+
appendFileSync(`${stateFile}.spawns`, `${JSON.stringify(process.argv.slice(2))}\n`);
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
if (process.argv.includes("--version")) {
|
|
8
15
|
if (process.env.CLAUDE_SDK_FIXTURE_UNKNOWN_VERSION === "1") process.exit(1);
|
|
9
16
|
console.log("2.1.263 (Claude Code)");
|