@desplega.ai/agent-swarm 1.152.2 → 1.154.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 +2 -2
- package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
- package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
- package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
- package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
- package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
- package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
- package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
- package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
- package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
- package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
- package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
- package/dist/cli-0x5qzvkg.js +4279 -0
- package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
- package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
- package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
- package/dist/cli-3bkz2p7j.js +2828 -0
- package/dist/cli-3er6gcsn.js +1849 -0
- package/dist/cli-4k7jazkq.js +3339 -0
- package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
- package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
- package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
- package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
- package/dist/cli-8sea27q4.js +14075 -0
- package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
- package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
- package/dist/cli-95jnpnqg.js +230 -0
- package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
- package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
- package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
- package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
- package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
- package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
- package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
- package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
- package/dist/cli-h38x0qhg.js +6912 -0
- package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
- package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
- package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
- package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
- package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
- package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
- package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
- package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
- package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
- package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
- package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
- package/dist/cli.js +28 -35
- package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
- package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
- package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
- package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
- package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
- package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
- package/dist/dsh-adapter-gsb2qqf9.js +19 -0
- package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
- package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
- package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
- package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
- package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
- package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
- package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
- package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
- package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
- package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
- package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
- package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
- package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
- package/dist/index-w097eg0x.js +5 -0
- package/dist/jiti-loader-b4837mtm.js +34 -0
- package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
- package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
- package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
- package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
- package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
- package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
- package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
- package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
- package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
- package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
- package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
- package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
- package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
- package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
- package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
- package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
- package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
- package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
- package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
- package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
- package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
- package/dist/virtual-modules-2q5b2pk4.js +54 -0
- package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
- package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
- package/openapi.json +109 -16
- package/package.json +7 -6
- package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
- package/src/be/automation-preflight.ts +6 -4
- package/src/be/db/agents.ts +75 -2
- package/src/be/db/tasks/read.ts +2 -0
- package/src/be/db/tasks/write.ts +7 -1
- package/src/be/db.ts +140 -18
- package/src/be/extensions/db.ts +35 -4
- package/src/be/memory/raters/llm.ts +2 -6
- package/src/be/memory/raters/noop.ts +2 -3
- package/src/be/memory/raters/registry.ts +4 -10
- package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +213 -0
- package/src/be/rbac-audit.ts +2 -0
- package/src/be/seed/agent-fs-provision.ts +23 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/onboard/compose-generator.ts +16 -1
- package/src/commands/onboard/env-generator.ts +14 -1
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/integration-slack.tsx +56 -16
- package/src/commands/onboard/steps/review.tsx +5 -0
- package/src/commands/onboard/types.ts +6 -0
- package/src/commands/onboard.tsx +5 -1
- package/src/commands/provider-credentials.ts +14 -2
- package/src/commands/runner.ts +18 -2
- package/src/extensions/identity.ts +15 -7
- package/src/extensions/lifecycle.ts +15 -21
- package/src/hooks/codex-hook.ts +7 -1
- package/src/http/agents.ts +12 -4
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +5 -3
- package/src/http/extensions.ts +108 -34
- package/src/http/poll.ts +7 -0
- package/src/http/schedules.ts +10 -0
- package/src/http/script-runs.ts +35 -26
- package/src/http/status.ts +24 -11
- package/src/http/tasks.ts +6 -1
- package/src/prompts/base-prompt.ts +15 -7
- package/src/prompts/memories.ts +5 -7
- package/src/prompts/session-templates.ts +3 -2
- package/src/prompts/steering-delivery.ts +2 -0
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-adapter.ts +3 -6
- package/src/providers/codex-models.ts +15 -1
- package/src/providers/dsh-adapter.ts +248 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/modelsdev-reasoning.json +50 -0
- package/src/providers/pi-mono-extension.ts +2 -5
- package/src/rbac/legacy-policy.ts +15 -1
- package/src/rbac/types.ts +2 -0
- package/src/server.ts +7 -1
- package/src/slack/ack.ts +43 -8
- package/src/slack/app.ts +32 -10
- package/src/slack/config.ts +29 -0
- package/src/slack/handlers.ts +7 -1
- package/src/slack/render-v2.ts +186 -17
- package/src/slack/responses.ts +1 -3
- package/src/slack/task-output.ts +66 -8
- package/src/slack/thread-buffer.ts +27 -3
- package/src/telemetry.ts +2 -3
- package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
- package/src/tests/agents-harness-provider.test.ts +19 -0
- package/src/tests/agents-list-model-display.test.ts +60 -1
- package/src/tests/base-prompt.test.ts +25 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-gpt6-release.test.ts +97 -0
- package/src/tests/codex-steering-hook.test.ts +2 -2
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +9 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/defer-task.test.ts +115 -16
- package/src/tests/dsh-adapter.test.ts +276 -0
- package/src/tests/e2e-nightly-report.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +2 -0
- package/src/tests/env-flag.test.ts +6 -0
- package/src/tests/extension-agent-visibility.test.ts +474 -0
- package/src/tests/extensions-db.test.ts +41 -0
- package/src/tests/extensions-http.test.ts +148 -4
- package/src/tests/extensions-lifecycle.test.ts +21 -1
- package/src/tests/extensions-mcp-tools.test.ts +9 -5
- package/src/tests/fetch-resolved-env.test.ts +43 -0
- package/src/tests/internal-ai/complete-structured.test.ts +32 -9
- package/src/tests/internal-ai/credentials.test.ts +4 -4
- package/src/tests/internal-ai/models.test.ts +20 -0
- package/src/tests/memory-rate-tool.test.ts +7 -10
- package/src/tests/memory-rater-defaults.test.ts +23 -0
- package/src/tests/memory-rater-e2e.test.ts +2 -3
- package/src/tests/migration-046-budgets.test.ts +2 -0
- package/src/tests/model-control.test.ts +2 -2
- package/src/tests/onboard-compose.test.ts +14 -0
- package/src/tests/onboard-env.test.ts +17 -0
- package/src/tests/onboard-manifest.test.ts +12 -0
- package/src/tests/opus-5-5-registry.test.ts +48 -0
- package/src/tests/pricing-refresh.test.ts +1 -1
- package/src/tests/pricing-routes.test.ts +15 -3
- package/src/tests/provider-registration.test.ts +5 -2
- package/src/tests/rbac-admission-e2e.test.ts +54 -0
- package/src/tests/rbac-e2e-helpers.ts +2 -0
- package/src/tests/rbac-engine.test.ts +32 -0
- package/src/tests/rbac-wire-e2e.test.ts +33 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/reload-config.test.ts +33 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/slack-config.test.ts +136 -0
- package/src/tests/slack-mode-lifecycle.test.ts +69 -0
- package/src/tests/slack-render-v2.test.ts +446 -16
- package/src/tests/slack-steering.test.ts +69 -1
- package/src/tests/slack-task-output.test.ts +33 -12
- package/src/tests/status.test.ts +22 -2
- package/src/tests/steering-core.test.ts +35 -0
- package/src/tests/steering-delivery.test.ts +39 -0
- package/src/tests/steering-transport.test.ts +2 -2
- package/src/tests/task-tool-preload-mcp.test.ts +3 -1
- package/src/tests/telemetry-init.test.ts +18 -0
- package/src/tests/workflow-live-recovery.test.ts +126 -0
- package/src/tests/workflow-llm.test.ts +3 -3
- package/src/tools/defer-task.ts +64 -26
- package/src/tools/extension-common.ts +4 -0
- package/src/tools/extension-install.ts +1 -1
- package/src/tools/extension-list.ts +4 -3
- package/src/tools/join-swarm.ts +1 -1
- package/src/tools/schedules/create-schedule.ts +11 -1
- package/src/tools/schedules/patch-schedule.ts +5 -0
- package/src/tools/schedules/update-schedule.ts +5 -0
- package/src/tools/send-task.ts +6 -0
- package/src/tools/utils.ts +1 -1
- package/src/types.ts +30 -8
- package/src/utils/context-window.ts +1 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/internal-ai/complete-structured.ts +8 -0
- package/src/utils/internal-ai/models.ts +5 -5
- package/src/utils/memory-raters.ts +3 -0
- package/src/workflows/engine.ts +28 -0
- package/src/workflows/recovery.ts +7 -4
- package/templates/schedules/daily-status-report/content.md +25 -12
- package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
- package/templates/skills/swarm-extensions/content.md +9 -5
- package/dist/cli-4wppfkcm.js +0 -16384
- package/dist/cli-55qrx3q8.js +0 -6041
- package/dist/cli-cg4vnyh4.js +0 -305
- package/dist/cli-jyetr64j.js +0 -52
- package/dist/cli-pp9ax9pa.js +0 -520
- package/dist/cli-qa66qaf0.js +0 -6076
- package/dist/cli-qghpvca2.js +0 -590
- package/dist/cli-rembwgt8.js +0 -861
- package/dist/cli-tcvj7j2r.js +0 -1971
- package/dist/cli-vfcth11k.js +0 -36
- package/dist/cli-w1d1bgkk.js +0 -1422
- package/dist/cli-y6x0gqtg.js +0 -5977
- package/dist/es5-tj3tn4jj.js +0 -844
- package/dist/index-1kq5hw2p.js +0 -467
- package/dist/index-1p8wdstt.js +0 -7
- package/dist/index-8fdjzb2f.js +0 -100
- package/dist/index-bxmwzbgw.js +0 -365
- package/dist/index-nzerxd5z.js +0 -7
- package/dist/index-pyymnzat.js +0 -1168
- package/dist/index-qv6atbab.js +0 -619
- package/dist/index-rja32qa7.js +0 -801
- package/dist/index-tzg5srfp.js +0 -211
- package/dist/index-vhncw983.js +0 -90
- package/dist/index-xk036zsk.js +0 -859
- package/dist/index-zyazbzcb.js +0 -6
- package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
|
@@ -95,8 +95,12 @@ type:UpsertAssetKeyMappingInput
|
|
|
95
95
|
type:WorkflowFilters
|
|
96
96
|
type:WorkflowRunListOptions
|
|
97
97
|
type:WorkflowRunPage
|
|
98
|
+
value:EXTENSION_AGENT_ROLE
|
|
99
|
+
value:ExtensionAgentAssignmentError
|
|
98
100
|
value:KvTypeCollisionError
|
|
99
101
|
value:MAX_EMPTY_POLLS
|
|
102
|
+
value:NOT_EXTENSION_AGENT_SQL
|
|
103
|
+
value:ReservedAgentRoleError
|
|
100
104
|
value:SKILL_FILE_LIMITS
|
|
101
105
|
value:UNATTRIBUTED_USER_ID
|
|
102
106
|
value:__resetSqliteVecExtensionPathCacheForTests
|
|
@@ -196,6 +200,7 @@ value:emitBuiltInIntegrationConnectedOnce
|
|
|
196
200
|
value:emitMcpServerConnectedTelemetry
|
|
197
201
|
value:ensureSlackDelegationActivation
|
|
198
202
|
value:ensureSlackRenderV2Activation
|
|
203
|
+
value:extensionAgentAssignmentError
|
|
199
204
|
value:failPendingResumeIfUnclaimed
|
|
200
205
|
value:failTask
|
|
201
206
|
value:findCompletedTaskInThread
|
|
@@ -327,6 +332,7 @@ value:getRecentFailedTasks
|
|
|
327
332
|
value:getRecentlyCancelledTasksForAgent
|
|
328
333
|
value:getRecentlyFinishedWorkerTasks
|
|
329
334
|
value:getRemainingCapacity
|
|
335
|
+
value:getResolvableDeferralOutcomes
|
|
330
336
|
value:getResolvedConfig
|
|
331
337
|
value:getRetryableSteps
|
|
332
338
|
value:getRootTaskChain
|
|
@@ -419,6 +425,7 @@ value:installMcpServer
|
|
|
419
425
|
value:installSkill
|
|
420
426
|
value:installSystemDefaultSkillsForAgent
|
|
421
427
|
value:isAgentEligibleForTask
|
|
428
|
+
value:isExtensionAgent
|
|
422
429
|
value:isPendingSlackMessage
|
|
423
430
|
value:isPoolAffinityEnforcementEnabled
|
|
424
431
|
value:isSqliteVecAvailable
|
|
@@ -450,6 +457,7 @@ value:markInboxMessageDelegated
|
|
|
450
457
|
value:markInboxMessageRead
|
|
451
458
|
value:markInboxMessageResponded
|
|
452
459
|
value:markKeyRateLimited
|
|
460
|
+
value:markSlackDeferralResolved
|
|
453
461
|
value:markSlackRelayAttempted
|
|
454
462
|
value:markSlackRelayDelivered
|
|
455
463
|
value:markSlackTreeRendered
|
|
@@ -486,6 +494,7 @@ value:releaseStaleReviewingTasks
|
|
|
486
494
|
value:releaseTask
|
|
487
495
|
value:replaceTaskAttachment
|
|
488
496
|
value:reserveSlackMessage
|
|
497
|
+
value:reservedRoleViolation
|
|
489
498
|
value:resetEmptyPollCount
|
|
490
499
|
value:resetOrphanedInProgressTasksForAgent
|
|
491
500
|
value:resetPromptTemplateToDefault
|
|
@@ -184,16 +184,19 @@ describe("defer-task handler", () => {
|
|
|
184
184
|
|
|
185
185
|
const stored = await getTaskById(task.id);
|
|
186
186
|
expect(stored?.status).toBe("completed");
|
|
187
|
-
|
|
188
|
-
//
|
|
187
|
+
expect(stored?.deferredAt).toBeTruthy();
|
|
188
|
+
// Human-facing output for a time-based defer: when it comes back, nothing
|
|
189
|
+
// else. No requestedByUserId is set here, so it falls back to UTC and
|
|
189
190
|
// labels it explicitly.
|
|
190
|
-
const shortId = schedule.id.slice(0, 8);
|
|
191
191
|
expect(stored?.output).toMatch(
|
|
192
|
-
/^
|
|
193
|
-
);
|
|
194
|
-
expect(stored?.output).toContain(
|
|
195
|
-
`([${shortId}](https://app.agent-swarm.dev/schedules/${schedule.id})) -> deploy 42 is still running`,
|
|
192
|
+
/^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC$/,
|
|
196
193
|
);
|
|
194
|
+
// The agent's internal note, the checks, the schedule UUID and its link
|
|
195
|
+
// all stay out of the card — they live in the task log and the wake-up
|
|
196
|
+
// task's template.
|
|
197
|
+
expect(stored?.output).not.toContain("deploy 42 is still running");
|
|
198
|
+
expect(stored?.output).not.toContain(schedule.id);
|
|
199
|
+
expect(stored?.output).not.toContain("app.agent-swarm.dev");
|
|
197
200
|
expect(stored?.output).not.toContain(schedule.nextRunAt!);
|
|
198
201
|
expect(stored?.output).not.toContain("Checks:");
|
|
199
202
|
expect(stored?.output).not.toContain("- smoke tests pass");
|
|
@@ -441,16 +444,15 @@ describe("defer-task handler", () => {
|
|
|
441
444
|
)) as DeferTaskResult;
|
|
442
445
|
expect(result.structuredContent.success).toBe(true);
|
|
443
446
|
const stored = await getTaskById(task.id);
|
|
444
|
-
const shortId = result.structuredContent.scheduleId!.slice(0, 8);
|
|
445
447
|
expect(stored?.output).toMatch(
|
|
446
|
-
/^
|
|
448
|
+
/^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC$/,
|
|
447
449
|
);
|
|
448
|
-
expect(stored?.output).toContain(
|
|
449
|
-
expect(stored?.output).toContain("
|
|
450
|
+
expect(stored?.output).not.toContain(result.structuredContent.scheduleId!);
|
|
451
|
+
expect(stored?.output).not.toContain("pending");
|
|
450
452
|
expect(stored?.output).not.toContain(SUMMARY);
|
|
451
453
|
});
|
|
452
454
|
|
|
453
|
-
test("
|
|
455
|
+
test("a long note never reaches the card, only the log and the wake-up task", async () => {
|
|
454
456
|
const task = await startedTask("verbose note");
|
|
455
457
|
const longNote = `Pending: ${"x".repeat(300)}\nsecond line is dropped`;
|
|
456
458
|
const result = (await buildTool().handler(
|
|
@@ -460,10 +462,11 @@ describe("defer-task handler", () => {
|
|
|
460
462
|
|
|
461
463
|
expect(result.structuredContent.success).toBe(true);
|
|
462
464
|
const stored = await getTaskById(task.id);
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
+
// The card answers "when", so there is nothing left to truncate: no note
|
|
466
|
+
// fragment, no ellipsis, no mid-sentence cut.
|
|
467
|
+
expect(stored?.output).not.toContain("x");
|
|
465
468
|
expect(stored?.output).not.toContain("second line is dropped");
|
|
466
|
-
expect(stored?.output).toContain(
|
|
469
|
+
expect(stored?.output).not.toContain("…");
|
|
467
470
|
|
|
468
471
|
// The full untrimmed note still reaches the task log and the wake-up task.
|
|
469
472
|
const logs = (await getLogsByTaskId(task.id)).filter(
|
|
@@ -489,7 +492,7 @@ describe("defer-task handler", () => {
|
|
|
489
492
|
const stored = await getTaskById(task.id);
|
|
490
493
|
// A real timezone is on file: no explicit "UTC" label on the time.
|
|
491
494
|
expect(stored?.output).toMatch(
|
|
492
|
-
/^
|
|
495
|
+
/^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2}$/,
|
|
493
496
|
);
|
|
494
497
|
});
|
|
495
498
|
|
|
@@ -562,4 +565,100 @@ describe("defer-task handler", () => {
|
|
|
562
565
|
const stored = await getTaskById(task.id);
|
|
563
566
|
expect(stored?.status).toBe("completed");
|
|
564
567
|
});
|
|
568
|
+
|
|
569
|
+
test("a wakeOn defer names who it is waiting on, with the ceiling as the fallback", async () => {
|
|
570
|
+
const watchedOwner = (
|
|
571
|
+
await createAgent({
|
|
572
|
+
name: "Researcher",
|
|
573
|
+
description: "Agent whose task is watched",
|
|
574
|
+
role: "worker",
|
|
575
|
+
isLead: false,
|
|
576
|
+
status: "busy",
|
|
577
|
+
maxTasks: 1,
|
|
578
|
+
capabilities: [],
|
|
579
|
+
})
|
|
580
|
+
).id;
|
|
581
|
+
const watched = await startedTask("the work being waited on", watchedOwner);
|
|
582
|
+
const task = await startedTask("waiting on a sibling");
|
|
583
|
+
|
|
584
|
+
const result = (await buildTool().handler(
|
|
585
|
+
{
|
|
586
|
+
taskId: task.id,
|
|
587
|
+
delayMs: 1_800_000,
|
|
588
|
+
summary: SUMMARY,
|
|
589
|
+
note: "internal handoff note that must not appear",
|
|
590
|
+
wakeOn: { event: "settled", taskIds: [watched.id] },
|
|
591
|
+
},
|
|
592
|
+
meta(),
|
|
593
|
+
)) as DeferTaskResult;
|
|
594
|
+
|
|
595
|
+
expect(result.structuredContent.success).toBe(true);
|
|
596
|
+
const stored = await getTaskById(task.id);
|
|
597
|
+
// Event-based AND time-based: `delayMs`/`runAt` is required even with
|
|
598
|
+
// wakeOn, so the ceiling is always the honest fallback.
|
|
599
|
+
expect(stored?.output).toMatch(
|
|
600
|
+
/^Waiting on Researcher — or (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC at the latest$/,
|
|
601
|
+
);
|
|
602
|
+
expect(stored?.output).not.toContain("internal handoff note");
|
|
603
|
+
expect(stored?.output).not.toContain(watched.id);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test("a wakeOn defer on unassigned tasks falls back to a count, not a UUID", async () => {
|
|
607
|
+
const watched = await createTaskExtended("unassigned watched work", { source: "mcp" });
|
|
608
|
+
const task = await startedTask("waiting on an unassigned task");
|
|
609
|
+
|
|
610
|
+
await buildTool().handler(
|
|
611
|
+
{
|
|
612
|
+
taskId: task.id,
|
|
613
|
+
delayMs: 60_000,
|
|
614
|
+
summary: SUMMARY,
|
|
615
|
+
note: "pending",
|
|
616
|
+
wakeOn: { event: "settled", taskIds: [watched.id] },
|
|
617
|
+
},
|
|
618
|
+
meta(),
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
const stored = await getTaskById(task.id);
|
|
622
|
+
expect(stored?.output).toStartWith("Waiting on 1 task — or ");
|
|
623
|
+
expect(stored?.output).not.toContain(watched.id);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("the schedule keeps the delay that was actually requested", async () => {
|
|
627
|
+
const byDelay = await startedTask("delay provenance");
|
|
628
|
+
await buildTool().handler(
|
|
629
|
+
{ taskId: byDelay.id, delayMs: 1_800_000, summary: SUMMARY, note: "pending" },
|
|
630
|
+
meta(),
|
|
631
|
+
);
|
|
632
|
+
const delaySchedule = (await schedulesForTask(byDelay.id))[0]!;
|
|
633
|
+
expect(delaySchedule.requestedDelayMs).toBe(1_800_000);
|
|
634
|
+
expect(delaySchedule.requestedRunAt).toBeUndefined();
|
|
635
|
+
|
|
636
|
+
const runAt = new Date(Date.now() + 3_600_000).toISOString();
|
|
637
|
+
const byRunAt = await startedTask("runAt provenance");
|
|
638
|
+
await buildTool().handler(
|
|
639
|
+
{ taskId: byRunAt.id, runAt, summary: SUMMARY, note: "pending" },
|
|
640
|
+
meta(),
|
|
641
|
+
);
|
|
642
|
+
const runAtSchedule = (await schedulesForTask(byRunAt.id))[0]!;
|
|
643
|
+
expect(runAtSchedule.requestedRunAt).toBe(runAt);
|
|
644
|
+
expect(runAtSchedule.requestedDelayMs).toBeUndefined();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
test("deferredAt marks the deferring task, and a plain completion leaves it unset", async () => {
|
|
648
|
+
const deferred = await startedTask("ends in a deferral");
|
|
649
|
+
await buildTool().handler(
|
|
650
|
+
{ taskId: deferred.id, delayMs: 60_000, summary: SUMMARY, note: "pending" },
|
|
651
|
+
meta(),
|
|
652
|
+
);
|
|
653
|
+
expect((await getTaskById(deferred.id))?.deferredAt).toBeTruthy();
|
|
654
|
+
|
|
655
|
+
// Negative control: the same tag on a task that simply finished. A wake-up
|
|
656
|
+
// task inherits `deferred` from its schedule, so the tag alone cannot tell
|
|
657
|
+
// "parked" from "done" — only `deferredAt` can.
|
|
658
|
+
const finished = await startedTask("ends normally");
|
|
659
|
+
await completeTask(finished.id, "done", { addTags: ["deferred"] });
|
|
660
|
+
const storedFinished = await getTaskById(finished.id);
|
|
661
|
+
expect(storedFinished?.tags).toContain("deferred");
|
|
662
|
+
expect(storedFinished?.deferredAt).toBeUndefined();
|
|
663
|
+
});
|
|
565
664
|
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmod, mkdtemp, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { checkProviderCredentials } from "../commands/provider-credentials";
|
|
6
|
+
import { createProviderAdapter } from "../providers";
|
|
7
|
+
import { checkDshCredentials, DshAdapter } from "../providers/dsh-adapter";
|
|
8
|
+
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
9
|
+
import { DEFAULT_MODEL_TIER_MAP } from "../types";
|
|
10
|
+
import { getModelAwareCredentialVars } from "../utils/credentials";
|
|
11
|
+
import { resolveHarnessProvider } from "../utils/harness-provider";
|
|
12
|
+
import { DEFAULT_OPENROUTER_BASE_URL } from "../utils/openrouter-base-url";
|
|
13
|
+
|
|
14
|
+
const directories: string[] = [];
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(
|
|
17
|
+
directories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function fixture(mode = "success"): Promise<ProviderSessionConfig> {
|
|
22
|
+
const cwd = await mkdtemp(join(tmpdir(), "dsh-adapter-test-"));
|
|
23
|
+
directories.push(cwd);
|
|
24
|
+
const binary = join(cwd, "dsh");
|
|
25
|
+
await Bun.write(
|
|
26
|
+
binary,
|
|
27
|
+
`#!${process.execPath}
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const patchPath = args[args.indexOf("--patch") + 1];
|
|
30
|
+
const patch = await Bun.file(patchPath).json();
|
|
31
|
+
const prompt = await Bun.stdin.text();
|
|
32
|
+
await Bun.write("invocation.json", JSON.stringify({ args, patchPath, patch, prompt, cwd: process.cwd() }));
|
|
33
|
+
const emit = (event) => console.log(JSON.stringify(event));
|
|
34
|
+
emit({ type: "session", sessionId: "dsh-test-session" });
|
|
35
|
+
const mode = process.env.DSH_TEST_MODE;
|
|
36
|
+
if (mode === "abort") { setInterval(() => {}, 1000); }
|
|
37
|
+
else {
|
|
38
|
+
if (mode === "malformed") console.log("bad json");
|
|
39
|
+
emit({ type: "status", phase: "turn_end", reason: { kind: mode === "failure" ? "error" : "completed" } });
|
|
40
|
+
if (mode !== "missing-final") {
|
|
41
|
+
// A split UTF-8 character plus a final line without a newline exercises both decoders.
|
|
42
|
+
const line = Buffer.from(JSON.stringify({ type: "final", text: "Done ✓" }));
|
|
43
|
+
const split = line.indexOf(Buffer.from("✓")) + 1;
|
|
44
|
+
process.stdout.write(line.subarray(0, split));
|
|
45
|
+
await Bun.sleep(10);
|
|
46
|
+
process.stdout.write(line.subarray(split));
|
|
47
|
+
}
|
|
48
|
+
if (mode === "failure") { console.error(process.env.DEEPSEEK_API_KEY); process.exitCode = 7; }
|
|
49
|
+
}
|
|
50
|
+
`,
|
|
51
|
+
);
|
|
52
|
+
await chmod(binary, 0o700);
|
|
53
|
+
return {
|
|
54
|
+
prompt: "--not-a-flag\nKeep $() and `quotes` literal.",
|
|
55
|
+
systemPrompt: "System instructions:\n!!js must remain text",
|
|
56
|
+
model: "deepseek-v4-pro",
|
|
57
|
+
role: "worker",
|
|
58
|
+
agentId: "agent-test",
|
|
59
|
+
taskId: "task-test",
|
|
60
|
+
apiUrl: "http://unused.invalid",
|
|
61
|
+
apiKey: "unused",
|
|
62
|
+
cwd,
|
|
63
|
+
logFile: join(cwd, "log.jsonl"),
|
|
64
|
+
env: {
|
|
65
|
+
DSH_BINARY: binary,
|
|
66
|
+
DEEPSEEK_API_KEY: "dsh-test-credential-value",
|
|
67
|
+
OPENROUTER_API_KEY: "",
|
|
68
|
+
OPENROUTER_BASE_URL: "",
|
|
69
|
+
DSH_TEST_MODE: mode,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("dsh harness", () => {
|
|
75
|
+
test("registry, model defaults, and credential dispatch select dsh", async () => {
|
|
76
|
+
expect(await createProviderAdapter("dsh")).toBeInstanceOf(DshAdapter);
|
|
77
|
+
expect(resolveHarnessProvider({ HARNESS_PROVIDER: "dsh" }, {})).toBe("dsh");
|
|
78
|
+
expect(DEFAULT_MODEL_TIER_MAP.dsh).toEqual(DEFAULT_MODEL_TIER_MAP.pi);
|
|
79
|
+
expect(checkDshCredentials({ OPENAI_API_KEY: "unrelated" }).ready).toBe(false);
|
|
80
|
+
expect(checkDshCredentials({ DEEPSEEK_API_KEY: " " }).ready).toBe(false);
|
|
81
|
+
expect(checkDshCredentials({}).missing).toEqual(["DEEPSEEK_API_KEY", "OPENROUTER_API_KEY"]);
|
|
82
|
+
expect((await checkProviderCredentials("dsh", { OPENROUTER_API_KEY: "present" })).ready).toBe(
|
|
83
|
+
true,
|
|
84
|
+
);
|
|
85
|
+
expect(getModelAwareCredentialVars("dsh", DEFAULT_MODEL_TIER_MAP.dsh.ultra)).toEqual([
|
|
86
|
+
"OPENROUTER_API_KEY",
|
|
87
|
+
]);
|
|
88
|
+
expect(getModelAwareCredentialVars("dsh", "deepseek-v4-pro")).toEqual(["DEEPSEEK_API_KEY"]);
|
|
89
|
+
expect((await checkProviderCredentials("dsh", { DEEPSEEK_API_KEY: "present" })).ready).toBe(
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("passes prompt on stdin, patches model/system, preserves cwd and cleans up", async () => {
|
|
95
|
+
const config = await fixture();
|
|
96
|
+
const session = await new DshAdapter().createSession(config);
|
|
97
|
+
const events: ProviderEvent[] = [];
|
|
98
|
+
session.onEvent((event) => events.push(event));
|
|
99
|
+
expect(await session.waitForCompletion()).toMatchObject({
|
|
100
|
+
exitCode: 0,
|
|
101
|
+
isError: false,
|
|
102
|
+
output: "Done ✓",
|
|
103
|
+
sessionId: "dsh-test-session",
|
|
104
|
+
});
|
|
105
|
+
const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
106
|
+
expect(invocation.prompt).toBe(config.prompt);
|
|
107
|
+
expect(invocation.cwd).toBe(config.cwd);
|
|
108
|
+
expect(invocation.args).toEqual([
|
|
109
|
+
"--profile",
|
|
110
|
+
"headless",
|
|
111
|
+
"--patch",
|
|
112
|
+
invocation.patchPath,
|
|
113
|
+
"--json",
|
|
114
|
+
"-",
|
|
115
|
+
]);
|
|
116
|
+
expect(invocation.patch).toContainEqual({
|
|
117
|
+
id: "agent-default-model",
|
|
118
|
+
config: { provider: "deepseek-official", model: config.model },
|
|
119
|
+
});
|
|
120
|
+
expect(invocation.patch).toContainEqual({
|
|
121
|
+
id: "system-prompt",
|
|
122
|
+
config: { personaPrefix: config.systemPrompt },
|
|
123
|
+
});
|
|
124
|
+
expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
|
|
125
|
+
expect(events).toContainEqual({
|
|
126
|
+
type: "session_init",
|
|
127
|
+
sessionId: "dsh-test-session",
|
|
128
|
+
provider: "dsh",
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
for (const model of [
|
|
133
|
+
DEFAULT_MODEL_TIER_MAP.dsh.regular,
|
|
134
|
+
DEFAULT_MODEL_TIER_MAP.dsh.ultra,
|
|
135
|
+
"openrouter/vendor/new-model",
|
|
136
|
+
]) {
|
|
137
|
+
test(`routes ${model} through OpenRouter with only its key`, async () => {
|
|
138
|
+
const config = await fixture();
|
|
139
|
+
config.model = model;
|
|
140
|
+
config.env = {
|
|
141
|
+
...config.env,
|
|
142
|
+
DEEPSEEK_API_KEY: "",
|
|
143
|
+
OPENROUTER_API_KEY: "openrouter-test-key",
|
|
144
|
+
};
|
|
145
|
+
const session = await new DshAdapter().createSession(config);
|
|
146
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
147
|
+
const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
148
|
+
const modelId = model.slice("openrouter/".length);
|
|
149
|
+
expect(patch).toContainEqual({
|
|
150
|
+
id: "agent-default-model",
|
|
151
|
+
config: { provider: "openrouter", model: modelId },
|
|
152
|
+
});
|
|
153
|
+
expect(patch).toContainEqual({
|
|
154
|
+
id: "llm-pi-ai",
|
|
155
|
+
config: {
|
|
156
|
+
providers: {
|
|
157
|
+
openrouter: {
|
|
158
|
+
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
159
|
+
baseURL: DEFAULT_OPENROUTER_BASE_URL,
|
|
160
|
+
api: "openai-completions",
|
|
161
|
+
models: [{ id: modelId }],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
expect(JSON.stringify(patch)).not.toContain("openrouter-test-key");
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
test("defaults to OpenRouter and honors its base URL when both keys exist", async () => {
|
|
171
|
+
const config = await fixture();
|
|
172
|
+
config.model = undefined;
|
|
173
|
+
config.env = {
|
|
174
|
+
...config.env,
|
|
175
|
+
OPENROUTER_API_KEY: "openrouter-test-key",
|
|
176
|
+
OPENROUTER_BASE_URL: " https://gateway.example/proxy/v1/// ",
|
|
177
|
+
};
|
|
178
|
+
const session = await new DshAdapter().createSession(config);
|
|
179
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
180
|
+
const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
181
|
+
expect(patch[0].config).toEqual({
|
|
182
|
+
provider: "openrouter",
|
|
183
|
+
model: "deepseek/deepseek-v4.1-flash",
|
|
184
|
+
});
|
|
185
|
+
expect(patch[2].config.providers.openrouter.baseURL).toBe("https://gateway.example/proxy/v1");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("bare models keep direct DeepSeek when both keys exist", async () => {
|
|
189
|
+
const config = await fixture();
|
|
190
|
+
config.env = { ...config.env, OPENROUTER_API_KEY: "openrouter-test-key" };
|
|
191
|
+
const session = await new DshAdapter().createSession(config);
|
|
192
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
193
|
+
const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
194
|
+
expect(patch[0].config.provider).toBe("deepseek-official");
|
|
195
|
+
expect(patch[2]).toEqual({ id: "llm-deepseek", config: { apiKeyEnv: "DEEPSEEK_API_KEY" } });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("fails closed for absent or mismatched credentials and empty model IDs", async () => {
|
|
199
|
+
const config = await fixture();
|
|
200
|
+
config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "" };
|
|
201
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow(
|
|
202
|
+
"DEEPSEEK_API_KEY or OPENROUTER_API_KEY",
|
|
203
|
+
);
|
|
204
|
+
config.env.DEEPSEEK_API_KEY = "direct-test-key";
|
|
205
|
+
config.model = DEFAULT_MODEL_TIER_MAP.dsh.regular;
|
|
206
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow(
|
|
207
|
+
"requires OPENROUTER_API_KEY",
|
|
208
|
+
);
|
|
209
|
+
config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "openrouter-test-key" };
|
|
210
|
+
config.model = "deepseek-v4-pro";
|
|
211
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow(
|
|
212
|
+
"requires DEEPSEEK_API_KEY",
|
|
213
|
+
);
|
|
214
|
+
config.model = "openrouter/";
|
|
215
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow("requires a model ID");
|
|
216
|
+
expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("fails closed when only npx is installed", async () => {
|
|
220
|
+
const config = await fixture();
|
|
221
|
+
await rename(join(config.cwd, "dsh"), join(config.cwd, "npx"));
|
|
222
|
+
config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
|
|
223
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow(
|
|
224
|
+
"dsh CLI not found. Install @deepseek-ai/dsh@0.1.7-alpha.2",
|
|
225
|
+
);
|
|
226
|
+
expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("uses a preinstalled dsh from PATH without DSH_BINARY", async () => {
|
|
230
|
+
const config = await fixture();
|
|
231
|
+
config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
|
|
232
|
+
const session = await new DshAdapter().createSession(config);
|
|
233
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
234
|
+
const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
235
|
+
expect(invocation.args[0]).toBe("--profile");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("does not fall back when an explicit DSH_BINARY is missing", async () => {
|
|
239
|
+
const config = await fixture();
|
|
240
|
+
config.env = { ...config.env, DSH_BINARY: join(config.cwd, "missing"), PATH: config.cwd };
|
|
241
|
+
await expect(new DshAdapter().createSession(config)).rejects.toThrow();
|
|
242
|
+
expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
for (const mode of ["failure", "missing-final", "malformed"]) {
|
|
246
|
+
test(`reports ${mode} as failure even with output or exit zero`, async () => {
|
|
247
|
+
const config = await fixture(mode);
|
|
248
|
+
const session = await new DshAdapter().createSession(config);
|
|
249
|
+
const events: ProviderEvent[] = [];
|
|
250
|
+
session.onEvent((event) => events.push(event));
|
|
251
|
+
const result = await session.waitForCompletion();
|
|
252
|
+
expect(result.isError).toBe(true);
|
|
253
|
+
expect(result.exitCode).toBe(mode === "failure" ? 7 : 1);
|
|
254
|
+
expect(JSON.stringify({ result, events })).not.toContain(config.env!.DEEPSEEK_API_KEY);
|
|
255
|
+
const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
256
|
+
expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
test("abort terminates a running process and removes its patch", async () => {
|
|
261
|
+
const config = await fixture("abort");
|
|
262
|
+
const session = await new DshAdapter().createSession(config);
|
|
263
|
+
await new Promise<void>((resolve) =>
|
|
264
|
+
session.onEvent((event) => {
|
|
265
|
+
if (event.type === "session_init") resolve();
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
await session.abort();
|
|
269
|
+
expect(await session.waitForCompletion()).toMatchObject({
|
|
270
|
+
isError: true,
|
|
271
|
+
failureReason: "dsh session aborted",
|
|
272
|
+
});
|
|
273
|
+
const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
274
|
+
expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -85,11 +85,11 @@ describe("nightly report", () => {
|
|
|
85
85
|
});
|
|
86
86
|
expect(report.ok).toBe(true);
|
|
87
87
|
expect(report.missingLegs).toEqual([]);
|
|
88
|
-
expect(report.totalCostUsd).toBeCloseTo(0.
|
|
88
|
+
expect(report.totalCostUsd).toBeCloseTo(0.0738, 6);
|
|
89
89
|
expect(report.warnings).toEqual([]);
|
|
90
90
|
const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
|
|
91
91
|
expect(markdown).toContain("## Nightly E2E: PASS");
|
|
92
|
-
expect(markdown).toContain("
|
|
92
|
+
expect(markdown).toContain("6/6 harness legs passed");
|
|
93
93
|
expect(markdown).toContain("| claude | claude-model | PASS | 1 |");
|
|
94
94
|
expect(markdown).not.toContain("### Warnings");
|
|
95
95
|
});
|
|
@@ -159,7 +159,7 @@ describe("nightly report", () => {
|
|
|
159
159
|
results: [contractResult(), harnessResult([leg("claude")]), harnessResult([failed])],
|
|
160
160
|
});
|
|
161
161
|
expect(report.ok).toBe(false);
|
|
162
|
-
expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode"]);
|
|
162
|
+
expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode", "dsh"]);
|
|
163
163
|
expect(report.warnings).toContain("pi: no result file from the leg.");
|
|
164
164
|
expect(report.warnings).toContain("codex: needed 2 attempts.");
|
|
165
165
|
const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
|
|
@@ -43,6 +43,7 @@ describe("e2e redaction", () => {
|
|
|
43
43
|
const saved = { ...process.env };
|
|
44
44
|
try {
|
|
45
45
|
process.env.OPENROUTER_API_KEY = "example-sk-or-v1-known-value-1234";
|
|
46
|
+
process.env.DEEPSEEK_API_KEY = "deepseek-known-value-1234";
|
|
46
47
|
process.env.CODEX_OAUTH = JSON.stringify({
|
|
47
48
|
access: "access-token-value-1234",
|
|
48
49
|
refresh: "refresh-token-value-1234",
|
|
@@ -51,6 +52,7 @@ describe("e2e redaction", () => {
|
|
|
51
52
|
});
|
|
52
53
|
const known = knownSecrets(["extra-secret-value"]);
|
|
53
54
|
expect(known).toContain("example-sk-or-v1-known-value-1234");
|
|
55
|
+
expect(known).toContain("deepseek-known-value-1234");
|
|
54
56
|
expect(known).toContain("access-token-value-1234");
|
|
55
57
|
expect(known).toContain("refresh-token-value-1234");
|
|
56
58
|
expect(known).toContain("extra-secret-value");
|
|
@@ -62,6 +62,12 @@ describe("swarm-config-guard: Configuration-page value validation", () => {
|
|
|
62
62
|
expect(validateConfigValue("SLACK_THREAD_STEERING_MODE", "now")).toContain("must be one of");
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
test("Slack transport is constrained", () => {
|
|
66
|
+
expect(validateConfigValue("SLACK_MODE", "socket")).toBeNull();
|
|
67
|
+
expect(validateConfigValue("SLACK_MODE", "http")).toBeNull();
|
|
68
|
+
expect(validateConfigValue("SLACK_MODE", "webhook")).toContain("must be one of");
|
|
69
|
+
});
|
|
70
|
+
|
|
65
71
|
test("Slack reaction shortcode keys accept bare and colon-wrapped names", () => {
|
|
66
72
|
const keys = [
|
|
67
73
|
"SLACK_REACTION_ACCEPTED",
|