@desplega.ai/agent-swarm 2.5.2 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-csh0zz3b.js → acp-adapter-ek38cwsv.js} +5 -5
- package/dist/{actions-6xx079d1.js → actions-92mepq03.js} +19 -19
- package/dist/{amp-adapter-8n6f0hz8.js → amp-adapter-9gaxg015.js} +4 -4
- package/dist/{app-ft6r6v23.js → app-ctn8gsgx.js} +3 -3
- package/dist/{assistant-vt2cyfzd.js → assistant-zyr5kz47.js} +21 -21
- package/dist/{boot-reembed-tgrfy27t.js → boot-reembed-dwzfttdr.js} +1 -1
- package/dist/{boot-reembed-r3e808ky.js → boot-reembed-w75maga2.js} +1 -1
- package/dist/{boot-scrub-sweep-masdwxkm.js → boot-scrub-sweep-e9kq43kf.js} +3 -3
- package/dist/{boot-seeds-0n82qc7q.js → boot-seeds-x28v3fhb.js} +4 -4
- package/dist/{claude-adapter-s7j47k06.js → claude-adapter-f0afqnne.js} +5 -5
- package/dist/{claude-managed-adapter-gt6pa35d.js → claude-managed-adapter-x81a7r65.js} +3 -3
- package/dist/{claude-managed-setup-3hzrgrnk.js → claude-managed-setup-bqzwjd9x.js} +2 -2
- package/dist/{claude-sdk-session-0zrdzyx2.js → claude-sdk-session-7b7whkaq.js} +9307 -9254
- package/dist/{cli-a5ebc95n.js → cli-0shx9j93.js} +16 -13
- package/dist/{cli-4qkg9968.js → cli-19202kew.js} +4 -4
- package/dist/{cli-cyfgxnvb.js → cli-1r5ad7j5.js} +19 -17
- package/dist/{cli-jmcjbjkt.js → cli-25n3mv5d.js} +9 -7
- package/dist/cli-2655k16q.js +140 -0
- package/dist/{cli-wadzapwb.js → cli-277c31dw.js} +1 -1
- package/dist/{cli-n6kmqngw.js → cli-4b2z6n0m.js} +29 -7
- package/dist/{cli-7ccf7akh.js → cli-5bnsc0e1.js} +9 -7
- package/dist/{cli-qrjv7t18.js → cli-621ce7kw.js} +1 -1
- package/dist/{cli-xzge46sj.js → cli-64mh9mf3.js} +20 -6
- package/dist/{cli-b6tnjdvx.js → cli-6a1xg1p7.js} +6 -2
- package/dist/{cli-ydg6ep51.js → cli-6n8axjv1.js} +1 -1
- package/dist/{cli-e91vshb1.js → cli-7p4jxgtt.js} +5 -5
- package/dist/{cli-f3pfn4x3.js → cli-9j129brq.js} +9 -3
- package/dist/{cli-xw9q173h.js → cli-9s05422w.js} +1 -1
- package/dist/{cli-15xd0j7w.js → cli-abs8ndtr.js} +12 -12
- package/dist/{cli-h5d22pfs.js → cli-asq5ydm4.js} +219 -309
- package/dist/{cli-w9etnnxk.js → cli-bf8ma3f1.js} +120 -50
- package/dist/{cli-0vgy5wfw.js → cli-bvpgr9ny.js} +2 -2
- package/dist/{cli-3e9vs93g.js → cli-c61mbdds.js} +4 -4
- package/dist/{cli-e5bxnjps.js → cli-c952c7ac.js} +5 -5
- package/dist/{cli-p859hcc8.js → cli-dew6x255.js} +1 -1
- package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
- package/dist/{cli-gpv60278.js → cli-e42vss7b.js} +2 -2
- package/dist/{cli-b63d08rs.js → cli-eftzyh9y.js} +1 -1
- package/dist/{cli-fgn7yg5r.js → cli-ethgy7ha.js} +1 -1
- package/dist/{cli-86zpfje5.js → cli-ff1tm9n9.js} +1 -1
- package/dist/{cli-cf63dnxj.js → cli-g9vd9ece.js} +37 -12
- package/dist/{cli-j3dwd4kz.js → cli-gbfxp48k.js} +770 -126
- package/dist/{cli-n2918sed.js → cli-gn5af0x5.js} +21 -21
- package/dist/{cli-d172svcw.js → cli-gwhjmh8g.js} +4 -4
- package/dist/{cli-stxdrqs2.js → cli-hh5we352.js} +24 -5
- package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
- package/dist/{cli-csh8fnbg.js → cli-jb30dzvk.js} +473 -58
- package/dist/{cli-vaxw3bg0.js → cli-jbnn4vxb.js} +3 -3
- package/dist/{cli-ha22h0eg.js → cli-k4tjsdx9.js} +45 -40
- package/dist/{cli-f55b6ky0.js → cli-m4ecdxvz.js} +40 -13
- package/dist/{cli-mntxtkhn.js → cli-mz9afxxf.js} +2 -1
- package/dist/{cli-ta8bd7th.js → cli-pzatvw94.js} +1 -1
- package/dist/{cli-3qv8naq8.js → cli-rr7zx19j.js} +1591 -353
- package/dist/{cli-b1tv22wr.js → cli-s682zgqz.js} +1 -1
- package/dist/{cli-bpbcpd8b.js → cli-sbwsxvtd.js} +1 -1
- package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
- package/dist/{cli-6y43v79h.js → cli-tsma7p8v.js} +1 -1
- package/dist/{cli-edtm741g.js → cli-wghjkr0e.js} +5 -5
- package/dist/{cli-aaqnqpyw.js → cli-wx2nj19m.js} +30 -11
- package/dist/{cli-y3dzwyy3.js → cli-x0f2v69c.js} +180 -60
- package/dist/{cli-kcnwn85g.js → cli-xtrz6bzs.js} +7 -7
- package/dist/{cli-14v1sxxy.js → cli-xzfq4at3.js} +7 -7
- package/dist/cli.js +20 -20
- package/dist/{codex-adapter-brh2tcxr.js → codex-adapter-wch12q99.js} +3 -3
- package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
- package/dist/{codex-session-runner-0dnj21qs.js → codex-session-runner-41pfcr95.js} +2 -2
- package/dist/{commands-1cnvseqx.js → commands-9849dkj6.js} +2 -2
- package/dist/{controller-kc7s9xhn.js → controller-ja0sd95p.js} +29 -3
- package/dist/{controllers-connect-jzx7t4bn.js → controllers-connect-xp05dj6x.js} +20 -20
- package/dist/{cursor-adapter-qpwp5sfg.js → cursor-adapter-623qxqew.js} +1 -2
- package/dist/{db-4vq88jkc.js → db-47vj40dy.js} +3 -3
- package/dist/{db-v5ytg5pw.js → db-w759qmx0.js} +2 -2
- package/dist/{dispatcher-trtcrqae.js → dispatcher-7b8e4z2j.js} +17 -17
- package/dist/{docker-nfy2gc8t.js → docker-s84hjs91.js} +2 -2
- package/dist/{dsh-adapter-f20xwn1z.js → dsh-adapter-2ezw3xea.js} +2 -3
- package/dist/{external-channel-n9bymc2n.js → external-channel-e2s028da.js} +2 -2
- package/dist/{grok-adapter-4kpmmnr6.js → grok-adapter-ys7gaqee.js} +6 -6
- package/dist/{handlers-ptsraqa8.js → handlers-wy1dr6ja.js} +20 -20
- package/dist/{heartbeat-60qtxtjf.js → heartbeat-w3mntghj.js} +20 -20
- package/dist/{hook-rbktcstg.js → hook-3jjywnpb.js} +9 -3
- package/dist/{hook-ngtvh729.js → hook-g7kvv6x9.js} +3 -2
- package/dist/{http-z8xy72gp.js → http-tkp7d4kb.js} +2984 -2483
- package/dist/{index-r70q0xjx.js → index-1ef1myq2.js} +17 -17
- package/dist/{index-a9nsavhx.js → index-3n8m9ea2.js} +1 -1
- package/dist/{index-cbxkmfc5.js → index-9pn1pfkf.js} +1 -1
- package/dist/{index-s193j5fh.js → index-nmm79qgr.js} +20 -20
- package/dist/{index-6srwvd5b.js → index-p4xfz37z.js} +1 -1
- package/dist/{index-06qkmcx6.js → index-q7m26kvs.js} +33 -23
- package/dist/{index-1cscynkb.js → index-xdaahthb.js} +17 -17
- package/dist/{keepalive-a9srcphj.js → keepalive-v4ta8wgg.js} +3 -3
- package/dist/{lead-3devneqy.js → lead-sawnyqxk.js} +35 -35
- package/dist/{maintenance-0er16h2y.js → maintenance-84n1ex21.js} +1 -1
- package/dist/{oauth-refresh-sweep-b65jcyw2.js → oauth-refresh-sweep-vjnnandw.js} +2 -2
- package/dist/{onboard-wqywz5py.js → onboard-sfmpe4np.js} +2 -2
- package/dist/{opencode-adapter-e56086m1.js → opencode-adapter-jmpst4q4.js} +4 -4
- package/dist/{otel-impl-vk32s884.js → otel-impl-v6cc0zve.js} +1 -1
- package/dist/{pi-mono-adapter-tzps8f38.js → pi-mono-adapter-fk8fkcf6.js} +33 -6
- package/dist/{pricing-refresh-t27anv4z.js → pricing-refresh-fsk1e1zc.js} +3 -3
- package/dist/{rbac-roles-452665y0.js → rbac-roles-prmkdnkw.js} +2 -2
- package/dist/{rbac-roles-bmcgsxjn.js → rbac-roles-zxmxqa39.js} +1 -1
- package/dist/{render-v2-0ttsk2j4.js → render-v2-pjwxvnkw.js} +3 -3
- package/dist/{resume-a26kvr41.js → resume-frdtgfa0.js} +9 -5
- package/dist/{session-access-dgh9nkwk.js → session-access-fpez4y5q.js} +4 -4
- package/dist/{setup-9vc9qahm.js → setup-mq6m3m8q.js} +1 -1
- package/dist/{work-objects-vn8nkxwn.js → work-objects-kfahhjsq.js} +20 -20
- package/dist/{worker-1x1h8vnm.js → worker-v5e5rxj3.js} +35 -35
- package/openapi.json +3078 -2424
- package/package.json +7 -7
- package/plugin/commands/create-pr.md +2 -2
- package/plugin/pi-skills/create-pr/SKILL.md +2 -2
- package/src/be/approval-config-question.ts +389 -0
- package/src/be/automation-preflight.ts +37 -3
- package/src/be/db/approvals.ts +48 -0
- package/src/be/db/session-tree.ts +73 -15
- package/src/be/db/skill-search.ts +87 -0
- package/src/be/db/tasks/read.ts +29 -2
- package/src/be/db/tasks/write.ts +15 -5
- package/src/be/db/workflow-idempotency.ts +183 -0
- package/src/be/db/workflow-runs.ts +366 -8
- package/src/be/db.ts +92 -22
- package/src/be/migrations/239_skills_fts.sql +40 -0
- package/src/be/migrations/240_workflow_run_parent_step_attempt.sql +37 -0
- package/src/be/migrations/241_skills_meta_fts.sql +39 -0
- package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
- package/src/be/sealed-json.ts +1 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
- package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/sql-connections.ts +7 -1
- package/src/be/swarm-config-guard.ts +25 -0
- package/src/be/users.ts +9 -0
- package/src/commands/controller.ts +33 -0
- package/src/commands/runner.ts +6 -2
- package/src/extensions/apply-task-create.ts +57 -11
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +25 -0
- package/src/extensions/dispatcher.ts +4 -0
- package/src/extensions/post-bridge.ts +8 -0
- package/src/hooks/context-bound-note.ts +196 -0
- package/src/hooks/hook.ts +12 -0
- package/src/http/approval-requests.ts +209 -38
- package/src/http/config.ts +70 -53
- package/src/http/idempotency-key.ts +117 -0
- package/src/http/sessions.ts +27 -2
- package/src/http/workflows.ts +129 -24
- package/src/integrations/chat-sdk/inbound.ts +5 -4
- package/src/prompts/context-bound-note.ts +48 -0
- package/src/prompts/session-templates.ts +25 -1
- package/src/providers/amp-adapter.ts +30 -8
- package/src/providers/claude-adapter.ts +29 -4
- package/src/providers/claude-managed-adapter.ts +26 -0
- package/src/providers/claude-sdk-session.ts +10 -0
- package/src/providers/claude-session-events.ts +13 -0
- package/src/providers/codex-adapter.ts +12 -0
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +18 -0
- package/src/providers/pi-mono-adapter.ts +35 -0
- package/src/runtime-providers/docker.ts +6 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +35 -0
- package/src/tests/amp-adapter.test.ts +97 -0
- package/src/tests/approval-config-question.test.ts +702 -0
- package/src/tests/approval-requests.test.ts +10 -7
- package/src/tests/approval-sweeps.test.ts +12 -6
- package/src/tests/base-prompt.test.ts +1 -1
- package/src/tests/chat-sdk-inbound.test.ts +20 -0
- package/src/tests/claude-hook-output.test.ts +79 -1
- package/src/tests/claude-managed-adapter.test.ts +275 -0
- package/src/tests/codex-adapter.test.ts +163 -0
- package/src/tests/context-bound-note.test.ts +269 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/entrypoint-gitlab-credential-helper.test.ts +273 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +321 -12
- package/src/tests/extensions-pre-task.test.ts +273 -0
- package/src/tests/fetch-resolved-env.test.ts +17 -0
- package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
- package/src/tests/fixtures/fake-amp.ts +22 -8
- package/src/tests/fixtures/held-template-render.ts +42 -0
- package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
- package/src/tests/list-endpoint-slimming.test.ts +33 -0
- package/src/tests/migration-240-workflow-run-parent-step-attempt.test.ts +55 -0
- package/src/tests/openapi-route-headers.test.ts +2 -0
- package/src/tests/opencode-adapter.test.ts +134 -0
- package/src/tests/pi-mono-adapter.test.ts +105 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/runner-push-delivery.test.ts +25 -0
- package/src/tests/runtime-controller-liveness.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +2 -0
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-run-config-scope.test.ts +91 -3
- package/src/tests/session-logs.test.ts +70 -0
- package/src/tests/sessions-tree.test.ts +205 -1
- package/src/tests/skill-search-long-content.test.ts +193 -0
- package/src/tests/skill-search.test.ts +173 -22
- package/src/tests/workflow-cascade-cancel.test.ts +1169 -0
- package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
- package/src/tests/workflow-idempotency.test.ts +915 -0
- package/src/tests/workflow-sub-workflow.test.ts +538 -1
- package/src/tools/request-human-input.ts +27 -5
- package/src/tools/workflows/cancel-workflow-run.ts +1 -1
- package/src/types.ts +7 -0
- package/src/utils/context-window.ts +8 -6
- package/src/workflows/approval-cancel.ts +15 -13
- package/src/workflows/approval-notifications.ts +15 -4
- package/src/workflows/checkpoint.ts +72 -35
- package/src/workflows/engine.ts +262 -93
- package/src/workflows/executors/base.ts +6 -0
- package/src/workflows/executors/foreach.ts +2 -2
- package/src/workflows/executors/human-in-the-loop.ts +25 -1
- package/src/workflows/executors/sub-workflow.ts +45 -16
- package/src/workflows/foreach-driver.ts +10 -3
- package/src/workflows/foreach-join.ts +29 -6
- package/src/workflows/recovery.ts +68 -1
- package/src/workflows/resume.ts +228 -77
- package/src/workflows/retry-poller.ts +74 -7
- package/src/workflows/task-step-routing.ts +29 -7
- package/templates/ai-toolbox.manifest.json +8 -8
- package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
- package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
- package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
- package/templates/extensions/comms-adherence-gate/README.md +38 -13
- package/templates/extensions/comms-adherence-gate/hooks.ts +272 -69
- package/templates/extensions/comms-adherence-gate/manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +4 -4
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/code-quality/config.json +1 -1
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/delegate-work/content.md +29 -25
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/turso-interaction/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +8 -1
|
@@ -4,9 +4,26 @@ import {
|
|
|
4
4
|
scrubJsonValue,
|
|
5
5
|
init_scrub_json,
|
|
6
6
|
getDbClient2,
|
|
7
|
+
getAgentById2,
|
|
8
|
+
updateAgentStatus2,
|
|
9
|
+
updateAgentMaxTasks2,
|
|
10
|
+
getActiveTaskCount2,
|
|
11
|
+
approvalCancellationNotificationKey,
|
|
12
|
+
init_approvals,
|
|
7
13
|
getTaskById2,
|
|
14
|
+
getRunLineageBlock,
|
|
15
|
+
getWorkflowRunStatus,
|
|
16
|
+
getChildWorkflowRunId,
|
|
17
|
+
isParentStepAttemptCurrent,
|
|
8
18
|
createForeachChildSteps,
|
|
9
19
|
init_workflow_runs,
|
|
20
|
+
isApiOnlyConfigKey,
|
|
21
|
+
isInternalConfigKey,
|
|
22
|
+
internalConfigKeyError,
|
|
23
|
+
isReservedConfigKey,
|
|
24
|
+
reservedKeyError,
|
|
25
|
+
validateConfigEntry,
|
|
26
|
+
init_swarm_config_guard,
|
|
10
27
|
getOAuthApp,
|
|
11
28
|
getOAuthTokens,
|
|
12
29
|
init_oauth,
|
|
@@ -14,6 +31,7 @@ import {
|
|
|
14
31
|
init_workflow_replay,
|
|
15
32
|
createTaskExtended2,
|
|
16
33
|
getSwarmConfigs2,
|
|
34
|
+
upsertSwarmConfig2,
|
|
17
35
|
getWorkflow2,
|
|
18
36
|
createWorkflowRun2,
|
|
19
37
|
getWorkflowRun2,
|
|
@@ -37,23 +55,30 @@ import {
|
|
|
37
55
|
releaseApprovalCancellationNotificationClaim2,
|
|
38
56
|
updateApprovalRequestNotifications2,
|
|
39
57
|
init_db3
|
|
40
|
-
} from "./cli-
|
|
58
|
+
} from "./cli-jb30dzvk.js";
|
|
59
|
+
import {
|
|
60
|
+
isMultiRuntimeEnabled
|
|
61
|
+
} from "./cli-e8xyjvfc.js";
|
|
41
62
|
import {
|
|
42
63
|
getSlackApp2
|
|
43
|
-
} from "./cli-
|
|
64
|
+
} from "./cli-abs8ndtr.js";
|
|
44
65
|
import {
|
|
45
66
|
externalChannelToolError2,
|
|
46
67
|
isBlockExternalEnabled2,
|
|
47
68
|
getChannelBlockReason2
|
|
48
|
-
} from "./cli-
|
|
69
|
+
} from "./cli-eftzyh9y.js";
|
|
49
70
|
import {
|
|
50
71
|
DEFAULT_MODEL,
|
|
51
72
|
resolveCredential
|
|
52
73
|
} from "./cli-qqzmjxkf.js";
|
|
74
|
+
import {
|
|
75
|
+
revokeRuntimeTokensForRuntime,
|
|
76
|
+
init_users
|
|
77
|
+
} from "./cli-6a1xg1p7.js";
|
|
53
78
|
import {
|
|
54
79
|
TERMINAL_TASK_STATUSES,
|
|
55
80
|
init_types
|
|
56
|
-
} from "./cli-
|
|
81
|
+
} from "./cli-mz9afxxf.js";
|
|
57
82
|
import {
|
|
58
83
|
getSlackConfiguration,
|
|
59
84
|
isSlackConfigured,
|
|
@@ -63,18 +88,18 @@ import {
|
|
|
63
88
|
selectCredential,
|
|
64
89
|
init_credentials
|
|
65
90
|
} from "./cli-zw88zjba.js";
|
|
66
|
-
import {
|
|
67
|
-
deepInterpolate,
|
|
68
|
-
init_template,
|
|
69
|
-
resolveTemplate,
|
|
70
|
-
init_resolver
|
|
71
|
-
} from "./cli-tx00c0h6.js";
|
|
72
91
|
import {
|
|
73
92
|
DEFAULT_OPENROUTER_BASE_URL,
|
|
74
93
|
getOpenRouterBaseUrl,
|
|
75
94
|
getOpenRouterAttributionHeaders,
|
|
76
95
|
init_openrouter_base_url
|
|
77
96
|
} from "./cli-dfyv0jdy.js";
|
|
97
|
+
import {
|
|
98
|
+
deepInterpolate,
|
|
99
|
+
init_template,
|
|
100
|
+
resolveTemplate,
|
|
101
|
+
init_resolver
|
|
102
|
+
} from "./cli-dqygeeh5.js";
|
|
78
103
|
import {
|
|
79
104
|
init_zod
|
|
80
105
|
} from "./cli-4chz1hye.js";
|
|
@@ -124,7 +149,7 @@ async function notifyAutomationPreflightFailure(preflight) {
|
|
|
124
149
|
return;
|
|
125
150
|
}
|
|
126
151
|
try {
|
|
127
|
-
const { getSlackApp } = await import("./app-
|
|
152
|
+
const { getSlackApp } = await import("./app-ctn8gsgx.js");
|
|
128
153
|
const app = getSlackApp();
|
|
129
154
|
if (!app) {
|
|
130
155
|
console.warn("[Automation Preflight] Slack not available, cannot send notification");
|
|
@@ -362,6 +387,15 @@ function utcDayBounds(now) {
|
|
|
362
387
|
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
363
388
|
return { start: start.toISOString(), end: new Date(start.getTime() + 86400000).toISOString() };
|
|
364
389
|
}
|
|
390
|
+
async function isFirstWorkflowPreflightFailureToday(workflowId, runId, now = new Date) {
|
|
391
|
+
const bounds = utcDayBounds(now);
|
|
392
|
+
const row = await getDbClient2().get(`SELECT 1 AS found FROM workflow_runs
|
|
393
|
+
WHERE workflowId = ? AND id != ? AND status = 'failed'
|
|
394
|
+
AND error LIKE 'needs_setup:%'
|
|
395
|
+
AND startedAt >= ? AND startedAt < ?
|
|
396
|
+
LIMIT 1`, [workflowId, runId, bounds.start, bounds.end]);
|
|
397
|
+
return row == null;
|
|
398
|
+
}
|
|
365
399
|
async function recordSchedulePreflightFailure(scheduleId, failureReason, now = new Date) {
|
|
366
400
|
const message = scrubSecrets(failureReason);
|
|
367
401
|
return await getDbClient2().transaction(async (tx) => {
|
|
@@ -382,7 +416,7 @@ async function recordWorkflowPreflightFailure(input) {
|
|
|
382
416
|
const bounds = utcDayBounds(now);
|
|
383
417
|
return await getDbClient2().transaction(async (tx) => {
|
|
384
418
|
const existing = await tx.get(`SELECT id FROM workflow_runs
|
|
385
|
-
WHERE workflowId = ? AND status = 'failed'
|
|
419
|
+
WHERE workflowId = ? AND status = 'failed' AND parentStepId IS NULL
|
|
386
420
|
AND error LIKE 'needs_setup:%'
|
|
387
421
|
AND startedAt >= ? AND startedAt < ?
|
|
388
422
|
ORDER BY startedAt ASC LIMIT 1`, [input.workflowId, bounds.start, bounds.end]);
|
|
@@ -404,7 +438,10 @@ async function recordWorkflowPreflightFailure(input) {
|
|
|
404
438
|
timestamp,
|
|
405
439
|
timestamp
|
|
406
440
|
]);
|
|
407
|
-
return {
|
|
441
|
+
return {
|
|
442
|
+
runId,
|
|
443
|
+
recorded: await isFirstWorkflowPreflightFailureToday(input.workflowId, runId, now)
|
|
444
|
+
};
|
|
408
445
|
});
|
|
409
446
|
}
|
|
410
447
|
|
|
@@ -457,10 +494,475 @@ class BaseExecutor {
|
|
|
457
494
|
|
|
458
495
|
// src/workflows/executors/human-in-the-loop.ts
|
|
459
496
|
init_zod();
|
|
497
|
+
|
|
498
|
+
// src/be/approval-config-question.ts
|
|
499
|
+
init_zod();
|
|
500
|
+
init_db3();
|
|
501
|
+
|
|
502
|
+
// src/be/multi-runtime.ts
|
|
503
|
+
init_secret_scrubber();
|
|
504
|
+
init_db3();
|
|
505
|
+
init_users();
|
|
506
|
+
var AGENT_MAX_TASKS_CONFIG_KEY = "AGENT_MAX_TASKS";
|
|
507
|
+
function parseAgentMaxTasksValue(raw) {
|
|
508
|
+
const str = raw.trim();
|
|
509
|
+
if (!/^\d+$/.test(str))
|
|
510
|
+
return null;
|
|
511
|
+
const value = Number(str);
|
|
512
|
+
return value >= 1 && value <= 100 ? value : null;
|
|
513
|
+
}
|
|
514
|
+
function defaultMaxTasksForAgent(agent) {
|
|
515
|
+
return agent.isLead ? 2 : 1;
|
|
516
|
+
}
|
|
517
|
+
async function getAgentMaxTasksConfig(agentId) {
|
|
518
|
+
return (await getSwarmConfigs2({ scope: "agent", scopeId: agentId, key: AGENT_MAX_TASKS_CONFIG_KEY }))[0] ?? null;
|
|
519
|
+
}
|
|
520
|
+
async function reconcileAgentMaxTasksPolicy(agentId) {
|
|
521
|
+
const agent = await getAgentById2(agentId);
|
|
522
|
+
if (!agent)
|
|
523
|
+
return;
|
|
524
|
+
const existing = await getAgentMaxTasksConfig(agentId);
|
|
525
|
+
if (existing) {
|
|
526
|
+
const authoritative = parseAgentMaxTasksValue(existing.value);
|
|
527
|
+
if (authoritative !== null && authoritative !== (agent.maxTasks ?? 1)) {
|
|
528
|
+
await updateAgentMaxTasks2(agentId, authoritative);
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
await upsertSwarmConfig2({
|
|
533
|
+
scope: "agent",
|
|
534
|
+
scopeId: agentId,
|
|
535
|
+
key: AGENT_MAX_TASKS_CONFIG_KEY,
|
|
536
|
+
value: String(agent.maxTasks ?? defaultMaxTasksForAgent(agent)),
|
|
537
|
+
description: "Logical agent max-tasks policy (seeded from agents.maxTasks)"
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async function resetAgentMaxTasksMirror(agentId) {
|
|
541
|
+
const agent = await getAgentById2(agentId);
|
|
542
|
+
if (!agent)
|
|
543
|
+
return;
|
|
544
|
+
await updateAgentMaxTasks2(agentId, defaultMaxTasksForAgent(agent));
|
|
545
|
+
}
|
|
546
|
+
async function upsertSwarmConfigWithPolicyMirror(data) {
|
|
547
|
+
const scopeId = data.scope === "agent" ? data.scopeId : null;
|
|
548
|
+
if (!scopeId || data.key !== AGENT_MAX_TASKS_CONFIG_KEY) {
|
|
549
|
+
return upsertSwarmConfig2(data);
|
|
550
|
+
}
|
|
551
|
+
return await getDbClient2().transaction(async () => {
|
|
552
|
+
const row = await upsertSwarmConfig2(data);
|
|
553
|
+
const value = parseAgentMaxTasksValue(data.value);
|
|
554
|
+
if (value !== null)
|
|
555
|
+
await updateAgentMaxTasks2(scopeId, value);
|
|
556
|
+
return row;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function rowToRuntimeInstance(row) {
|
|
560
|
+
let metadata = null;
|
|
561
|
+
if (row.metadata) {
|
|
562
|
+
try {
|
|
563
|
+
metadata = JSON.parse(row.metadata);
|
|
564
|
+
} catch {
|
|
565
|
+
metadata = null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
id: row.id,
|
|
570
|
+
agentId: row.agent_id,
|
|
571
|
+
poolId: row.pool_id ?? null,
|
|
572
|
+
controllerId: row.controller_id ?? null,
|
|
573
|
+
externalHandle: row.external_handle ? JSON.parse(row.external_handle) : null,
|
|
574
|
+
provisionAttempts: row.provision_attempts ?? 0,
|
|
575
|
+
provisionError: row.provision_error ?? null,
|
|
576
|
+
provisionRequestedAt: row.provision_requested_at ?? null,
|
|
577
|
+
draining: row.draining === 1,
|
|
578
|
+
idleSince: row.idle_since ?? null,
|
|
579
|
+
leaseExpiresAt: row.lease_expires_at ?? null,
|
|
580
|
+
taskId: row.task_id ?? null,
|
|
581
|
+
status: row.status,
|
|
582
|
+
reportedSlots: row.reported_slots,
|
|
583
|
+
credentialReady: row.credential_ready === null ? null : row.credential_ready === 1,
|
|
584
|
+
metadata,
|
|
585
|
+
lastSeenAt: row.last_seen_at,
|
|
586
|
+
createdAt: row.created_at,
|
|
587
|
+
updatedAt: row.updated_at
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function runtimeStaleThresholdMinutes() {
|
|
591
|
+
return Number(process.env.RUNTIME_STALE_THRESHOLD_MIN) || 5;
|
|
592
|
+
}
|
|
593
|
+
function runtimeLivenessCutoff() {
|
|
594
|
+
return new Date(Date.now() - runtimeStaleThresholdMinutes() * 60 * 1000).toISOString();
|
|
595
|
+
}
|
|
596
|
+
async function upsertRuntimeInstance(instance) {
|
|
597
|
+
const metadataJson = instance.metadata ? JSON.stringify(instance.metadata) : null;
|
|
598
|
+
const row = await getDbClient2().transaction(async (tx) => {
|
|
599
|
+
const provisioning = await tx.get("SELECT id FROM runtime_instances WHERE id = ? AND status = 'provisioning' AND agent_id = ?", [instance.id, instance.agentId]);
|
|
600
|
+
const row = await tx.get(`INSERT INTO runtime_instances (id, agent_id, status, reported_slots, metadata)
|
|
601
|
+
VALUES (?, ?, 'active', ?, ?)
|
|
602
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
603
|
+
status = 'active',
|
|
604
|
+
reported_slots = excluded.reported_slots,
|
|
605
|
+
metadata = COALESCE(excluded.metadata, runtime_instances.metadata),
|
|
606
|
+
last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
607
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
608
|
+
WHERE runtime_instances.agent_id = excluded.agent_id
|
|
609
|
+
RETURNING *`, [instance.id, instance.agentId, instance.reportedSlots, metadataJson]);
|
|
610
|
+
if (row?.pool_id && provisioning) {
|
|
611
|
+
await tx.run("UPDATE agents SET runtime_provision_attempts = 0, runtime_provision_retry_at = NULL WHERE id = ?", [row.agent_id]);
|
|
612
|
+
await tx.run("UPDATE runtime_pools SET consecutive_failures = 0, circuit_open_until = NULL WHERE id = ?", [row.pool_id]);
|
|
613
|
+
}
|
|
614
|
+
return row;
|
|
615
|
+
});
|
|
616
|
+
return row ? rowToRuntimeInstance(row) : null;
|
|
617
|
+
}
|
|
618
|
+
async function getRuntimeInstanceById(id) {
|
|
619
|
+
const row = await getDbClient2().get("SELECT * FROM runtime_instances WHERE id = ?", [id]);
|
|
620
|
+
return row ? rowToRuntimeInstance(row) : null;
|
|
621
|
+
}
|
|
622
|
+
async function insertProvisioningRuntime(instance) {
|
|
623
|
+
const row = await getDbClient2().get(`INSERT INTO runtime_instances (id, agent_id, pool_id, controller_id, status,
|
|
624
|
+
reported_slots, provision_requested_at)
|
|
625
|
+
VALUES (?, ?, ?, ?, 'provisioning', ?, ?) RETURNING *`, [
|
|
626
|
+
instance.id,
|
|
627
|
+
instance.agentId,
|
|
628
|
+
instance.poolId,
|
|
629
|
+
instance.controllerId,
|
|
630
|
+
instance.reportedSlots ?? 1,
|
|
631
|
+
new Date().toISOString()
|
|
632
|
+
]);
|
|
633
|
+
return rowToRuntimeInstance(row);
|
|
634
|
+
}
|
|
635
|
+
async function markRuntimeTerminated(id, reason) {
|
|
636
|
+
await getDbClient2().transaction(async () => {
|
|
637
|
+
const runtime = await getRuntimeInstanceById(id);
|
|
638
|
+
if (!runtime)
|
|
639
|
+
return;
|
|
640
|
+
await markRuntimeInstanceOffline(id, runtime.agentId);
|
|
641
|
+
if (runtime.status === "provisioning") {
|
|
642
|
+
await getDbClient2().run("UPDATE runtime_instances SET provision_error = ? WHERE id = ?", [
|
|
643
|
+
scrubSecrets(reason),
|
|
644
|
+
id
|
|
645
|
+
]);
|
|
646
|
+
}
|
|
647
|
+
await reconcileAgentStatusFromRuntimes(runtime.agentId);
|
|
648
|
+
await revokeRuntimeTokensForRuntime(id);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
async function touchRuntimeInstance(id, agentId) {
|
|
652
|
+
const result = await getDbClient2().run(`UPDATE runtime_instances
|
|
653
|
+
SET last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
654
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
655
|
+
WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [id, agentId, runtimeLivenessCutoff()]);
|
|
656
|
+
return result.changes > 0;
|
|
657
|
+
}
|
|
658
|
+
async function markRuntimeInstanceOffline(id, agentId) {
|
|
659
|
+
const row = await getDbClient2().get(`UPDATE runtime_instances
|
|
660
|
+
SET status = 'offline', updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
661
|
+
WHERE id = ? AND agent_id = ? RETURNING *`, [id, agentId]);
|
|
662
|
+
return row ? rowToRuntimeInstance(row) : null;
|
|
663
|
+
}
|
|
664
|
+
async function countActiveRuntimeInstancesForAgent(agentId) {
|
|
665
|
+
const result = await getDbClient2().get(`SELECT COUNT(*) as count FROM runtime_instances
|
|
666
|
+
WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [agentId, runtimeLivenessCutoff()]);
|
|
667
|
+
return result?.count ?? 0;
|
|
668
|
+
}
|
|
669
|
+
async function listRuntimeInstancesForAgent(agentId) {
|
|
670
|
+
const rows = await getDbClient2().query(`SELECT *, (status = 'active' AND last_seen_at >= ?) AS is_live
|
|
671
|
+
FROM runtime_instances
|
|
672
|
+
WHERE agent_id = ?
|
|
673
|
+
ORDER BY created_at ASC, id ASC`, [runtimeLivenessCutoff(), agentId]);
|
|
674
|
+
return rows.map((row) => ({ ...rowToRuntimeInstance(row), isLive: row.is_live === 1 }));
|
|
675
|
+
}
|
|
676
|
+
async function setRuntimeCredentialReady(id, agentId, ready) {
|
|
677
|
+
const result = await getDbClient2().run(`UPDATE runtime_instances
|
|
678
|
+
SET credential_ready = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
679
|
+
WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`, [ready ? 1 : 0, id, agentId, runtimeLivenessCutoff()]);
|
|
680
|
+
return result.changes > 0;
|
|
681
|
+
}
|
|
682
|
+
async function hasReadyLiveRuntime(agentId) {
|
|
683
|
+
const row = await getDbClient2().get(`SELECT COUNT(*) c FROM runtime_instances
|
|
684
|
+
WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?
|
|
685
|
+
AND (credential_ready IS NULL OR credential_ready = 1)`, [agentId, runtimeLivenessCutoff()]);
|
|
686
|
+
return (row?.c ?? 0) > 0;
|
|
687
|
+
}
|
|
688
|
+
async function reconcileAgentStatusFromRuntimes(agentId) {
|
|
689
|
+
await getDbClient2().transaction(async () => {
|
|
690
|
+
const agent = await getAgentById2(agentId);
|
|
691
|
+
if (!agent)
|
|
692
|
+
return;
|
|
693
|
+
if (await countActiveRuntimeInstancesForAgent(agentId) === 0) {
|
|
694
|
+
if (agent.status !== "offline")
|
|
695
|
+
await updateAgentStatus2(agentId, "offline");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (!await hasReadyLiveRuntime(agentId)) {
|
|
699
|
+
if (agent.status !== "waiting_for_credentials") {
|
|
700
|
+
await updateAgentStatus2(agentId, "waiting_for_credentials");
|
|
701
|
+
}
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const next = await getActiveTaskCount2(agentId) > 0 ? "busy" : "idle";
|
|
705
|
+
if (agent.status !== next)
|
|
706
|
+
await updateAgentStatus2(agentId, next);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
async function agentsWithLiveRuntime() {
|
|
710
|
+
const rows = await getDbClient2().query(`SELECT DISTINCT agent_id FROM runtime_instances
|
|
711
|
+
WHERE status = 'active' AND last_seen_at >= ?`, [runtimeLivenessCutoff()]);
|
|
712
|
+
return new Set(rows.map((r) => r.agent_id));
|
|
713
|
+
}
|
|
714
|
+
async function expireStaleRuntimeInstances() {
|
|
715
|
+
if (!isMultiRuntimeEnabled()) {
|
|
716
|
+
return { expired: 0, agentsOffline: 0, pruned: 0 };
|
|
717
|
+
}
|
|
718
|
+
return await getDbClient2().transaction(async (tx) => {
|
|
719
|
+
const cutoff = runtimeLivenessCutoff();
|
|
720
|
+
const stale = await tx.query(`SELECT id, agent_id FROM runtime_instances
|
|
721
|
+
WHERE status = 'active' AND last_seen_at < ? AND pool_id IS NULL`, [cutoff]);
|
|
722
|
+
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;
|
|
723
|
+
let agentsOffline = 0;
|
|
724
|
+
for (const agentId of new Set(stale.map((r) => r.agent_id))) {
|
|
725
|
+
await reconcileAgentStatusFromRuntimes(agentId);
|
|
726
|
+
if ((await getAgentById2(agentId))?.status === "offline")
|
|
727
|
+
agentsOffline++;
|
|
728
|
+
}
|
|
729
|
+
return { expired: stale.length, agentsOffline, pruned };
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
async function listRuntimeInstancesForPool(poolId) {
|
|
733
|
+
return (await getDbClient2().query("SELECT * FROM runtime_instances WHERE pool_id = ? ORDER BY created_at", [poolId])).map(rowToRuntimeInstance);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/be/approval-config-question.ts
|
|
737
|
+
init_swarm_config_guard();
|
|
738
|
+
var ConfigQuestionSettingsSchema = object2({
|
|
739
|
+
key: string2().min(1).max(255).regex(/^\S+$/, "must not contain whitespace").describe("The swarm config key to set, e.g. BITBUCKET_TOKEN"),
|
|
740
|
+
scope: _enum2(["global", "agent", "repo"]).describe("Config scope the value is written to"),
|
|
741
|
+
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."),
|
|
742
|
+
isSecret: boolean().optional().describe("Store encrypted and never show the value (default true). An existing secret row always stays secret."),
|
|
743
|
+
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"),
|
|
744
|
+
options: array2(object2({
|
|
745
|
+
value: string2(),
|
|
746
|
+
label: string2(),
|
|
747
|
+
description: string2().optional()
|
|
748
|
+
})).optional().describe("Allowed values for a non-secret key, rendered as a choice"),
|
|
749
|
+
defaultValue: string2().optional().describe("Prefilled value for a non-secret key")
|
|
750
|
+
});
|
|
751
|
+
var ConfigAnswerSchema = discriminatedUnion("action", [
|
|
752
|
+
object2({ action: literal2("set"), value: string2() }),
|
|
753
|
+
object2({ action: literal2("keep") }),
|
|
754
|
+
object2({ action: literal2("skip") })
|
|
755
|
+
]);
|
|
756
|
+
var ConfigResultSchema = object2({
|
|
757
|
+
key: string2(),
|
|
758
|
+
scope: _enum2(["global", "agent", "repo"]),
|
|
759
|
+
scopeId: string2().nullable(),
|
|
760
|
+
isSecret: boolean(),
|
|
761
|
+
status: _enum2(["set", "kept", "skipped"]),
|
|
762
|
+
updatedAt: string2().nullable(),
|
|
763
|
+
value: string2().optional()
|
|
764
|
+
});
|
|
765
|
+
var ConfigStateSchema = object2({
|
|
766
|
+
exists: boolean(),
|
|
767
|
+
isSecret: boolean(),
|
|
768
|
+
updatedAt: string2().nullable(),
|
|
769
|
+
value: string2().optional().describe("Current value; present only for non-secret rows")
|
|
770
|
+
});
|
|
771
|
+
async function readConfigRow(settings) {
|
|
772
|
+
const scopeId = settings.scope === "global" ? null : settings.scopeId ?? null;
|
|
773
|
+
const row = await getDbClient2().get(`SELECT isSecret, lastUpdatedAt, envPath, description,
|
|
774
|
+
CASE WHEN isSecret = 1 THEN NULL ELSE value END AS plainValue
|
|
775
|
+
FROM swarm_config
|
|
776
|
+
WHERE scope = ? AND ${scopeId === null ? "scopeId IS NULL" : "scopeId = ?"} AND key = ?`, scopeId === null ? [settings.scope, settings.key] : [settings.scope, scopeId, settings.key]);
|
|
777
|
+
if (!row)
|
|
778
|
+
return null;
|
|
779
|
+
const isSecret = row.isSecret === 1 || isApiOnlyConfigKey(settings.key);
|
|
780
|
+
return {
|
|
781
|
+
isSecret,
|
|
782
|
+
updatedAt: row.lastUpdatedAt,
|
|
783
|
+
envPath: row.envPath,
|
|
784
|
+
description: row.description,
|
|
785
|
+
...isSecret || row.plainValue === null ? {} : { value: row.plainValue }
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async function readConfigState(settings) {
|
|
789
|
+
const row = await readConfigRow(settings);
|
|
790
|
+
if (!row)
|
|
791
|
+
return { exists: false, isSecret: settings.isSecret !== false, updatedAt: null };
|
|
792
|
+
const isSecret = row.isSecret || settings.isSecret !== false;
|
|
793
|
+
return {
|
|
794
|
+
exists: true,
|
|
795
|
+
isSecret,
|
|
796
|
+
updatedAt: row.updatedAt,
|
|
797
|
+
...!isSecret && row.value !== undefined ? { value: row.value } : {}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
async function normalizeConfigQuestion(question, ctx) {
|
|
801
|
+
const fail = (reason) => ({
|
|
802
|
+
ok: false,
|
|
803
|
+
error: `config question "${question.id}": ${reason}`
|
|
804
|
+
});
|
|
805
|
+
const parsed = ConfigQuestionSettingsSchema.safeParse(question.config);
|
|
806
|
+
if (!parsed.success) {
|
|
807
|
+
const issue = parsed.error.issues[0];
|
|
808
|
+
const path = issue?.path.length ? `config.${issue.path.map(String).join(".")}` : "config";
|
|
809
|
+
return fail(`${path}: ${issue?.message ?? "invalid"}`);
|
|
810
|
+
}
|
|
811
|
+
if (ctx.approvers.policy !== "any") {
|
|
812
|
+
return fail('a config question needs approvers.policy "any" (a config value has one author)');
|
|
813
|
+
}
|
|
814
|
+
const settings = { ...parsed.data };
|
|
815
|
+
if (settings.scope === "agent" && !settings.scopeId && ctx.callerAgentId) {
|
|
816
|
+
settings.scopeId = ctx.callerAgentId;
|
|
817
|
+
}
|
|
818
|
+
if (settings.scope === "global" && settings.scopeId) {
|
|
819
|
+
return fail("global scope must not have scopeId");
|
|
820
|
+
}
|
|
821
|
+
if (settings.scope !== "global" && !settings.scopeId) {
|
|
822
|
+
return fail(`${settings.scope} scope requires scopeId`);
|
|
823
|
+
}
|
|
824
|
+
if (isReservedConfigKey(settings.key))
|
|
825
|
+
return fail(reservedKeyError(settings.key).message);
|
|
826
|
+
if (isInternalConfigKey(settings.key))
|
|
827
|
+
return fail(internalConfigKeyError(settings.key).message);
|
|
828
|
+
const row = await readConfigRow(settings);
|
|
829
|
+
const isSecret = settings.isSecret !== false || row?.isSecret === true || isApiOnlyConfigKey(settings.key);
|
|
830
|
+
if (isSecret && (settings.options || settings.defaultValue !== undefined)) {
|
|
831
|
+
return fail("options and defaultValue are only allowed when isSecret is false (and the key is not already stored as a secret)");
|
|
832
|
+
}
|
|
833
|
+
if (settings.options?.length === 0)
|
|
834
|
+
return fail("options must not be empty");
|
|
835
|
+
if (settings.options && settings.defaultValue !== undefined && !settings.options.some((option) => option.value === settings.defaultValue)) {
|
|
836
|
+
return fail("defaultValue must be one of options");
|
|
837
|
+
}
|
|
838
|
+
const label = question.label?.trim() ? question.label : settings.key;
|
|
839
|
+
return {
|
|
840
|
+
ok: true,
|
|
841
|
+
question: {
|
|
842
|
+
...question,
|
|
843
|
+
label,
|
|
844
|
+
required: question.required ?? true,
|
|
845
|
+
config: { ...settings, isSecret }
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
async function normalizeApprovalQuestions(questions, ctx) {
|
|
850
|
+
const out = [];
|
|
851
|
+
for (const question of questions) {
|
|
852
|
+
if (question.type !== "config") {
|
|
853
|
+
if (question.config !== undefined) {
|
|
854
|
+
return {
|
|
855
|
+
ok: false,
|
|
856
|
+
error: `question "${question.id}": the config block is only valid on type "config"`
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (typeof question.label !== "string") {
|
|
860
|
+
return { ok: false, error: `question "${question.id}": label is required` };
|
|
861
|
+
}
|
|
862
|
+
out.push({ ...question, label: question.label });
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const normalized = await normalizeConfigQuestion(question, ctx);
|
|
866
|
+
if (!normalized.ok)
|
|
867
|
+
return normalized;
|
|
868
|
+
out.push(normalized.question);
|
|
869
|
+
}
|
|
870
|
+
return { ok: true, questions: out };
|
|
871
|
+
}
|
|
872
|
+
function isConfigQuestion(question) {
|
|
873
|
+
return question.type === "config" && typeof question.config === "object" && !!question.config;
|
|
874
|
+
}
|
|
875
|
+
function parseConfigAnswer(question, raw, state) {
|
|
876
|
+
const fail = (reason) => ({
|
|
877
|
+
ok: false,
|
|
878
|
+
error: `config question "${question.id}": ${reason}`
|
|
879
|
+
});
|
|
880
|
+
const required = question.required !== false;
|
|
881
|
+
if (raw === undefined || raw === null) {
|
|
882
|
+
return required ? fail("an answer is required") : { ok: true, answer: { action: "skip" } };
|
|
883
|
+
}
|
|
884
|
+
const parsed = ConfigAnswerSchema.safeParse(raw);
|
|
885
|
+
if (!parsed.success) {
|
|
886
|
+
return fail('answer must be { action: "set", value } , { action: "keep" } or { action: "skip" }');
|
|
887
|
+
}
|
|
888
|
+
const answer = parsed.data;
|
|
889
|
+
if (answer.action === "keep") {
|
|
890
|
+
return state.exists ? { ok: true, answer } : fail("cannot keep a key that is not set");
|
|
891
|
+
}
|
|
892
|
+
if (answer.action === "skip") {
|
|
893
|
+
return required ? fail("a required question cannot be skipped") : { ok: true, answer };
|
|
894
|
+
}
|
|
895
|
+
if (answer.value.trim().length === 0)
|
|
896
|
+
return fail("value must not be empty");
|
|
897
|
+
const { config } = question;
|
|
898
|
+
const isSecret = config.isSecret || state.isSecret;
|
|
899
|
+
if (!isSecret && config.options && !config.options.some((o) => o.value === answer.value)) {
|
|
900
|
+
return fail("value must be one of options");
|
|
901
|
+
}
|
|
902
|
+
const error = validateConfigEntry({
|
|
903
|
+
scope: config.scope,
|
|
904
|
+
scopeId: config.scopeId ?? null,
|
|
905
|
+
key: config.key,
|
|
906
|
+
value: answer.value
|
|
907
|
+
});
|
|
908
|
+
if (error)
|
|
909
|
+
return fail(isSecret ? `value rejected by ${config.key} validation` : error);
|
|
910
|
+
return { ok: true, answer };
|
|
911
|
+
}
|
|
912
|
+
function result(question, status, isSecret, updatedAt, value) {
|
|
913
|
+
const { key, scope, scopeId } = question.config;
|
|
914
|
+
return {
|
|
915
|
+
key,
|
|
916
|
+
scope,
|
|
917
|
+
scopeId: scope === "global" ? null : scopeId ?? null,
|
|
918
|
+
isSecret,
|
|
919
|
+
status,
|
|
920
|
+
updatedAt,
|
|
921
|
+
...!isSecret && value !== undefined ? { value } : {}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function skippedConfigResult(question) {
|
|
925
|
+
return result(question, "skipped", question.config.isSecret, null);
|
|
926
|
+
}
|
|
927
|
+
async function keptConfigResult(question) {
|
|
928
|
+
const row = await readConfigRow(question.config);
|
|
929
|
+
if (!row)
|
|
930
|
+
return null;
|
|
931
|
+
const isSecret = question.config.isSecret || row.isSecret;
|
|
932
|
+
return result(question, "kept", isSecret, row.updatedAt, row.value);
|
|
933
|
+
}
|
|
934
|
+
async function applyConfigAnswer(question, value) {
|
|
935
|
+
const { config } = question;
|
|
936
|
+
const existing = await readConfigRow(config);
|
|
937
|
+
const isSecret = config.isSecret || existing?.isSecret === true;
|
|
938
|
+
const written = await upsertSwarmConfigWithPolicyMirror({
|
|
939
|
+
scope: config.scope,
|
|
940
|
+
scopeId: config.scope === "global" ? null : config.scopeId ?? null,
|
|
941
|
+
key: config.key,
|
|
942
|
+
value,
|
|
943
|
+
isSecret,
|
|
944
|
+
envPath: existing?.envPath ?? null,
|
|
945
|
+
description: existing?.description ?? null
|
|
946
|
+
});
|
|
947
|
+
return result(question, "set", isSecret, written.lastUpdatedAt, value);
|
|
948
|
+
}
|
|
949
|
+
function formatConfigResult(value) {
|
|
950
|
+
const parsed = ConfigResultSchema.safeParse(value);
|
|
951
|
+
if (!parsed.success)
|
|
952
|
+
return null;
|
|
953
|
+
const r = parsed.data;
|
|
954
|
+
const where = `${r.scope}${r.scopeId ? `:${r.scopeId}` : ""}`;
|
|
955
|
+
const updated = r.updatedAt ? `, updated ${r.updatedAt}` : "";
|
|
956
|
+
const shown = !r.isSecret && r.value !== undefined ? ` = ${r.value}` : "";
|
|
957
|
+
return `${r.key} (${where}, ${r.isSecret ? "secret" : "plain"}): ${r.status}${updated}${shown}`;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/workflows/executors/human-in-the-loop.ts
|
|
460
961
|
init_secret_scrubber();
|
|
461
962
|
|
|
462
963
|
// src/workflows/approval-notifications.ts
|
|
463
964
|
init_db3();
|
|
965
|
+
init_approvals();
|
|
464
966
|
init_resolver();
|
|
465
967
|
|
|
466
968
|
// src/slack/dm.ts
|
|
@@ -489,7 +991,7 @@ function escapeSlackMrkdwn(text) {
|
|
|
489
991
|
function buildSlackQuestionsSummary(questions, timeoutText = "") {
|
|
490
992
|
const header = `*Questions:*
|
|
491
993
|
`;
|
|
492
|
-
const lines = questions.map((q) => `• ${escapeSlackMrkdwn(q.label.replace(/\s+/g, " ").trim())}`);
|
|
994
|
+
const lines = questions.map((q) => `• ${escapeSlackMrkdwn((q.label ?? "").replace(/\s+/g, " ").trim())}${q.type === "config" ? " (set on dashboard)" : ""}`);
|
|
493
995
|
const fits = (shown, omitted) => {
|
|
494
996
|
const tail = omitted > 0 ? `
|
|
495
997
|
_…and ${omitted} more. Answer all of them on the dashboard._` : "";
|
|
@@ -627,7 +1129,7 @@ async function postApprovalCancellationUpdates(approvals, reason, client = getSl
|
|
|
627
1129
|
for (const notification of slackThreads) {
|
|
628
1130
|
if (notification.channel !== "slack" || !notification.messageTs)
|
|
629
1131
|
continue;
|
|
630
|
-
const notificationKey =
|
|
1132
|
+
const notificationKey = approvalCancellationNotificationKey(notification.target, notification.messageTs);
|
|
631
1133
|
const claim = await claimApprovalCancellationNotification2(approval.id, notificationKey);
|
|
632
1134
|
if (!claim)
|
|
633
1135
|
continue;
|
|
@@ -687,6 +1189,8 @@ function formatResponses(questions, responses) {
|
|
|
687
1189
|
let answerText;
|
|
688
1190
|
if (answer == null) {
|
|
689
1191
|
answerText = "(no answer)";
|
|
1192
|
+
} else if (q.type === "config") {
|
|
1193
|
+
answerText = formatConfigResult(answer) ?? "(unreadable config result)";
|
|
690
1194
|
} else if (q.type === "approval") {
|
|
691
1195
|
const a = answer;
|
|
692
1196
|
answerText = a.approved ? "Approved" : "Rejected";
|
|
@@ -750,6 +1254,14 @@ var QuestionSchema = discriminatedUnion("type", [
|
|
|
750
1254
|
required: boolean().default(true),
|
|
751
1255
|
description: string2().optional(),
|
|
752
1256
|
defaultValue: boolean().optional()
|
|
1257
|
+
}),
|
|
1258
|
+
object2({
|
|
1259
|
+
id: string2(),
|
|
1260
|
+
type: literal2("config"),
|
|
1261
|
+
label: string2().optional(),
|
|
1262
|
+
required: boolean().default(true),
|
|
1263
|
+
description: string2().optional(),
|
|
1264
|
+
config: ConfigQuestionSettingsSchema
|
|
753
1265
|
})
|
|
754
1266
|
]);
|
|
755
1267
|
var ApproverConfigSchema = object2({
|
|
@@ -841,7 +1353,17 @@ class HumanInTheLoopExecutor extends BaseExecutor {
|
|
|
841
1353
|
const resolved = resolveHitlQuestions(input.config?.questions);
|
|
842
1354
|
if (!resolved.ok)
|
|
843
1355
|
return { status: "failed", error: resolved.error };
|
|
844
|
-
|
|
1356
|
+
const approvers = input.config?.approvers;
|
|
1357
|
+
const normalized = await normalizeApprovalQuestions(resolved.questions, {
|
|
1358
|
+
approvers: { policy: approvers?.policy }
|
|
1359
|
+
});
|
|
1360
|
+
if (!normalized.ok) {
|
|
1361
|
+
return {
|
|
1362
|
+
status: "failed",
|
|
1363
|
+
error: `human-in-the-loop questions are invalid: ${normalized.error}`
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
return super.run({ ...input, config: { ...input.config, questions: normalized.questions } });
|
|
845
1367
|
}
|
|
846
1368
|
async execute(config, _context, meta) {
|
|
847
1369
|
const { db } = this.deps;
|
|
@@ -2924,11 +3446,23 @@ class SystemOneDecisionExecutor extends BaseExecutor {
|
|
|
2924
3446
|
|
|
2925
3447
|
// src/workflows/foreach-join.ts
|
|
2926
3448
|
init_db3();
|
|
3449
|
+
init_workflow_runs();
|
|
2927
3450
|
|
|
2928
3451
|
// src/workflows/checkpoint.ts
|
|
2929
3452
|
init_db3();
|
|
2930
|
-
|
|
2931
|
-
|
|
3453
|
+
init_workflow_runs();
|
|
3454
|
+
async function holdsFence(runId, stepId, fence) {
|
|
3455
|
+
const row = await getDbClient2().get(`SELECT 1 AS found
|
|
3456
|
+
FROM workflow_run_steps s
|
|
3457
|
+
JOIN workflow_runs r ON r.id = s.runId
|
|
3458
|
+
WHERE s.id = ? AND s.runId = ? AND s.status = 'running' AND s.retryCount = ?
|
|
3459
|
+
AND r.status IN ('running', 'waiting')`, [stepId, runId, fence.retryCount]);
|
|
3460
|
+
return row != null && await getRunLineageBlock(runId) == null;
|
|
3461
|
+
}
|
|
3462
|
+
async function checkpointStep(runId, stepId, nodeId, result, ctx, options = {}) {
|
|
3463
|
+
return getDbClient2().transaction(async () => {
|
|
3464
|
+
if (options.fence && !await holdsFence(runId, stepId, options.fence))
|
|
3465
|
+
return false;
|
|
2932
3466
|
await updateWorkflowRunStep2(stepId, {
|
|
2933
3467
|
status: "completed",
|
|
2934
3468
|
output: result.output,
|
|
@@ -2939,45 +3473,53 @@ async function checkpointStep(runId, stepId, nodeId, result, ctx) {
|
|
|
2939
3473
|
await updateWorkflowRun2(runId, {
|
|
2940
3474
|
context: ctx
|
|
2941
3475
|
});
|
|
3476
|
+
return true;
|
|
2942
3477
|
});
|
|
2943
3478
|
}
|
|
2944
3479
|
async function checkpointStepFailure(runId, stepId, error, retryCount, retryPolicy, options) {
|
|
2945
3480
|
const now = new Date().toISOString();
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3481
|
+
return getDbClient2().transaction(async () => {
|
|
3482
|
+
if (options?.fence && !await holdsFence(runId, stepId, options.fence)) {
|
|
3483
|
+
return { shouldRetry: false, stale: true };
|
|
3484
|
+
}
|
|
3485
|
+
if (retryPolicy && retryCount < retryPolicy.maxRetries) {
|
|
3486
|
+
const delay = calculateBackoff(retryPolicy, retryCount);
|
|
3487
|
+
const nextRetryAt = new Date(Date.now() + delay).toISOString();
|
|
3488
|
+
await updateWorkflowRunStep2(stepId, {
|
|
3489
|
+
status: "failed",
|
|
3490
|
+
error,
|
|
3491
|
+
output: options?.output,
|
|
3492
|
+
retryCount: retryCount + 1,
|
|
3493
|
+
maxRetries: retryPolicy.maxRetries,
|
|
3494
|
+
nextRetryAt
|
|
3495
|
+
});
|
|
3496
|
+
return { shouldRetry: true };
|
|
3497
|
+
}
|
|
2949
3498
|
await updateWorkflowRunStep2(stepId, {
|
|
2950
3499
|
status: "failed",
|
|
2951
3500
|
error,
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
nextRetryAt
|
|
3501
|
+
output: options?.output,
|
|
3502
|
+
finishedAt: now,
|
|
3503
|
+
nextRetryAt: null
|
|
2955
3504
|
});
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
await updateWorkflowRunStep2(stepId, {
|
|
2959
|
-
status: "failed",
|
|
2960
|
-
error,
|
|
2961
|
-
finishedAt: now,
|
|
2962
|
-
nextRetryAt: null
|
|
2963
|
-
});
|
|
2964
|
-
const markRunFailed = options?.markRunFailed ?? true;
|
|
2965
|
-
if (markRunFailed) {
|
|
2966
|
-
await getDbClient2().transaction(async () => {
|
|
3505
|
+
const markRunFailed = options?.markRunFailed ?? true;
|
|
3506
|
+
if (markRunFailed) {
|
|
2967
3507
|
const run = await getWorkflowRun2(runId);
|
|
2968
|
-
if (run?.status
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
|
|
3508
|
+
if (run?.status === "running" || run?.status === "waiting") {
|
|
3509
|
+
await updateWorkflowRun2(runId, {
|
|
3510
|
+
status: "failed",
|
|
3511
|
+
error: `Step failed: ${error}`,
|
|
3512
|
+
finishedAt: now
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
return { shouldRetry: false };
|
|
3517
|
+
});
|
|
2978
3518
|
}
|
|
2979
3519
|
async function checkpointStepWaiting(runId, stepId, ctx, options = {}) {
|
|
2980
3520
|
return getDbClient2().transaction(async () => {
|
|
3521
|
+
if (await getRunLineageBlock(runId))
|
|
3522
|
+
return false;
|
|
2981
3523
|
const step = await getDbClient2().get(`UPDATE workflow_run_steps
|
|
2982
3524
|
SET status = 'waiting'
|
|
2983
3525
|
WHERE id = ? AND runId = ? AND status = 'running'
|
|
@@ -3014,6 +3556,7 @@ function calculateBackoff(policy, attempt) {
|
|
|
3014
3556
|
|
|
3015
3557
|
// src/workflows/foreach-driver.ts
|
|
3016
3558
|
init_db3();
|
|
3559
|
+
init_workflow_runs();
|
|
3017
3560
|
|
|
3018
3561
|
// src/workflows/constants.ts
|
|
3019
3562
|
var FAILED_TASK_OUTPUT_PREFIX = "[FAILED:";
|
|
@@ -3021,6 +3564,7 @@ var FOREACH_ITEMS_WAIT_FOR = "foreach.items";
|
|
|
3021
3564
|
|
|
3022
3565
|
// src/workflows/engine.ts
|
|
3023
3566
|
init_db3();
|
|
3567
|
+
init_workflow_runs();
|
|
3024
3568
|
init_workflow_replay();
|
|
3025
3569
|
init_telemetry();
|
|
3026
3570
|
|
|
@@ -3408,64 +3952,90 @@ async function resolveRenderedWorkflowInputs(input) {
|
|
|
3408
3952
|
}
|
|
3409
3953
|
async function startWorkflowExecution2(workflow, triggerData, registry, options = {}) {
|
|
3410
3954
|
const preflight = preflightAutomation(workflowPreflightInput(workflow), await getAutomationSetupStates());
|
|
3411
|
-
if (preflight.state === "needs_setup") {
|
|
3412
|
-
const { runId, recorded } = await
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3955
|
+
if (preflight.state === "needs_setup" && !options.parentStepId) {
|
|
3956
|
+
const { runId, recorded } = await getDbClient2().transaction(async () => {
|
|
3957
|
+
const failure = await recordWorkflowPreflightFailure({
|
|
3958
|
+
workflowId: workflow.id,
|
|
3959
|
+
triggerType: options.triggerType ?? "manual",
|
|
3960
|
+
triggerData,
|
|
3961
|
+
failureReason: preflight.failureReason,
|
|
3962
|
+
createdBy: options.requestedByUserId
|
|
3963
|
+
});
|
|
3964
|
+
await options.accept?.({ runId: failure.runId, skipped: false });
|
|
3965
|
+
return failure;
|
|
3418
3966
|
});
|
|
3419
3967
|
if (recorded)
|
|
3420
3968
|
await notifyAutomationPreflightFailure(preflight);
|
|
3421
3969
|
return runId;
|
|
3422
3970
|
}
|
|
3423
|
-
|
|
3424
|
-
if (
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3971
|
+
let initial;
|
|
3972
|
+
if (preflight.state === "needs_setup") {
|
|
3973
|
+
initial = {
|
|
3974
|
+
status: "failed",
|
|
3975
|
+
error: preflight.failureReason,
|
|
3976
|
+
finishedAt: new Date().toISOString(),
|
|
3977
|
+
preflight: true
|
|
3978
|
+
};
|
|
3979
|
+
} else {
|
|
3980
|
+
workflow = renderAutomationTokens(workflow, workflow.params ?? {});
|
|
3981
|
+
if (workflow.triggerSchema) {
|
|
3982
|
+
const validationErrors = validateJsonSchema(workflow.triggerSchema, triggerData);
|
|
3983
|
+
if (validationErrors.length > 0) {
|
|
3984
|
+
throw new TriggerSchemaError(validationErrors);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
if (workflow.cooldown && await shouldSkipCooldown(workflow.id, workflow.cooldown)) {
|
|
3988
|
+
initial = { status: "skipped", error: "cooldown", finishedAt: new Date().toISOString() };
|
|
3989
|
+
} else {
|
|
3990
|
+
initial = await buildInitialRunState(workflow, triggerData, registry, options);
|
|
3428
3991
|
}
|
|
3429
3992
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3993
|
+
const newRunId = crypto.randomUUID();
|
|
3994
|
+
const release = holdWorkflowRun(newRunId);
|
|
3995
|
+
try {
|
|
3996
|
+
const { runId, created, preflightAlert } = await createRunWithInitialState(workflow, triggerData, options, initial, newRunId);
|
|
3997
|
+
if (!created)
|
|
3998
|
+
return runId;
|
|
3999
|
+
if (initial.status === "failed" && initial.preflight) {
|
|
4000
|
+
if (preflightAlert)
|
|
4001
|
+
await notifyAutomationPreflightFailure(preflight);
|
|
4002
|
+
return runId;
|
|
4003
|
+
}
|
|
4004
|
+
if (initial.status === "skipped")
|
|
4005
|
+
return runId;
|
|
4006
|
+
telemetry.workflow("started", {
|
|
3434
4007
|
workflowId: workflow.id,
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
createdBy: options.requestedByUserId,
|
|
3438
|
-
parentStepId: options.parentStepId
|
|
3439
|
-
});
|
|
3440
|
-
await updateWorkflowRun2(runId, {
|
|
3441
|
-
status: "skipped",
|
|
3442
|
-
error: "cooldown",
|
|
3443
|
-
finishedAt: new Date().toISOString()
|
|
4008
|
+
nodeCount: workflow.definition.nodes.length,
|
|
4009
|
+
triggerType: options.triggerType ?? "manual"
|
|
3444
4010
|
});
|
|
4011
|
+
if (initial.status)
|
|
4012
|
+
return runId;
|
|
4013
|
+
const walk = walkGraph(workflow.definition, runId, initial.context, findEntryNodes(workflow.definition), registry, workflow.id, getSecretInputKeys(workflow.input), options);
|
|
4014
|
+
if (options.parentStepId) {
|
|
4015
|
+
walk.catch((err) => console.error(`[workflows] Child workflow run ${runId} failed:`, err));
|
|
4016
|
+
return runId;
|
|
4017
|
+
}
|
|
4018
|
+
await walk;
|
|
3445
4019
|
return runId;
|
|
4020
|
+
} finally {
|
|
4021
|
+
release();
|
|
3446
4022
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
telemetry.workflow("started", {
|
|
3457
|
-
workflowId: workflow.id,
|
|
3458
|
-
nodeCount: workflow.definition.nodes.length,
|
|
3459
|
-
triggerType: options.triggerType ?? "manual"
|
|
3460
|
-
});
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
class ParentStepNotCurrentError extends Error {
|
|
4026
|
+
constructor(parentStepId, attempt) {
|
|
4027
|
+
super(`Parent step ${parentStepId} (attempt ${attempt}) is no longer running; no child run started`);
|
|
4028
|
+
this.name = "ParentStepNotCurrentError";
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
async function buildInitialRunState(workflow, triggerData, registry, options) {
|
|
3461
4032
|
const notReady = await findWorkflowReadinessProblems(workflow.definition, registry);
|
|
3462
4033
|
if (notReady.length > 0) {
|
|
3463
|
-
|
|
4034
|
+
return {
|
|
3464
4035
|
status: "failed",
|
|
3465
4036
|
error: formatReadinessRunError(notReady),
|
|
3466
4037
|
finishedAt: new Date().toISOString()
|
|
3467
|
-
}
|
|
3468
|
-
return runId;
|
|
4038
|
+
};
|
|
3469
4039
|
}
|
|
3470
4040
|
const ctx = { trigger: triggerData };
|
|
3471
4041
|
if (options.requestedByUserId) {
|
|
@@ -3479,23 +4049,41 @@ async function startWorkflowExecution2(workflow, triggerData, registry, options
|
|
|
3479
4049
|
const resolved = await resolveRenderedWorkflowInputs(workflow.input);
|
|
3480
4050
|
Object.assign(ctx, { input: resolved });
|
|
3481
4051
|
} catch (err) {
|
|
3482
|
-
|
|
4052
|
+
return {
|
|
3483
4053
|
status: "failed",
|
|
3484
4054
|
error: `Input resolution failed: ${err}`,
|
|
3485
4055
|
finishedAt: new Date().toISOString()
|
|
3486
|
-
}
|
|
3487
|
-
return runId;
|
|
4056
|
+
};
|
|
3488
4057
|
}
|
|
3489
4058
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
4059
|
+
return { context: ctx };
|
|
4060
|
+
}
|
|
4061
|
+
async function createRunWithInitialState(workflow, triggerData, options, initial, runId) {
|
|
4062
|
+
return getDbClient2().transaction(async () => {
|
|
4063
|
+
const { parentStepId } = options;
|
|
4064
|
+
const attempt = options.parentStepAttempt ?? 0;
|
|
4065
|
+
if (parentStepId) {
|
|
4066
|
+
if (!await isParentStepAttemptCurrent(parentStepId, attempt)) {
|
|
4067
|
+
throw new ParentStepNotCurrentError(parentStepId, attempt);
|
|
4068
|
+
}
|
|
4069
|
+
const existingId = await getChildWorkflowRunId(parentStepId, attempt);
|
|
4070
|
+
if (existingId)
|
|
4071
|
+
return { runId: existingId, created: false };
|
|
4072
|
+
}
|
|
4073
|
+
await createWorkflowRun2({
|
|
4074
|
+
id: runId,
|
|
4075
|
+
workflowId: workflow.id,
|
|
4076
|
+
triggerType: options.triggerType ?? "manual",
|
|
4077
|
+
triggerData,
|
|
4078
|
+
createdBy: options.requestedByUserId,
|
|
4079
|
+
parentStepId,
|
|
4080
|
+
parentStepAttempt: parentStepId ? attempt : undefined
|
|
4081
|
+
});
|
|
4082
|
+
await updateWorkflowRun2(runId, initial.status ? { status: initial.status, error: initial.error, finishedAt: initial.finishedAt } : { context: initial.context });
|
|
4083
|
+
const preflightAlert = initial.status === "failed" && initial.preflight ? await isFirstWorkflowPreflightFailureToday(workflow.id, runId) : undefined;
|
|
4084
|
+
await options.accept?.({ runId, skipped: initial.status === "skipped" });
|
|
4085
|
+
return { runId, created: true, preflightAlert };
|
|
4086
|
+
});
|
|
3499
4087
|
}
|
|
3500
4088
|
var activeWalks = new Map;
|
|
3501
4089
|
var executingSteps = new Set;
|
|
@@ -3518,6 +4106,9 @@ async function failRunOnUnreadableReplay(runId, err, stepId) {
|
|
|
3518
4106
|
function isWorkflowRunActive(runId) {
|
|
3519
4107
|
return activeWalks.has(runId);
|
|
3520
4108
|
}
|
|
4109
|
+
function isStepExecuting(stepId) {
|
|
4110
|
+
return executingSteps.has(stepId);
|
|
4111
|
+
}
|
|
3521
4112
|
function holdWorkflowRun(runId) {
|
|
3522
4113
|
activeWalks.set(runId, (activeWalks.get(runId) ?? 0) + 1);
|
|
3523
4114
|
let released = false;
|
|
@@ -3652,6 +4243,8 @@ async function walkGraphOwned(def, runId, ctx, startNodes, registry, workflowId,
|
|
|
3652
4243
|
const run = await getWorkflowRun2(runId);
|
|
3653
4244
|
if (!run || run.status !== "running")
|
|
3654
4245
|
return;
|
|
4246
|
+
if (await getRunLineageBlock(runId))
|
|
4247
|
+
return;
|
|
3655
4248
|
const finalSteps = await getWorkflowRunStepsByRunId2(runId);
|
|
3656
4249
|
if (hasRunningStep(finalSteps))
|
|
3657
4250
|
return;
|
|
@@ -3760,17 +4353,34 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
|
|
|
3760
4353
|
if (!run || run.status !== "running" && run.status !== "waiting") {
|
|
3761
4354
|
return { halted: true };
|
|
3762
4355
|
}
|
|
4356
|
+
if (await getRunLineageBlock(runId))
|
|
4357
|
+
return { halted: true };
|
|
3763
4358
|
if (node.type !== "foreach") {
|
|
3764
4359
|
const current = await getCurrentStepForNode2(runId, node.id, getAllPredecessors(def, node.id));
|
|
3765
4360
|
if (current && (current.status !== "running" || executingSteps.has(current.id))) {
|
|
3766
4361
|
return { current };
|
|
3767
4362
|
}
|
|
4363
|
+
if (current && node.type === "sub-workflow") {
|
|
4364
|
+
claimedStepId = current.id;
|
|
4365
|
+
executingSteps.add(current.id);
|
|
4366
|
+
return {
|
|
4367
|
+
existingStep: current,
|
|
4368
|
+
stepId: current.id,
|
|
4369
|
+
fence: { retryCount: current.retryCount },
|
|
4370
|
+
deduped: false
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
3768
4373
|
}
|
|
3769
4374
|
const iteration = await getStepCountForNode2(runId, node.id);
|
|
3770
4375
|
const idempotencyKey = `${runId}:${node.id}:${iteration}`;
|
|
3771
4376
|
const existingStep = await getStepByIdempotencyKey2(idempotencyKey);
|
|
3772
4377
|
if (existingStep && (existingStep.status === "completed" || existingStep.status === "waiting")) {
|
|
3773
|
-
return {
|
|
4378
|
+
return {
|
|
4379
|
+
existingStep,
|
|
4380
|
+
stepId: existingStep.id,
|
|
4381
|
+
fence: { retryCount: existingStep.retryCount },
|
|
4382
|
+
deduped: true
|
|
4383
|
+
};
|
|
3774
4384
|
}
|
|
3775
4385
|
const latestForeachStep = node.type === "foreach" ? await getLatestStepForNode2(runId, node.id) : undefined;
|
|
3776
4386
|
const reusableForeachStep = latestForeachStep && !FOREACH_TERMINAL_STEP_STATUSES.has(latestForeachStep.status) ? latestForeachStep : undefined;
|
|
@@ -3787,7 +4397,8 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
|
|
|
3787
4397
|
}
|
|
3788
4398
|
claimedStepId = stepId;
|
|
3789
4399
|
executingSteps.add(stepId);
|
|
3790
|
-
|
|
4400
|
+
const fence = { retryCount: reusableForeachStep?.retryCount ?? 0 };
|
|
4401
|
+
return { existingStep, stepId, fence, deduped: false };
|
|
3791
4402
|
};
|
|
3792
4403
|
const dedup = await getDbClient2().transaction(claimStep).catch((err) => {
|
|
3793
4404
|
if (claimedStepId)
|
|
@@ -3811,7 +4422,7 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
|
|
|
3811
4422
|
return { outcome: "waiting", successors: [] };
|
|
3812
4423
|
}
|
|
3813
4424
|
try {
|
|
3814
|
-
return await runClaimedStep(def, runId, ctx, node, registry, dedup.existingStep, dedup.stepId, workflowId, options);
|
|
4425
|
+
return await runClaimedStep(def, runId, ctx, node, registry, dedup.existingStep, dedup.stepId, dedup.fence, workflowId, options);
|
|
3815
4426
|
} catch (err) {
|
|
3816
4427
|
if (await failRunOnUnreadableReplay(runId, err, dedup.stepId)) {
|
|
3817
4428
|
return { outcome: "failed", successors: [] };
|
|
@@ -3821,11 +4432,14 @@ async function executeStep(def, runId, ctx, node, registry, workflowId, secretKe
|
|
|
3821
4432
|
executingSteps.delete(dedup.stepId);
|
|
3822
4433
|
}
|
|
3823
4434
|
}
|
|
3824
|
-
async function runClaimedStep(def, runId, ctx, node, registry, existingStep, stepId, workflowId, options = {}) {
|
|
4435
|
+
async function runClaimedStep(def, runId, ctx, node, registry, existingStep, stepId, fence, workflowId, options = {}) {
|
|
4436
|
+
const stale = { outcome: "failed", successors: [] };
|
|
3825
4437
|
const executor = registry.get(node.type);
|
|
3826
4438
|
const systemOneRetryProblems = systemOneRetryViolations(node);
|
|
3827
4439
|
if (systemOneRetryProblems.length > 0) {
|
|
3828
|
-
await checkpointStepFailure(runId, stepId, systemOneRetryProblems.join("; "), 0
|
|
4440
|
+
await checkpointStepFailure(runId, stepId, systemOneRetryProblems.join("; "), 0, undefined, {
|
|
4441
|
+
fence
|
|
4442
|
+
});
|
|
3829
4443
|
return { outcome: "failed", successors: [] };
|
|
3830
4444
|
}
|
|
3831
4445
|
const interpolationCtx = buildNodeInterpolationCtx(node, ctx);
|
|
@@ -3833,7 +4447,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3833
4447
|
const inputErrors = validateJsonSchema(node.inputSchema, interpolationCtx);
|
|
3834
4448
|
if (inputErrors.length > 0) {
|
|
3835
4449
|
const errorMsg = `Input schema validation failed: ${inputErrors.join("; ")}`;
|
|
3836
|
-
await checkpointStepFailure(runId, stepId, errorMsg, 0
|
|
4450
|
+
const { stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
|
|
4451
|
+
fence
|
|
4452
|
+
});
|
|
4453
|
+
if (lost)
|
|
4454
|
+
return stale;
|
|
3837
4455
|
throw new Error(errorMsg);
|
|
3838
4456
|
}
|
|
3839
4457
|
}
|
|
@@ -3847,11 +4465,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3847
4465
|
const executionCtx = { ...ctx, ...interpolationCtx };
|
|
3848
4466
|
if (scriptBodyUnresolved && scriptBodyUnresolved.length > 0) {
|
|
3849
4467
|
const errorMsg = scriptBodyInterpolationError(node.id, scriptBodyUnresolved);
|
|
3850
|
-
await checkpointStepFailure(runId, stepId, errorMsg, 0);
|
|
4468
|
+
await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, { fence });
|
|
3851
4469
|
return { outcome: "failed", successors: [] };
|
|
3852
4470
|
}
|
|
3853
4471
|
if (strictUnresolved && strictUnresolved.length > 0) {
|
|
3854
|
-
await checkpointStepFailure(runId, stepId, systemOneUnresolvedError(node.id, strictUnresolved, node.type), 0);
|
|
4472
|
+
await checkpointStepFailure(runId, stepId, systemOneUnresolvedError(node.id, strictUnresolved, node.type), 0, undefined, { fence });
|
|
3855
4473
|
return { outcome: "failed", successors: [] };
|
|
3856
4474
|
}
|
|
3857
4475
|
if (unresolved.length > 0) {
|
|
@@ -3884,19 +4502,20 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3884
4502
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3885
4503
|
const retryPolicy = node.retry || executor.retryPolicy;
|
|
3886
4504
|
const currentRetryCount = existingStep?.retryCount || 0;
|
|
3887
|
-
const { shouldRetry } = await checkpointStepFailure(runId, stepId, errorMsg, currentRetryCount, retryPolicy);
|
|
4505
|
+
const { shouldRetry, stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, currentRetryCount, retryPolicy, { fence });
|
|
4506
|
+
if (lost)
|
|
4507
|
+
return stale;
|
|
3888
4508
|
if (!shouldRetry) {
|
|
3889
4509
|
throw err;
|
|
3890
4510
|
}
|
|
3891
4511
|
return { outcome: "completed", successors: [] };
|
|
3892
4512
|
}
|
|
3893
4513
|
if (result.status === "failed") {
|
|
3894
|
-
const retryPolicy = node.retry || executor.retryPolicy;
|
|
4514
|
+
const retryPolicy = result.retryable === false ? undefined : node.retry || executor.retryPolicy;
|
|
3895
4515
|
const currentRetryCount = existingStep?.retryCount || 0;
|
|
3896
|
-
const { shouldRetry } = await checkpointStepFailure(runId, stepId, result.error || "Executor returned failed status", currentRetryCount, retryPolicy);
|
|
3897
|
-
if (
|
|
3898
|
-
|
|
3899
|
-
}
|
|
4516
|
+
const { shouldRetry, stale: lost } = await checkpointStepFailure(runId, stepId, result.error || "Executor returned failed status", currentRetryCount, retryPolicy, { fence, output: result.output });
|
|
4517
|
+
if (lost)
|
|
4518
|
+
return stale;
|
|
3900
4519
|
if (!shouldRetry) {
|
|
3901
4520
|
throw new Error(result.error || "Step execution failed");
|
|
3902
4521
|
}
|
|
@@ -3917,7 +4536,7 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3917
4536
|
}
|
|
3918
4537
|
const waiting = await checkpointStepWaiting(runId, stepId, ctx);
|
|
3919
4538
|
if (waiting && result.waitFor === "workflow.child.finished") {
|
|
3920
|
-
const { resumeFromChildRun } = await import("./resume-
|
|
4539
|
+
const { resumeFromChildRun } = await import("./resume-frdtgfa0.js");
|
|
3921
4540
|
await resumeFromChildRun(stepId, registry);
|
|
3922
4541
|
}
|
|
3923
4542
|
return { outcome: waiting ? "waiting" : "completed", successors: [] };
|
|
@@ -3926,7 +4545,11 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3926
4545
|
const outputErrors = validateJsonSchema(node.outputSchema, result.output);
|
|
3927
4546
|
if (outputErrors.length > 0) {
|
|
3928
4547
|
const errorMsg = `Output schema validation failed: ${outputErrors.join("; ")}`;
|
|
3929
|
-
await checkpointStepFailure(runId, stepId, errorMsg, 0
|
|
4548
|
+
const { stale: lost } = await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
|
|
4549
|
+
fence
|
|
4550
|
+
});
|
|
4551
|
+
if (lost)
|
|
4552
|
+
return stale;
|
|
3930
4553
|
throw new Error(errorMsg);
|
|
3931
4554
|
}
|
|
3932
4555
|
}
|
|
@@ -3935,7 +4558,10 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3935
4558
|
validationResult = await runStepValidation(registry, node, result.output, executionCtx, meta);
|
|
3936
4559
|
if (validationResult.outcome === "halt") {
|
|
3937
4560
|
const errorMsg = "Validation failed (mustPass)";
|
|
3938
|
-
await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
|
|
4561
|
+
await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, {
|
|
4562
|
+
markRunFailed: false,
|
|
4563
|
+
fence
|
|
4564
|
+
});
|
|
3939
4565
|
return { outcome: "failed", successors: [] };
|
|
3940
4566
|
}
|
|
3941
4567
|
if (validationResult.outcome === "retry") {
|
|
@@ -3946,12 +4572,15 @@ async function runClaimedStep(def, runId, ctx, node, registry, existingStep, ste
|
|
|
3946
4572
|
}
|
|
3947
4573
|
const retryPolicy = node.validation.retry || node.retry;
|
|
3948
4574
|
const currentRetryCount = existingStep?.retryCount || 0;
|
|
3949
|
-
await checkpointStepFailure(runId, stepId, "Validation failed, retrying", currentRetryCount, retryPolicy);
|
|
4575
|
+
const { stale: lost } = await checkpointStepFailure(runId, stepId, "Validation failed, retrying", currentRetryCount, retryPolicy, { fence });
|
|
4576
|
+
if (lost)
|
|
4577
|
+
return stale;
|
|
3950
4578
|
return { outcome: "completed", successors: [] };
|
|
3951
4579
|
}
|
|
3952
4580
|
}
|
|
3953
4581
|
result.nextPort = resolveValidationPort(node.next, result.nextPort, validationResult?.passed);
|
|
3954
|
-
await checkpointStep(runId, stepId, node.id, result, ctx)
|
|
4582
|
+
if (!await checkpointStep(runId, stepId, node.id, result, ctx, { fence }))
|
|
4583
|
+
return stale;
|
|
3955
4584
|
const successors = result.nextPort ? getSuccessors(def, node.id, result.nextPort) : getSuccessors(def, node.id);
|
|
3956
4585
|
return { outcome: "completed", successors };
|
|
3957
4586
|
}
|
|
@@ -4199,7 +4828,7 @@ class ForeachExecutor extends BaseExecutor {
|
|
|
4199
4828
|
};
|
|
4200
4829
|
}));
|
|
4201
4830
|
const childSteps = (await this.deps.db.getWorkflowRunStepsByRunId(meta.runId)).filter((step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId);
|
|
4202
|
-
if (childSteps.every(
|
|
4831
|
+
if (childSteps.every(isForeachChildSettled)) {
|
|
4203
4832
|
return { status: "success", output: buildForeachAggregate(childSteps) };
|
|
4204
4833
|
}
|
|
4205
4834
|
return {
|
|
@@ -4369,7 +4998,7 @@ async function runForeachDrive(runId, parentNodeId, registry) {
|
|
|
4369
4998
|
if (drive.successors.length > 0) {
|
|
4370
4999
|
await walkGraph(workflow.definition, runId, ctx, drive.successors, registry, workflow.id, getSecretInputKeys(workflow.input));
|
|
4371
5000
|
} else {
|
|
4372
|
-
const { finalizeOrWait } = await import("./resume-
|
|
5001
|
+
const { finalizeOrWait } = await import("./resume-frdtgfa0.js");
|
|
4373
5002
|
await finalizeOrWait(runId);
|
|
4374
5003
|
}
|
|
4375
5004
|
return drive;
|
|
@@ -4408,6 +5037,11 @@ async function claimChildren(def, runId, parentNode) {
|
|
|
4408
5037
|
const claimed = [];
|
|
4409
5038
|
try {
|
|
4410
5039
|
await getDbClient2().transaction(async () => {
|
|
5040
|
+
const status = await getWorkflowRunStatus(runId);
|
|
5041
|
+
if (status !== "running" && status !== "waiting")
|
|
5042
|
+
return;
|
|
5043
|
+
if (await getRunLineageBlock(runId))
|
|
5044
|
+
return;
|
|
4411
5045
|
const children = latestForeachChildren(def, parentNode.id, await getWorkflowRunStepsByRunId2(runId));
|
|
4412
5046
|
for (const child of children) {
|
|
4413
5047
|
if (child.status === "running" && !ownedChildren.has(child.id)) {
|
|
@@ -4415,7 +5049,7 @@ async function claimChildren(def, runId, parentNode) {
|
|
|
4415
5049
|
claimed.push(child);
|
|
4416
5050
|
}
|
|
4417
5051
|
}
|
|
4418
|
-
const inFlight = children.filter((child) => child.status === "running" || child.status === "waiting").length;
|
|
5052
|
+
const inFlight = children.filter((child) => child.status === "running" || child.status === "waiting" || isRetryPending(child)).length;
|
|
4419
5053
|
let free = foreachConcurrencyCap(parentNode) - inFlight;
|
|
4420
5054
|
for (const child of children) {
|
|
4421
5055
|
if (free <= 0)
|
|
@@ -4511,7 +5145,7 @@ async function startChild(input) {
|
|
|
4511
5145
|
const parked = await getDbClient2().get("UPDATE workflow_run_steps SET status = 'waiting' WHERE id = ? AND status = 'running' RETURNING id", [child.id]);
|
|
4512
5146
|
if (parked && result.waitFor === "workflow.child.finished") {
|
|
4513
5147
|
ownedChildren.delete(child.id);
|
|
4514
|
-
const { resumeFromChildRun } = await import("./resume-
|
|
5148
|
+
const { resumeFromChildRun } = await import("./resume-frdtgfa0.js");
|
|
4515
5149
|
await resumeFromChildRun(child.id, registry);
|
|
4516
5150
|
}
|
|
4517
5151
|
return null;
|
|
@@ -4552,6 +5186,12 @@ var FOREACH_TERMINAL_STEP_STATUSES = new Set([
|
|
|
4552
5186
|
"cancelled",
|
|
4553
5187
|
"skipped"
|
|
4554
5188
|
]);
|
|
5189
|
+
function isRetryPending(step) {
|
|
5190
|
+
return step.status === "failed" && step.nextRetryAt != null;
|
|
5191
|
+
}
|
|
5192
|
+
function isForeachChildSettled(step) {
|
|
5193
|
+
return FOREACH_TERMINAL_STEP_STATUSES.has(step.status) && !isRetryPending(step);
|
|
5194
|
+
}
|
|
4555
5195
|
function parseSyntheticNodeId(nodeId) {
|
|
4556
5196
|
const separatorIndex = nodeId.indexOf("#");
|
|
4557
5197
|
if (separatorIndex <= 0 || separatorIndex === nodeId.length - 1)
|
|
@@ -4574,7 +5214,7 @@ async function joinForeach(def, runId, childStep, ctx) {
|
|
|
4574
5214
|
throw new Error(`Step "${childStep.nodeId}" is not a foreach child`);
|
|
4575
5215
|
}
|
|
4576
5216
|
const children = latestForeachChildren(def, parent.id, await getWorkflowRunStepsByRunId2(runId));
|
|
4577
|
-
if (children.some((step) => !
|
|
5217
|
+
if (children.some((step) => !isForeachChildSettled(step))) {
|
|
4578
5218
|
return { joined: false, parentNodeId: parent.id, successors: [] };
|
|
4579
5219
|
}
|
|
4580
5220
|
const parentStep = await getLatestStepForNode2(runId, parent.id);
|
|
@@ -4664,6 +5304,8 @@ async function settleForeachChild(def, runId, childStepId, outcome, ctx, options
|
|
|
4664
5304
|
});
|
|
4665
5305
|
if (run.status !== "running" && run.status !== "waiting")
|
|
4666
5306
|
return unclaimed;
|
|
5307
|
+
if (await getRunLineageBlock(runId))
|
|
5308
|
+
return unclaimed;
|
|
4667
5309
|
if (outcome.status !== "completed" && effectiveFailurePolicy(parent, def) === "fail-fast") {
|
|
4668
5310
|
const reason = options.runError ?? outcome.error ?? `foreach item ${current.nodeId} failed`;
|
|
4669
5311
|
await cancelQueuedForeachChildren(def, runId, parent.id, current.nodeId);
|
|
@@ -4693,9 +5335,11 @@ async function cancelQueuedForeachChildren(def, runId, parentNodeId, failedChild
|
|
|
4693
5335
|
await getDbClient2().transaction(async () => {
|
|
4694
5336
|
const children = latestForeachChildren(def, parentNodeId, await getWorkflowRunStepsByRunId2(runId));
|
|
4695
5337
|
for (const child of children) {
|
|
4696
|
-
if (child.status !== "pending")
|
|
5338
|
+
if (child.status !== "pending" && !isRetryPending(child))
|
|
4697
5339
|
continue;
|
|
4698
|
-
const row = await getDbClient2().get(
|
|
5340
|
+
const row = await getDbClient2().get(`UPDATE workflow_run_steps SET status = 'cancelled', error = ?, finishedAt = ?, nextRetryAt = NULL
|
|
5341
|
+
WHERE id = ? AND (status = 'pending' OR (status = 'failed' AND nextRetryAt IS NOT NULL))
|
|
5342
|
+
RETURNING id`, [error, finishedAt, child.id]);
|
|
4699
5343
|
if (row)
|
|
4700
5344
|
cancelled++;
|
|
4701
5345
|
}
|
|
@@ -5122,4 +5766,4 @@ function applyDefinitionPatch(def, patch) {
|
|
|
5122
5766
|
return { definition: patchedDef, errors };
|
|
5123
5767
|
}
|
|
5124
5768
|
|
|
5125
|
-
export { notifyAutomationPreflightFailure, automationIntegrationFixUrl, isNeedsSetupFailure, preflightAutomation, renderAutomationTokens, getAutomationSetupStates, schedulePreflightInput, listEnabledAutomationPreflightInputs, recordSchedulePreflightFailure, BaseExecutor, 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, 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 };
|
|
5769
|
+
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, isRetryPending, parseSyntheticNodeId, resolveForeachParent, settleForeachChild, resolveStepNode, stepFailureMode, getNextTargets, generateEdges, resolveValidationPort, getSuccessors, definitionNodeIds, validateDefinition, applyDefinitionPatch };
|