@desplega.ai/agent-swarm 2.5.1 → 2.5.3

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 (197) hide show
  1. package/dist/{acp-adapter-7g6bpwbk.js → acp-adapter-gr2fcyyj.js} +3 -3
  2. package/dist/{actions-w8xd014k.js → actions-wv766ghf.js} +18 -18
  3. package/dist/{amp-adapter-z4qvhacj.js → amp-adapter-speb906t.js} +3 -3
  4. package/dist/{app-nndcvftq.js → app-ag29n14f.js} +3 -3
  5. package/dist/{assistant-hjmqyscx.js → assistant-3x8bz3eg.js} +19 -19
  6. package/dist/{boot-reembed-r8rhf9h8.js → boot-reembed-5b3d2eea.js} +1 -1
  7. package/dist/{boot-reembed-vwcj2y5d.js → boot-reembed-jsmfrpzf.js} +1 -1
  8. package/dist/{boot-scrub-sweep-fr9q36ne.js → boot-scrub-sweep-s73gerxy.js} +2 -2
  9. package/dist/{boot-seeds-9jqwnegq.js → boot-seeds-5zmax2wr.js} +4 -4
  10. package/dist/{claude-adapter-2f8b85df.js → claude-adapter-hswdnm3t.js} +3 -3
  11. package/dist/{claude-managed-adapter-gt6pa35d.js → claude-managed-adapter-cvbzj1h6.js} +2 -2
  12. package/dist/{claude-managed-setup-c3hp36ph.js → claude-managed-setup-3k8m3hhc.js} +2 -2
  13. package/dist/{claude-sdk-session-05kat8rv.js → claude-sdk-session-fkbyk66x.js} +6905 -6855
  14. package/dist/{cli-y3zyaee5.js → cli-1fc85mvt.js} +5 -5
  15. package/dist/{cli-v2g93k4v.js → cli-210xd1f4.js} +4 -4
  16. package/dist/{cli-sn1vbtg2.js → cli-25j01dpj.js} +3 -3
  17. package/dist/{cli-n6kmqngw.js → cli-35gtk1p6.js} +1 -1
  18. package/dist/{cli-r9fy0ddc.js → cli-3ngdk5te.js} +1 -1
  19. package/dist/{cli-z63a269p.js → cli-40jsnrjm.js} +2 -2
  20. package/dist/{cli-ba8ne1es.js → cli-4rvr7zfx.js} +10 -0
  21. package/dist/{cli-qfm6my86.js → cli-502eqv93.js} +1 -1
  22. package/dist/{cli-xd4eqtr2.js → cli-6n8axjv1.js} +1 -1
  23. package/dist/{cli-hadv0725.js → cli-8s5wx3nz.js} +2 -2
  24. package/dist/{cli-z2dv25mf.js → cli-99xgw26r.js} +116 -5
  25. package/dist/{cli-jpxsrxcb.js → cli-9s05422w.js} +1 -1
  26. package/dist/{cli-kcsd1ez8.js → cli-aecdcr77.js} +343 -325
  27. package/dist/{cli-w9etnnxk.js → cli-bf8ma3f1.js} +120 -50
  28. package/dist/{cli-e9btqw4y.js → cli-ce0ak9ms.js} +18 -13
  29. package/dist/{cli-nj12tn2c.js → cli-e2ar4bp1.js} +71 -20
  30. package/dist/{cli-gwcf52gz.js → cli-e8jyq7d8.js} +11 -7
  31. package/dist/{cli-rngde2jv.js → cli-egjggehm.js} +1 -1
  32. package/dist/{cli-qfjm4gvt.js → cli-ewm3pwbq.js} +2 -2
  33. package/dist/{cli-9mm5ade4.js → cli-fsdprwrg.js} +1 -1
  34. package/dist/{cli-3ee5z0q0.js → cli-fzcn3d87.js} +1 -1
  35. package/dist/{cli-eym3jg7w.js → cli-gpsz7g88.js} +6 -6
  36. package/dist/{cli-cwyafhw1.js → cli-hcpj2xqz.js} +1 -1
  37. package/dist/{cli-j1eghqfm.js → cli-hhxjnjzs.js} +1 -1
  38. package/dist/{cli-pyy4c9ch.js → cli-jjznk3f7.js} +881 -232
  39. package/dist/{cli-q9pzwxvk.js → cli-kxawjrxg.js} +222 -37
  40. package/dist/{cli-rh5smt8t.js → cli-mz9afxxf.js} +14 -3
  41. package/dist/{cli-mz2ygeyq.js → cli-n45mr7zh.js} +2 -2
  42. package/dist/{cli-fes51ya2.js → cli-p0rzjkya.js} +2 -2
  43. package/dist/{cli-3qv8naq8.js → cli-p6zs9h4y.js} +827 -107
  44. package/dist/{cli-mq7kg1b1.js → cli-p9rafdrb.js} +41 -6
  45. package/dist/{cli-cah5detr.js → cli-qgf8emrn.js} +5 -5
  46. package/dist/{cli-2wba1ac9.js → cli-qzme5949.js} +26 -26
  47. package/dist/{cli-znvq0k3t.js → cli-s49hpn37.js} +2 -2
  48. package/dist/{cli-xc65x52q.js → cli-s53vp709.js} +1 -1
  49. package/dist/{cli-0jy85zz4.js → cli-svrj71pg.js} +18 -16
  50. package/dist/{cli-1dp1zj04.js → cli-vwn81jbs.js} +3 -3
  51. package/dist/{cli-ajqhxtnz.js → cli-w1zwctj2.js} +1 -1
  52. package/dist/{cli-22rmcjkg.js → cli-y561h5a9.js} +7 -6
  53. package/dist/{cli-2jg61wab.js → cli-z21whrrg.js} +15 -15
  54. package/dist/cli.js +19 -18
  55. package/dist/{codex-adapter-p4ptwar5.js → codex-adapter-n8ka66vf.js} +2 -2
  56. package/dist/{codex-session-runner-964c7dnk.js → codex-session-runner-fsebts37.js} +1 -1
  57. package/dist/{commands-mbvhbrcy.js → commands-t04rt2pc.js} +2 -2
  58. package/dist/{controller-gnq1k1mf.js → controller-rgna04r2.js} +2 -2
  59. package/dist/{controllers-connect-6dkm71tg.js → controllers-connect-bqm1rtft.js} +20 -20
  60. package/dist/{cursor-adapter-yq5xgfcg.js → cursor-adapter-swr2k95y.js} +1 -1
  61. package/dist/{db-88wcymxt.js → db-24ck09xy.js} +1 -1
  62. package/dist/{db-af9y8nkf.js → db-a4m7haw0.js} +3 -3
  63. package/dist/{dispatcher-606ydx5v.js → dispatcher-nwf9ss0q.js} +17 -17
  64. package/dist/{dsh-adapter-tvhc4k2d.js → dsh-adapter-kpdnhjst.js} +2 -2
  65. package/dist/{external-channel-y8q5z8z2.js → external-channel-j1e2p34p.js} +2 -2
  66. package/dist/{grok-adapter-39aha52t.js → grok-adapter-qtjz0whk.js} +4 -4
  67. package/dist/{handlers-rk9dqech.js → handlers-19xr6dnz.js} +20 -20
  68. package/dist/{heartbeat-jfe9zfjr.js → heartbeat-vtesda5d.js} +20 -20
  69. package/dist/{hook-b1mez1m8.js → hook-8wqaszda.js} +1 -1
  70. package/dist/{hook-ffevzed1.js → hook-b2hhj6rd.js} +1 -1
  71. package/dist/{http-d5b44f1r.js → http-mxcqhhcs.js} +3020 -2463
  72. package/dist/{index-5en23s5z.js → index-1r8wfpzs.js} +1 -1
  73. package/dist/{index-cbxkmfc5.js → index-9pn1pfkf.js} +1 -1
  74. package/dist/{index-mj46rk9k.js → index-kw2mmat7.js} +20 -20
  75. package/dist/{index-nq8hhv28.js → index-n1vbvta9.js} +19 -10
  76. package/dist/{index-6srwvd5b.js → index-p4xfz37z.js} +1 -1
  77. package/dist/{index-d2qeem97.js → index-spwxjgay.js} +17 -17
  78. package/dist/{index-4q3vj1yr.js → index-srg522m6.js} +17 -17
  79. package/dist/{keepalive-wsby1btq.js → keepalive-dsd8rtgx.js} +3 -3
  80. package/dist/{lead-paa141j6.js → lead-zyerk1x2.js} +30 -30
  81. package/dist/{maintenance-vnshcyv9.js → maintenance-jqmqwnet.js} +1 -1
  82. package/dist/{oauth-refresh-sweep-d6sqn5np.js → oauth-refresh-sweep-q2c3bnvm.js} +2 -2
  83. package/dist/{onboard-nr4xhpzy.js → onboard-w4yd7s8t.js} +2 -2
  84. package/dist/{opencode-adapter-e56086m1.js → opencode-adapter-9tppvxar.js} +2 -2
  85. package/dist/{otel-impl-ytn9wyv9.js → otel-impl-f36byzbd.js} +1 -1
  86. package/dist/{pricing-refresh-pva04zee.js → pricing-refresh-xvak5p4v.js} +3 -3
  87. package/dist/{rbac-roles-7rc1q3g4.js → rbac-roles-682k2mt5.js} +12 -2
  88. package/dist/rbac-roles-gfn30fb8.js +26 -0
  89. package/dist/{render-v2-pyfsg9zk.js → render-v2-hgytz8nn.js} +3 -3
  90. package/dist/{resume-phj8xhqr.js → resume-2dvq348g.js} +7 -5
  91. package/dist/{session-access-jzx2z9h5.js → session-access-7f21ekq9.js} +4 -4
  92. package/dist/{setup-rfg0f0sg.js → setup-epffxtbk.js} +1 -1
  93. package/dist/{work-objects-44z2yjg8.js → work-objects-xz1a5rqk.js} +20 -20
  94. package/dist/{worker-zsfqzxhm.js → worker-42t8emjt.js} +30 -30
  95. package/openapi.json +3163 -2114
  96. package/package.json +7 -6
  97. package/src/be/approval-config-question.ts +389 -0
  98. package/src/be/automation-preflight.ts +37 -3
  99. package/src/be/db/session-tree.ts +158 -13
  100. package/src/be/db/session-visibility.ts +31 -0
  101. package/src/be/db/skill-search.ts +87 -0
  102. package/src/be/db/tasks/read.ts +47 -7
  103. package/src/be/db/workflow-runs.ts +54 -7
  104. package/src/be/db.ts +27 -5
  105. package/src/be/migrations/233_script_run_session_tokens.sql +8 -0
  106. package/src/be/migrations/235_chat_relay_defer.sql +86 -0
  107. package/src/be/migrations/237_session_inbox_cleanup.sql +75 -0
  108. package/src/be/migrations/239_skills_fts.sql +40 -0
  109. package/src/be/migrations/240_workflow_run_parent_step_attempt.sql +37 -0
  110. package/src/be/migrations/241_skills_meta_fts.sql +39 -0
  111. package/src/be/rbac-roles.ts +138 -0
  112. package/src/be/scripts/run-credential.ts +64 -0
  113. package/src/be/scripts/run-saved.ts +3 -2
  114. package/src/be/swarm-config-guard.ts +23 -0
  115. package/src/be/users.ts +28 -4
  116. package/src/extensions/catalog.generated.json +4 -4
  117. package/src/http/all-routes.ts +1 -0
  118. package/src/http/approval-requests.ts +254 -43
  119. package/src/http/config.ts +22 -37
  120. package/src/http/index.ts +2 -0
  121. package/src/http/schedules.ts +26 -0
  122. package/src/http/scripts.ts +2 -2
  123. package/src/http/sessions.ts +118 -2
  124. package/src/http/user-roles.ts +173 -0
  125. package/src/http/users.ts +9 -0
  126. package/src/http/x.ts +2 -2
  127. package/src/integrations/chat-sdk/commands.ts +14 -1
  128. package/src/integrations/chat-sdk/inbound.ts +42 -6
  129. package/src/integrations/chat-sdk/outbound.ts +35 -10
  130. package/src/integrations/chat-sdk/reactions.ts +107 -0
  131. package/src/integrations/chat-sdk/relay.ts +75 -7
  132. package/src/integrations/chat-sdk/thread-buffer.ts +2 -0
  133. package/src/integrations/reaction-events.ts +20 -0
  134. package/src/providers/amp-adapter.ts +1 -1
  135. package/src/providers/dsh-adapter.ts +1 -1
  136. package/src/rbac/legacy-policy.ts +3 -0
  137. package/src/rbac/permissions.ts +8 -0
  138. package/src/scheduler/schedule-task.ts +44 -3
  139. package/src/scheduler/scheduler.ts +4 -4
  140. package/src/scripts-runtime/loader.ts +8 -1
  141. package/src/slack/ack.ts +2 -1
  142. package/src/slack/reaction-shortcode.ts +4 -7
  143. package/src/tasks/worker-follow-up.ts +30 -0
  144. package/src/tests/approval-config-question.test.ts +702 -0
  145. package/src/tests/approval-requests-notifications.test.ts +189 -0
  146. package/src/tests/chat-sdk-commands.test.ts +12 -0
  147. package/src/tests/chat-sdk-inbound.test.ts +183 -9
  148. package/src/tests/chat-sdk-outbound-files.test.ts +9 -2
  149. package/src/tests/chat-sdk-relay.test.ts +361 -10
  150. package/src/tests/chat-sdk-tools.test.ts +104 -0
  151. package/src/tests/docs-index-footprint.test.js +3 -3
  152. package/src/tests/dsh-adapter.test.ts +1 -1
  153. package/src/tests/entrypoint-gitlab-credential-helper.test.ts +273 -0
  154. package/src/tests/extensions-example-comms-adherence-gate.test.ts +321 -12
  155. package/src/tests/http-users.test.ts +38 -0
  156. package/src/tests/list-endpoint-slimming.test.ts +33 -0
  157. package/src/tests/migration-237-session-inbox-cleanup.test.ts +136 -0
  158. package/src/tests/migration-240-workflow-run-parent-step-attempt.test.ts +55 -0
  159. package/src/tests/rbac-engine.test.ts +2 -0
  160. package/src/tests/rbac-roles.test.ts +10 -0
  161. package/src/tests/schedule-http-triage-tools.test.ts +44 -0
  162. package/src/tests/script-api-credential.test.ts +173 -0
  163. package/src/tests/script-connections.test.ts +10 -0
  164. package/src/tests/script-run-config-scope.test.ts +120 -0
  165. package/src/tests/script-run-token.test.ts +75 -0
  166. package/src/tests/scripts-http.test.ts +19 -0
  167. package/src/tests/sessions-tree.test.ts +410 -1
  168. package/src/tests/skill-search-long-content.test.ts +193 -0
  169. package/src/tests/skill-search.test.ts +173 -22
  170. package/src/tests/user-roles-http.test.ts +204 -0
  171. package/src/tests/workflow-hitl-dynamic-questions.test.ts +1 -1
  172. package/src/tests/workflow-sub-workflow.test.ts +538 -1
  173. package/src/tools/chat-reply.ts +2 -0
  174. package/src/tools/request-human-input.ts +27 -5
  175. package/src/tools/schedules/patch-schedule.ts +18 -0
  176. package/src/tools/schedules/update-schedule.ts +18 -0
  177. package/src/types.ts +23 -0
  178. package/src/workflows/approval-notifications.ts +221 -0
  179. package/src/workflows/checkpoint.ts +69 -35
  180. package/src/workflows/engine.ts +234 -87
  181. package/src/workflows/executors/base.ts +6 -0
  182. package/src/workflows/executors/human-in-the-loop.ts +36 -173
  183. package/src/workflows/executors/sub-workflow.ts +45 -16
  184. package/src/workflows/executors/swarm-script.ts +2 -2
  185. package/src/workflows/recovery.ts +63 -1
  186. package/src/workflows/resume.ts +60 -21
  187. package/src/workflows/retry-poller.ts +25 -4
  188. package/templates/extensions/comms-adherence-gate/README.md +38 -13
  189. package/templates/extensions/comms-adherence-gate/hooks.ts +272 -69
  190. package/templates/extensions/comms-adherence-gate/manifest.json +2 -2
  191. package/templates/schedules/weekly-harness-upgrade-check/content.md +1 -1
  192. package/templates/skills/code-quality/content.md +1 -1
  193. package/templates/skills/turso-interaction/content.md +1 -1
  194. package/templates/skills/workflow-iterate/content.md +8 -1
  195. package/dist/rbac-roles-ff1ffmec.js +0 -12
  196. /package/dist/{cli-stxdrqs2.js → cli-avdvgcay.js} +0 -0
  197. /package/dist/{pi-mono-adapter-tzps8f38.js → pi-mono-adapter-tqsge1ay.js} +0 -0
