@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.
Files changed (210) hide show
  1. package/README.md +12 -3
  2. package/dist/acp-adapter-6nttxe2q.js +4787 -0
  3. package/dist/{actions-agcbc8nd.js → actions-z9dzgh3f.js} +7 -8
  4. package/dist/{app-g2s78xya.js → app-wb6bc7tt.js} +4 -5
  5. package/dist/{assistant-yd3jkb9d.js → assistant-08pd6pgy.js} +9 -10
  6. package/dist/{boot-reembed-6spqw4q0.js → boot-reembed-hstr4e2c.js} +4 -4
  7. package/dist/{boot-reembed-b22s9w5x.js → boot-reembed-y7p7qjbf.js} +5 -5
  8. package/dist/{boot-scrub-logs-6h3cqkc0.js → boot-scrub-logs-azbnxwhn.js} +3 -3
  9. package/dist/{claude-adapter-ng5y8zd7.js → claude-adapter-rwaq6eda.js} +10 -9
  10. package/dist/{claude-managed-adapter-5zr3n86v.js → claude-managed-adapter-vtseey1x.js} +3 -3
  11. package/dist/{cli-qckgdqmr.js → cli-005yr57a.js} +2 -2
  12. package/dist/{cli-54ht104h.js → cli-0cka8dnm.js} +3 -3
  13. package/dist/{cli-gpdtxzar.js → cli-23cpnqk9.js} +1 -1
  14. package/dist/{cli-qhfkxrpg.js → cli-2ypjdmqg.js} +223 -474
  15. package/dist/{cli-bnq8h7ea.js → cli-3719ggyf.js} +17 -10
  16. package/dist/cli-390sgxn5.js +257 -0
  17. package/dist/{cli-z6e8eqrm.js → cli-47esr8ht.js} +2 -2
  18. package/dist/{cli-jjhnma34.js → cli-4jaj24py.js} +248 -101
  19. package/dist/{cli-6gwgzdgw.js → cli-69377c2a.js} +5 -5
  20. package/dist/{cli-ads0hfw2.js → cli-6v4d8r8c.js} +205 -75
  21. package/dist/{cli-mgef08pc.js → cli-8rtyss6g.js} +1 -1
  22. package/dist/{cli-35jknh9e.js → cli-9w7vsmbm.js} +5 -5
  23. package/dist/{cli-m3z53bed.js → cli-b4zdc3x6.js} +1 -1
  24. package/dist/{cli-fpjmcbhz.js → cli-ba87cw29.js} +4 -4
  25. package/dist/{cli-4gzv54ss.js → cli-bzk75kdc.js} +43 -11
  26. package/dist/{cli-d5ygfv18.js → cli-cywkhzqs.js} +35 -11
  27. package/dist/{cli-0crn5kaz.js → cli-d62cd9qs.js} +16 -4
  28. package/dist/{cli-5bnz1t86.js → cli-f9skf1v7.js} +51 -7
  29. package/dist/{cli-06ghj6pe.js → cli-fytpjraz.js} +4 -4
  30. package/dist/{cli-j7qp8gtf.js → cli-h7c5dam1.js} +16 -11
  31. package/dist/{cli-4r4jkqep.js → cli-kkn46gfr.js} +9 -3
  32. package/dist/{cli-tmn19cdz.js → cli-kq9htgbr.js} +415 -62
  33. package/dist/{cli-8v57p983.js → cli-pkmqqxp8.js} +45 -69
  34. package/dist/{cli-84vrhesc.js → cli-qf34ej1d.js} +1 -1
  35. package/dist/{cli-v3qda694.js → cli-rnyttbh0.js} +0 -5
  36. package/dist/cli-rx5cxzn2.js +7 -0
  37. package/dist/{cli-cw70hymf.js → cli-s25avqg9.js} +2 -0
  38. package/dist/{cli-tee8w7tq.js → cli-s6t1qv67.js} +15 -10
  39. package/dist/{cli-68vayryq.js → cli-tb4csb08.js} +2 -2
  40. package/dist/{cli-y16cesaq.js → cli-vy48y9bx.js} +2 -2
  41. package/dist/{cli-3y553gws.js → cli-y294regh.js} +1 -1
  42. package/dist/{cli-spf767vc.js → cli-y43043ws.js} +1 -1
  43. package/dist/{cli-hz5hp6wn.js → cli-yh2eybyt.js} +1 -1
  44. package/dist/{cli-edwrx42s.js → cli-zmyxt25p.js} +5 -5
  45. package/dist/cli.js +40 -18
  46. package/dist/{codex-adapter-kw11ps3j.js → codex-adapter-yzep6mvb.js} +7 -6
  47. package/dist/{codex-session-runner-sffb69v7.js → codex-session-runner-25e8tn72.js} +7 -6
  48. package/dist/{commands-0xxp4tz8.js → commands-0vv72d6a.js} +3 -3
  49. package/dist/{db-0npp39zd.js → db-x61ncfa1.js} +19 -3
  50. package/dist/{google-generative-ai-6xxdj4t3.js → google-generative-ai-zyhy3dab.js} +1 -1
  51. package/dist/{google-vertex-zma18wb0.js → google-vertex-t25h65yd.js} +1 -1
  52. package/dist/{handlers-5p7a1jx6.js → handlers-fwdcaahf.js} +9 -10
  53. package/dist/{hook-jjpxn4se.js → hook-mpwfgynm.js} +6 -5
  54. package/dist/{hook-hpefr0a8.js → hook-x18504ja.js} +8 -7
  55. package/dist/{http-wfp33653.js → http-83m6qdds.js} +590 -192
  56. package/dist/{index-pe58mf1r.js → index-1nsdwvn5.js} +2 -1
  57. package/dist/{index-17h657rg.js → index-38f98ck3.js} +9 -9
  58. package/dist/{index-m2p7ctyh.js → index-7kxvtd38.js} +24 -12
  59. package/dist/{index-racb4wfq.js → index-a4naswjn.js} +23 -10
  60. package/dist/{index-p13cfsc2.js → index-agykc6k2.js} +2 -1
  61. package/dist/{index-zh66wm65.js → index-dawtzfhw.js} +22 -9
  62. package/dist/{keepalive-wxjd633a.js → keepalive-y0m8g5wn.js} +5 -5
  63. package/dist/{lead-359ec2z0.js → lead-ka18ef6a.js} +36 -36
  64. package/dist/{maintenance-rr03qjtb.js → maintenance-x585p56c.js} +5 -5
  65. package/dist/{oauth-refresh-sweep-jwj22yb8.js → oauth-refresh-sweep-kdm9vj7a.js} +4 -4
  66. package/dist/{onboard-asnhchcv.js → onboard-5v229nvt.js} +664 -248
  67. package/dist/{opencode-adapter-sx38y7f7.js → opencode-adapter-46nnmnvv.js} +3 -3
  68. package/dist/{otel-impl-54cra6mw.js → otel-impl-k47aj4sd.js} +73 -4
  69. package/dist/{pi-mono-adapter-29g7axgj.js → pi-mono-adapter-wawfj09v.js} +42 -40
  70. package/dist/{pricing-refresh-mkyy7c2y.js → pricing-refresh-ky2gjp05.js} +5 -5
  71. package/dist/{rbac-roles-kb3afm95.js → rbac-roles-0j0f7s8r.js} +4 -4
  72. package/dist/{rbac-roles-ak56tr75.js → rbac-roles-az4wc15e.js} +3 -3
  73. package/dist/{render-v2-434asg49.js → render-v2-seyxq61z.js} +4 -5
  74. package/dist/{seed-pricing-nbmd7ah8.js → seed-pricing-fev611hx.js} +4 -4
  75. package/dist/{setup-wh4qmjs4.js → setup-hy404zwh.js} +2 -2
  76. package/dist/{worker-j1d27tjv.js → worker-g1j5hn16.js} +36 -36
  77. package/openapi.json +197 -27
  78. package/package.json +9 -3
  79. package/src/be/db-client.ts +58 -5
  80. package/src/be/db-retention.ts +550 -121
  81. package/src/be/db.ts +367 -55
  82. package/src/be/memory/raters/retrieval.ts +40 -0
  83. package/src/be/migrations/140_approval_request_cancelled_status.sql +64 -0
  84. package/src/be/pricing-normalize.ts +3 -0
  85. package/src/be/script-connections.ts +26 -6
  86. package/src/be/seed-pricing.ts +18 -8
  87. package/src/be/swarm-config-guard.ts +33 -0
  88. package/src/cli.tsx +20 -1
  89. package/src/commands/onboard/compose-generator.ts +63 -7
  90. package/src/commands/onboard/env-generator.ts +32 -4
  91. package/src/commands/onboard/manifest.ts +4 -1
  92. package/src/commands/onboard/non-interactive.ts +159 -0
  93. package/src/commands/onboard/steps/generate.tsx +1 -34
  94. package/src/commands/onboard/steps/harness-credentials.tsx +181 -1
  95. package/src/commands/onboard/steps/harness.tsx +20 -6
  96. package/src/commands/onboard/steps/review.tsx +3 -2
  97. package/src/commands/onboard/types.ts +48 -1
  98. package/src/commands/onboard.tsx +18 -13
  99. package/src/commands/provider-credentials.ts +17 -3
  100. package/src/commands/runner.ts +1 -0
  101. package/src/heartbeat/heartbeat.ts +47 -7
  102. package/src/http/approval-requests.ts +53 -8
  103. package/src/http/index.ts +9 -1
  104. package/src/http/mcp-servers.ts +2 -0
  105. package/src/http/memory.ts +27 -1
  106. package/src/http/oauth-callback.ts +33 -0
  107. package/src/http/repos.ts +6 -0
  108. package/src/http/script-connections.ts +77 -0
  109. package/src/http/session-data.ts +5 -1
  110. package/src/http/stats.ts +21 -30
  111. package/src/http/tasks.ts +10 -0
  112. package/src/oauth/presets.ts +29 -0
  113. package/src/otel-impl.ts +96 -1
  114. package/src/otel.ts +34 -0
  115. package/src/prompts/memories.ts +2 -2
  116. package/src/providers/acp-adapter.ts +325 -0
  117. package/src/providers/acp-swarm-events.ts +113 -0
  118. package/src/providers/acp-targets.ts +103 -0
  119. package/src/providers/claude-managed-models.ts +17 -1
  120. package/src/providers/codex-adapter.ts +3 -3
  121. package/src/providers/codex-oauth/auth-json-fs.ts +2 -2
  122. package/src/providers/codex-oauth/home.ts +13 -0
  123. package/src/providers/index.ts +5 -1
  124. package/src/providers/types.ts +1 -1
  125. package/src/server-user.ts +1 -0
  126. package/src/slack/app.ts +5 -1
  127. package/src/tasks/worker-follow-up.ts +78 -7
  128. package/src/telemetry.ts +151 -0
  129. package/src/tests/acp-adapter.test.ts +192 -0
  130. package/src/tests/acp-swarm-events.test.ts +132 -0
  131. package/src/tests/agents-list-model-display.test.ts +20 -0
  132. package/src/tests/approval-cancellation-migration.test.ts +134 -0
  133. package/src/tests/approval-requests.test.ts +456 -2
  134. package/src/tests/apps-spike2.test.ts +4 -1
  135. package/src/tests/apps-sync-engine.test.ts +22 -18
  136. package/src/tests/claude-managed-adapter.test.ts +12 -0
  137. package/src/tests/codex-oauth-auth-json-fs.test.ts +19 -0
  138. package/src/tests/connection-embedded-auth.test.ts +4 -1
  139. package/src/tests/context-window.test.ts +2 -0
  140. package/src/tests/credential-check.test.ts +6 -0
  141. package/src/tests/db-client-busy-retry.test.ts +18 -0
  142. package/src/tests/db-client.test.ts +23 -0
  143. package/src/tests/db-retention-telemetry.test.ts +569 -0
  144. package/src/tests/db-retention.test.ts +456 -117
  145. package/src/tests/heartbeat.test.ts +140 -0
  146. package/src/tests/memory-http-recall-gating.test.ts +120 -13
  147. package/src/tests/memory-store.test.ts +41 -0
  148. package/src/tests/migration-046-budgets.test.ts +27 -5
  149. package/src/tests/oauth-presets.test.ts +17 -5
  150. package/src/tests/onboard-compose.test.ts +99 -0
  151. package/src/tests/onboard-env.test.ts +43 -0
  152. package/src/tests/onboard-manifest.test.ts +17 -1
  153. package/src/tests/onboard-non-interactive.test.ts +89 -0
  154. package/src/tests/pool-affinity.test.ts +100 -0
  155. package/src/tests/promote-draft-task-route.test.ts +32 -1
  156. package/src/tests/sandbox-spawn-test-helpers.ts +3 -0
  157. package/src/tests/sandboxed-process.test.ts +2 -2
  158. package/src/tests/schedule-target-type.test.ts +6 -3
  159. package/src/tests/script-connections-http.test.ts +4 -3
  160. package/src/tests/script-connections.test.ts +6 -1
  161. package/src/tests/script-workflows-runtime-e2e.test.ts +36 -28
  162. package/src/tests/scripts-external-api.test.ts +8 -5
  163. package/src/tests/scripts-http.test.ts +6 -3
  164. package/src/tests/scripts-mcp-e2e.test.ts +65 -60
  165. package/src/tests/scripts-retention.test.ts +6 -3
  166. package/src/tests/scripts-runtime-identity.test.ts +4 -2
  167. package/src/tests/scripts-runtime.test.ts +18 -16
  168. package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
  169. package/src/tests/slack-read-boundaries.test.ts +3 -1
  170. package/src/tests/task-lifecycle-telemetry.test.ts +1 -0
  171. package/src/tests/task-tools-ctx.test.ts +88 -1
  172. package/src/tests/telemetry-init.test.ts +61 -0
  173. package/src/tests/tool-annotations.test.ts +4 -1
  174. package/src/tests/vendored-openapi.test.ts +15 -2
  175. package/src/tests/workflow-e2e.test.ts +4 -1
  176. package/src/tests/workflow-engine-v2.test.ts +5 -2
  177. package/src/tests/workflow-executors.test.ts +24 -19
  178. package/src/tests/workflow-http-v2.test.ts +17 -2
  179. package/src/tests/workflow-llm.test.ts +57 -0
  180. package/src/tests/workflow-retry-v2.test.ts +142 -1
  181. package/src/tests/workflow-swarm-script.test.ts +20 -17
  182. package/src/tools/get-task-details.ts +1 -0
  183. package/src/tools/mcp-servers/mcp-server-create.ts +7 -1
  184. package/src/tools/memory-search.ts +27 -2
  185. package/src/tools/send-task.ts +28 -10
  186. package/src/tools/task-action.ts +13 -5
  187. package/src/types.ts +31 -2
  188. package/src/utils/context-window.ts +2 -0
  189. package/src/workflows/approval-notifications.ts +72 -0
  190. package/src/workflows/checkpoint.ts +16 -9
  191. package/src/workflows/completed-step-routing.ts +31 -0
  192. package/src/workflows/engine.ts +59 -43
  193. package/src/workflows/executors/human-in-the-loop.ts +38 -4
  194. package/src/workflows/executors/raw-llm.ts +8 -6
  195. package/src/workflows/executors/validate.ts +6 -5
  196. package/src/workflows/executors/workflow-llm.ts +35 -0
  197. package/src/workflows/resume.ts +90 -13
  198. package/src/workflows/retry-poller.ts +8 -1
  199. package/vendored-openapi/README.md +4 -2
  200. package/vendored-openapi/manifest.json +33 -0
  201. package/vendored-openapi/microsoft-graph.json +781 -0
  202. package/dist/cli-5tnr64pm.js +0 -29
  203. package/dist/{anthropic-messages-pm7pp23c.js → anthropic-messages-tvgm75yc.js} +3 -3
  204. package/dist/{azure-openai-responses-cwesc845.js → azure-openai-responses-hed7waek.js} +4 -4
  205. package/dist/{cli-s9gpxh9q.js → cli-5kmzp1wx.js} +0 -0
  206. package/dist/{cli-s73zwwqn.js → cli-xjx3fbtn.js} +4 -4
  207. package/dist/{openai-codex-responses-xxca1qr1.js → openai-codex-responses-gv3n8rg0.js} +3 -3
  208. package/dist/{openai-completions-yzftqrft.js → openai-completions-dkrj9433.js} +8 -8
  209. package/dist/{openai-responses-e4dcwm9g.js → openai-responses-spk6t7kh.js} +11 -11
  210. package/dist/{openrouter-images-asqqnbvh.js → openrouter-images-71hthwh9.js} +3 -3
