@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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { unlinkSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
closeDb,
|
|
5
5
|
completeTask,
|
|
6
6
|
createAgent,
|
|
7
7
|
createTaskExtended,
|
|
8
|
+
createUser,
|
|
8
9
|
getChildTasks,
|
|
9
10
|
getLatestActiveTaskInThread,
|
|
10
11
|
getLogsByTaskId,
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
|
|
16
17
|
process.env.SLACK_RENDER_V2 = "false";
|
|
17
18
|
|
|
19
|
+
import { linkIdentity } from "../be/users";
|
|
20
|
+
import * as slackApp from "../slack/app";
|
|
18
21
|
import { buildTreeBlocks } from "../slack/blocks";
|
|
19
22
|
import { routeMessage } from "../slack/router";
|
|
20
23
|
import { formatSlackSteeringAck, requestSlackThreadSteering } from "../slack/steering";
|
|
@@ -37,6 +40,7 @@ async function createRunningSlackTask(
|
|
|
37
40
|
channelId: string,
|
|
38
41
|
threadTs: string,
|
|
39
42
|
task = "existing Slack task",
|
|
43
|
+
requestedByUserId?: string,
|
|
40
44
|
) {
|
|
41
45
|
const created = await createTaskExtended(task, {
|
|
42
46
|
agentId,
|
|
@@ -44,6 +48,7 @@ async function createRunningSlackTask(
|
|
|
44
48
|
slackChannelId: channelId,
|
|
45
49
|
slackThreadTs: threadTs,
|
|
46
50
|
slackUserId: "U_REQUESTER",
|
|
51
|
+
requestedByUserId,
|
|
47
52
|
});
|
|
48
53
|
const started = await startTask(created.id);
|
|
49
54
|
expect(started?.status).toBe("in_progress");
|
|
@@ -220,6 +225,69 @@ describe("Slack thread steering", () => {
|
|
|
220
225
|
expect(await getChildTasks(leadTask.id)).toEqual([]);
|
|
221
226
|
});
|
|
222
227
|
|
|
228
|
+
test("buffered replies resolve registered senders and keep mixed authors separate", async () => {
|
|
229
|
+
process.env.SLACK_THREAD_STEERING = "lead";
|
|
230
|
+
process.env.SLACK_THREAD_STEERING_MODE = "queue";
|
|
231
|
+
const channelId = "C_STEER_SENDERS";
|
|
232
|
+
const threadTs = "5100.0001";
|
|
233
|
+
const taras = await createUser({ name: "Taras", email: "taras-steering@example.com" });
|
|
234
|
+
const other = await createUser({ name: "Other", email: "other-steering@example.com" });
|
|
235
|
+
const task = await createRunningSlackTask(leadId, channelId, threadTs, undefined, other.id);
|
|
236
|
+
await linkIdentity(other.id, "slack", "U_OTHER_STEER", { kind: "system", id: "test" });
|
|
237
|
+
// Taras has no Slack alias yet: exercise the same email cascade as task creation.
|
|
238
|
+
const infoUsers: string[] = [];
|
|
239
|
+
const appSpy = spyOn(slackApp, "getSlackApp").mockReturnValue({
|
|
240
|
+
client: {
|
|
241
|
+
users: {
|
|
242
|
+
info: async ({ user }: { user: string }) => {
|
|
243
|
+
infoUsers.push(user);
|
|
244
|
+
return {
|
|
245
|
+
user: { profile: user === "U_TARAS_STEER" ? { email: taras.email } : {} },
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
chat: { postMessage: async () => ({ ok: true }) },
|
|
250
|
+
},
|
|
251
|
+
} as unknown as NonNullable<ReturnType<typeof slackApp.getSlackApp>>);
|
|
252
|
+
try {
|
|
253
|
+
bufferThreadMessage(channelId, threadTs, "first", "U_TARAS_STEER", "5100.0002");
|
|
254
|
+
bufferThreadMessage(channelId, threadTs, "second", "U_TARAS_STEER", "5100.0003");
|
|
255
|
+
bufferThreadMessage(channelId, threadTs, "other correction", "U_OTHER_STEER", "5100.0004");
|
|
256
|
+
bufferThreadMessage(
|
|
257
|
+
channelId,
|
|
258
|
+
threadTs,
|
|
259
|
+
"unmapped correction",
|
|
260
|
+
"U_UNKNOWN_STEER",
|
|
261
|
+
"5100.0005",
|
|
262
|
+
);
|
|
263
|
+
bufferThreadMessage(channelId, threadTs, "last", "U_TARAS_STEER", "5100.0006");
|
|
264
|
+
await instantFlush(`${channelId}:${threadTs}`);
|
|
265
|
+
|
|
266
|
+
const messages = await getSteeringMessagesForTask(task.id);
|
|
267
|
+
expect(messages).toHaveLength(4);
|
|
268
|
+
for (const [body, userId, label] of [
|
|
269
|
+
["first\n---\nsecond", taras.id, "Taras (user)"],
|
|
270
|
+
["other correction", other.id, "Other (user)"],
|
|
271
|
+
["unmapped correction", undefined, "Unknown (user)"],
|
|
272
|
+
["last", taras.id, "Taras (user)"],
|
|
273
|
+
]) {
|
|
274
|
+
expect(messages.find((message) => message.body === body)).toMatchObject({
|
|
275
|
+
source: "slack",
|
|
276
|
+
createdByKind: "user",
|
|
277
|
+
createdByUserId: userId,
|
|
278
|
+
senderLabel: label,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
expect(infoUsers).toEqual(["U_TARAS_STEER", "U_UNKNOWN_STEER"]);
|
|
282
|
+
expect(
|
|
283
|
+
(await getLogsByTaskId(task.id)).filter((log) => log.newValue === "slack_reaction"),
|
|
284
|
+
).toHaveLength(5);
|
|
285
|
+
expect(await getChildTasks(task.id)).toEqual([]);
|
|
286
|
+
} finally {
|
|
287
|
+
appSpy.mockRestore();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
223
291
|
test("all mode targets the latest active task", async () => {
|
|
224
292
|
process.env.SLACK_THREAD_STEERING = "all";
|
|
225
293
|
const channelId = "C_STEER_ALL";
|
|
@@ -1,25 +1,46 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { slackTaskOutput } from "../slack/task-output";
|
|
3
3
|
|
|
4
|
+
/** The shape `defer-task` writes today: the human card, already rendered. */
|
|
4
5
|
const task = {
|
|
5
6
|
status: "completed" as const,
|
|
6
7
|
tags: ["deferred"],
|
|
8
|
+
output: "Waiting on Researcher — or tomorrow at 17:30 at the latest",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** The shape written before defer-task stored the card directly. */
|
|
12
|
+
const legacy = {
|
|
13
|
+
...task,
|
|
7
14
|
output:
|
|
8
15
|
"Deferred until tomorrow 17:30 UTC ([12345678](https://example.com/schedules/12345678)) -> check the build",
|
|
9
16
|
};
|
|
10
17
|
|
|
11
18
|
describe("Slack deferral output", () => {
|
|
12
|
-
test("
|
|
13
|
-
expect(slackTaskOutput(task)).toBe(
|
|
14
|
-
|
|
19
|
+
test("posts the stored deferral card verbatim", () => {
|
|
20
|
+
expect(slackTaskOutput(task)).toBe(
|
|
21
|
+
"Waiting on Researcher — or tomorrow at 17:30 at the latest",
|
|
22
|
+
);
|
|
23
|
+
expect(slackTaskOutput({ ...task, output: "Checking back today at 00:31" })).toBe(
|
|
24
|
+
"Checking back today at 00:31",
|
|
25
|
+
);
|
|
15
26
|
});
|
|
16
27
|
|
|
17
|
-
test("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
21
|
-
expect(
|
|
22
|
-
|
|
28
|
+
test("rewrites a legacy notice to its ETA, dropping the note and the schedule link", () => {
|
|
29
|
+
const rendered = slackTaskOutput(legacy);
|
|
30
|
+
expect(rendered).toBe("Checking back tomorrow 17:30 UTC");
|
|
31
|
+
expect(rendered).not.toContain("check the build");
|
|
32
|
+
expect(rendered).not.toContain("schedules/12345678");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a legacy notice with no pending text still yields its ETA, not an empty card", () => {
|
|
36
|
+
expect(
|
|
37
|
+
slackTaskOutput({ ...legacy, output: legacy.output.replace("check the build", "") }),
|
|
38
|
+
).toBe("Checking back tomorrow 17:30 UTC");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("an arrow inside a legacy note does not extend the ETA", () => {
|
|
42
|
+
expect(slackTaskOutput({ ...legacy, output: `${legacy.output} (CI) -> deploy` })).toBe(
|
|
43
|
+
"Checking back tomorrow 17:30 UTC",
|
|
23
44
|
);
|
|
24
45
|
});
|
|
25
46
|
|
|
@@ -28,9 +49,9 @@ describe("Slack deferral output", () => {
|
|
|
28
49
|
});
|
|
29
50
|
|
|
30
51
|
test("preserves schema output and non-deferral output verbatim", () => {
|
|
31
|
-
expect(slackTaskOutput({ ...
|
|
32
|
-
expect(slackTaskOutput({ ...
|
|
33
|
-
expect(slackTaskOutput({ ...
|
|
52
|
+
expect(slackTaskOutput({ ...legacy, outputSchema: { type: "string" } })).toBe(legacy.output);
|
|
53
|
+
expect(slackTaskOutput({ ...legacy, tags: [] })).toBe(legacy.output);
|
|
54
|
+
expect(slackTaskOutput({ ...legacy, status: "in_progress" })).toBe(legacy.output);
|
|
34
55
|
expect(slackTaskOutput({ ...task, output: undefined })).toBeUndefined();
|
|
35
56
|
});
|
|
36
57
|
});
|
package/src/tests/status.test.ts
CHANGED
|
@@ -96,6 +96,8 @@ const ENV_KEYS_TO_RESET = [
|
|
|
96
96
|
"DEVIN_ORG_ID",
|
|
97
97
|
"SLACK_BOT_TOKEN",
|
|
98
98
|
"SLACK_APP_TOKEN",
|
|
99
|
+
"SLACK_SIGNING_SECRET",
|
|
100
|
+
"SLACK_MODE",
|
|
99
101
|
"SLACK_DISABLE",
|
|
100
102
|
"GITHUB_WEBHOOK_SECRET",
|
|
101
103
|
"GITHUB_APP_ID",
|
|
@@ -409,20 +411,38 @@ describe("setup milestones", () => {
|
|
|
409
411
|
});
|
|
410
412
|
});
|
|
411
413
|
|
|
412
|
-
test("slack:
|
|
414
|
+
test("slack: reports mode-aware credentials as configured, not live-verified", async () => {
|
|
413
415
|
process.env.SLACK_BOT_TOKEN = "xoxb-test";
|
|
414
416
|
const a = await buildStatusPayload();
|
|
415
417
|
expect(getMilestone(a, "slack").state).toBe("unverified");
|
|
416
418
|
|
|
417
419
|
process.env.SLACK_APP_TOKEN = "xapp-test";
|
|
418
420
|
const b = await buildStatusPayload();
|
|
419
|
-
expect(getMilestone(b, "slack").state).toBe("
|
|
421
|
+
expect(getMilestone(b, "slack").state).toBe("configured");
|
|
422
|
+
expect(getMilestone(b, "slack").hint).toContain("not been verified");
|
|
423
|
+
|
|
424
|
+
process.env.SLACK_MODE = "http";
|
|
425
|
+
delete process.env.SLACK_APP_TOKEN;
|
|
426
|
+
process.env.SLACK_SIGNING_SECRET = "synthetic-signing-secret";
|
|
427
|
+
const http = await buildStatusPayload();
|
|
428
|
+
expect(getMilestone(http, "slack").state).toBe("unverified");
|
|
429
|
+
expect(getMilestone(http, "slack").hint).toContain("unavailable");
|
|
420
430
|
|
|
421
431
|
process.env.SLACK_DISABLE = "true";
|
|
422
432
|
const c = await buildStatusPayload();
|
|
423
433
|
expect(getMilestone(c, "slack").state).toBe("unverified");
|
|
424
434
|
});
|
|
425
435
|
|
|
436
|
+
test("slack: invalid mode fails closed with a specific hint", async () => {
|
|
437
|
+
process.env.SLACK_MODE = "webhook";
|
|
438
|
+
process.env.SLACK_BOT_TOKEN = "xoxb-test";
|
|
439
|
+
process.env.SLACK_APP_TOKEN = "xapp-test";
|
|
440
|
+
|
|
441
|
+
const milestone = getMilestone(await buildStatusPayload(), "slack");
|
|
442
|
+
expect(milestone.state).toBe("unverified");
|
|
443
|
+
expect(milestone.hint).toContain("Invalid SLACK_MODE");
|
|
444
|
+
});
|
|
445
|
+
|
|
426
446
|
test("github: needs webhook secret + app id + private key", async () => {
|
|
427
447
|
process.env.GITHUB_WEBHOOK_SECRET = "secret";
|
|
428
448
|
process.env.GITHUB_APP_ID = "12345";
|
|
@@ -144,6 +144,41 @@ describe("task steering core", () => {
|
|
|
144
144
|
return started!;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
test("sender labels resolve on task reads, worker polls, and lifecycle responses", async () => {
|
|
148
|
+
const task = await runningTask("pi", "sender labels");
|
|
149
|
+
const user = await createUser({ name: "Taras" });
|
|
150
|
+
const unnamed = await createUser({ name: " " });
|
|
151
|
+
for (const [creator, expected] of [
|
|
152
|
+
[{ createdByKind: "user", createdByUserId: user.id }, "Taras (user)"],
|
|
153
|
+
[
|
|
154
|
+
{ createdByKind: "agent", createdByAgentId: agentIds.get("lead")! },
|
|
155
|
+
"Steering lead (agent)",
|
|
156
|
+
],
|
|
157
|
+
[{ createdByKind: "system" }, "system"],
|
|
158
|
+
[{ createdByKind: "user", createdByUserId: unnamed.id }, `${unnamed.id} (user)`],
|
|
159
|
+
[{ createdByKind: "agent", createdByAgentId: "deleted-agent" }, "deleted-agent (agent)"],
|
|
160
|
+
[{ createdByKind: "user" }, "Unknown (user)"],
|
|
161
|
+
] as const) {
|
|
162
|
+
const message = await createSteeringMessage({
|
|
163
|
+
taskId: task.id,
|
|
164
|
+
body: "sender test",
|
|
165
|
+
mode: "queue",
|
|
166
|
+
source: "api",
|
|
167
|
+
...creator,
|
|
168
|
+
});
|
|
169
|
+
expect(message.senderLabel).toBe(expected);
|
|
170
|
+
expect((await getSteeringMessageById(message.id))?.senderLabel).toBe(expected);
|
|
171
|
+
const taskRead = await api("GET", `/api/tasks/${task.id}/steering-messages`);
|
|
172
|
+
expect(taskRead.status).toBe(200);
|
|
173
|
+
expect(taskRead.body.messages).toContainEqual(
|
|
174
|
+
expect.objectContaining({ id: message.id, senderLabel: expected }),
|
|
175
|
+
);
|
|
176
|
+
expect(await getPendingSteeringForAgent(agentIds.get("pi")!)).toContainEqual(message);
|
|
177
|
+
expect((await markSteeringDelivered(message.id, "queue"))?.senderLabel).toBe(expected);
|
|
178
|
+
expect((await markSteeringHandled(message.id))?.senderLabel).toBe(expected);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
147
182
|
test("row lifecycle transitions pending -> delivered -> handled", async () => {
|
|
148
183
|
const task = await runningTask("pi", "lifecycle");
|
|
149
184
|
const created = await createSteeringMessage({
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { configureDbResolver, resetDbResolver } from "../prompts/resolver";
|
|
3
|
+
import { renderSteeringDelivery } from "../prompts/steering-delivery";
|
|
4
|
+
|
|
5
|
+
afterEach(resetDbResolver);
|
|
6
|
+
|
|
7
|
+
describe("steering delivery sender", () => {
|
|
8
|
+
test.each([
|
|
9
|
+
"Taras (user)",
|
|
10
|
+
"Lead (agent)",
|
|
11
|
+
"system",
|
|
12
|
+
"deleted-user (user)",
|
|
13
|
+
])("renders %s and preserves the acknowledgement marker", async (sender) => {
|
|
14
|
+
const id = crypto.randomUUID();
|
|
15
|
+
expect(await renderSteeringDelivery(id, "change course", sender)).toContain(
|
|
16
|
+
`[steering ${id}] From ${sender}: change course`,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("sender is a template variable and whitespace is flattened", async () => {
|
|
21
|
+
configureDbResolver(() => ({
|
|
22
|
+
template: { id: "custom", scope: "global", body: "{{sender}}: {{body}}" },
|
|
23
|
+
}));
|
|
24
|
+
expect(await renderSteeringDelivery("id", "body", "Taras\n(user)")).toBe("Taras (user): body");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("falls back to the bare body on skipped, blank, and failing templates", async () => {
|
|
28
|
+
for (const resolver of [
|
|
29
|
+
() => ({ skip: true as const }),
|
|
30
|
+
() => ({ template: { id: "blank", scope: "global", body: " " } }),
|
|
31
|
+
() => {
|
|
32
|
+
throw new Error("unavailable");
|
|
33
|
+
},
|
|
34
|
+
]) {
|
|
35
|
+
configureDbResolver(resolver);
|
|
36
|
+
expect(await renderSteeringDelivery("id", "body", "Taras (user)")).toBe("body");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -190,7 +190,7 @@ describe("steering worker transport", () => {
|
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
test("delivers pending rows once and reports the adapter's actual mode", async () => {
|
|
193
|
-
const pending = pendingMessage();
|
|
193
|
+
const pending = pendingMessage({ createdByKind: "agent", senderLabel: "Lead (agent)" });
|
|
194
194
|
const nonPending = pendingMessage({ id: crypto.randomUUID(), status: "delivered" });
|
|
195
195
|
const deliveries: Array<{ mode: string; text: string }> = [];
|
|
196
196
|
const reports: Array<{ path: string; body: unknown }> = [];
|
|
@@ -220,7 +220,7 @@ describe("steering worker transport", () => {
|
|
|
220
220
|
// The body is wrapped in the delivery envelope, which MUST carry the
|
|
221
221
|
// steering message ID — `accept-steer` needs it, and it is the only route
|
|
222
222
|
// to `handled`. Without it the agent obeys but can never acknowledge.
|
|
223
|
-
expect(deliveries[0]?.text).toContain("change course");
|
|
223
|
+
expect(deliveries[0]?.text).toContain("From Lead (agent): change course");
|
|
224
224
|
expect(deliveries[0]?.text).toContain(pending.id);
|
|
225
225
|
expect(deliveries[0]?.text).toContain("accept-steer");
|
|
226
226
|
expect(state.dispatchedIds.has(pending.id)).toBe(true);
|
|
@@ -73,6 +73,8 @@ async function pins(client: Client): Promise<string[]> {
|
|
|
73
73
|
.sort();
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Eight real MCP sessions approach the 10s CI default under parallel runner load.
|
|
77
|
+
// Keep the session-lifetime assertions together with room for connection setup.
|
|
76
78
|
test("wire response is default-on, per-task, additive, and fixed for one MCP session", async () => {
|
|
77
79
|
const task = await createTaskExtended("review fixture", { agentId, taskType: "review" });
|
|
78
80
|
const other = await createTaskExtended("other fixture", { agentId, taskType: "other" });
|
|
@@ -115,7 +117,7 @@ test("wire response is default-on, per-task, additive, and fixed for one MCP ses
|
|
|
115
117
|
await configure("TASK_TOOL_PRELOAD_ENABLED", "true");
|
|
116
118
|
await configure("TASK_TOOL_MANIFESTS", "invalid deployment JSON");
|
|
117
119
|
expect(await pins(await connect(task.id))).toEqual([]);
|
|
118
|
-
});
|
|
120
|
+
}, 30_000);
|
|
119
121
|
|
|
120
122
|
test("a manifest cannot expand the scripts-only capability surface", async () => {
|
|
121
123
|
const task = await createTaskExtended("script fixture", { agentId, taskType: "script" });
|
|
@@ -794,6 +794,24 @@ describe("initTelemetry", () => {
|
|
|
794
794
|
).toBe(false);
|
|
795
795
|
});
|
|
796
796
|
|
|
797
|
+
test("Slack HTTP mode uses the signing secret instead of an app token", () => {
|
|
798
|
+
expect(
|
|
799
|
+
_hasSlackChannel({
|
|
800
|
+
SLACK_MODE: "http",
|
|
801
|
+
SLACK_BOT_TOKEN: "xoxb-1",
|
|
802
|
+
SLACK_SIGNING_SECRET: "synthetic-signing-secret",
|
|
803
|
+
}),
|
|
804
|
+
).toBe(true);
|
|
805
|
+
expect(_hasSlackChannel({ SLACK_MODE: "http", SLACK_BOT_TOKEN: "xoxb-1" })).toBe(false);
|
|
806
|
+
expect(
|
|
807
|
+
_hasSlackChannel({
|
|
808
|
+
SLACK_MODE: "invalid",
|
|
809
|
+
SLACK_BOT_TOKEN: "xoxb-1",
|
|
810
|
+
SLACK_APP_TOKEN: "xapp-1",
|
|
811
|
+
}),
|
|
812
|
+
).toBe(false);
|
|
813
|
+
});
|
|
814
|
+
|
|
797
815
|
test("Email requires AGENTMAIL_WEBHOOK_SECRET", () => {
|
|
798
816
|
expect(_hasEmailChannel({})).toBe(false);
|
|
799
817
|
expect(_hasEmailChannel({ AGENTMAIL_WEBHOOK_SECRET: "whsec_1" })).toBe(true);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { afterAll, beforeAll, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createWorkflow,
|
|
6
|
+
createWorkflowRun,
|
|
7
|
+
deleteWorkflow,
|
|
8
|
+
getWorkflowRun,
|
|
9
|
+
getWorkflowRunStepsByRunId,
|
|
10
|
+
initDb,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import { codeLevelTriage } from "../heartbeat/heartbeat";
|
|
13
|
+
import { getExecutorRegistry } from "../workflows";
|
|
14
|
+
import { isWorkflowRunActive, walkGraph } from "../workflows/engine";
|
|
15
|
+
import { ScriptExecutor } from "../workflows/executors/script";
|
|
16
|
+
import { recoverIncompleteRuns } from "../workflows/recovery";
|
|
17
|
+
|
|
18
|
+
const dbPath = `./test-workflow-live-recovery-${crypto.randomUUID()}.sqlite`;
|
|
19
|
+
const workflowIds: string[] = [];
|
|
20
|
+
const output = { exitCode: 0, stdout: "done", stderr: "" };
|
|
21
|
+
|
|
22
|
+
beforeAll(() => initDb(dbPath));
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
for (const id of workflowIds) await deleteWorkflow(id);
|
|
25
|
+
closeDb();
|
|
26
|
+
for (const suffix of ["", "-wal", "-shm"]) await unlink(dbPath + suffix).catch(() => {});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("periodic heartbeat leaves an in-flight long script owned and activates its successor once", async () => {
|
|
30
|
+
const workflow = await createWorkflow({
|
|
31
|
+
name: "live recovery regression",
|
|
32
|
+
definition: {
|
|
33
|
+
nodes: [
|
|
34
|
+
{
|
|
35
|
+
id: "long-script",
|
|
36
|
+
type: "script",
|
|
37
|
+
config: { runtime: "bash", script: "sleep 90", timeout: 120_000 },
|
|
38
|
+
next: "successor",
|
|
39
|
+
},
|
|
40
|
+
{ id: "successor", type: "script", config: { runtime: "bash", script: "echo done" } },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
workflowIds.push(workflow.id);
|
|
45
|
+
const runId = crypto.randomUUID();
|
|
46
|
+
await createWorkflowRun({ id: runId, workflowId: workflow.id, triggerType: "manual" });
|
|
47
|
+
const entered = Promise.withResolvers<void>();
|
|
48
|
+
const release = Promise.withResolvers<void>();
|
|
49
|
+
const calls: string[] = [];
|
|
50
|
+
// Hold the executor at an explicit barrier instead of sleeping for 90 seconds.
|
|
51
|
+
// A duplicate invocation returns immediately so the broken recovery fails
|
|
52
|
+
// assertions rather than hanging the test.
|
|
53
|
+
const executor = spyOn(ScriptExecutor.prototype, "run").mockImplementation(async (input) => {
|
|
54
|
+
calls.push(input.meta.nodeId);
|
|
55
|
+
if (calls.length === 1) {
|
|
56
|
+
entered.resolve();
|
|
57
|
+
await release.promise;
|
|
58
|
+
}
|
|
59
|
+
return { status: "success", output };
|
|
60
|
+
});
|
|
61
|
+
const walking = walkGraph(
|
|
62
|
+
workflow.definition,
|
|
63
|
+
runId,
|
|
64
|
+
{},
|
|
65
|
+
[workflow.definition.nodes[0]!],
|
|
66
|
+
getExecutorRegistry(),
|
|
67
|
+
workflow.id,
|
|
68
|
+
);
|
|
69
|
+
try {
|
|
70
|
+
await entered.promise;
|
|
71
|
+
for (let sweep = 0; sweep < 2; sweep++) {
|
|
72
|
+
const findings = await codeLevelTriage();
|
|
73
|
+
expect(findings.staleCleanup.workflowRuns).toBe(0);
|
|
74
|
+
const run = await getWorkflowRun(runId);
|
|
75
|
+
expect(run?.status).toBe("running");
|
|
76
|
+
expect(run?.finishedAt).toBeFalsy();
|
|
77
|
+
expect(calls).toEqual(["long-script"]);
|
|
78
|
+
}
|
|
79
|
+
release.resolve();
|
|
80
|
+
await walking;
|
|
81
|
+
expect(calls).toEqual(["long-script", "successor"]);
|
|
82
|
+
expect((await getWorkflowRun(runId))?.status).toBe("completed");
|
|
83
|
+
expect((await getWorkflowRunStepsByRunId(runId)).map((step) => step.status)).toEqual([
|
|
84
|
+
"completed",
|
|
85
|
+
"completed",
|
|
86
|
+
]);
|
|
87
|
+
expect(isWorkflowRunActive(runId)).toBe(false);
|
|
88
|
+
} finally {
|
|
89
|
+
release.resolve();
|
|
90
|
+
await walking;
|
|
91
|
+
executor.mockRestore();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("an interrupted run without a live walk still recovers and executes its successor", async () => {
|
|
96
|
+
const workflow = await createWorkflow({
|
|
97
|
+
name: "interrupted recovery control",
|
|
98
|
+
definition: {
|
|
99
|
+
nodes: [
|
|
100
|
+
{
|
|
101
|
+
id: "first",
|
|
102
|
+
type: "script",
|
|
103
|
+
config: { runtime: "bash", script: "echo first" },
|
|
104
|
+
next: "last",
|
|
105
|
+
},
|
|
106
|
+
{ id: "last", type: "script", config: { runtime: "bash", script: "echo last" } },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
workflowIds.push(workflow.id);
|
|
111
|
+
const runId = crypto.randomUUID();
|
|
112
|
+
await createWorkflowRun({ id: runId, workflowId: workflow.id, triggerType: "manual" });
|
|
113
|
+
const calls: string[] = [];
|
|
114
|
+
const executor = spyOn(ScriptExecutor.prototype, "run").mockImplementation(async (input) => {
|
|
115
|
+
calls.push(input.meta.nodeId);
|
|
116
|
+
return { status: "success", output };
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
expect(await recoverIncompleteRuns(getExecutorRegistry())).toBe(1);
|
|
120
|
+
expect(calls).toEqual(["first", "last"]);
|
|
121
|
+
expect((await getWorkflowRun(runId))?.status).toBe("completed");
|
|
122
|
+
expect(isWorkflowRunActive(runId)).toBe(false);
|
|
123
|
+
} finally {
|
|
124
|
+
executor.mockRestore();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
@@ -11,7 +11,7 @@ describe("resolveWorkflowLlmConfig", () => {
|
|
|
11
11
|
expect(config).toEqual({
|
|
12
12
|
apiKey: "sk-or-test",
|
|
13
13
|
baseURL: "https://gateway.example.test/v1",
|
|
14
|
-
model: "
|
|
14
|
+
model: "deepseek/deepseek-v4.1-flash",
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -23,7 +23,7 @@ describe("resolveWorkflowLlmConfig", () => {
|
|
|
23
23
|
expect(config).toEqual({
|
|
24
24
|
apiKey: "example-sk-openai-test",
|
|
25
25
|
baseURL: undefined,
|
|
26
|
-
model: "gpt-
|
|
26
|
+
model: "gpt-6-luna",
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -34,7 +34,7 @@ describe("resolveWorkflowLlmConfig", () => {
|
|
|
34
34
|
const config = await resolveWorkflowLlmConfig(undefined, {
|
|
35
35
|
OPENAI_API_KEY: "example-sk-openai-test",
|
|
36
36
|
});
|
|
37
|
-
expect(config.model).toBe("gpt-
|
|
37
|
+
expect(config.model).toBe("gpt-6-luna");
|
|
38
38
|
} finally {
|
|
39
39
|
if (previous === undefined) delete process.env.MEMORY_RATER_MODEL;
|
|
40
40
|
else process.env.MEMORY_RATER_MODEL = previous;
|