@desplega.ai/agent-swarm 1.136.0 → 1.138.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 +12 -3
- package/dist/acp-adapter-6nttxe2q.js +4787 -0
- package/dist/{actions-agcbc8nd.js → actions-z9dzgh3f.js} +7 -8
- package/dist/{app-g2s78xya.js → app-wb6bc7tt.js} +4 -5
- package/dist/{assistant-yd3jkb9d.js → assistant-08pd6pgy.js} +9 -10
- package/dist/{boot-reembed-6spqw4q0.js → boot-reembed-hstr4e2c.js} +4 -4
- package/dist/{boot-reembed-b22s9w5x.js → boot-reembed-y7p7qjbf.js} +5 -5
- package/dist/{boot-scrub-logs-6h3cqkc0.js → boot-scrub-logs-azbnxwhn.js} +3 -3
- package/dist/{claude-adapter-ng5y8zd7.js → claude-adapter-rwaq6eda.js} +10 -9
- package/dist/{claude-managed-adapter-5zr3n86v.js → claude-managed-adapter-vtseey1x.js} +3 -3
- package/dist/{cli-qckgdqmr.js → cli-005yr57a.js} +2 -2
- package/dist/{cli-54ht104h.js → cli-0cka8dnm.js} +3 -3
- package/dist/{cli-gpdtxzar.js → cli-23cpnqk9.js} +1 -1
- package/dist/{cli-qhfkxrpg.js → cli-2ypjdmqg.js} +223 -474
- package/dist/{cli-bnq8h7ea.js → cli-3719ggyf.js} +17 -10
- package/dist/cli-390sgxn5.js +257 -0
- package/dist/{cli-z6e8eqrm.js → cli-47esr8ht.js} +2 -2
- package/dist/{cli-jjhnma34.js → cli-4jaj24py.js} +248 -101
- package/dist/{cli-6gwgzdgw.js → cli-69377c2a.js} +5 -5
- package/dist/{cli-ads0hfw2.js → cli-6v4d8r8c.js} +205 -75
- package/dist/{cli-mgef08pc.js → cli-8rtyss6g.js} +1 -1
- package/dist/{cli-35jknh9e.js → cli-9w7vsmbm.js} +5 -5
- package/dist/{cli-m3z53bed.js → cli-b4zdc3x6.js} +1 -1
- package/dist/{cli-fpjmcbhz.js → cli-ba87cw29.js} +4 -4
- package/dist/{cli-4gzv54ss.js → cli-bzk75kdc.js} +43 -11
- package/dist/{cli-d5ygfv18.js → cli-cywkhzqs.js} +35 -11
- package/dist/{cli-0crn5kaz.js → cli-d62cd9qs.js} +16 -4
- package/dist/{cli-5bnz1t86.js → cli-f9skf1v7.js} +51 -7
- package/dist/{cli-06ghj6pe.js → cli-fytpjraz.js} +4 -4
- package/dist/{cli-j7qp8gtf.js → cli-h7c5dam1.js} +16 -11
- package/dist/{cli-4r4jkqep.js → cli-kkn46gfr.js} +9 -3
- package/dist/{cli-tmn19cdz.js → cli-kq9htgbr.js} +415 -62
- package/dist/{cli-8v57p983.js → cli-pkmqqxp8.js} +45 -69
- package/dist/{cli-84vrhesc.js → cli-qf34ej1d.js} +1 -1
- package/dist/{cli-v3qda694.js → cli-rnyttbh0.js} +0 -5
- package/dist/cli-rx5cxzn2.js +7 -0
- package/dist/{cli-cw70hymf.js → cli-s25avqg9.js} +2 -0
- package/dist/{cli-tee8w7tq.js → cli-s6t1qv67.js} +15 -10
- package/dist/{cli-68vayryq.js → cli-tb4csb08.js} +2 -2
- package/dist/{cli-y16cesaq.js → cli-vy48y9bx.js} +2 -2
- package/dist/{cli-3y553gws.js → cli-y294regh.js} +1 -1
- package/dist/{cli-spf767vc.js → cli-y43043ws.js} +1 -1
- package/dist/{cli-hz5hp6wn.js → cli-yh2eybyt.js} +1 -1
- package/dist/{cli-edwrx42s.js → cli-zmyxt25p.js} +5 -5
- package/dist/cli.js +40 -18
- package/dist/{codex-adapter-kw11ps3j.js → codex-adapter-yzep6mvb.js} +7 -6
- package/dist/{codex-session-runner-sffb69v7.js → codex-session-runner-25e8tn72.js} +7 -6
- package/dist/{commands-0xxp4tz8.js → commands-0vv72d6a.js} +3 -3
- package/dist/{db-0npp39zd.js → db-x61ncfa1.js} +19 -3
- package/dist/{google-generative-ai-6xxdj4t3.js → google-generative-ai-zyhy3dab.js} +1 -1
- package/dist/{google-vertex-zma18wb0.js → google-vertex-t25h65yd.js} +1 -1
- package/dist/{handlers-5p7a1jx6.js → handlers-fwdcaahf.js} +9 -10
- package/dist/{hook-jjpxn4se.js → hook-mpwfgynm.js} +6 -5
- package/dist/{hook-hpefr0a8.js → hook-x18504ja.js} +8 -7
- package/dist/{http-wfp33653.js → http-83m6qdds.js} +590 -192
- package/dist/{index-pe58mf1r.js → index-1nsdwvn5.js} +2 -1
- package/dist/{index-17h657rg.js → index-38f98ck3.js} +9 -9
- package/dist/{index-m2p7ctyh.js → index-7kxvtd38.js} +24 -12
- package/dist/{index-racb4wfq.js → index-a4naswjn.js} +23 -10
- package/dist/{index-p13cfsc2.js → index-agykc6k2.js} +2 -1
- package/dist/{index-zh66wm65.js → index-dawtzfhw.js} +22 -9
- package/dist/{keepalive-wxjd633a.js → keepalive-y0m8g5wn.js} +5 -5
- package/dist/{lead-359ec2z0.js → lead-ka18ef6a.js} +36 -36
- package/dist/{maintenance-rr03qjtb.js → maintenance-x585p56c.js} +5 -5
- package/dist/{oauth-refresh-sweep-jwj22yb8.js → oauth-refresh-sweep-kdm9vj7a.js} +4 -4
- package/dist/{onboard-asnhchcv.js → onboard-5v229nvt.js} +664 -248
- package/dist/{opencode-adapter-sx38y7f7.js → opencode-adapter-46nnmnvv.js} +3 -3
- package/dist/{otel-impl-54cra6mw.js → otel-impl-k47aj4sd.js} +73 -4
- package/dist/{pi-mono-adapter-29g7axgj.js → pi-mono-adapter-wawfj09v.js} +42 -40
- package/dist/{pricing-refresh-mkyy7c2y.js → pricing-refresh-ky2gjp05.js} +5 -5
- package/dist/{rbac-roles-kb3afm95.js → rbac-roles-0j0f7s8r.js} +4 -4
- package/dist/{rbac-roles-ak56tr75.js → rbac-roles-az4wc15e.js} +3 -3
- package/dist/{render-v2-434asg49.js → render-v2-seyxq61z.js} +4 -5
- package/dist/{seed-pricing-nbmd7ah8.js → seed-pricing-fev611hx.js} +4 -4
- package/dist/{setup-wh4qmjs4.js → setup-hy404zwh.js} +2 -2
- package/dist/{worker-j1d27tjv.js → worker-g1j5hn16.js} +36 -36
- package/openapi.json +197 -27
- package/package.json +9 -3
- package/src/be/db-client.ts +58 -5
- package/src/be/db-retention.ts +550 -121
- package/src/be/db.ts +367 -55
- package/src/be/memory/raters/retrieval.ts +40 -0
- package/src/be/migrations/140_approval_request_cancelled_status.sql +64 -0
- package/src/be/pricing-normalize.ts +3 -0
- package/src/be/script-connections.ts +26 -6
- package/src/be/seed-pricing.ts +18 -8
- package/src/be/swarm-config-guard.ts +33 -0
- package/src/cli.tsx +20 -1
- package/src/commands/onboard/compose-generator.ts +63 -7
- package/src/commands/onboard/env-generator.ts +32 -4
- package/src/commands/onboard/manifest.ts +4 -1
- package/src/commands/onboard/non-interactive.ts +159 -0
- package/src/commands/onboard/steps/generate.tsx +1 -34
- package/src/commands/onboard/steps/harness-credentials.tsx +181 -1
- package/src/commands/onboard/steps/harness.tsx +20 -6
- package/src/commands/onboard/steps/review.tsx +3 -2
- package/src/commands/onboard/types.ts +48 -1
- package/src/commands/onboard.tsx +18 -13
- package/src/commands/provider-credentials.ts +17 -3
- package/src/commands/runner.ts +1 -0
- package/src/heartbeat/heartbeat.ts +47 -7
- package/src/http/approval-requests.ts +53 -8
- package/src/http/index.ts +9 -1
- package/src/http/mcp-servers.ts +2 -0
- package/src/http/memory.ts +27 -1
- package/src/http/oauth-callback.ts +33 -0
- package/src/http/repos.ts +6 -0
- package/src/http/script-connections.ts +77 -0
- package/src/http/session-data.ts +5 -1
- package/src/http/stats.ts +21 -30
- package/src/http/tasks.ts +10 -0
- package/src/oauth/presets.ts +29 -0
- package/src/otel-impl.ts +96 -1
- package/src/otel.ts +34 -0
- package/src/prompts/memories.ts +2 -2
- package/src/providers/acp-adapter.ts +325 -0
- package/src/providers/acp-swarm-events.ts +113 -0
- package/src/providers/acp-targets.ts +103 -0
- package/src/providers/claude-managed-models.ts +17 -1
- package/src/providers/codex-adapter.ts +3 -3
- package/src/providers/codex-oauth/auth-json-fs.ts +2 -2
- package/src/providers/codex-oauth/home.ts +13 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/types.ts +1 -1
- package/src/server-user.ts +1 -0
- package/src/slack/app.ts +5 -1
- package/src/tasks/worker-follow-up.ts +78 -7
- package/src/telemetry.ts +151 -0
- package/src/tests/acp-adapter.test.ts +192 -0
- package/src/tests/acp-swarm-events.test.ts +132 -0
- package/src/tests/agents-list-model-display.test.ts +20 -0
- package/src/tests/approval-cancellation-migration.test.ts +134 -0
- package/src/tests/approval-requests.test.ts +456 -2
- package/src/tests/apps-spike2.test.ts +4 -1
- package/src/tests/apps-sync-engine.test.ts +22 -18
- package/src/tests/claude-managed-adapter.test.ts +12 -0
- package/src/tests/codex-oauth-auth-json-fs.test.ts +19 -0
- package/src/tests/connection-embedded-auth.test.ts +4 -1
- package/src/tests/context-window.test.ts +2 -0
- package/src/tests/credential-check.test.ts +6 -0
- package/src/tests/db-client-busy-retry.test.ts +18 -0
- package/src/tests/db-client.test.ts +23 -0
- package/src/tests/db-retention-telemetry.test.ts +569 -0
- package/src/tests/db-retention.test.ts +456 -117
- package/src/tests/heartbeat.test.ts +140 -0
- package/src/tests/memory-http-recall-gating.test.ts +120 -13
- package/src/tests/memory-store.test.ts +41 -0
- package/src/tests/migration-046-budgets.test.ts +27 -5
- package/src/tests/oauth-presets.test.ts +17 -5
- package/src/tests/onboard-compose.test.ts +99 -0
- package/src/tests/onboard-env.test.ts +43 -0
- package/src/tests/onboard-manifest.test.ts +17 -1
- package/src/tests/onboard-non-interactive.test.ts +89 -0
- package/src/tests/pool-affinity.test.ts +100 -0
- package/src/tests/promote-draft-task-route.test.ts +32 -1
- package/src/tests/sandbox-spawn-test-helpers.ts +3 -0
- package/src/tests/sandboxed-process.test.ts +2 -2
- package/src/tests/schedule-target-type.test.ts +6 -3
- package/src/tests/script-connections-http.test.ts +4 -3
- package/src/tests/script-connections.test.ts +6 -1
- package/src/tests/script-workflows-runtime-e2e.test.ts +36 -28
- package/src/tests/scripts-external-api.test.ts +8 -5
- package/src/tests/scripts-http.test.ts +6 -3
- package/src/tests/scripts-mcp-e2e.test.ts +65 -60
- package/src/tests/scripts-retention.test.ts +6 -3
- package/src/tests/scripts-runtime-identity.test.ts +4 -2
- package/src/tests/scripts-runtime.test.ts +18 -16
- package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
- package/src/tests/slack-read-boundaries.test.ts +3 -1
- package/src/tests/task-lifecycle-telemetry.test.ts +1 -0
- package/src/tests/task-tools-ctx.test.ts +88 -1
- package/src/tests/telemetry-init.test.ts +61 -0
- package/src/tests/tool-annotations.test.ts +4 -1
- package/src/tests/vendored-openapi.test.ts +15 -2
- package/src/tests/workflow-e2e.test.ts +4 -1
- package/src/tests/workflow-engine-v2.test.ts +5 -2
- package/src/tests/workflow-executors.test.ts +24 -19
- package/src/tests/workflow-http-v2.test.ts +17 -2
- package/src/tests/workflow-llm.test.ts +57 -0
- package/src/tests/workflow-retry-v2.test.ts +142 -1
- package/src/tests/workflow-swarm-script.test.ts +20 -17
- package/src/tools/get-task-details.ts +1 -0
- package/src/tools/mcp-servers/mcp-server-create.ts +7 -1
- package/src/tools/memory-search.ts +27 -2
- package/src/tools/send-task.ts +28 -10
- package/src/tools/task-action.ts +13 -5
- package/src/types.ts +31 -2
- package/src/utils/context-window.ts +2 -0
- package/src/workflows/approval-notifications.ts +72 -0
- package/src/workflows/checkpoint.ts +16 -9
- package/src/workflows/completed-step-routing.ts +31 -0
- package/src/workflows/engine.ts +59 -43
- package/src/workflows/executors/human-in-the-loop.ts +38 -4
- package/src/workflows/executors/raw-llm.ts +8 -6
- package/src/workflows/executors/validate.ts +6 -5
- package/src/workflows/executors/workflow-llm.ts +35 -0
- package/src/workflows/resume.ts +90 -13
- package/src/workflows/retry-poller.ts +8 -1
- package/vendored-openapi/README.md +4 -2
- package/vendored-openapi/manifest.json +33 -0
- package/vendored-openapi/microsoft-graph.json +781 -0
- package/dist/cli-5tnr64pm.js +0 -29
- package/dist/{anthropic-messages-pm7pp23c.js → anthropic-messages-tvgm75yc.js} +3 -3
- package/dist/{azure-openai-responses-cwesc845.js → azure-openai-responses-hed7waek.js} +4 -4
- package/dist/{cli-s9gpxh9q.js → cli-5kmzp1wx.js} +0 -0
- package/dist/{cli-s73zwwqn.js → cli-xjx3fbtn.js} +4 -4
- package/dist/{openai-codex-responses-xxca1qr1.js → openai-codex-responses-gv3n8rg0.js} +3 -3
- package/dist/{openai-completions-yzftqrft.js → openai-completions-dkrj9433.js} +8 -8
- package/dist/{openai-responses-e4dcwm9g.js → openai-responses-spk6t7kh.js} +11 -11
- package/dist/{openrouter-images-asqqnbvh.js → openrouter-images-71hthwh9.js} +3 -3
|
@@ -30,6 +30,9 @@ import {
|
|
|
30
30
|
SwarmScriptExecutor,
|
|
31
31
|
} from "../workflows/executors/swarm-script";
|
|
32
32
|
import { interpolate } from "../workflows/template";
|
|
33
|
+
import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
|
|
34
|
+
|
|
35
|
+
const skip = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
|
|
33
36
|
|
|
34
37
|
const TEST_DB_PATH = "./test-workflow-swarm-script.sqlite";
|
|
35
38
|
const API_KEY = "test-workflow-swarm-script-key-1234567890";
|
|
@@ -185,7 +188,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
185
188
|
).toBe(SWARM_SCRIPT_MAX_TIMEOUT_MS);
|
|
186
189
|
});
|
|
187
190
|
|
|
188
|
-
|
|
191
|
+
skip("A workflow with one swarm-script node resolves by name + runs + returns result", async () => {
|
|
189
192
|
await saveScript(
|
|
190
193
|
"scratch-add-one-a1b2c3d4",
|
|
191
194
|
`export default async (args: { value: number }) => ({ value: args.value + 1 });`,
|
|
@@ -248,7 +251,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
248
251
|
expect(result.output).toBeUndefined();
|
|
249
252
|
});
|
|
250
253
|
|
|
251
|
-
|
|
254
|
+
skip("named script source may contain literal mustache template data", async () => {
|
|
252
255
|
await saveScript(
|
|
253
256
|
"literal-mustache-source",
|
|
254
257
|
`export default async () => ({ template: "Hello {{customer_name}}" });`,
|
|
@@ -272,7 +275,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
272
275
|
expect(result.output?.result).toEqual({ template: "Hello {{customer_name}}" });
|
|
273
276
|
});
|
|
274
277
|
|
|
275
|
-
|
|
278
|
+
skip("unrelated upstream node names do not turn literal source templates into workflow tokens", async () => {
|
|
276
279
|
await saveScript(
|
|
277
280
|
"literal-upstream-name",
|
|
278
281
|
`export default async () => ({ template: "Hello {{customer.name}}" });`,
|
|
@@ -325,7 +328,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
325
328
|
expect(result.error).toContain("{{customer.name}}");
|
|
326
329
|
});
|
|
327
330
|
|
|
328
|
-
|
|
331
|
+
skip("swarm-script executor includes API connection descriptors in ctx.api", async () => {
|
|
329
332
|
await upsertScriptConnection({
|
|
330
333
|
slug: "workflowVendor",
|
|
331
334
|
kind: "openapi",
|
|
@@ -367,7 +370,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
367
370
|
expect(result.output?.result).toEqual({ apiKeys: ["workflowVendor"] });
|
|
368
371
|
});
|
|
369
372
|
|
|
370
|
-
|
|
373
|
+
skip("pinHash correctly resolves to a historic script_versions row", async () => {
|
|
371
374
|
const first = await saveScript("versioned", `export default async () => ({ version: "old" });`);
|
|
372
375
|
await saveScript("versioned", `export default async () => ({ version: "new" });`);
|
|
373
376
|
|
|
@@ -391,7 +394,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
391
394
|
expect(result.output?.version).toBe(1);
|
|
392
395
|
});
|
|
393
396
|
|
|
394
|
-
|
|
397
|
+
skip("inputs mapping from a predecessor node correctly populates args", async () => {
|
|
395
398
|
await saveScript(
|
|
396
399
|
"from-input",
|
|
397
400
|
`export default async (args: { value: string }) => ({ seen: args.value });`,
|
|
@@ -439,7 +442,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
439
442
|
expect(echoStep?.output).toEqual({ value: '{"a":1}' });
|
|
440
443
|
});
|
|
441
444
|
|
|
442
|
-
|
|
445
|
+
skip("{{path}} args: object arg is injected as raw object, not JSON string", async () => {
|
|
443
446
|
await saveScript(
|
|
444
447
|
"echo-obj",
|
|
445
448
|
`export default async (args: { data: Record<string, unknown> }) => ({ isObject: typeof args.data === "object" && !Array.isArray(args.data), keys: Object.keys(args.data ?? {}) });`,
|
|
@@ -467,7 +470,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
467
470
|
expect(scriptStep?.output).toMatchObject({ result: { isObject: true, keys: ["a", "b"] } });
|
|
468
471
|
});
|
|
469
472
|
|
|
470
|
-
|
|
473
|
+
skip("{{path}} args: array arg is injected as raw array, not JSON string", async () => {
|
|
471
474
|
await saveScript(
|
|
472
475
|
"echo-arr",
|
|
473
476
|
`export default async (args: { items: string[] }) => ({ isArray: Array.isArray(args.items), length: args.items.length });`,
|
|
@@ -495,7 +498,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
495
498
|
expect(scriptStep?.output).toMatchObject({ result: { isArray: true, length: 3 } });
|
|
496
499
|
});
|
|
497
500
|
|
|
498
|
-
|
|
501
|
+
skip("{{path}} args: empty array is injected as raw empty array with length 0, not '[]' string", async () => {
|
|
499
502
|
await saveScript(
|
|
500
503
|
"echo-empty-arr",
|
|
501
504
|
`export default async (args: { items: string[] }) => ({ isArray: Array.isArray(args.items), length: args.items.length });`,
|
|
@@ -523,7 +526,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
523
526
|
expect(scriptStep?.output).toMatchObject({ result: { isArray: true, length: 0 } });
|
|
524
527
|
});
|
|
525
528
|
|
|
526
|
-
|
|
529
|
+
skip("{{path}} args: string scalar arg is injected as the string value", async () => {
|
|
527
530
|
await saveScript(
|
|
528
531
|
"echo-str",
|
|
529
532
|
`export default async (args: { name: string }) => ({ isString: typeof args.name === "string", value: args.name });`,
|
|
@@ -557,7 +560,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
557
560
|
});
|
|
558
561
|
});
|
|
559
562
|
|
|
560
|
-
|
|
563
|
+
skip("{{path}} args: number scalar arg is injected as a number, not a string", async () => {
|
|
561
564
|
await saveScript(
|
|
562
565
|
"echo-num",
|
|
563
566
|
`export default async (args: { count: number }) => ({ isNumber: typeof args.count === "number", value: args.count });`,
|
|
@@ -585,7 +588,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
585
588
|
expect(scriptStep?.output).toMatchObject({ result: { isNumber: true, value: 3 } });
|
|
586
589
|
});
|
|
587
590
|
|
|
588
|
-
|
|
591
|
+
skip("{{path}} args: boolean scalar arg is injected as a boolean, not a string", async () => {
|
|
589
592
|
await saveScript(
|
|
590
593
|
"echo-bool",
|
|
591
594
|
`export default async (args: { enabled: boolean }) => ({ isBoolean: typeof args.enabled === "boolean", value: args.enabled });`,
|
|
@@ -613,7 +616,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
613
616
|
expect(scriptStep?.output).toMatchObject({ result: { isBoolean: true, value: false } });
|
|
614
617
|
});
|
|
615
618
|
|
|
616
|
-
|
|
619
|
+
skip("{{path}} args: mixed string template still produces a string via interpolation", async () => {
|
|
617
620
|
await saveScript(
|
|
618
621
|
"echo-mixed",
|
|
619
622
|
`export default async (args: { label: string }) => ({ isString: typeof args.label === "string", value: args.label });`,
|
|
@@ -643,7 +646,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
643
646
|
});
|
|
644
647
|
});
|
|
645
648
|
|
|
646
|
-
|
|
649
|
+
skip("fsMode workspace-rw is rejected at config validation with a clear error message", async () => {
|
|
647
650
|
await saveScript("noop", `export default async () => ({ ok: true });`);
|
|
648
651
|
const executor = new SwarmScriptExecutor(deps);
|
|
649
652
|
const wf = await makeWorkflow({ nodes: [] });
|
|
@@ -676,7 +679,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
676
679
|
expect(success.status).toBe("success");
|
|
677
680
|
});
|
|
678
681
|
|
|
679
|
-
|
|
682
|
+
skip("timeoutMs not set — script completes with the default 30s window", async () => {
|
|
680
683
|
await saveScript("quick", `export default async () => ({ done: true });`);
|
|
681
684
|
const executor = new SwarmScriptExecutor(deps);
|
|
682
685
|
const wf = await makeWorkflow({ nodes: [] });
|
|
@@ -696,7 +699,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
696
699
|
expect(result.output?.result).toEqual({ done: true });
|
|
697
700
|
});
|
|
698
701
|
|
|
699
|
-
|
|
702
|
+
skip("timeoutMs set — a long-running script is killed before it finishes", async () => {
|
|
700
703
|
await saveScript(
|
|
701
704
|
"sleeper",
|
|
702
705
|
`export default async () => { await new Promise(r => setTimeout(r, 3000)); return { done: true }; };`,
|
|
@@ -719,7 +722,7 @@ describe("SwarmScriptExecutor", () => {
|
|
|
719
722
|
expect(result.output?.exitCode).not.toBe(0);
|
|
720
723
|
});
|
|
721
724
|
|
|
722
|
-
|
|
725
|
+
skip("Failure in the script surfaces as a workflow-node failure", async () => {
|
|
723
726
|
await saveScript("throws", `export default async () => { throw new Error("boom"); };`);
|
|
724
727
|
const executor = new SwarmScriptExecutor(deps);
|
|
725
728
|
const wf = await makeWorkflow({ nodes: [] });
|
|
@@ -40,6 +40,7 @@ const looseRoutingAffinitySchema = z.looseObject({
|
|
|
40
40
|
role: z.string().optional(),
|
|
41
41
|
harnessProvider: z.string().optional(),
|
|
42
42
|
capabilities: z.array(z.string()).optional(),
|
|
43
|
+
leadOnly: z.boolean().optional(),
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
export const looseAgentTaskOutputSchema = z.looseObject({
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createMcpServer,
|
|
5
|
+
emitMcpServerConnectedTelemetry,
|
|
6
|
+
getAgentById,
|
|
7
|
+
installMcpServer,
|
|
8
|
+
} from "@/be/db";
|
|
4
9
|
import { can } from "@/rbac";
|
|
5
10
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
6
11
|
|
|
@@ -102,6 +107,7 @@ export const registerMcpServerCreateTool = (server: McpServer) => {
|
|
|
102
107
|
headerConfigKeys: args.headerConfigKeys,
|
|
103
108
|
extraAuthorizeParams: args.extraAuthorizeParams,
|
|
104
109
|
});
|
|
110
|
+
await emitMcpServerConnectedTelemetry();
|
|
105
111
|
|
|
106
112
|
// Auto-install for the creating agent
|
|
107
113
|
await installMcpServer(requestInfo.agentId, created.id);
|
|
@@ -4,7 +4,11 @@ import { getAgentById } from "@/be/db";
|
|
|
4
4
|
import { getEmbeddingProvider, getMemoryStore } from "@/be/memory";
|
|
5
5
|
import { CANDIDATE_SET_MULTIPLIER } from "@/be/memory/constants";
|
|
6
6
|
import { expandCandidatesWithGraph } from "@/be/memory/graph-expansion";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
dedupeMemoryDocumentIds,
|
|
9
|
+
recordMemoryAccesses,
|
|
10
|
+
recordRetrievals,
|
|
11
|
+
} from "@/be/memory/raters/retrieval";
|
|
8
12
|
import { rerank } from "@/be/memory/reranker";
|
|
9
13
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
10
14
|
import type { AgentMemorySource } from "@/types";
|
|
@@ -26,6 +30,7 @@ type MemorySearchResult = {
|
|
|
26
30
|
retrievalSource?: string;
|
|
27
31
|
tags?: string[];
|
|
28
32
|
createdAt: string;
|
|
33
|
+
accessCount?: number;
|
|
29
34
|
rateHint?: string;
|
|
30
35
|
};
|
|
31
36
|
|
|
@@ -57,6 +62,7 @@ export const memorySearchOutputSchema = swarmToolOutputSchema({
|
|
|
57
62
|
retrievalSource: z.enum(["vec", "fts", "hybrid", "fallback", "graph"]).optional(),
|
|
58
63
|
tags: z.array(z.string()).optional(),
|
|
59
64
|
createdAt: z.string().optional(),
|
|
65
|
+
accessCount: z.number().int().optional(),
|
|
60
66
|
rateHint: z.string().optional(),
|
|
61
67
|
}),
|
|
62
68
|
)
|
|
@@ -70,7 +76,8 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
70
76
|
title: "Search memories",
|
|
71
77
|
description:
|
|
72
78
|
"Search your accumulated memories using natural language. Returns summaries with IDs — use memory-get to retrieve full content.",
|
|
73
|
-
|
|
79
|
+
// Search updates access telemetry for every logical document returned.
|
|
80
|
+
annotations: { readOnlyHint: false },
|
|
74
81
|
|
|
75
82
|
inputSchema: z.object({
|
|
76
83
|
query: z.string().min(1).describe("Natural language search query."),
|
|
@@ -148,6 +155,14 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
148
155
|
}
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
const consumedIds = dedupeMemoryDocumentIds(ranked);
|
|
159
|
+
try {
|
|
160
|
+
await recordMemoryAccesses(consumedIds);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error("[memory-search] recordMemoryAccesses failed:", (err as Error).message);
|
|
163
|
+
}
|
|
164
|
+
const consumedIdSet = new Set(consumedIds);
|
|
165
|
+
|
|
151
166
|
const inTaskContext = !!requestInfo.sourceTaskId;
|
|
152
167
|
const mapped = ranked.map((r) => ({
|
|
153
168
|
id: r.id,
|
|
@@ -159,6 +174,7 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
159
174
|
retrievalSource: r.retrievalSource,
|
|
160
175
|
tags: r.tags,
|
|
161
176
|
createdAt: r.createdAt,
|
|
177
|
+
accessCount: (r.accessCount ?? 0) + (consumedIdSet.has(r.id) ? 1 : 0),
|
|
162
178
|
...(inTaskContext && NUDGE_ELIGIBLE_SOURCES.has(r.source as AgentMemorySource)
|
|
163
179
|
? { rateHint: rateHintFor(r.id) }
|
|
164
180
|
: {}),
|
|
@@ -180,6 +196,14 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
180
196
|
source,
|
|
181
197
|
});
|
|
182
198
|
|
|
199
|
+
const consumedIds = dedupeMemoryDocumentIds(recent);
|
|
200
|
+
try {
|
|
201
|
+
await recordMemoryAccesses(consumedIds);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error("[memory-search] recordMemoryAccesses failed:", (err as Error).message);
|
|
204
|
+
}
|
|
205
|
+
const consumedIdSet = new Set(consumedIds);
|
|
206
|
+
|
|
183
207
|
const mapped = recent.map((r) => ({
|
|
184
208
|
id: r.id,
|
|
185
209
|
name: r.name,
|
|
@@ -188,6 +212,7 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
188
212
|
scope: r.scope,
|
|
189
213
|
tags: r.tags,
|
|
190
214
|
createdAt: r.createdAt,
|
|
215
|
+
accessCount: (r.accessCount ?? 0) + (consumedIdSet.has(r.id) ? 1 : 0),
|
|
191
216
|
}));
|
|
192
217
|
|
|
193
218
|
return toolOk(
|
package/src/tools/send-task.ts
CHANGED
|
@@ -62,8 +62,12 @@ export const sendTaskInputSchema = z
|
|
|
62
62
|
requiredCapabilities: z
|
|
63
63
|
.array(z.string())
|
|
64
64
|
.optional()
|
|
65
|
+
.describe("Capabilities required for pool routing."),
|
|
66
|
+
leadOnly: z
|
|
67
|
+
.boolean()
|
|
68
|
+
.default(false)
|
|
65
69
|
.describe(
|
|
66
|
-
"
|
|
70
|
+
"Structured authorization constraint for merge or other privileged work. Only Lead agents may be assigned, offered, or claim it; never inferred from task text.",
|
|
67
71
|
),
|
|
68
72
|
priority: z.number().int().min(0).max(100).optional().describe("Priority 0-100 (default: 50)."),
|
|
69
73
|
dependsOn: z.array(z.uuid()).optional().describe("Task IDs this task depends on."),
|
|
@@ -198,6 +202,7 @@ export async function sendTaskHandler(
|
|
|
198
202
|
taskType,
|
|
199
203
|
tags,
|
|
200
204
|
requiredCapabilities,
|
|
205
|
+
leadOnly,
|
|
201
206
|
priority,
|
|
202
207
|
dependsOn,
|
|
203
208
|
dir,
|
|
@@ -240,6 +245,14 @@ export async function sendTaskHandler(
|
|
|
240
245
|
const effectiveParentTask = effectiveParentTaskId
|
|
241
246
|
? await getTaskById(effectiveParentTaskId)
|
|
242
247
|
: null;
|
|
248
|
+
if (effectiveParentTask?.routingAffinityInvalid) {
|
|
249
|
+
return toolErr("Cannot continue a task with an invalid routing affinity.", {
|
|
250
|
+
data: { yourAgentId: creatorAgentId },
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// A public continuation cannot accidentally declassify its parent before
|
|
254
|
+
// createTaskExtended performs the authoritative merge.
|
|
255
|
+
const effectiveLeadOnly = leadOnly || effectiveParentTask?.routingAffinity?.leadOnly === true;
|
|
243
256
|
|
|
244
257
|
// Slack-routing coherence guard: reject a hand-typed slackChannelId/slackThreadTs
|
|
245
258
|
// that disagrees with the parent task or the contextKey this child will inherit.
|
|
@@ -423,13 +436,10 @@ export async function sendTaskHandler(
|
|
|
423
436
|
slackUserId,
|
|
424
437
|
overrideSlackContext,
|
|
425
438
|
followUpConfig,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
routingAffinity: requiredCapabilities?.length
|
|
431
|
-
? { capabilities: requiredCapabilities }
|
|
432
|
-
: undefined,
|
|
439
|
+
routingAffinity:
|
|
440
|
+
effectiveLeadOnly || requiredCapabilities?.length
|
|
441
|
+
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
442
|
+
: undefined,
|
|
433
443
|
});
|
|
434
444
|
await transferTrackerSyncToResumeChild({
|
|
435
445
|
parentTaskId: effectiveParentTaskId,
|
|
@@ -453,10 +463,10 @@ export async function sendTaskHandler(
|
|
|
453
463
|
};
|
|
454
464
|
}
|
|
455
465
|
|
|
456
|
-
if (agent.isLead) {
|
|
466
|
+
if (effectiveLeadOnly && !agent.isLead) {
|
|
457
467
|
return {
|
|
458
468
|
success: false,
|
|
459
|
-
message: `
|
|
469
|
+
message: `Lead-only task requires a Lead agent; "${agent.name}" is not a Lead.`,
|
|
460
470
|
};
|
|
461
471
|
}
|
|
462
472
|
|
|
@@ -492,6 +502,10 @@ export async function sendTaskHandler(
|
|
|
492
502
|
slackUserId,
|
|
493
503
|
overrideSlackContext,
|
|
494
504
|
followUpConfig,
|
|
505
|
+
routingAffinity:
|
|
506
|
+
effectiveLeadOnly || requiredCapabilities?.length
|
|
507
|
+
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
508
|
+
: undefined,
|
|
495
509
|
});
|
|
496
510
|
await transferTrackerSyncToResumeChild({
|
|
497
511
|
parentTaskId: effectiveParentTaskId,
|
|
@@ -528,6 +542,10 @@ export async function sendTaskHandler(
|
|
|
528
542
|
slackUserId,
|
|
529
543
|
overrideSlackContext,
|
|
530
544
|
followUpConfig,
|
|
545
|
+
routingAffinity:
|
|
546
|
+
effectiveLeadOnly || requiredCapabilities?.length
|
|
547
|
+
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
548
|
+
: undefined,
|
|
531
549
|
});
|
|
532
550
|
await transferTrackerSyncToResumeChild({
|
|
533
551
|
parentTaskId: effectiveParentTaskId,
|
package/src/tools/task-action.ts
CHANGED
|
@@ -102,8 +102,12 @@ export const taskActionInputSchema = z.object({
|
|
|
102
102
|
requiredCapabilities: z
|
|
103
103
|
.array(z.string())
|
|
104
104
|
.optional()
|
|
105
|
+
.describe("Capabilities required for pool routing."),
|
|
106
|
+
leadOnly: z
|
|
107
|
+
.boolean()
|
|
108
|
+
.default(false)
|
|
105
109
|
.describe(
|
|
106
|
-
"
|
|
110
|
+
"Structured authorization constraint: only Lead agents may claim this privileged task.",
|
|
107
111
|
),
|
|
108
112
|
});
|
|
109
113
|
|
|
@@ -193,6 +197,7 @@ export async function taskActionHandler(
|
|
|
193
197
|
dir,
|
|
194
198
|
model,
|
|
195
199
|
requiredCapabilities,
|
|
200
|
+
leadOnly,
|
|
196
201
|
} = input;
|
|
197
202
|
const normalizedModel = splitLegacyModelAlias({ model, modelTier: input.modelTier });
|
|
198
203
|
|
|
@@ -296,9 +301,10 @@ export async function taskActionHandler(
|
|
|
296
301
|
model: normalizedModel.model,
|
|
297
302
|
modelTier: normalizedModel.modelTier,
|
|
298
303
|
effort: input.effort,
|
|
299
|
-
routingAffinity:
|
|
300
|
-
|
|
301
|
-
|
|
304
|
+
routingAffinity:
|
|
305
|
+
leadOnly || requiredCapabilities?.length
|
|
306
|
+
? { leadOnly, capabilities: requiredCapabilities ?? [] }
|
|
307
|
+
: undefined,
|
|
302
308
|
});
|
|
303
309
|
return {
|
|
304
310
|
success: true,
|
|
@@ -349,7 +355,9 @@ export async function taskActionHandler(
|
|
|
349
355
|
if (claimingAgent && !isAgentEligibleForTask(claimingAgent, existingTask)) {
|
|
350
356
|
return {
|
|
351
357
|
success: false,
|
|
352
|
-
message:
|
|
358
|
+
message: existingTask.routingAffinity?.leadOnly
|
|
359
|
+
? `Task "${taskId}" is Lead-only; you are not authorized to claim it.`
|
|
360
|
+
: `Task "${taskId}" requires role "${existingTask.routingAffinity?.role ?? "(unspecified)"}"; yours is "${claimingAgent.role ?? "(unspecified)"}". Cannot claim.`,
|
|
353
361
|
};
|
|
354
362
|
}
|
|
355
363
|
// Atomic claim — only one agent can win this race
|
package/src/types.ts
CHANGED
|
@@ -152,6 +152,16 @@ export const DEFAULT_MODEL_TIER_MAP: Record<ProviderName, Record<ModelTier, stri
|
|
|
152
152
|
smart: "devin",
|
|
153
153
|
ultra: "devin",
|
|
154
154
|
},
|
|
155
|
+
// A generic ACP target owns its own model selection — the swarm never sends
|
|
156
|
+
// one over the wire. Empty strings resolve to `{ source: "none" }`, while
|
|
157
|
+
// `MODEL_TIER_*` / `MODEL_TIER_MAP` env overrides still apply for targets
|
|
158
|
+
// that do accept a model.
|
|
159
|
+
acp: {
|
|
160
|
+
smol: "",
|
|
161
|
+
regular: "",
|
|
162
|
+
smart: "",
|
|
163
|
+
ultra: "",
|
|
164
|
+
},
|
|
155
165
|
};
|
|
156
166
|
|
|
157
167
|
export function parseModelTier(value: string | null | undefined): ModelTier | undefined {
|
|
@@ -344,6 +354,7 @@ export const ProviderNameSchema = z.enum([
|
|
|
344
354
|
"devin",
|
|
345
355
|
"claude-managed",
|
|
346
356
|
"opencode",
|
|
357
|
+
"acp",
|
|
347
358
|
]);
|
|
348
359
|
export type ProviderName = z.infer<typeof ProviderNameSchema>;
|
|
349
360
|
|
|
@@ -433,6 +444,10 @@ export const PROVIDER_STEER_CAPABILITIES: Record<ProviderName, SteerMode[]> = {
|
|
|
433
444
|
// hook `additionalContext`, so the runner must leave codex rows `pending`
|
|
434
445
|
// (`ProviderSession.steeringDeliveredExternally`).
|
|
435
446
|
codex: ["queue"],
|
|
447
|
+
// The ACP adapter implements no steering primitive: `session/prompt` is a
|
|
448
|
+
// single in-flight turn and the only interrupt is `session/cancel` (abort).
|
|
449
|
+
// Advertise nothing rather than promise semantics we can't honor.
|
|
450
|
+
acp: [],
|
|
436
451
|
};
|
|
437
452
|
|
|
438
453
|
export type DevinProviderMeta = {
|
|
@@ -451,6 +466,7 @@ export type ProviderMetaMap = {
|
|
|
451
466
|
pi: NoProviderMeta;
|
|
452
467
|
"claude-managed": NoProviderMeta;
|
|
453
468
|
opencode: NoProviderMeta;
|
|
469
|
+
acp: NoProviderMeta;
|
|
454
470
|
};
|
|
455
471
|
|
|
456
472
|
export const FollowUpConfigSchema = z
|
|
@@ -475,6 +491,8 @@ export const RoutingAffinitySchema = z
|
|
|
475
491
|
role: z.string().max(100).optional(),
|
|
476
492
|
harnessProvider: ProviderNameSchema.optional(),
|
|
477
493
|
capabilities: z.array(z.string()).default([]),
|
|
494
|
+
/** Explicit authorization boundary for merge and other control-plane work. */
|
|
495
|
+
leadOnly: z.boolean().optional(),
|
|
478
496
|
})
|
|
479
497
|
.openapi("RoutingAffinity");
|
|
480
498
|
export type RoutingAffinity = z.infer<typeof RoutingAffinitySchema>;
|
|
@@ -631,6 +649,9 @@ export const AgentTaskSchema = z
|
|
|
631
649
|
// behavior. Inherited from parentTaskId when not explicitly set (see
|
|
632
650
|
// `createTaskExtended` in src/be/db.ts). See `isAgentEligibleForTask`.
|
|
633
651
|
routingAffinity: RoutingAffinitySchema.optional(),
|
|
652
|
+
// Stored affinity that fails validation is quarantined rather than silently
|
|
653
|
+
// treated as an ordinary, claimable task. Internal read-path signal.
|
|
654
|
+
routingAffinityInvalid: z.boolean().optional(),
|
|
634
655
|
})
|
|
635
656
|
.openapi("AgentTask");
|
|
636
657
|
|
|
@@ -722,8 +743,10 @@ export const CreateTaskOptionsSchema = z.object({
|
|
|
722
743
|
*/
|
|
723
744
|
bypassTrackerContextDedup: z.boolean().optional(),
|
|
724
745
|
/**
|
|
725
|
-
* Routing-affinity snapshot gating
|
|
726
|
-
* `isAgentEligibleForTask`).
|
|
746
|
+
* Routing-affinity snapshot gating task authorization (see
|
|
747
|
+
* `isAgentEligibleForTask`). `leadOnly: true` is an explicit, structured
|
|
748
|
+
* constraint: only an agent with `isLead` may be assigned, offered, claim,
|
|
749
|
+
* or recover this task. It is never inferred from task text. Inherited from the parent (via `parentTaskId`)
|
|
727
750
|
* when not explicitly set — same treatment as `vcsRepo`/`contextKey`.
|
|
728
751
|
*/
|
|
729
752
|
routingAffinity: RoutingAffinitySchema.optional(),
|
|
@@ -1296,6 +1319,8 @@ export const AgentLogEventTypeSchema = z.enum([
|
|
|
1296
1319
|
"task_rejected",
|
|
1297
1320
|
"task_claimed",
|
|
1298
1321
|
"task_claim_rejected_affinity",
|
|
1322
|
+
"task_authorization_rejected",
|
|
1323
|
+
"task_recovery_authorization",
|
|
1299
1324
|
"task_released",
|
|
1300
1325
|
"channel_message",
|
|
1301
1326
|
// Service registry events
|
|
@@ -2897,6 +2922,10 @@ export const PricingProviderSchema = z.enum([
|
|
|
2897
2922
|
"opencode",
|
|
2898
2923
|
"devin",
|
|
2899
2924
|
"gemini",
|
|
2925
|
+
// No seeded rate rows: a generic ACP target owns its own billing and the
|
|
2926
|
+
// adapter reports `totalCostUsd: 0`, so these rows settle at
|
|
2927
|
+
// `costSource: 'unpriced'`. Accepted here so the row is recorded at all.
|
|
2928
|
+
"acp",
|
|
2900
2929
|
]);
|
|
2901
2930
|
export type PricingProvider = z.infer<typeof PricingProviderSchema>;
|
|
2902
2931
|
|
|
@@ -27,6 +27,8 @@ export const CONTEXT_FORMULA = "input-cache-output" as const;
|
|
|
27
27
|
|
|
28
28
|
const CONTEXT_WINDOW_DEFAULTS: Record<string, number> = {
|
|
29
29
|
// Anthropic Fable / Mythos tier
|
|
30
|
+
"claude-fable-5-1": 1_000_000,
|
|
31
|
+
"claude-mythos-5-1": 1_000_000,
|
|
30
32
|
"claude-opus-5": 1_000_000,
|
|
31
33
|
"claude-fable-5": 1_000_000,
|
|
32
34
|
"claude-mythos-5": 1_000_000,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ApprovalRequest,
|
|
3
|
+
claimApprovalCancellationNotification,
|
|
4
|
+
completeApprovalCancellationNotificationClaim,
|
|
5
|
+
releaseApprovalCancellationNotificationClaim,
|
|
6
|
+
} from "../be/db";
|
|
7
|
+
import { getSlackApp } from "../slack/app";
|
|
8
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
9
|
+
|
|
10
|
+
export interface ApprovalSlackClient {
|
|
11
|
+
chat: {
|
|
12
|
+
postMessage(input: {
|
|
13
|
+
channel: string;
|
|
14
|
+
thread_ts: string;
|
|
15
|
+
text: string;
|
|
16
|
+
unfurl_links: false;
|
|
17
|
+
unfurl_media: false;
|
|
18
|
+
}): Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function postApprovalCancellationUpdates(
|
|
23
|
+
approvals: ApprovalRequest[],
|
|
24
|
+
reason: string,
|
|
25
|
+
client: ApprovalSlackClient | undefined = getSlackApp()?.client,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
if (!client) return;
|
|
28
|
+
|
|
29
|
+
const text = `This approval is no longer actionable: ${reason}`;
|
|
30
|
+
for (const approval of approvals) {
|
|
31
|
+
const channels = approval.notificationChannels as Array<{
|
|
32
|
+
channel: string;
|
|
33
|
+
target: string;
|
|
34
|
+
messageTs?: string;
|
|
35
|
+
}> | null;
|
|
36
|
+
const slackThreads = (channels ?? []).filter(
|
|
37
|
+
(notification) => notification.channel === "slack" && notification.messageTs,
|
|
38
|
+
);
|
|
39
|
+
if (slackThreads.length === 0) continue;
|
|
40
|
+
|
|
41
|
+
for (const notification of slackThreads) {
|
|
42
|
+
if (notification.channel !== "slack" || !notification.messageTs) continue;
|
|
43
|
+
const notificationKey = `${notification.target}:${notification.messageTs}`;
|
|
44
|
+
const claim = await claimApprovalCancellationNotification(approval.id, notificationKey);
|
|
45
|
+
if (!claim) continue;
|
|
46
|
+
try {
|
|
47
|
+
await client.chat.postMessage({
|
|
48
|
+
channel: notification.target,
|
|
49
|
+
thread_ts: notification.messageTs,
|
|
50
|
+
text,
|
|
51
|
+
unfurl_links: false,
|
|
52
|
+
unfurl_media: false,
|
|
53
|
+
});
|
|
54
|
+
await completeApprovalCancellationNotificationClaim(
|
|
55
|
+
approval.id,
|
|
56
|
+
notificationKey,
|
|
57
|
+
claim.leaseToken,
|
|
58
|
+
);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
await releaseApprovalCancellationNotificationClaim(
|
|
61
|
+
approval.id,
|
|
62
|
+
notificationKey,
|
|
63
|
+
claim.leaseToken,
|
|
64
|
+
);
|
|
65
|
+
console.error(
|
|
66
|
+
`[HITL] Failed to post cancellation update for approval ${approval.id}:`,
|
|
67
|
+
scrubSecrets(error instanceof Error ? error.message : String(error)),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -83,16 +83,23 @@ export async function checkpointStepWaiting(
|
|
|
83
83
|
runId: string,
|
|
84
84
|
stepId: string,
|
|
85
85
|
ctx: Record<string, unknown>,
|
|
86
|
-
): Promise<
|
|
87
|
-
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
return getDbClient().transaction(async () => {
|
|
88
|
+
const step = await getDbClient().get<{ id: string }>(
|
|
89
|
+
`UPDATE workflow_run_steps
|
|
90
|
+
SET status = 'waiting'
|
|
91
|
+
WHERE id = ? AND runId = ? AND status = 'running'
|
|
92
|
+
AND EXISTS (
|
|
93
|
+
SELECT 1 FROM workflow_runs
|
|
94
|
+
WHERE id = ? AND status IN ('running', 'waiting')
|
|
95
|
+
)
|
|
96
|
+
RETURNING id`,
|
|
97
|
+
[stepId, runId, runId],
|
|
98
|
+
);
|
|
99
|
+
if (!step) return false;
|
|
91
100
|
|
|
92
|
-
await updateWorkflowRun(runId, {
|
|
93
|
-
|
|
94
|
-
context: ctx,
|
|
95
|
-
});
|
|
101
|
+
await updateWorkflowRun(runId, { status: "waiting", context: ctx });
|
|
102
|
+
return true;
|
|
96
103
|
});
|
|
97
104
|
}
|
|
98
105
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getLatestStepForNode } from "../be/db";
|
|
2
|
+
import type { WorkflowDefinition } from "../types";
|
|
3
|
+
import { getSuccessors } from "./definition";
|
|
4
|
+
import { resolveForeachParent } from "./foreach-join";
|
|
5
|
+
|
|
6
|
+
type CompletedStep = NonNullable<Awaited<ReturnType<typeof getLatestStepForNode>>>;
|
|
7
|
+
|
|
8
|
+
export async function loadCompletedStepRouting(
|
|
9
|
+
def: WorkflowDefinition,
|
|
10
|
+
runId: string,
|
|
11
|
+
completedNodeIds: Set<string>,
|
|
12
|
+
): Promise<{ activeEdges: Set<string>; stepsByNodeId: Map<string, CompletedStep> }> {
|
|
13
|
+
const activeEdges = new Set<string>();
|
|
14
|
+
const stepsByNodeId = new Map<string, CompletedStep>();
|
|
15
|
+
|
|
16
|
+
for (const nodeId of completedNodeIds) {
|
|
17
|
+
if (resolveForeachParent(def, nodeId)) continue;
|
|
18
|
+
|
|
19
|
+
const step = await getLatestStepForNode(runId, nodeId);
|
|
20
|
+
if (step) stepsByNodeId.set(nodeId, step);
|
|
21
|
+
|
|
22
|
+
const successors = step?.nextPort
|
|
23
|
+
? getSuccessors(def, nodeId, step.nextPort)
|
|
24
|
+
: getSuccessors(def, nodeId);
|
|
25
|
+
for (const successor of successors) {
|
|
26
|
+
activeEdges.add(`${nodeId}→${successor.id}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { activeEdges, stepsByNodeId };
|
|
31
|
+
}
|