@@ -1,3 +1,7 @@
1
+ import {
2
+ DEFAULT_MODEL,
3
+ resolveCredential
4
+ } from "./cli-2ypjdmqg.js";
1
5
  import {
2
6
  getOpenRouterBaseUrl
3
7
  } from "./cli-3dpj7atn.js";
@@ -18,15 +22,21 @@ import {
18
22
  sandboxSpawnEnv,
19
23
  snapshotCapped,
20
24
  touchScratchScriptLastUsed
21
- } from "./cli-bnq8h7ea.js";
25
+ } from "./cli-3719ggyf.js";
22
26
  import {
23
27
  withSiblingAwareness
24
- } from "./cli-gpdtxzar.js";
28
+ } from "./cli-23cpnqk9.js";
29
+ import {
30
+ getSlackApp
31
+ } from "./cli-cywkhzqs.js";
25
32
  import {
26
33
  FollowUpConfigSchema,
27
34
  ModelTierSchema,
28
35
  ReasoningEffortSchema,
36
+ cancelPendingApprovalRequestsForRun,
29
37
  cancelTask,
38
+ claimApprovalCancellationNotification,
39
+ completeApprovalCancellationNotificationClaim,
30
40
  createWorkflowRun,
31
41
  createWorkflowRunStep,
32
42
  createWorkflowVersion,
@@ -58,7 +68,9 @@ import {
58
68
  init_db,
59
69
  init_telemetry,
60
70
  init_types,
71
+ listCancelledApprovalRequestsForRun,
61
72
  listWorkflows,
73
+ releaseApprovalCancellationNotificationClaim,
62
74
  resolveApprovalRequest,
63
75
  resolveWaitState,
64
76
  splitLegacyModelAlias,
@@ -67,7 +79,7 @@ import {
67
79
  updateWorkflowRun,
68
80
  updateWorkflowRunStep,
69
81
  workflowContextKey
70
- } from "./cli-tmn19cdz.js";
82
+ } from "./cli-kq9htgbr.js";
71
83
  import {
72
84
  deepInterpolate,
73
85
  findInterpolationTokens,
@@ -81,14 +93,14 @@ import {
81
93
  import {
82
94
  init_zod
83
95
  } from "./cli-s35981d0.js";
96
+ import {
97
+ exports_external
98
+ } from "./cli-pqv5gvyf.js";
84
99
  import {
85
100
  init_secret_scrubber,
86
101
  isSensitiveKey,
87
102
  scrubSecrets
88
103
  } from "./cli-mkq1rk7b.js";
89
- import {
90
- exports_external
91
- } from "./cli-pqv5gvyf.js";
92
104
  import {
93
105
  init_event_bus,
94
106
  workflowEventBus
@@ -467,14 +479,19 @@ async function checkpointStepFailure(runId, stepId, error, retryCount, retryPoli
467
479
  return { shouldRetry: false };
468
480
  }
469
481
  async function checkpointStepWaiting(runId, stepId, ctx) {
470
- await getDbClient().transaction(async () => {
471
- await updateWorkflowRunStep(stepId, {
472
- status: "waiting"
473
- });
474
- await updateWorkflowRun(runId, {
475
- status: "waiting",
476
- context: ctx
477
- });
482
+ return getDbClient().transaction(async () => {
483
+ const step = await getDbClient().get(`UPDATE workflow_run_steps
484
+ SET status = 'waiting'
485
+ WHERE id = ? AND runId = ? AND status = 'running'
486
+ AND EXISTS (
487
+ SELECT 1 FROM workflow_runs
488
+ WHERE id = ? AND status IN ('running', 'waiting')
489
+ )
490
+ RETURNING id`, [stepId, runId, runId]);
491
+ if (!step)
492
+ return false;
493
+ await updateWorkflowRun(runId, { status: "waiting", context: ctx });
494
+ return true;
478
495
  });
479
496
  }
480
497
  function calculateBackoff(policy, attempt) {
@@ -497,35 +514,8 @@ function calculateBackoff(policy, attempt) {
497
514
  return Math.min(delay, policy.maxDelayMs);
498
515
  }
499
516
 
500
- // src/workflows/cooldown.ts
517
+ // src/workflows/completed-step-routing.ts
501
518
  init_db();
502
- function cooldownToMs(cooldown) {
503
- let ms = 0;
504
- if (cooldown.hours)
505
- ms += cooldown.hours * 60 * 60 * 1000;
506
- if (cooldown.minutes)
507
- ms += cooldown.minutes * 60 * 1000;
508
- if (cooldown.seconds)
509
- ms += cooldown.seconds * 1000;
510
- return ms;
511
- }
512
- async function shouldSkipCooldown(workflowId, cooldown) {
513
- const cooldownMs = cooldownToMs(cooldown);
514
- const now = Date.now();
515
- const lastSuccess = await getLastSuccessfulRun(workflowId);
516
- if (lastSuccess?.finishedAt) {
517
- const lastFinished = new Date(lastSuccess.finishedAt).getTime();
518
- if (now - lastFinished < cooldownMs)
519
- return true;
520
- }
521
- const lastAttempt = await getLastRunStart(workflowId);
522
- if (lastAttempt?.startedAt) {
523
- const lastStarted = new Date(lastAttempt.startedAt).getTime();
524
- if (now - lastStarted < cooldownMs)
525
- return true;
526
- }
527
- return false;
528
- }
529
519
 
530
520
  // src/workflows/foreach-join.ts
531
521
  init_db();
@@ -606,6 +596,54 @@ function resultStatus(step) {
606
596
  return step.error ? "failed" : "completed";
607
597
  }
608
598
 
599
+ // src/workflows/completed-step-routing.ts
600
+ async function loadCompletedStepRouting(def, runId, completedNodeIds) {
601
+ const activeEdges = new Set;
602
+ const stepsByNodeId = new Map;
603
+ for (const nodeId of completedNodeIds) {
604
+ if (resolveForeachParent(def, nodeId))
605
+ continue;
606
+ const step = await getLatestStepForNode(runId, nodeId);
607
+ if (step)
608
+ stepsByNodeId.set(nodeId, step);
609
+ const successors = step?.nextPort ? getSuccessors(def, nodeId, step.nextPort) : getSuccessors(def, nodeId);
610
+ for (const successor of successors) {
611
+ activeEdges.add(`${nodeId}→${successor.id}`);
612
+ }
613
+ }
614
+ return { activeEdges, stepsByNodeId };
615
+ }
616
+
617
+ // src/workflows/cooldown.ts
618
+ init_db();
619
+ function cooldownToMs(cooldown) {
620
+ let ms = 0;
621
+ if (cooldown.hours)
622
+ ms += cooldown.hours * 60 * 60 * 1000;
623
+ if (cooldown.minutes)
624
+ ms += cooldown.minutes * 60 * 1000;
625
+ if (cooldown.seconds)
626
+ ms += cooldown.seconds * 1000;
627
+ return ms;
628
+ }
629
+ async function shouldSkipCooldown(workflowId, cooldown) {
630
+ const cooldownMs = cooldownToMs(cooldown);
631
+ const now = Date.now();
632
+ const lastSuccess = await getLastSuccessfulRun(workflowId);
633
+ if (lastSuccess?.finishedAt) {
634
+ const lastFinished = new Date(lastSuccess.finishedAt).getTime();
635
+ if (now - lastFinished < cooldownMs)
636
+ return true;
637
+ }
638
+ const lastAttempt = await getLastRunStart(workflowId);
639
+ if (lastAttempt?.startedAt) {
640
+ const lastStarted = new Date(lastAttempt.startedAt).getTime();
641
+ if (now - lastStarted < cooldownMs)
642
+ return true;
643
+ }
644
+ return false;
645
+ }
646
+
609
647
  // src/workflows/input.ts
610
648
  init_db();
611
649
  init_secret_scrubber();
@@ -912,31 +950,8 @@ async function walkGraph(def, runId, ctx, startNodes, registry, workflowId, secr
912
950
  if (!("run" in ctx)) {
913
951
  ctx.run = { id: runId };
914
952
  }
915
- const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
916
- const activeEdges = new Set;
917
- if (completedNodeIds.size > 0) {
918
- for (const nodeId of completedNodeIds) {
919
- if (resolveForeachParent(def, nodeId))
920
- continue;
921
- const step = await getLatestStepForNode(runId, nodeId);
922
- if (step?.output !== undefined) {
923
- const node = def.nodes.find((n) => n.id === nodeId);
924
- if (node && registry.has(node.type)) {
925
- const executor = registry.get(node.type);
926
- const parseResult = executor.outputSchema.safeParse(step.output);
927
- if (!parseResult.success) {
928
- console.warn(`[workflow] Recovery: step ${nodeId} output failed validation: ${parseResult.error.message}`);
929
- continue;
930
- }
931
- }
932
- ctx[nodeId] = step.output;
933
- }
934
- const successors = step?.nextPort ? getSuccessors(def, nodeId, step.nextPort) : getSuccessors(def, nodeId);
935
- for (const succ of successors) {
936
- activeEdges.add(`${nodeId}→${succ.id}`);
937
- }
938
- }
939
- }
953
+ const { completedNodeIds } = await rehydrateCompletedStepOutputs(def, runId, ctx, registry);
954
+ const { activeEdges } = await loadCompletedStepRouting(def, runId, completedNodeIds);
940
955
  const allSteps = await getWorkflowRunStepsByRunId(runId);
941
956
  if (allSteps.length >= getMaxWorkflowStepsPerRun()) {
942
957
  await updateWorkflowRun(runId, {
@@ -1054,6 +1069,31 @@ async function walkGraph(def, runId, ctx, startNodes, registry, workflowId, secr
1054
1069
  }
1055
1070
  });
1056
1071
  }
1072
+ async function rehydrateCompletedStepOutputs(def, runId, ctx, registry) {
1073
+ const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
1074
+ const latestSteps = new Map;
1075
+ for (const nodeId of completedNodeIds) {
1076
+ if (resolveForeachParent(def, nodeId))
1077
+ continue;
1078
+ const step = await getLatestStepForNode(runId, nodeId);
1079
+ if (!step)
1080
+ continue;
1081
+ if (step.output !== undefined) {
1082
+ const node = def.nodes.find((candidate) => candidate.id === nodeId);
1083
+ if (node && registry.has(node.type)) {
1084
+ const executor = registry.get(node.type);
1085
+ const parseResult = executor.outputSchema.safeParse(step.output);
1086
+ if (!parseResult.success) {
1087
+ console.warn(`[workflow] Recovery: step ${nodeId} output failed validation: ${parseResult.error.message}`);
1088
+ continue;
1089
+ }
1090
+ }
1091
+ ctx[nodeId] = step.output;
1092
+ }
1093
+ latestSteps.set(nodeId, step);
1094
+ }
1095
+ return { completedNodeIds, latestSteps };
1096
+ }
1057
1097
  function getAllPredecessors(def, nodeId) {
1058
1098
  const preds = [];
1059
1099
  for (const node of def.nodes) {
@@ -1067,6 +1107,10 @@ function getAllPredecessors(def, nodeId) {
1067
1107
  }
1068
1108
  async function executeStep(def, runId, ctx, node, registry, workflowId, secretKeys = new Set, options = {}) {
1069
1109
  const dedup = await getDbClient().transaction(async () => {
1110
+ const run = await getWorkflowRun(runId);
1111
+ if (!run || run.status !== "running" && run.status !== "waiting") {
1112
+ return { halted: true };
1113
+ }
1070
1114
  const iteration = await getStepCountForNode(runId, node.id);
1071
1115
  const idempotencyKey = `${runId}:${node.id}:${iteration}`;
1072
1116
  const existingStep2 = await getStepByIdempotencyKey(idempotencyKey);
@@ -1088,6 +1132,8 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
1088
1132
  }
1089
1133
  return { existingStep: existingStep2, stepId: stepId2, deduped: false };
1090
1134
  });
1135
+ if ("halted" in dedup)
1136
+ return { outcome: "completed", successors: [] };
1091
1137
  if (dedup.deduped && dedup.existingStep) {
1092
1138
  if (dedup.existingStep.status === "completed") {
1093
1139
  ctx[node.id] = dedup.existingStep.output;
@@ -1170,8 +1216,8 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
1170
1216
  return { outcome: "completed", successors: [] };
1171
1217
  }
1172
1218
  if ("async" in result && result.async) {
1173
- await checkpointStepWaiting(runId, stepId, ctx);
1174
- return { outcome: "waiting", successors: [] };
1219
+ const waiting = await checkpointStepWaiting(runId, stepId, ctx);
1220
+ return { outcome: waiting ? "waiting" : "completed", successors: [] };
1175
1221
  }
1176
1222
  if (node.outputSchema && result.status === "success") {
1177
1223
  const outputErrors = validateJsonSchema(node.outputSchema, result.output);
@@ -1347,6 +1393,44 @@ var FAILED_TASK_OUTPUT_PREFIX = "[FAILED:";
1347
1393
  // src/workflows/resume.ts
1348
1394
  init_db();
1349
1395
  init_secret_scrubber();
1396
+
1397
+ // src/workflows/approval-notifications.ts
1398
+ init_db();
1399
+ init_secret_scrubber();
1400
+ async function postApprovalCancellationUpdates(approvals, reason, client = getSlackApp()?.client) {
1401
+ if (!client)
1402
+ return;
1403
+ const text = `This approval is no longer actionable: ${reason}`;
1404
+ for (const approval of approvals) {
1405
+ const channels = approval.notificationChannels;
1406
+ const slackThreads = (channels ?? []).filter((notification) => notification.channel === "slack" && notification.messageTs);
1407
+ if (slackThreads.length === 0)
1408
+ continue;
1409
+ for (const notification of slackThreads) {
1410
+ if (notification.channel !== "slack" || !notification.messageTs)
1411
+ continue;
1412
+ const notificationKey = `${notification.target}:${notification.messageTs}`;
1413
+ const claim = await claimApprovalCancellationNotification(approval.id, notificationKey);
1414
+ if (!claim)
1415
+ continue;
1416
+ try {
1417
+ await client.chat.postMessage({
1418
+ channel: notification.target,
1419
+ thread_ts: notification.messageTs,
1420
+ text,
1421
+ unfurl_links: false,
1422
+ unfurl_media: false
1423
+ });
1424
+ await completeApprovalCancellationNotificationClaim(approval.id, notificationKey, claim.leaseToken);
1425
+ } catch (error) {
1426
+ await releaseApprovalCancellationNotificationClaim(approval.id, notificationKey, claim.leaseToken);
1427
+ console.error(`[HITL] Failed to post cancellation update for approval ${approval.id}:`, scrubSecrets(error instanceof Error ? error.message : String(error)));
1428
+ }
1429
+ }
1430
+ }
1431
+ }
1432
+
1433
+ // src/workflows/resume.ts
1350
1434
  init_event_bus();
1351
1435
 
1352
1436
  // src/workflows/executors/wait.ts
@@ -1886,23 +1970,31 @@ async function retryFailedRun(runId, registry) {
1886
1970
  const failedStep = steps.find((s) => s.status === "failed");
1887
1971
  if (!failedStep)
1888
1972
  throw new Error("No failed step found");
1973
+ const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
1974
+ const { activeEdges } = await loadCompletedStepRouting(workflow.definition, runId, completedNodeIds);
1975
+ const foreachParent = resolveForeachParent(workflow.definition, failedStep.nodeId);
1976
+ const failedNode = foreachParent ?? workflow.definition.nodes.find((n) => n.id === failedStep.nodeId);
1977
+ if (!failedNode)
1978
+ throw new Error(`Node ${failedStep.nodeId} not found in workflow definition`);
1979
+ const hasStructuralPredecessor = workflow.definition.nodes.some((node) => node.next && getNextTargets(node.next).includes(failedNode.id));
1980
+ const hasActivePredecessor = [...activeEdges].some((edge) => edge.endsWith(`→${failedNode.id}`));
1981
+ const shouldRetryFailedNode = foreachParent != null || !hasStructuralPredecessor || hasActivePredecessor;
1889
1982
  const ctx = run.context ?? {};
1890
1983
  const claimed = await getDbClient().transaction(async () => {
1891
1984
  const current = await getWorkflowRun(runId);
1892
1985
  if (!current || current.status !== "failed")
1893
1986
  return false;
1894
- await updateWorkflowRunStep(failedStep.id, { status: "pending", error: null });
1987
+ await updateWorkflowRunStep(failedStep.id, {
1988
+ status: shouldRetryFailedNode ? "pending" : "cancelled",
1989
+ error: shouldRetryFailedNode ? null : "Skipped on retry because its branch was not active"
1990
+ });
1895
1991
  await updateWorkflowRun(runId, { status: "running", error: null, context: ctx });
1896
1992
  return true;
1897
1993
  });
1898
1994
  if (!claimed)
1899
1995
  throw new Error("Run is not in failed state");
1900
- const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
1901
- const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
1902
- const failedNode = resolveForeachParent(workflow.definition, failedStep.nodeId) ?? workflow.definition.nodes.find((n) => n.id === failedStep.nodeId);
1903
- if (!failedNode)
1904
- throw new Error(`Node ${failedStep.nodeId} not found in workflow definition`);
1905
- const nodesToRun = readyNodes.some((n) => n.id === failedNode.id) ? readyNodes : [failedNode, ...readyNodes];
1996
+ const readyNodes = findReadyNodes(workflow.definition, completedNodeIds, activeEdges);
1997
+ const nodesToRun = !shouldRetryFailedNode || readyNodes.some((n) => n.id === failedNode.id) ? readyNodes : [failedNode, ...readyNodes];
1906
1998
  const secretKeys = getSecretInputKeys(workflow.input);
1907
1999
  await walkGraph(workflow.definition, runId, ctx, nodesToRun, registry, workflow.id, secretKeys);
1908
2000
  }
@@ -1911,15 +2003,17 @@ async function cancelWorkflowRun(runId, reason) {
1911
2003
  if (!run)
1912
2004
  throw new Error("Workflow run not found");
1913
2005
  const terminalStatuses = ["completed", "failed", "cancelled", "skipped"];
1914
- if (terminalStatuses.includes(run.status)) {
2006
+ if (terminalStatuses.includes(run.status) && run.status !== "cancelled") {
1915
2007
  throw new Error(`Cannot cancel run in '${run.status}' state`);
1916
2008
  }
1917
2009
  const now = new Date().toISOString();
1918
2010
  const cancelReason = reason ?? "Cancelled by user";
2011
+ let applied = false;
1919
2012
  await getDbClient().transaction(async () => {
1920
2013
  const current = await getWorkflowRun(runId);
1921
2014
  if (!current || terminalStatuses.includes(current.status))
1922
2015
  return;
2016
+ applied = true;
1923
2017
  const steps = await getWorkflowRunStepsByRunId(runId);
1924
2018
  for (const step of steps) {
1925
2019
  if (terminalStatuses.includes(step.status))
@@ -1934,12 +2028,19 @@ async function cancelWorkflowRun(runId, reason) {
1934
2028
  finishedAt: now
1935
2029
  });
1936
2030
  }
2031
+ await cancelPendingApprovalRequestsForRun(runId, cancelReason);
1937
2032
  await updateWorkflowRun(runId, {
1938
2033
  status: "cancelled",
1939
2034
  error: cancelReason,
1940
2035
  finishedAt: now
1941
2036
  });
1942
2037
  });
2038
+ const cancelledApprovals = await listCancelledApprovalRequestsForRun(runId);
2039
+ const latestRun = await getWorkflowRun(runId);
2040
+ if (!applied && latestRun?.status !== "cancelled") {
2041
+ throw new Error(`Cannot cancel run in '${latestRun?.status ?? "missing"}' state`);
2042
+ }
2043
+ await postApprovalCancellationUpdates(cancelledApprovals, applied ? cancelReason : latestRun?.error ?? cancelReason);
1943
2044
  }
1944
2045
  async function resumeFromApprovalResolution(event, registry) {
1945
2046
  const run = await getWorkflowRun(event.workflowRunId);
@@ -2324,6 +2425,7 @@ function startRetryPoller(registry, intervalMs = 5000) {
2324
2425
  const ctx = run.context ?? {};
2325
2426
  if (!("run" in ctx))
2326
2427
  ctx.run = { id: run.id };
2428
+ await rehydrateCompletedStepOutputs(workflow.definition, run.id, ctx, registry);
2327
2429
  const inputCtx = buildNodeInterpolationCtx(node, ctx);
2328
2430
  const { value: interpolatedValue, scriptBodyUnresolved } = interpolateNodeConfig(node, inputCtx);
2329
2431
  if (scriptBodyUnresolved && scriptBodyUnresolved.length > 0) {
@@ -2351,7 +2453,9 @@ function startRetryPoller(registry, intervalMs = 5000) {
2351
2453
  const retryPolicy = node.retry || executor.retryPolicy;
2352
2454
  await checkpointStepFailure(run.id, step.id, result.error || "Retry failed", step.retryCount, retryPolicy);
2353
2455
  } else if ("async" in result && result.async) {
2354
- await checkpointStepWaiting(run.id, step.id, ctx);
2456
+ const waiting = await checkpointStepWaiting(run.id, step.id, ctx);
2457
+ if (!waiting)
2458
+ continue;
2355
2459
  } else {
2356
2460
  if (node.validation) {
2357
2461
  const validationResult = await runStepValidation(registry, node, result.output, ctx, meta);
@@ -3054,6 +3158,7 @@ function normalizeExistingTaskOutput(output) {
3054
3158
  // src/workflows/executors/human-in-the-loop.ts
3055
3159
  init_zod();
3056
3160
  init_constants();
3161
+ init_secret_scrubber();
3057
3162
  var SelectOptionSchema = exports_external.object({
3058
3163
  value: exports_external.string(),
3059
3164
  label: exports_external.string(),
@@ -3138,6 +3243,12 @@ class HumanInTheLoopExecutor extends BaseExecutor {
3138
3243
  const existing = await db.getApprovalRequestByStepId(meta.stepId);
3139
3244
  if (existing) {
3140
3245
  if (existing.status !== "pending") {
3246
+ if (existing.status === "cancelled") {
3247
+ return {
3248
+ status: "failed",
3249
+ error: `Approval request was cancelled: ${existing.resolutionReason ?? "workflow run cancelled"}`
3250
+ };
3251
+ }
3141
3252
  const nextPort = existing.status === "timeout" ? "timeout" : existing.status === "rejected" ? "rejected" : "approved";
3142
3253
  return {
3143
3254
  status: "success",
@@ -3157,7 +3268,7 @@ class HumanInTheLoopExecutor extends BaseExecutor {
3157
3268
  };
3158
3269
  }
3159
3270
  const requestId = crypto.randomUUID();
3160
- await db.createApprovalRequest({
3271
+ const created = await db.createApprovalRequest({
3161
3272
  id: requestId,
3162
3273
  title: config.title,
3163
3274
  questions: config.questions,
@@ -3166,11 +3277,20 @@ class HumanInTheLoopExecutor extends BaseExecutor {
3166
3277
  workflowRunStepId: meta.stepId,
3167
3278
  timeoutSeconds: config.timeout?.seconds,
3168
3279
  notificationChannels: config.notifications,
3169
- createdBy: meta.requestedByUserId
3280
+ createdBy: meta.requestedByUserId,
3281
+ requireActionableWorkflow: true
3170
3282
  });
3283
+ if (created.status === "cancelled") {
3284
+ return {
3285
+ status: "success",
3286
+ async: true,
3287
+ waitFor: "approval.resolved",
3288
+ correlationId: requestId
3289
+ };
3290
+ }
3171
3291
  if (config.notifications?.length) {
3172
3292
  this.dispatchNotifications(requestId, config, db).catch((err) => {
3173
- console.error("[HITL] Unexpected error dispatching notifications:", err);
3293
+ console.error("[HITL] Unexpected error dispatching notifications:", scrubSecrets(err instanceof Error ? err.message : String(err)));
3174
3294
  });
3175
3295
  }
3176
3296
  return {
@@ -3194,8 +3314,8 @@ class HumanInTheLoopExecutor extends BaseExecutor {
3194
3314
  }
3195
3315
  if (notification.channel === "slack") {
3196
3316
  try {
3197
- const { getSlackApp } = await import("./app-g2s78xya.js");
3198
- const slackApp = getSlackApp();
3317
+ const { getSlackApp: getSlackApp2 } = await import("./app-wb6bc7tt.js");
3318
+ const slackApp = getSlackApp2();
3199
3319
  if (!slackApp) {
3200
3320
  console.warn("[HITL] Slack not initialized — cannot send notification");
3201
3321
  continue;
@@ -3245,15 +3365,19 @@ ${questionsSummary}${timeoutText}`
3245
3365
  }
3246
3366
  console.log(`[HITL] Slack notification sent to ${notification.target} for request ${requestId}`);
3247
3367
  } catch (err) {
3248
- console.error(`[HITL] Failed to send Slack notification to ${notification.target}:`, err);
3368
+ console.error(`[HITL] Failed to send Slack notification to ${notification.target}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
3249
3369
  }
3250
3370
  }
3251
3371
  }
3252
3372
  if (updated) {
3253
3373
  try {
3254
3374
  await db.updateApprovalRequestNotifications(requestId, updatedChannels);
3375
+ const latest = await db.getApprovalRequestById(requestId);
3376
+ if (latest?.status === "cancelled") {
3377
+ await postApprovalCancellationUpdates([latest], latest.resolutionReason ?? "Workflow run cancelled");
3378
+ }
3255
3379
  } catch (err) {
3256
- console.error("[HITL] Failed to update notification channels in DB:", err);
3380
+ console.error("[HITL] Failed to update notification channels in DB:", scrubSecrets(err instanceof Error ? err.message : String(err)));
3257
3381
  }
3258
3382
  }
3259
3383
  }
@@ -3310,8 +3434,8 @@ class NotifyExecutor extends BaseExecutor {
3310
3434
  }
3311
3435
  }
3312
3436
  case "slack": {
3313
- const { getSlackApp } = await import("./app-g2s78xya.js");
3314
- const app = getSlackApp();
3437
+ const { getSlackApp: getSlackApp2 } = await import("./app-wb6bc7tt.js");
3438
+ const app = getSlackApp2();
3315
3439
  if (!app) {
3316
3440
  return {
3317
3441
  status: "success",
@@ -3422,6 +3546,26 @@ function evaluateCondition(op, actual, expected) {
3422
3546
 
3423
3547
  // src/workflows/executors/raw-llm.ts
3424
3548
  init_zod();
3549
+
3550
+ // src/workflows/executors/workflow-llm.ts
3551
+ async function resolveWorkflowLlmConfig(requestedModel, env = process.env) {
3552
+ const credential = await resolveCredential({ env });
3553
+ if (!credential) {
3554
+ throw new Error("No workflow LLM credential found. Set OPENROUTER_API_KEY or OPENAI_API_KEY.");
3555
+ }
3556
+ if (credential.kind !== "openrouter" && credential.kind !== "openai") {
3557
+ throw new Error(`Workflow LLM nodes do not support the resolved ${credential.kind} credential yet. Set OPENROUTER_API_KEY or OPENAI_API_KEY.`);
3558
+ }
3559
+ const providerPrefix = `${credential.kind}/`;
3560
+ const model = requestedModel ?? DEFAULT_MODEL[credential.kind];
3561
+ return {
3562
+ apiKey: credential.apiKey,
3563
+ baseURL: credential.kind === "openrouter" ? getOpenRouterBaseUrl(env) : undefined,
3564
+ model: model.startsWith(providerPrefix) ? model.slice(providerPrefix.length) : model
3565
+ };
3566
+ }
3567
+
3568
+ // src/workflows/executors/raw-llm.ts
3425
3569
  var RawLlmConfigSchema = exports_external.object({
3426
3570
  prompt: exports_external.string(),
3427
3571
  model: exports_external.string().optional(),
@@ -3433,16 +3577,18 @@ var RawLlmOutputSchema = exports_external.object({
3433
3577
  model: exports_external.string()
3434
3578
  });
3435
3579
  async function executeRawLlm(config) {
3436
- const modelName = config.model ?? "google/gemini-3-flash-preview";
3580
+ let modelName = config.model ?? "google/gemini-3-flash-preview";
3437
3581
  try {
3438
- const { createOpenAI } = await import("./index-p13cfsc2.js");
3439
- const openrouter = createOpenAI({
3440
- baseURL: getOpenRouterBaseUrl(),
3441
- apiKey: process.env.OPENROUTER_API_KEY
3582
+ const { createOpenAI } = await import("./index-agykc6k2.js");
3583
+ const llmConfig = await resolveWorkflowLlmConfig(config.model);
3584
+ modelName = llmConfig.model;
3585
+ const provider = createOpenAI({
3586
+ baseURL: llmConfig.baseURL,
3587
+ apiKey: llmConfig.apiKey
3442
3588
  });
3443
- const model = openrouter(modelName);
3589
+ const model = provider(llmConfig.model);
3444
3590
  if (config.schema) {
3445
- const { generateObject, jsonSchema } = await import("./index-pe58mf1r.js");
3591
+ const { generateObject, jsonSchema } = await import("./index-1nsdwvn5.js");
3446
3592
  const { object } = await generateObject({
3447
3593
  model,
3448
3594
  schema: jsonSchema(config.schema),
@@ -3456,7 +3602,7 @@ async function executeRawLlm(config) {
3456
3602
  output: { result: object, model: modelName }
3457
3603
  };
3458
3604
  }
3459
- const { generateText } = await import("./index-pe58mf1r.js");
3605
+ const { generateText } = await import("./index-1nsdwvn5.js");
3460
3606
  const { text } = await generateText({
3461
3607
  model,
3462
3608
  prompt: config.prompt
@@ -3889,14 +4035,15 @@ class ValidateExecutor extends BaseExecutor {
3889
4035
  const interpolatedPrompt = this.deps.interpolate(prompt, context);
3890
4036
  const outputStr = typeof targetOutput === "string" ? targetOutput : JSON.stringify(targetOutput);
3891
4037
  try {
3892
- const { createOpenAI } = await import("./index-p13cfsc2.js");
3893
- const { generateObject, jsonSchema } = await import("./index-pe58mf1r.js");
3894
- const openrouter = createOpenAI({
3895
- baseURL: getOpenRouterBaseUrl(),
3896
- apiKey: process.env.OPENROUTER_API_KEY
4038
+ const { createOpenAI } = await import("./index-agykc6k2.js");
4039
+ const { generateObject, jsonSchema } = await import("./index-1nsdwvn5.js");
4040
+ const llmConfig = await resolveWorkflowLlmConfig();
4041
+ const provider = createOpenAI({
4042
+ baseURL: llmConfig.baseURL,
4043
+ apiKey: llmConfig.apiKey
3897
4044
  });
3898
4045
  const { object } = await generateObject({
3899
- model: openrouter("google/gemini-3-flash-preview"),
4046
+ model: provider(llmConfig.model),
3900
4047
  schema: jsonSchema({
3901
4048
  type: "object",
3902
4049
  properties: {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  summarizeSession
3
- } from "./cli-qhfkxrpg.js";
3
+ } from "./cli-390sgxn5.js";
4
4
  import {
5
5
  checkToolLoop,
6
6
  clearToolHistory
@@ -27,14 +27,14 @@ import {
27
27
  getMcpBaseUrl,
28
28
  init_constants
29
29
  } from "./cli-a1rrkrb4.js";
30
+ import {
31
+ init_package,
32
+ package_default
33
+ } from "./cli-kkn46gfr.js";
30
34
  import {
31
35
  init_secret_scrubber,
32
36
  scrubSecrets
33
37
  } from "./cli-mkq1rk7b.js";
34
- import {
35
- init_package,
36
- package_default
37
- } from "./cli-4r4jkqep.js";
38
38
 
39
39
  // src/hooks/hook.ts
40
40
  init_package();