@@ -4,9 +4,22 @@ import {
4
4
  scrubJsonValue,
5
5
  init_scrub_json,
6
6
  getDbClient2,
7
+ getAgentById2,
8
+ updateAgentStatus2,
9
+ updateAgentMaxTasks2,
10
+ getActiveTaskCount2,
7
11
  getTaskById2,
12
+ getChildWorkflowRunId,
13
+ isParentStepAttemptCurrent,
8
14
  createForeachChildSteps,
9
15
  init_workflow_runs,
16
+ isApiOnlyConfigKey,
17
+ isInternalConfigKey,
18
+ internalConfigKeyError,
19
+ isReservedConfigKey,
20
+ reservedKeyError,
21
+ validateConfigEntry,
22
+ init_swarm_config_guard,
10
23
  getOAuthApp,
11
24
  getOAuthTokens,
12
25
  init_oauth,
@@ -14,6 +27,7 @@ import {
14
27
  init_workflow_replay,
15
28
  createTaskExtended2,
16
29
  getSwarmConfigs2,
30
+ upsertSwarmConfig2,
17
31
  getWorkflow2,
18
32
  createWorkflowRun2,
19
33
  getWorkflowRun2,
@@ -31,22 +45,36 @@ import {
31
45
  getStepCountForNode2,
32
46
  getLatestStepForNode2,
33
47
  getCurrentStepForNode2,
48
+ getApprovalRequestById2,
34
49
  claimApprovalCancellationNotification2,
35
50
  completeApprovalCancellationNotificationClaim2,
36
51
  releaseApprovalCancellationNotificationClaim2,
52
+ updateApprovalRequestNotifications2,
37
53
  init_db3
38
- } from "./cli-q9pzwxvk.js";
54
+ } from "./cli-kxawjrxg.js";
55
+ import {
56
+ isMultiRuntimeEnabled
57
+ } from "./cli-e8xyjvfc.js";
39
58
  import {
40
59
  getSlackApp2
41
- } from "./cli-e9btqw4y.js";
60
+ } from "./cli-ce0ak9ms.js";
61
+ import {
62
+ externalChannelToolError2,
63
+ isBlockExternalEnabled2,
64
+ getChannelBlockReason2
65
+ } from "./cli-s53vp709.js";
42
66
  import {
43
67
  DEFAULT_MODEL,
44
68
  resolveCredential
45
69
  } from "./cli-qqzmjxkf.js";
70
+ import {
71
+ revokeRuntimeTokensForRuntime,
72
+ init_users
73
+ } from "./cli-e8jyq7d8.js";
46
74
  import {
47
75
  TERMINAL_TASK_STATUSES,
48
76
  init_types
49
- } from "./cli-rh5smt8t.js";
77
+ } from "./cli-mz9afxxf.js";
50
78
  import {
51
79
  getSlackConfiguration,
52
80
  isSlackConfigured,
@@ -117,7 +145,7 @@ async function notifyAutomationPreflightFailure(preflight) {
117
145
  return;
118
146
  }
119
147
  try {
120
- const { getSlackApp } = await import("./app-nndcvftq.js");
148
+ const { getSlackApp } = await import("./app-ag29n14f.js");
121
149
  const app = getSlackApp();
122
150
  if (!app) {
123
151
  console.warn("[Automation Preflight] Slack not available, cannot send notification");
@@ -355,6 +383,15 @@ function utcDayBounds(now) {
355
383
  const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
356
384
  return { start: start.toISOString(), end: new Date(start.getTime() + 86400000).toISOString() };
357
385
  }
386
+ async function isFirstWorkflowPreflightFailureToday(workflowId, runId, now = new Date) {
387
+ const bounds = utcDayBounds(now);
388
+ const row = await getDbClient2().get(`SELECT 1 AS found FROM workflow_runs
389
+ WHERE workflowId = ? AND id != ? AND status = 'failed'
390
+ AND error LIKE 'needs_setup:%'
391
+ AND startedAt >= ? AND startedAt < ?
392
+ LIMIT 1`, [workflowId, runId, bounds.start, bounds.end]);
393
+ return row == null;
394
+ }
358
395
  async function recordSchedulePreflightFailure(scheduleId, failureReason, now = new Date) {
359
396
  const message = scrubSecrets(failureReason);
360
397
  return await getDbClient2().transaction(async (tx) => {
@@ -375,7 +412,7 @@ async function recordWorkflowPreflightFailure(input) {
375
412
  const bounds = utcDayBounds(now);
376
413
  return await getDbClient2().transaction(async (tx) => {
377
414
  const existing = await tx.get(`SELECT id FROM workflow_runs
378
- WHERE workflowId = ? AND status = 'failed'
415
+ WHERE workflowId = ? AND status = 'failed' AND parentStepId IS NULL
379
416
  AND error LIKE 'needs_setup:%'
380
417
  AND startedAt >= ? AND startedAt < ?
381
418
  ORDER BY startedAt ASC LIMIT 1`, [input.workflowId, bounds.start, bounds.end]);
@@ -397,7 +434,10 @@ async function recordWorkflowPreflightFailure(input) {
397
434
  timestamp,
398
435
  timestamp
399
436
  ]);
400
- return { runId, recorded: true };
437
+ return {
438
+ runId,
439
+ recorded: await isFirstWorkflowPreflightFailureToday(input.workflowId, runId, now)
440
+ };
401
441
  });
402
442
  }
403
443
 
@@ -450,14 +490,628 @@ class BaseExecutor {
450
490
 
451
491
  // src/workflows/executors/human-in-the-loop.ts
452
492
  init_zod();
453
- init_constants2();
493
+
494
+ // src/be/approval-config-question.ts
495
+ init_zod();
496
+ init_db3();
497
+
498
+ // src/be/multi-runtime.ts
499
+ init_secret_scrubber();
500
+ init_db3();
501
+ init_users();
502
+ var AGENT_MAX_TASKS_CONFIG_KEY = "AGENT_MAX_TASKS";
503
+ function parseAgentMaxTasksValue(raw) {
504
+ const str = raw.trim();
505
+ if (!/^\d+$/.test(str))
506
+ return null;
507
+ const value = Number(str);
508
+ return value >= 1 && value <= 100 ? value : null;
509
+ }
510
+ function defaultMaxTasksForAgent(agent) {
511
+ return agent.isLead ? 2 : 1;
512
+ }
513
+ async function getAgentMaxTasksConfig(agentId) {
514
+ return (await getSwarmConfigs2({ scope: "agent", scopeId: agentId, key: AGENT_MAX_TASKS_CONFIG_KEY }))[0] ?? null;
515
+ }
516
+ async function reconcileAgentMaxTasksPolicy(agentId) {
517
+ const agent = await getAgentById2(agentId);
518
+ if (!agent)
519
+ return;
520
+ const existing = await getAgentMaxTasksConfig(agentId);
521
+ if (existing) {
522
+ const authoritative = parseAgentMaxTasksValue(existing.value);
523
+ if (authoritative !== null && authoritative !== (agent.maxTasks ?? 1)) {
524
+ await updateAgentMaxTasks2(agentId, authoritative);
525
+ }
526
+ return;
527
+ }
528
+ await upsertSwarmConfig2({
529
+ scope: "agent",
530
+ scopeId: agentId,
531
+ key: AGENT_MAX_TASKS_CONFIG_KEY,
532
+ value: String(agent.maxTasks ?? defaultMaxTasksForAgent(agent)),
533
+ description: "Logical agent max-tasks policy (seeded from agents.maxTasks)"
534
+ });
535
+ }
536
+ async function resetAgentMaxTasksMirror(agentId) {
537
+ const agent = await getAgentById2(agentId);
538
+ if (!agent)
539
+ return;
540
+ await updateAgentMaxTasks2(agentId, defaultMaxTasksForAgent(agent));
541
+ }
542
+ async function upsertSwarmConfigWithPolicyMirror(data) {
543
+ const scopeId = data.scope === "agent" ? data.scopeId : null;
544
+ if (!scopeId || data.key !== AGENT_MAX_TASKS_CONFIG_KEY) {
545
+ return upsertSwarmConfig2(data);
546
+ }
547
+ return await getDbClient2().transaction(async () => {
548
+ const row = await upsertSwarmConfig2(data);
549
+ const value = parseAgentMaxTasksValue(data.value);
550
+ if (value !== null)
551
+ await updateAgentMaxTasks2(scopeId, value);
552
+ return row;
553
+ });
554
+ }
555
+ function rowToRuntimeInstance(row) {
556
+ let metadata = null;
557
+ if (row.metadata) {
558
+ try {
559
+ metadata = JSON.parse(row.metadata);
560
+ } catch {
561
+ metadata = null;
562
+ }
563
+ }
564
+ return {
565
+ id: row.id,
566
+ agentId: row.agent_id,
567
+ poolId: row.pool_id ?? null,
568
+ controllerId: row.controller_id ?? null,
569
+ externalHandle: row.external_handle ? JSON.parse(row.external_handle) : null,
570
+ provisionAttempts: row.provision_attempts ?? 0,
571
+ provisionError: row.provision_error ?? null,
572
+ provisionRequestedAt: row.provision_requested_at ?? null,
573
+ draining: row.draining === 1,
574
+ idleSince: row.idle_since ?? null,
575
+ leaseExpiresAt: row.lease_expires_at ?? null,
576
+ taskId: row.task_id ?? null,
577
+ status: row.status,
578
+ reportedSlots: row.reported_slots,
579
+ credentialReady: row.credential_ready === null ? null : row.credential_ready === 1,
580
+ metadata,
581
+ lastSeenAt: row.last_seen_at,
582
+ createdAt: row.created_at,
583
+ updatedAt: row.updated_at
584
+ };
585
+ }
586
+ function runtimeStaleThresholdMinutes() {
587
+ return Number(process.env.RUNTIME_STALE_THRESHOLD_MIN) || 5;
588
+ }
589
+ function runtimeLivenessCutoff() {
590
+ return new Date(Date.now() - runtimeStaleThresholdMinutes() * 60 * 1000).toISOString();
591
+ }
592
+ async function upsertRuntimeInstance(instance) {
593
+ const metadataJson = instance.metadata ? JSON.stringify(instance.metadata) : null;
594
+ const row = await getDbClient2().transaction(async (tx) => {
595
+ const provisioning = await tx.get("SELECT id FROM runtime_instances WHERE id = ? AND status = 'provisioning' AND agent_id = ?", [instance.id, instance.agentId]);
596
+ const row = await tx.get(`INSERT INTO runtime_instances (id, agent_id, status, reported_slots, metadata)
597
+ VALUES (?, ?, 'active', ?, ?)
598
+ ON CONFLICT(id) DO UPDATE SET
599
+ status = 'active',
600
+ reported_slots = excluded.reported_slots,
601
+ metadata = COALESCE(excluded.metadata, runtime_instances.metadata),
602
+ last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
603
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
604
+ WHERE runtime_instances.agent_id = excluded.agent_id
605
+ RETURNING *`, [instance.id, instance.agentId, instance.reportedSlots, metadataJson]);
606
+ if (row?.pool_id && provisioning) {
607
+ await tx.run("UPDATE agents SET runtime_provision_attempts = 0, runtime_provision_retry_at = NULL WHERE id = ?", [row.agent_id]);
608
+ await tx.run("UPDATE runtime_pools SET consecutive_failures = 0, circuit_open_until = NULL WHERE id = ?", [row.pool_id]);
609
+ }
610
+ return row;
611
+ });
612
+ return row ? rowToRuntimeInstance(row) : null;
613
+ }
614
+ async function getRuntimeInstanceById(id) {
615
+ const row = await getDbClient2().get("SELECT * FROM runtime_instances WHERE id = ?", [id]);
616
+ return row ? rowToRuntimeInstance(row) : null;
617
+ }
618
+ async function insertProvisioningRuntime(instance) {
619
+ const row = await getDbClient2().get(`INSERT INTO runtime_instances (id, agent_id, pool_id, controller_id, status,
620
+ reported_slots, provision_requested_at)
621
+ VALUES (?, ?, ?, ?, 'provisioning', ?, ?) RETURNING *`, [
622
+ instance.id,
623
+ instance.agentId,
624
+ instance.poolId,
625
+ instance.controllerId,
626
+ instance.reportedSlots ?? 1,
627
+ new Date().toISOString()
628
+ ]);
629
+ return rowToRuntimeInstance(row);
630
+ }
631
+ async function markRuntimeTerminated(id, reason) {
632
+ await getDbClient2().transaction(async () => {
633
+ const runtime = await getRuntimeInstanceById(id);
634
+ if (!runtime)
635
+ return;
636
+ await markRuntimeInstanceOffline(id, runtime.agentId);
637
+ if (runtime.status === "provisioning") {
638
+ await getDbClient2().run("UPDATE runtime_instances SET provision_error = ? WHERE id = ?", [
639
+ scrubSecrets(reason),
640
+ id
641
+ ]);
642
+ }
643
+ await reconcileAgentStatusFromRuntimes(runtime.agentId);
644
+ await revokeRuntimeTokensForRuntime(id);
645
+ });
646
+ }
647
+ async function touchRuntimeInstance(id, agentId) {
648
+ const result = await getDbClient2().run(`UPDATE runtime_instances
649
+ SET last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
650
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
651
+ WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [id, agentId, runtimeLivenessCutoff()]);
652
+ return result.changes > 0;
653
+ }
654
+ async function markRuntimeInstanceOffline(id, agentId) {
655
+ const row = await getDbClient2().get(`UPDATE runtime_instances
656
+ SET status = 'offline', updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
657
+ WHERE id = ? AND agent_id = ? RETURNING *`, [id, agentId]);
658
+ return row ? rowToRuntimeInstance(row) : null;
659
+ }
660
+ async function countActiveRuntimeInstancesForAgent(agentId) {
661
+ const result = await getDbClient2().get(`SELECT COUNT(*) as count FROM runtime_instances
662
+ WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [agentId, runtimeLivenessCutoff()]);
663
+ return result?.count ?? 0;
664
+ }
665
+ async function listRuntimeInstancesForAgent(agentId) {
666
+ const rows = await getDbClient2().query(`SELECT *, (status = 'active' AND last_seen_at >= ?) AS is_live
667
+ FROM runtime_instances
668
+ WHERE agent_id = ?
669
+ ORDER BY created_at ASC, id ASC`, [runtimeLivenessCutoff(), agentId]);
670
+ return rows.map((row) => ({ ...rowToRuntimeInstance(row), isLive: row.is_live === 1 }));
671
+ }
672
+ async function setRuntimeCredentialReady(id, agentId, ready) {
673
+ const result = await getDbClient2().run(`UPDATE runtime_instances
674
+ SET credential_ready = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
675
+ WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [ready ? 1 : 0, id, agentId, runtimeLivenessCutoff()]);
676
+ return result.changes > 0;
677
+ }
678
+ async function hasReadyLiveRuntime(agentId) {
679
+ const row = await getDbClient2().get(`SELECT COUNT(*) c FROM runtime_instances
680
+ WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?
681
+ AND (credential_ready IS NULL OR credential_ready = 1)`, [agentId, runtimeLivenessCutoff()]);
682
+ return (row?.c ?? 0) > 0;
683
+ }
684
+ async function reconcileAgentStatusFromRuntimes(agentId) {
685
+ await getDbClient2().transaction(async () => {
686
+ const agent = await getAgentById2(agentId);
687
+ if (!agent)
688
+ return;
689
+ if (await countActiveRuntimeInstancesForAgent(agentId) === 0) {
690
+ if (agent.status !== "offline")
691
+ await updateAgentStatus2(agentId, "offline");
692
+ return;
693
+ }
694
+ if (!await hasReadyLiveRuntime(agentId)) {
695
+ if (agent.status !== "waiting_for_credentials") {
696
+ await updateAgentStatus2(agentId, "waiting_for_credentials");
697
+ }
698
+ return;
699
+ }
700
+ const next = await getActiveTaskCount2(agentId) > 0 ? "busy" : "idle";
701
+ if (agent.status !== next)
702
+ await updateAgentStatus2(agentId, next);
703
+ });
704
+ }
705
+ async function agentsWithLiveRuntime() {
706
+ const rows = await getDbClient2().query(`SELECT DISTINCT agent_id FROM runtime_instances
707
+ WHERE status = 'active' AND last_seen_at >= ?`, [runtimeLivenessCutoff()]);
708
+ return new Set(rows.map((r) => r.agent_id));
709
+ }
710
+ async function expireStaleRuntimeInstances() {
711
+ if (!isMultiRuntimeEnabled()) {
712
+ return { expired: 0, agentsOffline: 0, pruned: 0 };
713
+ }
714
+ return await getDbClient2().transaction(async (tx) => {
715
+ const cutoff = runtimeLivenessCutoff();
716
+ const stale = await tx.query(`SELECT id, agent_id FROM runtime_instances
717
+ WHERE status = 'active' AND last_seen_at < ? AND pool_id IS NULL`, [cutoff]);
718
+ const pruned = (await tx.run("DELETE FROM runtime_instances WHERE last_seen_at < ? AND status != 'provisioning' AND (pool_id IS NULL OR status = 'offline')", [cutoff])).changes;
719
+ let agentsOffline = 0;
720
+ for (const agentId of new Set(stale.map((r) => r.agent_id))) {
721
+ await reconcileAgentStatusFromRuntimes(agentId);
722
+ if ((await getAgentById2(agentId))?.status === "offline")
723
+ agentsOffline++;
724
+ }
725
+ return { expired: stale.length, agentsOffline, pruned };
726
+ });
727
+ }
728
+ async function listRuntimeInstancesForPool(poolId) {
729
+ return (await getDbClient2().query("SELECT * FROM runtime_instances WHERE pool_id = ? ORDER BY created_at", [poolId])).map(rowToRuntimeInstance);
730
+ }
731
+
732
+ // src/be/approval-config-question.ts
733
+ init_swarm_config_guard();
734
+ var ConfigQuestionSettingsSchema = object2({
735
+ key: string2().min(1).max(255).regex(/^\S+$/, "must not contain whitespace").describe("The swarm config key to set, e.g. BITBUCKET_TOKEN"),
736
+ scope: _enum2(["global", "agent", "repo"]).describe("Config scope the value is written to"),
737
+ scopeId: string2().min(1).optional().describe("Agent or repo id for agent/repo scope. From request-human-input, agent scope defaults to the calling agent; pass it explicitly when asking on behalf of another agent."),
738
+ isSecret: boolean().optional().describe("Store encrypted and never show the value (default true). An existing secret row always stays secret."),
739
+ helpUrl: string2().url().refine((url) => /^https?:\/\//i.test(url), "must be an http(s) URL").optional().describe("Link shown next to the field, e.g. where to create the token"),
740
+ options: array2(object2({
741
+ value: string2(),
742
+ label: string2(),
743
+ description: string2().optional()
744
+ })).optional().describe("Allowed values for a non-secret key, rendered as a choice"),
745
+ defaultValue: string2().optional().describe("Prefilled value for a non-secret key")
746
+ });
747
+ var ConfigAnswerSchema = discriminatedUnion("action", [
748
+ object2({ action: literal2("set"), value: string2() }),
749
+ object2({ action: literal2("keep") }),
750
+ object2({ action: literal2("skip") })
751
+ ]);
752
+ var ConfigResultSchema = object2({
753
+ key: string2(),
754
+ scope: _enum2(["global", "agent", "repo"]),
755
+ scopeId: string2().nullable(),
756
+ isSecret: boolean(),
757
+ status: _enum2(["set", "kept", "skipped"]),
758
+ updatedAt: string2().nullable(),
759
+ value: string2().optional()
760
+ });
761
+ var ConfigStateSchema = object2({
762
+ exists: boolean(),
763
+ isSecret: boolean(),
764
+ updatedAt: string2().nullable(),
765
+ value: string2().optional().describe("Current value; present only for non-secret rows")
766
+ });
767
+ async function readConfigRow(settings) {
768
+ const scopeId = settings.scope === "global" ? null : settings.scopeId ?? null;
769
+ const row = await getDbClient2().get(`SELECT isSecret, lastUpdatedAt, envPath, description,
770
+ CASE WHEN isSecret = 1 THEN NULL ELSE value END AS plainValue
771
+ FROM swarm_config
772
+ WHERE scope = ? AND ${scopeId === null ? "scopeId IS NULL" : "scopeId = ?"} AND key = ?`, scopeId === null ? [settings.scope, settings.key] : [settings.scope, scopeId, settings.key]);
773
+ if (!row)
774
+ return null;
775
+ const isSecret = row.isSecret === 1 || isApiOnlyConfigKey(settings.key);
776
+ return {
777
+ isSecret,
778
+ updatedAt: row.lastUpdatedAt,
779
+ envPath: row.envPath,
780
+ description: row.description,
781
+ ...isSecret || row.plainValue === null ? {} : { value: row.plainValue }
782
+ };
783
+ }
784
+ async function readConfigState(settings) {
785
+ const row = await readConfigRow(settings);
786
+ if (!row)
787
+ return { exists: false, isSecret: settings.isSecret !== false, updatedAt: null };
788
+ const isSecret = row.isSecret || settings.isSecret !== false;
789
+ return {
790
+ exists: true,
791
+ isSecret,
792
+ updatedAt: row.updatedAt,
793
+ ...!isSecret && row.value !== undefined ? { value: row.value } : {}
794
+ };
795
+ }
796
+ async function normalizeConfigQuestion(question, ctx) {
797
+ const fail = (reason) => ({
798
+ ok: false,
799
+ error: `config question "${question.id}": ${reason}`
800
+ });
801
+ const parsed = ConfigQuestionSettingsSchema.safeParse(question.config);
802
+ if (!parsed.success) {
803
+ const issue = parsed.error.issues[0];
804
+ const path = issue?.path.length ? `config.${issue.path.map(String).join(".")}` : "config";
805
+ return fail(`${path}: ${issue?.message ?? "invalid"}`);
806
+ }
807
+ if (ctx.approvers.policy !== "any") {
808
+ return fail('a config question needs approvers.policy "any" (a config value has one author)');
809
+ }
810
+ const settings = { ...parsed.data };
811
+ if (settings.scope === "agent" && !settings.scopeId && ctx.callerAgentId) {
812
+ settings.scopeId = ctx.callerAgentId;
813
+ }
814
+ if (settings.scope === "global" && settings.scopeId) {
815
+ return fail("global scope must not have scopeId");
816
+ }
817
+ if (settings.scope !== "global" && !settings.scopeId) {
818
+ return fail(`${settings.scope} scope requires scopeId`);
819
+ }
820
+ if (isReservedConfigKey(settings.key))
821
+ return fail(reservedKeyError(settings.key).message);
822
+ if (isInternalConfigKey(settings.key))
823
+ return fail(internalConfigKeyError(settings.key).message);
824
+ const row = await readConfigRow(settings);
825
+ const isSecret = settings.isSecret !== false || row?.isSecret === true || isApiOnlyConfigKey(settings.key);
826
+ if (isSecret && (settings.options || settings.defaultValue !== undefined)) {
827
+ return fail("options and defaultValue are only allowed when isSecret is false (and the key is not already stored as a secret)");
828
+ }
829
+ if (settings.options?.length === 0)
830
+ return fail("options must not be empty");
831
+ if (settings.options && settings.defaultValue !== undefined && !settings.options.some((option) => option.value === settings.defaultValue)) {
832
+ return fail("defaultValue must be one of options");
833
+ }
834
+ const label = question.label?.trim() ? question.label : settings.key;
835
+ return {
836
+ ok: true,
837
+ question: {
838
+ ...question,
839
+ label,
840
+ required: question.required ?? true,
841
+ config: { ...settings, isSecret }
842
+ }
843
+ };
844
+ }
845
+ async function normalizeApprovalQuestions(questions, ctx) {
846
+ const out = [];
847
+ for (const question of questions) {
848
+ if (question.type !== "config") {
849
+ if (question.config !== undefined) {
850
+ return {
851
+ ok: false,
852
+ error: `question "${question.id}": the config block is only valid on type "config"`
853
+ };
854
+ }
855
+ if (typeof question.label !== "string") {
856
+ return { ok: false, error: `question "${question.id}": label is required` };
857
+ }
858
+ out.push({ ...question, label: question.label });
859
+ continue;
860
+ }
861
+ const normalized = await normalizeConfigQuestion(question, ctx);
862
+ if (!normalized.ok)
863
+ return normalized;
864
+ out.push(normalized.question);
865
+ }
866
+ return { ok: true, questions: out };
867
+ }
868
+ function isConfigQuestion(question) {
869
+ return question.type === "config" && typeof question.config === "object" && !!question.config;
870
+ }
871
+ function parseConfigAnswer(question, raw, state) {
872
+ const fail = (reason) => ({
873
+ ok: false,
874
+ error: `config question "${question.id}": ${reason}`
875
+ });
876
+ const required = question.required !== false;
877
+ if (raw === undefined || raw === null) {
878
+ return required ? fail("an answer is required") : { ok: true, answer: { action: "skip" } };
879
+ }
880
+ const parsed = ConfigAnswerSchema.safeParse(raw);
881
+ if (!parsed.success) {
882
+ return fail('answer must be { action: "set", value } , { action: "keep" } or { action: "skip" }');
883
+ }
884
+ const answer = parsed.data;
885
+ if (answer.action === "keep") {
886
+ return state.exists ? { ok: true, answer } : fail("cannot keep a key that is not set");
887
+ }
888
+ if (answer.action === "skip") {
889
+ return required ? fail("a required question cannot be skipped") : { ok: true, answer };
890
+ }
891
+ if (answer.value.trim().length === 0)
892
+ return fail("value must not be empty");
893
+ const { config } = question;
894
+ const isSecret = config.isSecret || state.isSecret;
895
+ if (!isSecret && config.options && !config.options.some((o) => o.value === answer.value)) {
896
+ return fail("value must be one of options");
897
+ }
898
+ const error = validateConfigEntry({
899
+ scope: config.scope,
900
+ scopeId: config.scopeId ?? null,
901
+ key: config.key,
902
+ value: answer.value
903
+ });
904
+ if (error)
905
+ return fail(isSecret ? `value rejected by ${config.key} validation` : error);
906
+ return { ok: true, answer };
907
+ }
908
+ function result(question, status, isSecret, updatedAt, value) {
909
+ const { key, scope, scopeId } = question.config;
910
+ return {
911
+ key,
912
+ scope,
913
+ scopeId: scope === "global" ? null : scopeId ?? null,
914
+ isSecret,
915
+ status,
916
+ updatedAt,
917
+ ...!isSecret && value !== undefined ? { value } : {}
918
+ };
919
+ }
920
+ function skippedConfigResult(question) {
921
+ return result(question, "skipped", question.config.isSecret, null);
922
+ }
923
+ async function keptConfigResult(question) {
924
+ const row = await readConfigRow(question.config);
925
+ if (!row)
926
+ return null;
927
+ const isSecret = question.config.isSecret || row.isSecret;
928
+ return result(question, "kept", isSecret, row.updatedAt, row.value);
929
+ }
930
+ async function applyConfigAnswer(question, value) {
931
+ const { config } = question;
932
+ const existing = await readConfigRow(config);
933
+ const isSecret = config.isSecret || existing?.isSecret === true;
934
+ const written = await upsertSwarmConfigWithPolicyMirror({
935
+ scope: config.scope,
936
+ scopeId: config.scope === "global" ? null : config.scopeId ?? null,
937
+ key: config.key,
938
+ value,
939
+ isSecret,
940
+ envPath: existing?.envPath ?? null,
941
+ description: existing?.description ?? null
942
+ });
943
+ return result(question, "set", isSecret, written.lastUpdatedAt, value);
944
+ }
945
+ function formatConfigResult(value) {
946
+ const parsed = ConfigResultSchema.safeParse(value);
947
+ if (!parsed.success)
948
+ return null;
949
+ const r = parsed.data;
950
+ const where = `${r.scope}${r.scopeId ? `:${r.scopeId}` : ""}`;
951
+ const updated = r.updatedAt ? `, updated ${r.updatedAt}` : "";
952
+ const shown = !r.isSecret && r.value !== undefined ? ` = ${r.value}` : "";
953
+ return `${r.key} (${where}, ${r.isSecret ? "secret" : "plain"}): ${r.status}${updated}${shown}`;
954
+ }
955
+
956
+ // src/workflows/executors/human-in-the-loop.ts
454
957
  init_secret_scrubber();
455
958
 
456
959
  // src/workflows/approval-notifications.ts
457
960
  init_db3();
458
961
  init_resolver();
962
+
963
+ // src/slack/dm.ts
964
+ function isSlackUserId(id) {
965
+ return /^[UW][A-Z0-9]+$/.test(id);
966
+ }
967
+ async function resolveSlackConversationId(client, channelId) {
968
+ if (!isSlackUserId(channelId))
969
+ return channelId;
970
+ const result = await client.conversations.open({ users: channelId });
971
+ const dmId = result.channel?.id;
972
+ if (!dmId) {
973
+ throw new Error(`conversations.open returned no DM channel for user ${channelId}`);
974
+ }
975
+ return dmId;
976
+ }
977
+
978
+ // src/workflows/approval-notifications.ts
459
979
  init_types();
980
+ init_constants2();
460
981
  init_secret_scrubber();
982
+ var SLACK_SECTION_TEXT_LIMIT = 3000;
983
+ function escapeSlackMrkdwn(text) {
984
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
985
+ }
986
+ function buildSlackQuestionsSummary(questions, timeoutText = "") {
987
+ const header = `*Questions:*
988
+ `;
989
+ const lines = questions.map((q) => `• ${escapeSlackMrkdwn((q.label ?? "").replace(/\s+/g, " ").trim())}${q.type === "config" ? " (set on dashboard)" : ""}`);
990
+ const fits = (shown, omitted) => {
991
+ const tail = omitted > 0 ? `
992
+ _…and ${omitted} more. Answer all of them on the dashboard._` : "";
993
+ const text = `${header}${shown.join(`
994
+ `)}${tail}${timeoutText}`;
995
+ return text.length <= SLACK_SECTION_TEXT_LIMIT ? text : null;
996
+ };
997
+ const full = fits(lines, 0);
998
+ if (full)
999
+ return full;
1000
+ for (let count = lines.length - 1;count >= 0; count--) {
1001
+ const text = fits(lines.slice(0, count), lines.length - count);
1002
+ if (text)
1003
+ return text;
1004
+ }
1005
+ return `${header}_${questions.length} questions. Answer them on the dashboard._`;
1006
+ }
1007
+ function formatTimeout(seconds) {
1008
+ if (seconds < 60)
1009
+ return `${seconds}s`;
1010
+ if (seconds < 3600)
1011
+ return `${Math.round(seconds / 60)}m`;
1012
+ const hours = Math.floor(seconds / 3600);
1013
+ const mins = Math.round(seconds % 3600 / 60);
1014
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
1015
+ }
1016
+ async function dispatchApprovalNotifications(input, db = { updateApprovalRequestNotifications: updateApprovalRequestNotifications2, getApprovalRequestById: getApprovalRequestById2 }) {
1017
+ const { requestId, title, questions, timeoutSeconds, notifications } = input;
1018
+ if (!notifications.length)
1019
+ return;
1020
+ const approvalUrl = `${getAppUrl()}/approval-requests/${requestId}`;
1021
+ const updatedChannels = [
1022
+ ...notifications
1023
+ ];
1024
+ let updated = false;
1025
+ for (let i = 0;i < notifications.length; i++) {
1026
+ const notification = notifications[i];
1027
+ if (notification.channel === "email") {
1028
+ console.warn(`[HITL] Email notifications not yet supported (target: ${notification.target})`);
1029
+ continue;
1030
+ }
1031
+ if (notification.channel === "slack") {
1032
+ try {
1033
+ const slackApp = getSlackApp2();
1034
+ if (!slackApp) {
1035
+ console.warn("[HITL] Slack not initialized — cannot send notification");
1036
+ continue;
1037
+ }
1038
+ if (isBlockExternalEnabled2()) {
1039
+ const conversationId = await resolveSlackConversationId(slackApp.client, notification.target);
1040
+ const blockReason = await getChannelBlockReason2(slackApp.client, conversationId);
1041
+ if (blockReason) {
1042
+ console.warn(`[HITL] ${externalChannelToolError2(conversationId, blockReason)}`);
1043
+ continue;
1044
+ }
1045
+ }
1046
+ const questionsText = buildSlackQuestionsSummary(questions);
1047
+ const timeoutCaption = timeoutSeconds !== undefined ? [
1048
+ {
1049
+ type: "context",
1050
+ elements: [
1051
+ {
1052
+ type: "mrkdwn",
1053
+ text: `⏱ Timeout: ${formatTimeout(timeoutSeconds)} — times out if not responded`
1054
+ }
1055
+ ]
1056
+ }
1057
+ ] : [];
1058
+ const blocks = [
1059
+ {
1060
+ type: "section",
1061
+ text: {
1062
+ type: "mrkdwn",
1063
+ text: `\uD83D\uDD14 *Approval Required: ${escapeSlackMrkdwn(title)}*`
1064
+ }
1065
+ },
1066
+ {
1067
+ type: "section",
1068
+ text: {
1069
+ type: "mrkdwn",
1070
+ text: questionsText
1071
+ }
1072
+ },
1073
+ {
1074
+ type: "actions",
1075
+ elements: [
1076
+ {
1077
+ type: "button",
1078
+ text: { type: "plain_text", text: "Review & Respond" },
1079
+ url: approvalUrl,
1080
+ style: "primary"
1081
+ }
1082
+ ]
1083
+ },
1084
+ ...timeoutCaption
1085
+ ];
1086
+ const result = await slackApp.client.chat.postMessage({
1087
+ channel: notification.target,
1088
+ text: `Approval Required: ${escapeSlackMrkdwn(title)} — ${approvalUrl}`,
1089
+ unfurl_links: false,
1090
+ unfurl_media: false,
1091
+ blocks
1092
+ });
1093
+ if (result.ts) {
1094
+ updatedChannels[i] = { ...notification, messageTs: result.ts };
1095
+ updated = true;
1096
+ }
1097
+ console.log(`[HITL] Slack notification sent to ${notification.target} for request ${requestId}`);
1098
+ } catch (err) {
1099
+ console.error(`[HITL] Failed to send Slack notification to ${notification.target}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
1100
+ }
1101
+ }
1102
+ }
1103
+ if (updated) {
1104
+ try {
1105
+ await db.updateApprovalRequestNotifications(requestId, updatedChannels);
1106
+ const latest = await db.getApprovalRequestById(requestId);
1107
+ if (latest?.status === "cancelled") {
1108
+ await postApprovalCancellationUpdates([latest], latest.resolutionReason ?? "Workflow run cancelled");
1109
+ }
1110
+ } catch (err) {
1111
+ console.error("[HITL] Failed to update notification channels in DB:", scrubSecrets(err instanceof Error ? err.message : String(err)));
1112
+ }
1113
+ }
1114
+ }
461
1115
  async function postApprovalCancellationUpdates(approvals, reason, client = getSlackApp2()?.client) {
462
1116
  if (!client)
463
1117
  return;
@@ -530,6 +1184,8 @@ function formatResponses(questions, responses) {
530
1184
  let answerText;
531
1185
  if (answer == null) {
532
1186
  answerText = "(no answer)";
1187
+ } else if (q.type === "config") {
1188
+ answerText = formatConfigResult(answer) ?? "(unreadable config result)";
533
1189
  } else if (q.type === "approval") {
534
1190
  const a = answer;
535
1191
  answerText = a.approved ? "Approved" : "Rejected";
@@ -593,6 +1249,14 @@ var QuestionSchema = discriminatedUnion("type", [
593
1249
  required: boolean().default(true),
594
1250
  description: string2().optional(),
595
1251
  defaultValue: boolean().optional()
1252
+ }),
1253
+ object2({
1254
+ id: string2(),
1255
+ type: literal2("config"),
1256
+ label: string2().optional(),
1257
+ required: boolean().default(true),
1258
+ description: string2().optional(),
1259
+ config: ConfigQuestionSettingsSchema
596
1260
  })
597
1261
  ]);
598
1262
  var ApproverConfigSchema = object2({
@@ -610,7 +1274,6 @@ var HITLTimeoutSchema = object2({
610
1274
  });
611
1275
  var DYNAMIC_QUESTIONS_TOKEN_RE = /^\{\{[^}]+\}\}$/;
612
1276
  var MAX_HITL_QUESTIONS = 100;
613
- var SLACK_SECTION_TEXT_LIMIT = 3000;
614
1277
  var MAX_REPORTED_ISSUES = 5;
615
1278
  var HITLConfigSchema = object2({
616
1279
  title: string2(),
@@ -675,30 +1338,6 @@ function resolveHitlQuestions(value) {
675
1338
  }
676
1339
  return { ok: true, questions };
677
1340
  }
678
- function escapeSlackMrkdwn(text) {
679
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
680
- }
681
- function buildSlackQuestionsSummary(questions, timeoutText = "") {
682
- const header = `*Questions:*
683
- `;
684
- const lines = questions.map((q) => `• ${escapeSlackMrkdwn(q.label.replace(/\s+/g, " ").trim())}`);
685
- const fits = (shown, omitted) => {
686
- const tail = omitted > 0 ? `
687
- _…and ${omitted} more. Answer all of them on the dashboard._` : "";
688
- const text = `${header}${shown.join(`
689
- `)}${tail}${timeoutText}`;
690
- return text.length <= SLACK_SECTION_TEXT_LIMIT ? text : null;
691
- };
692
- const full = fits(lines, 0);
693
- if (full)
694
- return full;
695
- for (let count = lines.length - 1;count >= 0; count--) {
696
- const text = fits(lines.slice(0, count), lines.length - count);
697
- if (text)
698
- return text;
699
- }
700
- return `${header}_${questions.length} questions. Answer them on the dashboard._`;
701
- }
702
1341
 
703
1342
  class HumanInTheLoopExecutor extends BaseExecutor {
704
1343
  type = "human-in-the-loop";
@@ -709,7 +1348,17 @@ class HumanInTheLoopExecutor extends BaseExecutor {
709
1348
  const resolved = resolveHitlQuestions(input.config?.questions);
710
1349
  if (!resolved.ok)
711
1350
  return { status: "failed", error: resolved.error };
712
- return super.run({ ...input, config: { ...input.config, questions: resolved.questions } });
1351
+ const approvers = input.config?.approvers;
1352
+ const normalized = await normalizeApprovalQuestions(resolved.questions, {
1353
+ approvers: { policy: approvers?.policy }
1354
+ });
1355
+ if (!normalized.ok) {
1356
+ return {
1357
+ status: "failed",
1358
+ error: `human-in-the-loop questions are invalid: ${normalized.error}`
1359
+ };
1360
+ }
1361
+ return super.run({ ...input, config: { ...input.config, questions: normalized.questions } });
713
1362
  }
714
1363
  async execute(config, _context, meta) {
715
1364
  const { db } = this.deps;
@@ -765,7 +1414,13 @@ class HumanInTheLoopExecutor extends BaseExecutor {
765
1414
  };
766
1415
  }
767
1416
  if (config.notifications?.length) {
768
- this.dispatchNotifications(requestId, config, db).catch((err) => {
1417
+ dispatchApprovalNotifications({
1418
+ requestId,
1419
+ title: config.title,
1420
+ questions: config.questions,
1421
+ timeoutSeconds: config.timeout?.seconds,
1422
+ notifications: config.notifications
1423
+ }, db).catch((err) => {
769
1424
  console.error("[HITL] Unexpected error dispatching notifications:", scrubSecrets(err instanceof Error ? err.message : String(err)));
770
1425
  });
771
1426
  }
@@ -776,105 +1431,6 @@ class HumanInTheLoopExecutor extends BaseExecutor {
776
1431
  correlationId: requestId
777
1432
  };
778
1433
  }
779
- async dispatchNotifications(requestId, config, db) {
780
- if (!config.notifications?.length)
781
- return;
782
- const approvalUrl = `${getAppUrl()}/approval-requests/${requestId}`;
783
- const updatedChannels = [...config.notifications];
784
- let updated = false;
785
- for (let i = 0;i < config.notifications.length; i++) {
786
- const notification = config.notifications[i];
787
- if (notification.channel === "email") {
788
- console.warn(`[HITL] Email notifications not yet supported (target: ${notification.target})`);
789
- continue;
790
- }
791
- if (notification.channel === "slack") {
792
- try {
793
- const { getSlackApp } = await import("./app-nndcvftq.js");
794
- const slackApp = getSlackApp();
795
- if (!slackApp) {
796
- console.warn("[HITL] Slack not initialized — cannot send notification");
797
- continue;
798
- }
799
- const questions = Array.isArray(config.questions) ? config.questions : [];
800
- const questionsText = buildSlackQuestionsSummary(questions);
801
- const timeoutCaption = config.timeout ? [
802
- {
803
- type: "context",
804
- elements: [
805
- {
806
- type: "mrkdwn",
807
- text: `⏱ Timeout: ${formatTimeout(config.timeout.seconds)} — times out if not responded`
808
- }
809
- ]
810
- }
811
- ] : [];
812
- const blocks = [
813
- {
814
- type: "section",
815
- text: {
816
- type: "mrkdwn",
817
- text: `\uD83D\uDD14 *Approval Required: ${config.title}*`
818
- }
819
- },
820
- {
821
- type: "section",
822
- text: {
823
- type: "mrkdwn",
824
- text: questionsText
825
- }
826
- },
827
- {
828
- type: "actions",
829
- elements: [
830
- {
831
- type: "button",
832
- text: { type: "plain_text", text: "Review & Respond" },
833
- url: approvalUrl,
834
- style: "primary"
835
- }
836
- ]
837
- },
838
- ...timeoutCaption
839
- ];
840
- const result = await slackApp.client.chat.postMessage({
841
- channel: notification.target,
842
- text: `Approval Required: ${config.title} — ${approvalUrl}`,
843
- unfurl_links: false,
844
- unfurl_media: false,
845
- blocks
846
- });
847
- if (result.ts) {
848
- updatedChannels[i] = { ...notification, messageTs: result.ts };
849
- updated = true;
850
- }
851
- console.log(`[HITL] Slack notification sent to ${notification.target} for request ${requestId}`);
852
- } catch (err) {
853
- console.error(`[HITL] Failed to send Slack notification to ${notification.target}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
854
- }
855
- }
856
- }
857
- if (updated) {
858
- try {
859
- await db.updateApprovalRequestNotifications(requestId, updatedChannels);
860
- const latest = await db.getApprovalRequestById(requestId);
861
- if (latest?.status === "cancelled") {
862
- await postApprovalCancellationUpdates([latest], latest.resolutionReason ?? "Workflow run cancelled");
863
- }
864
- } catch (err) {
865
- console.error("[HITL] Failed to update notification channels in DB:", scrubSecrets(err instanceof Error ? err.message : String(err)));
866
- }
867
- }
868
- }
869
- }
870
- function formatTimeout(seconds) {
871
- if (seconds < 60)
872
- return `${seconds}s`;
873
- if (seconds < 3600)
874
- return `${Math.round(seconds / 60)}m`;
875
- const hours = Math.floor(seconds / 3600);
876
- const mins = Math.round(seconds % 3600 / 60);
877
- return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
878
1434
  }
879
1435
 
880
1436
  // src/workflows/executors/system-one-extraction.ts
@@ -2888,8 +3444,18 @@ init_db3();
2888
3444
 
2889
3445
  // src/workflows/checkpoint.ts
2890
3446
  init_db3();
2891
- async function checkpointStep(runId, stepId, nodeId, result, ctx) {
2892
- await getDbClient2().transaction(async () => {
3447
+ async function holdsFence(runId, stepId, fence) {
3448
+ const row = await getDbClient2().get(`SELECT 1 AS found
3449
+ FROM workflow_run_steps s
3450
+ JOIN workflow_runs r ON r.id = s.runId
3451
+ WHERE s.id = ? AND s.runId = ? AND s.status = 'running' AND s.retryCount = ?
3452
+ AND r.status IN ('running', 'waiting')`, [stepId, runId, fence.retryCount]);
3453
+ return row != null;
3454
+ }
3455
+ async function checkpointStep(runId, stepId, nodeId, result, ctx, options = {}) {
3456
+ return getDbClient2().transaction(async () => {
3457
+ if (options.fence && !await holdsFence(runId, stepId, options.fence))
3458
+ return false;
2893
3459
  await updateWorkflowRunStep2(stepId, {
2894
3460
  status: "completed",
2895
3461
  output: result.output,
@@ -2900,42 +3466,48 @@ async function checkpointStep(runId, stepId, nodeId, result, ctx) {
2900
3466
  await updateWorkflowRun2(runId, {
2901
3467
  context: ctx
2902
3468
  });
3469
+ return true;
2903
3470
  });
2904
3471
  }
2905
3472
  async function checkpointStepFailure(runId, stepId, error, retryCount, retryPolicy, options) {
2906
3473
  const now = new Date().toISOString();
2907
- if (retryPolicy && retryCount < retryPolicy.maxRetries) {
2908
- const delay = calculateBackoff(retryPolicy, retryCount);
2909
- const nextRetryAt = new Date(Date.now() + delay).toISOString();
3474
+ return getDbClient2().transaction(async () => {
3475
+ if (options?.fence && !await holdsFence(runId, stepId, options.fence)) {
3476
+ return { shouldRetry: false, stale: true };
3477
+ }
3478
+ if (retryPolicy && retryCount < retryPolicy.maxRetries) {
3479
+ const delay = calculateBackoff(retryPolicy, retryCount);
3480
+ const nextRetryAt = new Date(Date.now() + delay).toISOString();
3481
+ await updateWorkflowRunStep2(stepId, {
3482
+ status: "failed",
3483
+ error,
3484
+ output: options?.output,
3485
+ retryCount: retryCount + 1,
3486
+ maxRetries: retryPolicy.maxRetries,
3487
+ nextRetryAt
3488
+ });
3489
+ return { shouldRetry: true };
3490
+ }
2910
3491
  await updateWorkflowRunStep2(stepId, {
2911
3492
  status: "failed",
2912
3493
  error,
2913
- retryCount: retryCount + 1,
2914
- maxRetries: retryPolicy.maxRetries,
2915
- nextRetryAt
3494
+ output: options?.output,
3495
+ finishedAt: now,
3496
+ nextRetryAt: null
2916
3497
  });
2917
- return { shouldRetry: true };
2918
- }
2919
- await updateWorkflowRunStep2(stepId, {
2920
- status: "failed",
2921
- error,
2922
- finishedAt: now,
2923
- nextRetryAt: null
2924
- });
2925
- const markRunFailed = options?.markRunFailed ?? true;
2926
- if (markRunFailed) {
2927
- await getDbClient2().transaction(async () => {
3498
+ const markRunFailed = options?.markRunFailed ?? true;
3499
+ if (markRunFailed) {
2928
3500
  const run = await getWorkflowRun2(runId);
2929
- if (run?.status !== "running" && run?.status !== "waiting")
2930
- return;
2931
- await updateWorkflowRun2(runId, {
2932
- status: "failed",
2933
- error: `Step failed: ${error}`,
2934
- finishedAt: now
2935
- });
2936
- });
2937
- }
2938
- return { shouldRetry: false };
3501
+ if (run?.status === "running" || run?.status === "waiting") {
3502
+ await updateWorkflowRun2(runId, {
3503
+ status: "failed",
3504
+ error: `Step failed: ${error}`,
3505
+ finishedAt: now
3506
+ });
3507
+ }
3508
+ }
3509
+ return { shouldRetry: false };
3510
+ });
2939
3511
  }
2940
3512
  async function checkpointStepWaiting(runId, stepId, ctx, options = {}) {
2941
3513
  return getDbClient2().transaction(async () => {
@@ -2982,6 +3554,7 @@ var FOREACH_ITEMS_WAIT_FOR = "foreach.items";
2982
3554
 
2983
3555
  // src/workflows/engine.ts
2984
3556
  init_db3();
3557
+ init_workflow_runs();
2985
3558
  init_workflow_replay();
2986
3559
  init_telemetry();
2987
3560
 
@@ -3369,7 +3942,7 @@ async function resolveRenderedWorkflowInputs(input) {
3369
3942
  }
3370
3943
  async function startWorkflowExecution2(workflow, triggerData, registry, options = {}) {
3371
3944
  const preflight = preflightAutomation(workflowPreflightInput(workflow), await getAutomationSetupStates());
3372
- if (preflight.state === "needs_setup") {
3945
+ if (preflight.state === "needs_setup" && !options.parentStepId) {
3373
3946
  const { runId, recorded } = await recordWorkflowPreflightFailure({
3374
3947
  workflowId: workflow.id,
3375
3948
  triggerType: options.triggerType ?? "manual",
@@ -3381,52 +3954,74 @@ async function startWorkflowExecution2(workflow, triggerData, registry, options
3381
3954
  await notifyAutomationPreflightFailure(preflight);
3382
3955
  return runId;
3383
3956
  }
3384
- workflow = renderAutomationTokens(workflow, workflow.params ?? {});
3385
- if (workflow.triggerSchema) {
3386
- const validationErrors = validateJsonSchema(workflow.triggerSchema, triggerData);
3387
- if (validationErrors.length > 0) {
3388
- throw new TriggerSchemaError(validationErrors);
3957
+ let initial;
3958
+ if (preflight.state === "needs_setup") {
3959
+ initial = {
3960
+ status: "failed",
3961
+ error: preflight.failureReason,
3962
+ finishedAt: new Date().toISOString(),
3963
+ preflight: true
3964
+ };
3965
+ } else {
3966
+ workflow = renderAutomationTokens(workflow, workflow.params ?? {});
3967
+ if (workflow.triggerSchema) {
3968
+ const validationErrors = validateJsonSchema(workflow.triggerSchema, triggerData);
3969
+ if (validationErrors.length > 0) {
3970
+ throw new TriggerSchemaError(validationErrors);
3971
+ }
3972
+ }
3973
+ if (workflow.cooldown && await shouldSkipCooldown(workflow.id, workflow.cooldown)) {
3974
+ initial = { status: "skipped", error: "cooldown", finishedAt: new Date().toISOString() };
3975
+ } else {
3976
+ initial = await buildInitialRunState(workflow, triggerData, registry, options);
3389
3977
  }
3390
3978
  }
3391
- if (workflow.cooldown && await shouldSkipCooldown(workflow.id, workflow.cooldown)) {
3392
- const runId = crypto.randomUUID();
3393
- await createWorkflowRun2({
3394
- id: runId,
3979
+ const newRunId = crypto.randomUUID();
3980
+ const release = holdWorkflowRun(newRunId);
3981
+ try {
3982
+ const { runId, created, preflightAlert } = await createRunWithInitialState(workflow, triggerData, options, initial, newRunId);
3983
+ if (!created)
3984
+ return runId;
3985
+ if (initial.status === "failed" && initial.preflight) {
3986
+ if (preflightAlert)
3987
+ await notifyAutomationPreflightFailure(preflight);
3988
+ return runId;
3989
+ }
3990
+ if (initial.status === "skipped")
3991
+ return runId;
3992
+ telemetry.workflow("started", {
3395
3993
  workflowId: workflow.id,
3396
- triggerType: options.triggerType ?? "manual",
3397
- triggerData,
3398
- createdBy: options.requestedByUserId,
3399
- parentStepId: options.parentStepId
3400
- });
3401
- await updateWorkflowRun2(runId, {
3402
- status: "skipped",
3403
- error: "cooldown",
3404
- finishedAt: new Date().toISOString()
3994
+ nodeCount: workflow.definition.nodes.length,
3995
+ triggerType: options.triggerType ?? "manual"
3405
3996
  });
3997
+ if (initial.status)
3998
+ return runId;
3999
+ const walk = walkGraph(workflow.definition, runId, initial.context, findEntryNodes(workflow.definition), registry, workflow.id, getSecretInputKeys(workflow.input), options);
4000
+ if (options.parentStepId) {
4001
+ walk.catch((err) => console.error(`[workflows] Child workflow run ${runId} failed:`, err));
4002
+ return runId;
4003
+ }
4004
+ await walk;
3406
4005
  return runId;
4006
+ } finally {
4007
+ release();
3407
4008
  }
3408
- const runId = crypto.randomUUID();
3409
- await createWorkflowRun2({
3410
- id: runId,
3411
- workflowId: workflow.id,
3412
- triggerType: options.triggerType ?? "manual",
3413
- triggerData,
3414
- createdBy: options.requestedByUserId,
3415
- parentStepId: options.parentStepId
3416
- });
3417
- telemetry.workflow("started", {
3418
- workflowId: workflow.id,
3419
- nodeCount: workflow.definition.nodes.length,
3420
- triggerType: options.triggerType ?? "manual"
3421
- });
4009
+ }
4010
+
4011
+ class ParentStepNotCurrentError extends Error {
4012
+ constructor(parentStepId, attempt) {
4013
+ super(`Parent step ${parentStepId} (attempt ${attempt}) is no longer running; no child run started`);
4014
+ this.name = "ParentStepNotCurrentError";
4015
+ }
4016
+ }
4017
+ async function buildInitialRunState(workflow, triggerData, registry, options) {
3422
4018
  const notReady = await findWorkflowReadinessProblems(workflow.definition, registry);
3423
4019
  if (notReady.length > 0) {
3424
- await updateWorkflowRun2(runId, {
4020
+ return {
3425
4021
  status: "failed",
3426
4022
  error: formatReadinessRunError(notReady),
3427
4023
  finishedAt: new Date().toISOString()
3428
- });
3429
- return runId;
4024
+ };
3430
4025
  }
3431
4026
  const ctx = { trigger: triggerData };
3432
4027
  if (options.requestedByUserId) {
@@ -3440,23 +4035,40 @@ async function startWorkflowExecution2(workflow, triggerData, registry, options
3440
4035
  const resolved = await resolveRenderedWorkflowInputs(workflow.input);
3441
4036
  Object.assign(ctx, { input: resolved });
3442
4037
  } catch (err) {
3443
- await updateWorkflowRun2(runId, {
4038
+ return {
3444
4039
  status: "failed",
3445
4040
  error: `Input resolution failed: ${err}`,
3446
4041
  finishedAt: new Date().toISOString()
3447
- });
3448
- return runId;
4042
+ };
3449
4043
  }
3450
4044
  }
3451
- const entryNodes = findEntryNodes(workflow.definition);
3452
- const secretKeys = getSecretInputKeys(workflow.input);
3453
- const walk = walkGraph(workflow.definition, runId, ctx, entryNodes, registry, workflow.id, secretKeys, options);
3454
- if (options.parentStepId) {
3455
- walk.catch((err) => console.error(`[workflows] Child workflow run ${runId} failed:`, err));
3456
- return runId;
3457
- }
3458
- await walk;
3459
- return runId;
4045
+ return { context: ctx };
4046
+ }
4047
+ async function createRunWithInitialState(workflow, triggerData, options, initial, runId) {
4048
+ return getDbClient2().transaction(async () => {
4049
+ const { parentStepId } = options;
4050
+ const attempt = options.parentStepAttempt ?? 0;
4051
+ if (parentStepId) {
4052
+ if (!await isParentStepAttemptCurrent(parentStepId, attempt)) {
4053
+ throw new ParentStepNotCurrentError(parentStepId, attempt);
4054
+ }
4055
+ const existingId = await getChildWorkflowRunId(parentStepId, attempt);
4056
+ if (existingId)
4057
+ return { runId: existingId, created: false };
4058
+ }
4059
+ await createWorkflowRun2({
4060
+ id: runId,
4061
+ workflowId: workflow.id,
4062
+ triggerType: options.triggerType ?? "manual",
4063
+ triggerData,
4064
+ createdBy: options.requestedByUserId,
4065
+ parentStepId,
4066
+ parentStepAttempt: parentStepId ? attempt : undefined
4067
+ });
4068
+ await updateWorkflowRun2(runId, initial.status ? { status: initial.status, error: initial.error, finishedAt: initial.finishedAt } : { context: initial.context });
4069
+ const preflightAlert = initial.status === "failed" && initial.preflight ? await isFirstWorkflowPreflightFailureToday(workflow.id, runId) : undefined;
4070
+ return { runId, created: true, preflightAlert };
4071
+ });
3460
4072
  }
3461
4073
  var activeWalks = new Map;
3462
4074
  var executingSteps = new Set;
@@ -3479,6 +4091,9 @@ async function failRunOnUnreadableReplay(runId, err, stepId) {
3479
4091
  function isWorkflowRunActive(runId) {
3480
4092
  return activeWalks.has(runId);
3481
4093
  }
4094
+ function isStepExecuting(stepId) {
4095
+ return executingSteps.has(stepId);
4096
+ }
3482
4097
  function holdWorkflowRun(runId) {
3483
4098
  activeWalks.set(runId, (activeWalks.get(runId) ?? 0) + 1);
3484
4099
  let released = false;
@@ -3726,12 +4341,27 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
3726
4341
  if (current && (current.status !== "running" || executingSteps.has(current.id))) {
3727
4342
  return { current };
3728
4343
  }
4344
+ if (current && node.type === "sub-workflow") {
4345
+ claimedStepId = current.id;
4346
+ executingSteps.add(current.id);
4347
+ return {
4348
+ existingStep: current,
4349
+ stepId: current.id,
4350
+ fence: { retryCount: current.retryCount },
4351
+ deduped: false
4352
+ };
4353
+ }
3729
4354
  }
3730
4355
  const iteration = await getStepCountForNode2(runId, node.id);
3731
4356
  const idempotencyKey = `${runId}:${node.id}:${iteration}`;
3732
4357
  const existingStep = await getStepByIdempotencyKey2(idempotencyKey);
3733
4358
  if (existingStep && (existingStep.status === "completed" || existingStep.status === "waiting")) {
3734
- return { existingStep, stepId: existingStep.id, deduped: true };
4359
+ return {
4360
+ existingStep,
4361
+ stepId: existingStep.id,
4362
+ fence: { retryCount: existingStep.retryCount },
4363
+ deduped: true
4364
+ };
3735
4365
  }
3736
4366
  const latestForeachStep = node.type === "foreach" ? await getLatestStepForNode2(runId, node.id) : undefined;
3737
4367
  const reusableForeachStep = latestForeachStep && !FOREACH_TERMINAL_STEP_STATUSES.has(latestForeachStep.status) ? latestForeachStep : undefined;
@@ -3748,7 +4378,8 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
3748
4378
  }
3749
4379
  claimedStepId = stepId;
3750
4380
  executingSteps.add(stepId);
3751
- return { existingStep, stepId, deduped: false };
4381
+ const fence = { retryCount: reusableForeachStep?.retryCount ?? 0 };
4382
+ return { existingStep, stepId, fence, deduped: false };
3752
4383
  };
3753
4384
  const dedup = await getDbClient2().transaction(claimStep).catch((err) => {
3754
4385
  if (claimedStepId)
@@ -3772,7 +4403,7 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
3772
4403
  return { outcome: "waiting", successors: [] };
3773
4404
  }
3774
4405
  try {
3775
- return await runClaimedStep(def, runId, ctx, node, registry, dedup.existingStep, dedup.stepId, workflowId, options);
4406
+ return await runClaimedStep(def, runId, ctx, node, registry, dedup.existingStep, dedup.stepId, dedup.fence, workflowId, options);
3776
4407
  } catch (err) {
3777
4408
  if (await failRunOnUnreadableReplay(runId, err, dedup.stepId)) {
3778
4409
  return { outcome: "failed", successors: [] };
@@ -3782,11 +4413,14 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
3782
4413
  executingSteps.delete(dedup.stepId);
3783
4414
  }
3784
4415
  }
3785
- async function runClaimedStep(def, runId, ctx, node, registry, existingStep, stepId, workflowId, options = {}) {
4416
+ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, stepId, fence, workflowId, options = {}) {
4417
+ const stale = { outcome: "failed", successors: [] };
3786
4418
  const executor = registry.get(node.type);
3787
4419
  const systemOneRetryProblems = systemOneRetryViolations(node);
3788
4420
  if (systemOneRetryProblems.length > 0) {
3789
- await checkpointStepFailure(runId, stepId, systemOneRetryProblems.join("; "), 0);
4421
+ await checkpointStepFailure(runId, stepId, systemOneRetryProblems.join("; "), 0, undefined, {
4422
+ fence
4423
+ });
3790
4424
  return { outcome: "failed", successors: [] };
3791
4425
  }
3792
4426
  const interpolationCtx = buildNodeInterpolationCtx(node, ctx);
@@ -3794,7 +4428,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3794
4428
  const inputErrors = validateJsonSchema(node.inputSchema, interpolationCtx);
3795
4429
  if (inputErrors.length > 0) {
3796
4430
  const errorMsg = `Input schema validation failed: ${inputErrors.join("; ")}`;
3797
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
4431
+ const { stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
4432
+ fence
4433
+ });
4434
+ if (lost)
4435
+ return stale;
3798
4436
  throw new Error(errorMsg);
3799
4437
  }
3800
4438
  }
@@ -3808,11 +4446,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3808
4446
  const executionCtx = { ...ctx, ...interpolationCtx };
3809
4447
  if (scriptBodyUnresolved && scriptBodyUnresolved.length > 0) {
3810
4448
  const errorMsg = scriptBodyInterpolationError(node.id, scriptBodyUnresolved);
3811
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
4449
+ await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, { fence });
3812
4450
  return { outcome: "failed", successors: [] };
3813
4451
  }
3814
4452
  if (strictUnresolved && strictUnresolved.length > 0) {
3815
- await checkpointStepFailure(runId, stepId, systemOneUnresolvedError(node.id, strictUnresolved, node.type), 0);
4453
+ await checkpointStepFailure(runId, stepId, systemOneUnresolvedError(node.id, strictUnresolved, node.type), 0, undefined, { fence });
3816
4454
  return { outcome: "failed", successors: [] };
3817
4455
  }
3818
4456
  if (unresolved.length > 0) {
@@ -3845,19 +4483,20 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3845
4483
  const errorMsg = err instanceof Error ? err.message : String(err);
3846
4484
  const retryPolicy = node.retry || executor.retryPolicy;
3847
4485
  const currentRetryCount = existingStep?.retryCount || 0;
3848
- const { shouldRetry } = await checkpointStepFailure(runId, stepId, errorMsg, currentRetryCount, retryPolicy);
4486
+ const { shouldRetry, stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, currentRetryCount, retryPolicy, { fence });
4487
+ if (lost)
4488
+ return stale;
3849
4489
  if (!shouldRetry) {
3850
4490
  throw err;
3851
4491
  }
3852
4492
  return { outcome: "completed", successors: [] };
3853
4493
  }
3854
4494
  if (result.status === "failed") {
3855
- const retryPolicy = node.retry || executor.retryPolicy;
4495
+ const retryPolicy = result.retryable === false ? undefined : node.retry || executor.retryPolicy;
3856
4496
  const currentRetryCount = existingStep?.retryCount || 0;
3857
- const { shouldRetry } = await checkpointStepFailure(runId, stepId, result.error || "Executor returned failed status", currentRetryCount, retryPolicy);
3858
- if (result.output !== undefined) {
3859
- await updateWorkflowRunStep2(stepId, { output: result.output });
3860
- }
4497
+ const { shouldRetry, stale: lost } = await checkpointStepFailure(runId, stepId, result.error || "Executor returned failed status", currentRetryCount, retryPolicy, { fence, output: result.output });
4498
+ if (lost)
4499
+ return stale;
3861
4500
  if (!shouldRetry) {
3862
4501
  throw new Error(result.error || "Step execution failed");
3863
4502
  }
@@ -3878,7 +4517,7 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3878
4517
  }
3879
4518
  const waiting = await checkpointStepWaiting(runId, stepId, ctx);
3880
4519
  if (waiting && result.waitFor === "workflow.child.finished") {
3881
- const { resumeFromChildRun } = await import("./resume-phj8xhqr.js");
4520
+ const { resumeFromChildRun } = await import("./resume-2dvq348g.js");
3882
4521
  await resumeFromChildRun(stepId, registry);
3883
4522
  }
3884
4523
  return { outcome: waiting ? "waiting" : "completed", successors: [] };
@@ -3887,7 +4526,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3887
4526
  const outputErrors = validateJsonSchema(node.outputSchema, result.output);
3888
4527
  if (outputErrors.length > 0) {
3889
4528
  const errorMsg = `Output schema validation failed: ${outputErrors.join("; ")}`;
3890
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
4529
+ const { stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
4530
+ fence
4531
+ });
4532
+ if (lost)
4533
+ return stale;
3891
4534
  throw new Error(errorMsg);
3892
4535
  }
3893
4536
  }
@@ -3896,7 +4539,10 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3896
4539
  validationResult = await runStepValidation(registry, node, result.output, executionCtx, meta);
3897
4540
  if (validationResult.outcome === "halt") {
3898
4541
  const errorMsg = "Validation failed (mustPass)";
3899
- await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, { markRunFailed: false });
4542
+ await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
4543
+ markRunFailed: false,
4544
+ fence
4545
+ });
3900
4546
  return { outcome: "failed", successors: [] };
3901
4547
  }
3902
4548
  if (validationResult.outcome === "retry") {
@@ -3907,12 +4553,15 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
3907
4553
  }
3908
4554
  const retryPolicy = node.validation.retry || node.retry;
3909
4555
  const currentRetryCount = existingStep?.retryCount || 0;
3910
- await checkpointStepFailure(runId, stepId, "Validation failed, retrying", currentRetryCount, retryPolicy);
4556
+ const { stale: lost } = await checkpointStepFailure(runId, stepId, "Validation failed, retrying", currentRetryCount, retryPolicy, { fence });
4557
+ if (lost)
4558
+ return stale;
3911
4559
  return { outcome: "completed", successors: [] };
3912
4560
  }
3913
4561
  }
3914
4562
  result.nextPort = resolveValidationPort(node.next, result.nextPort, validationResult?.passed);
3915
- await checkpointStep(runId, stepId, node.id, result, ctx);
4563
+ if (!await checkpointStep(runId, stepId, node.id, result, ctx, { fence }))
4564
+ return stale;
3916
4565
  const successors = result.nextPort ? getSuccessors(def, node.id, result.nextPort) : getSuccessors(def, node.id);
3917
4566
  return { outcome: "completed", successors };
3918
4567
  }
@@ -4330,7 +4979,7 @@ async function runForeachDrive(runId, parentNodeId, registry) {
4330
4979
  if (drive.successors.length > 0) {
4331
4980
  await walkGraph(workflow.definition, runId, ctx, drive.successors, registry, workflow.id, getSecretInputKeys(workflow.input));
4332
4981
  } else {
4333
- const { finalizeOrWait } = await import("./resume-phj8xhqr.js");
4982
+ const { finalizeOrWait } = await import("./resume-2dvq348g.js");
4334
4983
  await finalizeOrWait(runId);
4335
4984
  }
4336
4985
  return drive;
@@ -4472,7 +5121,7 @@ async function startChild(input) {
4472
5121
  const parked = await getDbClient2().get("UPDATE workflow_run_steps SET status = 'waiting' WHERE id = ? AND status = 'running' RETURNING id", [child.id]);
4473
5122
  if (parked && result.waitFor === "workflow.child.finished") {
4474
5123
  ownedChildren.delete(child.id);
4475
- const { resumeFromChildRun } = await import("./resume-phj8xhqr.js");
5124
+ const { resumeFromChildRun } = await import("./resume-2dvq348g.js");
4476
5125
  await resumeFromChildRun(child.id, registry);
4477
5126
  }
4478
5127
  return null;
@@ -5083,4 +5732,4 @@ function applyDefinitionPatch(def, patch) {
5083
5732
  return { definition: patchedDef, errors };
5084
5733
  }
5085
5734
 
5086
- export { notifyAutomationPreflightFailure, automationIntegrationFixUrl, isNeedsSetupFailure, preflightAutomation, renderAutomationTokens, getAutomationSetupStates, schedulePreflightInput, listEnabledAutomationPreflightInputs, recordSchedulePreflightFailure, BaseExecutor, postApprovalCancellationUpdates, createApprovalFollowUpTask, HumanInTheLoopExecutor, resolveWorkflowLlmConfig, SYSTEM_ONE_PROVIDER_IDS, SystemOneExtractionExecutor, SYSTEM_ONE_DECISION_NODE_TYPE, SystemOneDecisionConfigSchema, SystemOneDecisionExecutor, checkpointStep, checkpointStepFailure, checkpointStepWaiting, FAILED_TASK_OUTPUT_PREFIX, FOREACH_ITEMS_WAIT_FOR, loadCompletedStepRouting, resolveInputValue, getSecretInputKeys, validateJsonSchema, findJsonSchemaShapeErrors, getMaxWorkflowStepsPerRun, findWorkflowReadinessProblems, formatReadinessProblems, workflowSaveWarnings, withSaveWarnings, runStepValidation, TriggerSchemaError, startWorkflowExecution2, failRunOnUnreadableReplay, isWorkflowRunActive, holdWorkflowRun, walkGraph, hasRunningStep, findReadyNodes, scriptBodyInterpolationError, ForeachExecutor, rebuildNodeExecutionInput, isForeachChildOwned, parkAndDriveForeach, startForeachDrive, runForeachDrive, FOREACH_TERMINAL_STEP_STATUSES, parseSyntheticNodeId, resolveForeachParent, settleForeachChild, resolveStepNode, stepFailureMode, getNextTargets, generateEdges, resolveValidationPort, getSuccessors, definitionNodeIds, validateDefinition, applyDefinitionPatch };
5735
+ export { notifyAutomationPreflightFailure, automationIntegrationFixUrl, isNeedsSetupFailure, preflightAutomation, renderAutomationTokens, getAutomationSetupStates, schedulePreflightInput, listEnabledAutomationPreflightInputs, recordSchedulePreflightFailure, BaseExecutor, AGENT_MAX_TASKS_CONFIG_KEY, reconcileAgentMaxTasksPolicy, resetAgentMaxTasksMirror, upsertSwarmConfigWithPolicyMirror, runtimeStaleThresholdMinutes, upsertRuntimeInstance, getRuntimeInstanceById, insertProvisioningRuntime, markRuntimeTerminated, touchRuntimeInstance, markRuntimeInstanceOffline, countActiveRuntimeInstancesForAgent, listRuntimeInstancesForAgent, setRuntimeCredentialReady, reconcileAgentStatusFromRuntimes, agentsWithLiveRuntime, expireStaleRuntimeInstances, listRuntimeInstancesForPool, ConfigQuestionSettingsSchema, ConfigStateSchema, readConfigState, normalizeApprovalQuestions, isConfigQuestion, parseConfigAnswer, skippedConfigResult, keptConfigResult, applyConfigAnswer, resolveSlackConversationId, dispatchApprovalNotifications, postApprovalCancellationUpdates, createApprovalFollowUpTask, HumanInTheLoopExecutor, resolveWorkflowLlmConfig, SYSTEM_ONE_PROVIDER_IDS, SystemOneExtractionExecutor, SYSTEM_ONE_DECISION_NODE_TYPE, SystemOneDecisionConfigSchema, SystemOneDecisionExecutor, checkpointStep, checkpointStepFailure, checkpointStepWaiting, FAILED_TASK_OUTPUT_PREFIX, FOREACH_ITEMS_WAIT_FOR, loadCompletedStepRouting, resolveInputValue, getSecretInputKeys, validateJsonSchema, findJsonSchemaShapeErrors, getMaxWorkflowStepsPerRun, findWorkflowReadinessProblems, formatReadinessProblems, workflowSaveWarnings, withSaveWarnings, runStepValidation, TriggerSchemaError, startWorkflowExecution2, ParentStepNotCurrentError, failRunOnUnreadableReplay, isWorkflowRunActive, isStepExecuting, holdWorkflowRun, walkGraph, hasRunningStep, findReadyNodes, scriptBodyInterpolationError, ForeachExecutor, rebuildNodeExecutionInput, isForeachChildOwned, parkAndDriveForeach, startForeachDrive, runForeachDrive, FOREACH_TERMINAL_STEP_STATUSES, parseSyntheticNodeId, resolveForeachParent, settleForeachChild, resolveStepNode, stepFailureMode, getNextTargets, generateEdges, resolveValidationPort, getSuccessors, definitionNodeIds, validateDefinition, applyDefinitionPatch };