@codemation/core 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +167 -0
- package/dist/{CostCatalogContract-Dxq1BTyi.d.cts → CostCatalogContract-Dwo-ZamG.d.cts} +2 -2
- package/dist/EngineRuntimeRegistration.types-BiNasx3G.d.cts +54 -0
- package/dist/EngineRuntimeRegistration.types-Dq4ucrdo.d.ts +21 -0
- package/dist/{InMemoryRunDataFactory-Csy2evr_.d.cts → InMemoryRunDataFactory-D2U9azmZ.d.cts} +4 -20
- package/dist/{InMemoryRunEventBusRegistry-Sa86VxuV.cjs → InMemoryRunEventBusRegistry-DO0WM9Lw.cjs} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-Sa86VxuV.cjs.map → InMemoryRunEventBusRegistry-DO0WM9Lw.cjs.map} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-Bwunvt1T.js → InMemoryRunEventBusRegistry-Layt2xgm.js} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-Bwunvt1T.js.map → InMemoryRunEventBusRegistry-Layt2xgm.js.map} +1 -1
- package/dist/{ItemsInputNormalizer-Bi8m-Ijs.d.cts → ItemsInputNormalizer-A5txcOWX.d.cts} +3 -98
- package/dist/{ItemsInputNormalizer-BbQTSEkZ.cjs → ItemsInputNormalizer-C1fv3sMW.cjs} +2 -2
- package/dist/ItemsInputNormalizer-C1fv3sMW.cjs.map +1 -0
- package/dist/{ItemsInputNormalizer-BYljnXU0.d.ts → ItemsInputNormalizer-D2vrMrX1.d.ts} +2 -62
- package/dist/{ItemsInputNormalizer-CSZGMgl3.js → ItemsInputNormalizer-fUYo4GLV.js} +2 -2
- package/dist/ItemsInputNormalizer-fUYo4GLV.js.map +1 -0
- package/dist/{RunIntentService-BitgkKaT.d.cts → RunIntentService-DKxuHTUz.d.cts} +2 -15
- package/dist/{RunIntentService-DYpqfu6D.d.ts → RunIntentService-DrpKli2k.d.ts} +2 -22
- package/dist/{agentMcpTypes-DGIwk6Ue.d.cts → agentMcpTypes-BHX4RQCC.d.cts} +25 -518
- package/dist/bootstrap/index.cjs +9 -5
- package/dist/bootstrap/index.d.cts +32 -106
- package/dist/bootstrap/index.d.ts +18 -17
- package/dist/bootstrap/index.js +6 -5
- package/dist/{bootstrap-DIv-vloi.cjs → bootstrap-CTB53rEF.cjs} +9 -60
- package/dist/bootstrap-CTB53rEF.cjs.map +1 -0
- package/dist/{bootstrap-Bkd-Nfbn.js → bootstrap-DmqKheCI.js} +6 -57
- package/dist/bootstrap-DmqKheCI.js.map +1 -0
- package/dist/browser.cjs +12 -11
- package/dist/browser.d.cts +4 -4
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +3 -2
- package/dist/contracts-7L1wJHdk.cjs +569 -0
- package/dist/contracts-7L1wJHdk.cjs.map +1 -0
- package/dist/contracts-CjJ5CZ7N.js +447 -0
- package/dist/contracts-CjJ5CZ7N.js.map +1 -0
- package/dist/contracts.cjs +9 -2
- package/dist/contracts.d.cts +5 -5
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.js +3 -2
- package/dist/{executionPersistenceContracts-CN9d7AnL.d.cts → deploymentManifestTypes-B8CDmZZK.d.cts} +65 -81
- package/dist/di-C6Ubf9o5.cjs +179 -0
- package/dist/di-C6Ubf9o5.cjs.map +1 -0
- package/dist/di-Cjiil7U-.js +114 -0
- package/dist/di-Cjiil7U-.js.map +1 -0
- package/dist/{index-rllWL4r-.d.ts → index-CRv3_pY3.d.ts} +112 -808
- package/dist/{index-BSQ2LoIh.d.ts → index-mnLS0iQl.d.ts} +39 -372
- package/dist/index.cjs +53 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -438
- package/dist/index.d.ts +5 -5
- package/dist/index.js +28 -94
- package/dist/index.js.map +1 -1
- package/dist/{params-DRUr0F5v.d.cts → params-CrK4iuG1.d.cts} +3 -13
- package/dist/{runtime-CWPdvJpC.js → runtime-CBFDpmiz.js} +112 -648
- package/dist/runtime-CBFDpmiz.js.map +1 -0
- package/dist/{runtime-_VdHwGkJ.cjs → runtime-Due-FOZ2.cjs} +137 -717
- package/dist/runtime-Due-FOZ2.cjs.map +1 -0
- package/dist/testing.cjs +8 -40
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -32
- package/dist/testing.d.ts +3 -32
- package/dist/testing.js +6 -38
- package/dist/testing.js.map +1 -1
- package/dist/{di-LP2qSHkY.cjs → workflowTypes-BW6Hhee7.cjs} +4 -230
- package/dist/workflowTypes-BW6Hhee7.cjs.map +1 -0
- package/dist/{di-tom0pM2h.js → workflowTypes-DZtBTmKf.js} +3 -163
- package/dist/workflowTypes-DZtBTmKf.js.map +1 -0
- package/package.json +1 -1
- package/src/ai/AgentConnectionNodeCollector.ts +0 -4
- package/src/ai/AgentMessageConfigNormalizerFactory.ts +0 -4
- package/src/ai/AiHost.ts +0 -38
- package/src/ai/CallableToolConfig.ts +0 -9
- package/src/ai/CallableToolKindToken.ts +0 -4
- package/src/authoring/callableTool.types.ts +0 -3
- package/src/authoring/defineCollection.types.ts +0 -11
- package/src/authoring/defineHumanApprovalNode.types.ts +0 -116
- package/src/authoring/defineNode.types.ts +18 -32
- package/src/authoring/definePollingTrigger.types.ts +36 -155
- package/src/authoring/definePollingTriggerInternals.ts +0 -4
- package/src/authoring/index.ts +1 -0
- package/src/authoring/nodeBaseOptions.types.ts +4 -0
- package/src/binaries/boundedReadBinary.types.ts +0 -16
- package/src/bootstrap/index.ts +8 -2
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +0 -5
- package/src/bootstrap/runtime/EngineRuntimeRegistration.types.ts +0 -23
- package/src/browser.ts +0 -3
- package/src/contracts/AgentBindError.ts +0 -5
- package/src/contracts/Clock.ts +0 -1
- package/src/contracts/CodemationTelemetryAttributeNames.ts +0 -10
- package/src/contracts/NoOpAgentMcpIntegration.ts +0 -6
- package/src/contracts/NoOpTelemetrySpanScope.ts +0 -7
- package/src/contracts/RetryPolicy.ts +0 -2
- package/src/contracts/agentMcpTypes.ts +0 -34
- package/src/contracts/assertionTypes.ts +0 -33
- package/src/contracts/baseTypes.ts +0 -6
- package/src/contracts/collectionTypes.ts +0 -25
- package/src/contracts/credentialTypes.ts +13 -60
- package/src/contracts/deploymentManifestTypes.ts +158 -0
- package/src/contracts/dispatchTypes.ts +29 -0
- package/src/contracts/executionPersistenceContracts.ts +0 -33
- package/src/contracts/hitlSeamTypes.ts +0 -14
- package/src/contracts/humanTaskStoreTypes.ts +0 -2
- package/src/contracts/inboxChannelTypes.ts +0 -9
- package/src/contracts/index.ts +3 -0
- package/src/contracts/itemExpr.ts +7 -21
- package/src/contracts/itemMeta.ts +0 -3
- package/src/contracts/mcpTypes.ts +0 -16
- package/src/contracts/retryPolicySpec.types.ts +0 -10
- package/src/contracts/runFinishedAtFactory.ts +0 -1
- package/src/contracts/runTypes.ts +0 -74
- package/src/contracts/runtimeTypes.ts +4 -131
- package/src/contracts/telemetryTypes.ts +0 -7
- package/src/contracts/testTriggerTypes.ts +0 -43
- package/src/contracts/triggerInvokerTypes.ts +6 -0
- package/src/contracts/webhookTypes.ts +0 -8
- package/src/contracts/workflowActivationPolicy.ts +0 -5
- package/src/contracts/workflowTypes.ts +5 -80
- package/src/contracts/workspaceFileTypes.ts +10 -42
- package/src/contracts.ts +18 -10
- package/src/credentials/CredentialMaterialProvider.types.ts +0 -28
- package/src/credentials/ManagedCredentialMaterialWriteError.ts +0 -6
- package/src/credentials/ManagedMaterialFetchError.ts +0 -6
- package/src/credentials/OAuthFlowExecutor.types.ts +0 -15
- package/src/di/CoreTokens.ts +2 -6
- package/src/events/ConnectionInvocationEventPublisher.ts +0 -7
- package/src/events/NodeEventPublisher.ts +0 -1
- package/src/events/runEvents.ts +0 -8
- package/src/execution/ActivationEnqueueService.ts +0 -10
- package/src/execution/ChildExecutionScopeFactory.ts +0 -13
- package/src/execution/FanInMergeByOriginMerger.ts +0 -11
- package/src/execution/InProcessRetryRunner.ts +0 -1
- package/src/execution/ItemExprResolver.ts +0 -3
- package/src/execution/NodeActivationRequestComposer.ts +0 -3
- package/src/execution/NodeActivationRequestInputPreparer.ts +0 -5
- package/src/execution/NodeExecutionSnapshotFactory.ts +0 -1
- package/src/execution/NodeExecutor.ts +1 -17
- package/src/execution/NodeOutputNormalizer.ts +8 -1
- package/src/execution/NodeSuspensionHandler.ts +1 -39
- package/src/execution/PersistedRunStateTerminalBuilder.ts +0 -5
- package/src/execution/RunSuspendedError.ts +0 -10
- package/src/execution/RunnableOutputBehaviorResolver.ts +12 -0
- package/src/execution/WorkflowRunExecutionContextFactory.ts +0 -3
- package/src/index.ts +10 -2
- package/src/orchestration/AbortControllerFactory.ts +0 -4
- package/src/orchestration/Engine.ts +0 -9
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +3 -4
- package/src/orchestration/RunContinuationService.ts +7 -39
- package/src/orchestration/RunStartService.ts +0 -7
- package/src/orchestration/TestSuiteOrchestrator.ts +0 -18
- package/src/orchestration/TestSuiteRunIdFactory.ts +0 -4
- package/src/orchestration/TriggerRuntimeService.ts +3 -2
- package/src/planning/CurrentStateFrontierPlanner.ts +0 -1
- package/src/planning/RunQueuePlanner.ts +0 -6
- package/src/policies/executionLimits/EngineExecutionLimitsPolicy.ts +0 -8
- package/src/policies/executionLimits/EngineExecutionLimitsPolicyFactory.ts +0 -3
- package/src/runStorage/RunSummaryMapper.ts +0 -1
- package/src/runtime/EngineFactory.ts +6 -11
- package/src/runtime/RunIntentService.ts +0 -4
- package/src/runtime/WorkflowRepositoryWebhookTriggerMatcher.ts +0 -4
- package/src/runtime-types/InjectableRuntimeDecoratorComposerRegistry.ts +0 -4
- package/src/runtime-types/PersistedRuntimeTypeMetadataStoreRegistry.ts +0 -4
- package/src/runtime-types/PersistedRuntimeTypeNameResolver.ts +0 -1
- package/src/runtime-types/persistedRuntimeTypeModelRegistry.ts +0 -4
- package/src/runtime-types/runtimeTypeDecorators.types.ts +0 -12
- package/src/scheduler/ConfigDrivenOffloadPolicy.ts +0 -1
- package/src/scheduler/DefaultDrivingScheduler.ts +0 -6
- package/src/scheduler/InlineDrivingScheduler.ts +0 -13
- package/src/serialization/ItemsInputNormalizer.ts +0 -5
- package/src/testing/CapturingScheduler.ts +0 -3
- package/src/testing/EngineTestKitRunIdFactory.ts +0 -3
- package/src/testing/ItemHarnessNode.ts +0 -3
- package/src/testing/ItemHarnessNodeConfig.ts +0 -4
- package/src/testing/PrefixedSequentialIdGenerator.ts +0 -3
- package/src/testing/RegistrarEngineTestKit.types.ts +0 -2
- package/src/testing/RejectingCredentialSessionService.ts +0 -4
- package/src/testing/SubWorkflowRunnerTestNode.ts +0 -3
- package/src/testing/WorkflowTestHarnessManualTrigger.ts +0 -3
- package/src/testing/WorkflowTestKitBuilder.ts +0 -4
- package/src/testing/WorkflowTestKitRunNodeWorkflowFactory.ts +0 -3
- package/src/testing.ts +0 -3
- package/src/triggers/polling/PollingTriggerDedupWindow.ts +0 -4
- package/src/triggers/polling/PollingTriggerLogger.ts +0 -5
- package/src/triggers/polling/PollingTriggerRuntime.ts +0 -5
- package/src/types/index.ts +0 -6
- package/src/validation/WorkflowEdgePortValidator.ts +0 -5
- package/src/workflow/definition/ConnectionInvocationIdFactory.ts +0 -7
- package/src/workflow/definition/ConnectionNodeIdFactory.ts +0 -6
- package/src/workflow/definition/NodeIterationIdFactory.ts +0 -13
- package/src/workflow/definition/WorkflowExecutableNodeClassifier.ts +0 -6
- package/src/workflow/dsl/ChainCursorResolver.ts +8 -15
- package/src/workflow/dsl/NodeIdSlugifier.ts +0 -9
- package/src/workflow/dsl/WhenBuilder.ts +49 -2
- package/src/workflow/dsl/WorkflowDefinitionError.ts +0 -9
- package/src/workflow/dsl/workflowBuilderTypes.ts +5 -0
- package/src/workflowSnapshots/MissingRuntimeParityGuard.ts +24 -0
- package/src/workflowSnapshots/PersistedWorkflowTokenRegistry.ts +0 -6
- package/src/workflowSnapshots/WorkflowParityMismatchError.ts +18 -0
- package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +1 -5
- package/src/workflowSnapshots/index.ts +3 -0
- package/dist/EngineRuntimeRegistration.types-CqcTWexS.d.cts +0 -81
- package/dist/EngineRuntimeRegistration.types-Cr75cSfL.d.ts +0 -44
- package/dist/ItemsInputNormalizer-BbQTSEkZ.cjs.map +0 -1
- package/dist/ItemsInputNormalizer-CSZGMgl3.js.map +0 -1
- package/dist/bootstrap-Bkd-Nfbn.js.map +0 -1
- package/dist/bootstrap-DIv-vloi.cjs.map +0 -1
- package/dist/contracts-CK0x6w_G.cjs +0 -74
- package/dist/contracts-CK0x6w_G.cjs.map +0 -1
- package/dist/contracts-DXdfTdpW.js +0 -50
- package/dist/contracts-DXdfTdpW.js.map +0 -1
- package/dist/di-LP2qSHkY.cjs.map +0 -1
- package/dist/di-tom0pM2h.js.map +0 -1
- package/dist/runtime-CWPdvJpC.js.map +0 -1
- package/dist/runtime-_VdHwGkJ.cjs.map +0 -1
|
@@ -7,19 +7,6 @@ import type {
|
|
|
7
7
|
TelemetrySpanScope,
|
|
8
8
|
} from "../types";
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Builds a re-rooted child execution context for sub-agent (and other deeply-nested) invocations.
|
|
12
|
-
*
|
|
13
|
-
* At the orchestrator's `agent.tool.call` boundary the inner runtime needs a ctx whose:
|
|
14
|
-
* - `nodeId` is the tool's connection node id (so inner LLM/tool connection ids derive correctly),
|
|
15
|
-
* - `activationId` is fresh (so its connection-invocation rows are uniquely identifiable),
|
|
16
|
-
* - `telemetry` parents children under the tool-call span (not the orchestrator's node span),
|
|
17
|
-
* - `binary` is scoped to the new (nodeId, activationId),
|
|
18
|
-
* - `parentInvocationId` points back to the tool-call invocation for downstream lineage.
|
|
19
|
-
*
|
|
20
|
-
* Registered via factory in {@link EngineRuntimeRegistrar} so constructors stay free of parameter
|
|
21
|
-
* decorators (Next/SWC and coverage tooling cannot parse them on in-repo sources).
|
|
22
|
-
*/
|
|
23
10
|
export class ChildExecutionScopeFactory {
|
|
24
11
|
constructor(private readonly activationIdFactory: ActivationIdFactory) {}
|
|
25
12
|
|
|
@@ -2,17 +2,6 @@ import type { InputPortKey, Item, Items, NodeInputsByPort } from "../types";
|
|
|
2
2
|
|
|
3
3
|
import { getOriginIndexFromItem } from "../contracts/itemMeta";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Default fan-in: combine multi-port {@link NodeInputsByPort} into one {@link Items} batch for per-item nodes.
|
|
7
|
-
*
|
|
8
|
-
* This is used when a single-input per-item node has multiple inbound edges (for example, branch reconverge
|
|
9
|
-
* after an `If` / `Switch`). The default behavior is **append / union** (preserving item payloads) with a
|
|
10
|
-
* deterministic order:
|
|
11
|
-
*
|
|
12
|
-
* - When router origin metadata exists (`meta._cm.originIndex`), items are sorted by origin index so the
|
|
13
|
-
* downstream batch preserves original ordering across branches.
|
|
14
|
-
* - Otherwise, items are appended by port-key order, preserving each port's local order.
|
|
15
|
-
*/
|
|
16
5
|
export class FanInMergeByOriginMerger {
|
|
17
6
|
merge(inputsByPort: NodeInputsByPort): Items {
|
|
18
7
|
const portKeys = Object.keys(inputsByPort).sort();
|
|
@@ -7,7 +7,6 @@ import type { AsyncSleeper } from "./asyncSleeper.types";
|
|
|
7
7
|
|
|
8
8
|
export type { AsyncSleeper } from "./asyncSleeper.types";
|
|
9
9
|
|
|
10
|
-
/** Maximum permitted retry attempts — workflow-declared values above this are clamped. */
|
|
11
10
|
const HARD_MAX_RETRY_ATTEMPTS = 10;
|
|
12
11
|
|
|
13
12
|
type NormalizedPolicy =
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { resolveItemExprsForExecution } from "../contracts/itemExpr";
|
|
2
2
|
import type { Item, NodeExecutionContext, RunnableNodeConfig } from "../types";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Resolves {@link import("../contracts/itemExpr").ItemExpr} leaves on runnable config before {@link RunnableNode.execute}.
|
|
6
|
-
*/
|
|
7
4
|
export class ItemExprResolver {
|
|
8
5
|
async resolveConfigForItem<TConfig extends RunnableNodeConfig<any, any>>(
|
|
9
6
|
ctx: NodeExecutionContext<TConfig>,
|
|
@@ -46,9 +46,6 @@ export type PlannedNodeActivationRequest = NodeActivationContextArgs & {
|
|
|
46
46
|
nodeDefinition: NodeExecutionDefinition;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Builds {@link NodeActivationRequest} values shared by workflow starters and continuation.
|
|
51
|
-
*/
|
|
52
49
|
export class NodeActivationRequestComposer {
|
|
53
50
|
constructor(
|
|
54
51
|
private readonly activationIdFactory: ActivationIdFactory,
|
|
@@ -5,11 +5,6 @@ import type { Item, NodeActivationRequest, RunnableNodeConfig, WorkflowNodeInsta
|
|
|
5
5
|
import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
|
|
6
6
|
import { NodeInputContractError } from "./NodeInputContractError";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Validates per-item inputs for {@link RunnableNode} before enqueue persistence (Zod on `item.json`).
|
|
10
|
-
* Does not rewrite `item.json` (wire stays as emitted upstream; engine passes parsed input via `execute` args).
|
|
11
|
-
* Converts multi-input activations into a single-input batch when the node is per-item only (engine fan-in).
|
|
12
|
-
*/
|
|
13
8
|
export class NodeActivationRequestInputPreparer {
|
|
14
9
|
private readonly fanInMerger = new FanInMergeByOriginMerger();
|
|
15
10
|
|
|
@@ -71,7 +71,6 @@ export class NodeExecutionSnapshotFactory {
|
|
|
71
71
|
inputsByPort: NodeInputsByPort;
|
|
72
72
|
outputs: NodeOutputs;
|
|
73
73
|
fromPinnedOutput?: boolean;
|
|
74
|
-
/** Override the terminal status for HITL outcomes (defaults to `"completed"`). */
|
|
75
74
|
hitlStatus?: Extract<
|
|
76
75
|
NodeExecutionStatus,
|
|
77
76
|
"hitl-approved" | "hitl-rejected" | "hitl-timeout" | "hitl-auto-accepted" | "hitl-cancelled"
|
|
@@ -38,9 +38,7 @@ export class NodeExecutor {
|
|
|
38
38
|
private readonly retryRunner: InProcessRetryRunner,
|
|
39
39
|
itemExprResolver?: ItemExprResolver,
|
|
40
40
|
outputBehaviorResolver?: RunnableOutputBehaviorResolver,
|
|
41
|
-
/** Required for HITL suspension support. When omitted, `SuspensionRequest` throws upward. */
|
|
42
41
|
private readonly suspensionHandler?: NodeSuspensionHandler,
|
|
43
|
-
/** Required alongside `suspensionHandler`. */
|
|
44
42
|
private readonly loadRunState?: (runId: RunId) => Promise<PersistedRunState | undefined>,
|
|
45
43
|
) {
|
|
46
44
|
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
@@ -190,8 +188,6 @@ export class NodeExecutor {
|
|
|
190
188
|
const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
|
|
191
189
|
const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
|
|
192
190
|
const baseCtx = this.pickExecutionContext(runnableCtx, resolvedCtx);
|
|
193
|
-
// Mint a per-item iteration id and stamp it (with the item index) onto the ctx so connection
|
|
194
|
-
// invocations and telemetry written from inside `node.execute` carry the per-item identity.
|
|
195
191
|
const iterationCtx = {
|
|
196
192
|
...baseCtx,
|
|
197
193
|
iterationId: NodeIterationIdFactory.create(),
|
|
@@ -208,9 +204,6 @@ export class NodeExecutor {
|
|
|
208
204
|
try {
|
|
209
205
|
raw = await Promise.resolve(node.execute(args));
|
|
210
206
|
} catch (e) {
|
|
211
|
-
// Use both instanceof AND name check: under tsx/dev with mixed source/dist resolution,
|
|
212
|
-
// SuspensionRequest may load as two distinct class objects and instanceof fails. The
|
|
213
|
-
// name brand survives the duality because both copies set name="SuspensionRequest".
|
|
214
207
|
const isSuspension =
|
|
215
208
|
e instanceof SuspensionRequest ||
|
|
216
209
|
(e instanceof Error &&
|
|
@@ -218,23 +211,17 @@ export class NodeExecutor {
|
|
|
218
211
|
typeof (e as { request?: unknown }).request === "object");
|
|
219
212
|
if (isSuspension) {
|
|
220
213
|
if (!this.suspensionHandler || !this.loadRunState) {
|
|
221
|
-
// Suspension not supported in this executor configuration — propagate as a regular error.
|
|
222
214
|
throw new Error(
|
|
223
215
|
`Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`,
|
|
224
216
|
{ cause: e },
|
|
225
217
|
);
|
|
226
218
|
}
|
|
227
|
-
// Per-item suspension: load current state, persist the suspension entry, and
|
|
228
|
-
// continue processing remaining items. If deliver throws it propagates upward.
|
|
229
219
|
const state = await this.loadRunState(request.runId);
|
|
230
220
|
if (!state) {
|
|
231
221
|
throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, {
|
|
232
222
|
cause: e,
|
|
233
223
|
});
|
|
234
224
|
}
|
|
235
|
-
// handleSuspension throws RunSuspendedError after persisting — we re-throw it
|
|
236
|
-
// to exit the loop immediately. Partial byPort outputs are intentionally dropped
|
|
237
|
-
// (TODO: consider stashing outputs of non-suspended items alongside the suspension).
|
|
238
225
|
await this.suspensionHandler.handle({
|
|
239
226
|
runId: request.runId,
|
|
240
227
|
nodeId: request.nodeId,
|
|
@@ -244,7 +231,7 @@ export class NodeExecutor {
|
|
|
244
231
|
state,
|
|
245
232
|
telemetry: iterationCtx.telemetry,
|
|
246
233
|
});
|
|
247
|
-
hasSuspension = true;
|
|
234
|
+
hasSuspension = true;
|
|
248
235
|
continue;
|
|
249
236
|
}
|
|
250
237
|
throw e;
|
|
@@ -264,14 +251,11 @@ export class NodeExecutor {
|
|
|
264
251
|
}
|
|
265
252
|
}
|
|
266
253
|
if (hasSuspension) {
|
|
267
|
-
// Unreachable in practice (suspensionHandler always throws RunSuspendedError) but
|
|
268
|
-
// guards against future refactors that might change handler behaviour.
|
|
269
254
|
throw new RunSuspendedError(request.runId, "unknown");
|
|
270
255
|
}
|
|
271
256
|
return byPort as NodeOutputs;
|
|
272
257
|
}
|
|
273
258
|
|
|
274
|
-
/** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
|
|
275
259
|
private pickExecutionContext<TConfig extends RunnableNodeConfig<any, any>>(
|
|
276
260
|
runnableCtx: NodeExecutionContext<TConfig>,
|
|
277
261
|
resolvedCtx: NodeExecutionContext<TConfig> | null | undefined,
|
|
@@ -73,10 +73,17 @@ export class NodeOutputNormalizer {
|
|
|
73
73
|
return typeof value === "object" && value !== null && "json" in value;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
private isPlainJsonObject(value: unknown): value is Record<string, unknown> {
|
|
77
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
private applyOutput(baseItem: Item, next: Item, behavior: RunnableOutputBehavior): Item {
|
|
77
81
|
const explicitBinary = next.binary;
|
|
78
82
|
return {
|
|
79
|
-
json:
|
|
83
|
+
json:
|
|
84
|
+
behavior.mergeJson && this.isPlainJsonObject(baseItem.json) && this.isPlainJsonObject(next.json)
|
|
85
|
+
? { ...baseItem.json, ...next.json }
|
|
86
|
+
: next.json,
|
|
80
87
|
...(explicitBinary !== undefined
|
|
81
88
|
? { binary: explicitBinary }
|
|
82
89
|
: behavior.keepBinaries && baseItem.binary
|
|
@@ -19,33 +19,12 @@ import { CodemationTelemetryAttributeNames } from "../contracts/CodemationTeleme
|
|
|
19
19
|
import { RunSuspendedError } from "./RunSuspendedError";
|
|
20
20
|
export { RunSuspendedError };
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Handles per-item `SuspensionRequest` catches in the engine's item execution loop.
|
|
24
|
-
*
|
|
25
|
-
* Responsibilities:
|
|
26
|
-
* 1. Generate a `taskId` (UUID v4).
|
|
27
|
-
* 2. Persist a `HumanTask` row via `HumanTaskStore.create`.
|
|
28
|
-
* 3. Sign a resume URL via `HitlResumeTokenSigner.sign`.
|
|
29
|
-
* 4. Enqueue a delayed BullMQ timeout job via `HitlTimeoutJobScheduler.enqueue`.
|
|
30
|
-
* 5. Build a `HumanTaskHandle` and call `deliver`.
|
|
31
|
-
* 6. Append a `PersistedSuspensionEntry` to the run state and flip status to `"suspended"`.
|
|
32
|
-
* 7. Persist via `WorkflowExecutionRepository.save`.
|
|
33
|
-
* 8. Throw `RunSuspendedError` so the caller can exit cleanly.
|
|
34
|
-
*
|
|
35
|
-
* If `deliver` throws, the error propagates up to `NodeExecutionRequestHandlerService`
|
|
36
|
-
* which routes it through `resumeFromNodeError` → run status becomes `"failed"`.
|
|
37
|
-
*
|
|
38
|
-
* `humanTaskStore`, `tokenSigner`, and `timeoutScheduler` are optional —
|
|
39
|
-
* when not registered (e.g. in unit tests), the handler still suspends the run but
|
|
40
|
-
* skips persistence, token signing, and job scheduling.
|
|
41
|
-
*/
|
|
42
22
|
export class NodeSuspensionHandler {
|
|
43
23
|
constructor(
|
|
44
24
|
private readonly workflowExecutionRepository: WorkflowExecutionRepository,
|
|
45
25
|
private readonly humanTaskStore?: HumanTaskStore,
|
|
46
26
|
private readonly tokenSigner?: HitlResumeTokenSignerSeam,
|
|
47
27
|
private readonly timeoutScheduler?: HitlTimeoutJobSchedulerSeam,
|
|
48
|
-
/** Workspace ID to stamp on HumanTaskRecord in managed mode (T7 security fix). Null in non-managed mode. */
|
|
49
28
|
private readonly workspaceId?: string,
|
|
50
29
|
) {}
|
|
51
30
|
|
|
@@ -56,7 +35,6 @@ export class NodeSuspensionHandler {
|
|
|
56
35
|
itemIndex: number;
|
|
57
36
|
suspensionRequest: SuspensionRequest;
|
|
58
37
|
state: PersistedRunState;
|
|
59
|
-
/** Telemetry scope of the node's per-item span. Used to emit `hitl.task.*` span events. */
|
|
60
38
|
telemetry?: TelemetryScope;
|
|
61
39
|
}): Promise<never> {
|
|
62
40
|
const taskId = `htask_${globalThis.crypto.randomUUID()}`;
|
|
@@ -68,12 +46,11 @@ export class NodeSuspensionHandler {
|
|
|
68
46
|
const decisionSchemaHash = this.hashSchema(decisionSchema);
|
|
69
47
|
const decisionSchemaJson = this.schemaToJson(decisionSchema);
|
|
70
48
|
|
|
71
|
-
// Build resume token (when signer is available)
|
|
72
49
|
let resumeUrl = "";
|
|
73
50
|
let resumeTokenHash = "";
|
|
74
51
|
if (this.tokenSigner) {
|
|
75
52
|
const token = this.tokenSigner.sign({ taskId, expiresAt, schemaHash: decisionSchemaHash });
|
|
76
|
-
resumeUrl = token;
|
|
53
|
+
resumeUrl = token;
|
|
77
54
|
resumeTokenHash = this.tokenSigner.hashToken(token);
|
|
78
55
|
}
|
|
79
56
|
|
|
@@ -86,7 +63,6 @@ export class NodeSuspensionHandler {
|
|
|
86
63
|
...(metadata !== undefined ? { metadata } : {}),
|
|
87
64
|
};
|
|
88
65
|
|
|
89
|
-
// Emit hitl.task.created before calling deliver.
|
|
90
66
|
const channel = (metadata as Record<string, unknown> | undefined)?.["channel"];
|
|
91
67
|
await args.telemetry?.addSpanEvent?.({
|
|
92
68
|
name: "hitl.task.created",
|
|
@@ -99,8 +75,6 @@ export class NodeSuspensionHandler {
|
|
|
99
75
|
},
|
|
100
76
|
});
|
|
101
77
|
|
|
102
|
-
// D5: deliver throws → emit hitl.task.delivery_failed, then propagate upward;
|
|
103
|
-
// caller routes to resumeFromNodeError → "failed"
|
|
104
78
|
let deliveryRef: Awaited<ReturnType<typeof deliver>>;
|
|
105
79
|
try {
|
|
106
80
|
deliveryRef = await deliver(handle);
|
|
@@ -116,15 +90,11 @@ export class NodeSuspensionHandler {
|
|
|
116
90
|
throw deliverError;
|
|
117
91
|
}
|
|
118
92
|
|
|
119
|
-
// Persist HumanTask row
|
|
120
93
|
if (this.humanTaskStore) {
|
|
121
94
|
const record: HumanTaskRecord = {
|
|
122
95
|
id: taskId,
|
|
123
96
|
runId: args.runId,
|
|
124
97
|
workflowId: args.state.workflowId,
|
|
125
|
-
// T7: stamp workspaceId in managed mode so HitlCallbackHandler can assert workspace identity.
|
|
126
|
-
// Non-managed mode leaves this undefined (null in DB) — the check in HitlCallbackHandler
|
|
127
|
-
// is guarded by `task.workspaceId !== undefined` and is a no-op when null.
|
|
128
98
|
workspaceId: this.workspaceId ?? undefined,
|
|
129
99
|
nodeId: args.nodeId,
|
|
130
100
|
activationId: args.activationId,
|
|
@@ -144,7 +114,6 @@ export class NodeSuspensionHandler {
|
|
|
144
114
|
await this.humanTaskStore.create(record);
|
|
145
115
|
}
|
|
146
116
|
|
|
147
|
-
// Enqueue timeout job
|
|
148
117
|
if (this.timeoutScheduler) {
|
|
149
118
|
await this.timeoutScheduler.enqueueTimeoutJob({ taskId, expiresAt });
|
|
150
119
|
}
|
|
@@ -172,13 +141,7 @@ export class NodeSuspensionHandler {
|
|
|
172
141
|
throw new RunSuspendedError(args.runId, taskId);
|
|
173
142
|
}
|
|
174
143
|
|
|
175
|
-
/**
|
|
176
|
-
* Parse a duration string into milliseconds.
|
|
177
|
-
* Accepts ISO 8601 durations ("PT24H", "PT30M") and shorthand ("24h", "30m", "1d").
|
|
178
|
-
* Throws for unrecognised formats.
|
|
179
|
-
*/
|
|
180
144
|
private parseDurationMs(duration: string): number {
|
|
181
|
-
// Shorthand: "24h", "30m", "7d", "3600s"
|
|
182
145
|
const shorthand = /^(\d+(?:\.\d+)?)(s|m|h|d)$/i.exec(duration);
|
|
183
146
|
if (shorthand) {
|
|
184
147
|
const value = parseFloat(shorthand[1]!);
|
|
@@ -191,7 +154,6 @@ export class NodeSuspensionHandler {
|
|
|
191
154
|
};
|
|
192
155
|
return value * multipliers[unit]!;
|
|
193
156
|
}
|
|
194
|
-
// ISO 8601 duration subset: PTnHnMnS (days handled via P1D)
|
|
195
157
|
const iso = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(duration);
|
|
196
158
|
if (iso) {
|
|
197
159
|
const days = parseFloat(iso[1] ?? "0");
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { EngineRunCounters, PersistedRunState, RunHaltReason, RunQueueEntry } from "../types";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Merges common terminal-run fields onto a loaded {@link PersistedRunState} without repeating object literals.
|
|
5
|
-
*/
|
|
6
3
|
export class PersistedRunStateTerminalBuilder {
|
|
7
4
|
mergeTerminal(args: {
|
|
8
5
|
state: PersistedRunState;
|
|
9
6
|
engineCounters: EngineRunCounters;
|
|
10
7
|
status: "completed" | "failed" | "halted";
|
|
11
|
-
/** Populated when `status === "halted"`. */
|
|
12
8
|
reason?: RunHaltReason;
|
|
13
9
|
queue: RunQueueEntry[];
|
|
14
10
|
outputsByNode: PersistedRunState["outputsByNode"];
|
|
15
11
|
nodeSnapshotsByNodeId: NonNullable<PersistedRunState["nodeSnapshotsByNodeId"]>;
|
|
16
|
-
/** When set, persisted on the run root for listings and retention pruning. */
|
|
17
12
|
finishedAtIso?: string;
|
|
18
13
|
}): PersistedRunState {
|
|
19
14
|
return {
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import type { RunId } from "../types";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
|
|
5
|
-
* entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
|
|
6
|
-
* no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
|
|
7
|
-
* overwriting the `"suspended"` run status.
|
|
8
|
-
*
|
|
9
|
-
* The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
|
|
10
|
-
* user-facing error — it is an engine-internal control-flow primitive and should NOT be
|
|
11
|
-
* exported from the public barrel.
|
|
12
|
-
*/
|
|
13
3
|
export class RunSuspendedError extends Error {
|
|
14
4
|
constructor(
|
|
15
5
|
readonly runId: RunId,
|
|
@@ -5,14 +5,21 @@ type BinaryKeepingRunnableNodeConfig = RunnableNodeConfig &
|
|
|
5
5
|
keepBinaries?: boolean;
|
|
6
6
|
}>;
|
|
7
7
|
|
|
8
|
+
type MergeJsonRunnableNodeConfig = RunnableNodeConfig &
|
|
9
|
+
Readonly<{
|
|
10
|
+
mergeJson?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
8
13
|
export type RunnableOutputBehavior = Readonly<{
|
|
9
14
|
keepBinaries: boolean;
|
|
15
|
+
mergeJson: boolean;
|
|
10
16
|
}>;
|
|
11
17
|
|
|
12
18
|
export class RunnableOutputBehaviorResolver {
|
|
13
19
|
resolve(config: RunnableNodeConfig): RunnableOutputBehavior {
|
|
14
20
|
return {
|
|
15
21
|
keepBinaries: this.isKeepBinariesEnabled(config),
|
|
22
|
+
mergeJson: this.isMergeJsonEnabled(config),
|
|
16
23
|
};
|
|
17
24
|
}
|
|
18
25
|
|
|
@@ -20,4 +27,9 @@ export class RunnableOutputBehaviorResolver {
|
|
|
20
27
|
const candidate = config as BinaryKeepingRunnableNodeConfig;
|
|
21
28
|
return candidate.keepBinaries === true;
|
|
22
29
|
}
|
|
30
|
+
|
|
31
|
+
private isMergeJsonEnabled(config: RunnableNodeConfig): boolean {
|
|
32
|
+
const candidate = config as MergeJsonRunnableNodeConfig;
|
|
33
|
+
return candidate.mergeJson === true;
|
|
34
|
+
}
|
|
23
35
|
}
|
|
@@ -11,9 +11,6 @@ import type {
|
|
|
11
11
|
|
|
12
12
|
import { CredentialResolverFactory } from "./CredentialResolverFactory";
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Shared {@link ExecutionContextFactory#create} wiring for workflow runners (base context before node-specific fields).
|
|
16
|
-
*/
|
|
17
14
|
export class WorkflowRunExecutionContextFactory {
|
|
18
15
|
constructor(
|
|
19
16
|
private readonly executionContextFactory: ExecutionContextFactory,
|
package/src/index.ts
CHANGED
|
@@ -84,5 +84,13 @@ export type {
|
|
|
84
84
|
export { IllegalMaterialSourceError } from "./credentials/CredentialMaterialProvider.types";
|
|
85
85
|
export { ManagedCredentialMaterialWriteError } from "./credentials/ManagedCredentialMaterialWriteError";
|
|
86
86
|
export { ManagedMaterialFetchError } from "./credentials/ManagedMaterialFetchError";
|
|
87
|
-
export type {
|
|
88
|
-
|
|
87
|
+
export type {
|
|
88
|
+
IWorkspaceFileStorage,
|
|
89
|
+
IWorkspaceFileRegistrar,
|
|
90
|
+
WorkspaceFileMetadata,
|
|
91
|
+
} from "./contracts/workspaceFileTypes";
|
|
92
|
+
export {
|
|
93
|
+
WorkspaceFileNotFoundError,
|
|
94
|
+
WorkspaceFileStorageToken,
|
|
95
|
+
WorkspaceFileRegistrarToken,
|
|
96
|
+
} from "./contracts/workspaceFileTypes";
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mints fresh {@link AbortController}s. Injected (rather than direct `new`) to honor the
|
|
3
|
-
* codebase's no-direct-construction rule and to give tests a seam for substituting a fake.
|
|
4
|
-
*/
|
|
5
1
|
export class AbortControllerFactory {
|
|
6
2
|
create(): AbortController {
|
|
7
3
|
return new AbortController();
|
|
@@ -98,11 +98,6 @@ export interface EngineFacadeDeps {
|
|
|
98
98
|
nodeExecutionRequestHandler: EngineNodeExecutionRequestHandler;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/**
|
|
102
|
-
* Runtime facade for orchestration, continuation, triggers, and webhook routing.
|
|
103
|
-
* Prefer {@link import("../intents/RunIntentService").RunIntentService} for host/HTTP invocation boundaries.
|
|
104
|
-
* The class token is exported from `@codemation/core/bootstrap` (not the main `@codemation/core` barrel).
|
|
105
|
-
*/
|
|
106
101
|
export class Engine implements NodeActivationContinuation, NodeExecutionRequestHandler {
|
|
107
102
|
constructor(private readonly deps: EngineFacadeDeps) {}
|
|
108
103
|
|
|
@@ -236,10 +231,6 @@ export class Engine implements NodeActivationContinuation, NodeExecutionRequestH
|
|
|
236
231
|
return await this.deps.runContinuationService.waitForWebhookResponse(runId);
|
|
237
232
|
}
|
|
238
233
|
|
|
239
|
-
/**
|
|
240
|
-
* Re-activate a suspended run item with a human decision (HITL).
|
|
241
|
-
* The HTTP resume endpoint calls this; this method exposes the engine primitive.
|
|
242
|
-
*/
|
|
243
234
|
async resumeRun(args: { runId: RunId; taskId: string; resumeContext: ResumeContext }): Promise<RunResult> {
|
|
244
235
|
return await this.deps.runContinuationService.resumeRun(args);
|
|
245
236
|
}
|
|
@@ -15,6 +15,7 @@ import { RunSuspendedError } from "../execution/RunSuspendedError";
|
|
|
15
15
|
import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
|
|
16
16
|
import { NodeRunStateWriterFactory } from "../execution/NodeRunStateWriterFactory";
|
|
17
17
|
import { WorkflowRunExecutionContextFactory } from "../execution/WorkflowRunExecutionContextFactory";
|
|
18
|
+
import { MissingRuntimeParityGuard } from "../workflowSnapshots/MissingRuntimeParityGuard";
|
|
18
19
|
|
|
19
20
|
type PersistedWorkflowLike = Readonly<{
|
|
20
21
|
workflowId: PersistedRunState["workflowId"];
|
|
@@ -32,6 +33,7 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
|
|
|
32
33
|
private readonly nodeExecutor: NodeExecutor,
|
|
33
34
|
private readonly continuation: NodeActivationContinuation,
|
|
34
35
|
private readonly executionLimitsPolicy: EngineExecutionLimitsPolicy,
|
|
36
|
+
private readonly parityGuard: MissingRuntimeParityGuard,
|
|
35
37
|
) {}
|
|
36
38
|
|
|
37
39
|
async handleNodeExecutionRequest(request: NodeExecutionRequest): Promise<void> {
|
|
@@ -86,7 +88,6 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
|
|
|
86
88
|
const portKeys = Object.keys(inputsByPort);
|
|
87
89
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? ("single" as const) : ("multi" as const);
|
|
88
90
|
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
89
|
-
// Splice resumeContext from pendingResume if this activation is a HITL resume.
|
|
90
91
|
const pendingResume = state.pendingResume;
|
|
91
92
|
const resumeContext: ResumeContext | undefined =
|
|
92
93
|
pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId
|
|
@@ -127,7 +128,6 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
|
|
|
127
128
|
input: inputsByPort.in ?? request.input ?? [],
|
|
128
129
|
});
|
|
129
130
|
|
|
130
|
-
// Clear pendingResume from state now that we have consumed it.
|
|
131
131
|
if (resumeContext != null) {
|
|
132
132
|
const clearedState = await this.workflowExecutionRepository.load(request.runId);
|
|
133
133
|
if (clearedState?.pendingResume?.activationId === request.activationId) {
|
|
@@ -144,11 +144,10 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
|
|
|
144
144
|
|
|
145
145
|
let outputs;
|
|
146
146
|
try {
|
|
147
|
+
this.parityGuard.assertNone(workflow, [request.nodeId]);
|
|
147
148
|
outputs = await this.nodeExecutor.execute(activationRequest);
|
|
148
149
|
} catch (error) {
|
|
149
150
|
if (error instanceof RunSuspendedError) {
|
|
150
|
-
// The node threw SuspensionRequest; NodeSuspensionHandler already persisted the
|
|
151
|
-
// suspension entry and flipped status to "suspended". Nothing more to do here.
|
|
152
151
|
return;
|
|
153
152
|
}
|
|
154
153
|
await this.resumeAfterExecutionError(activationRequest, this.asError(error));
|
|
@@ -46,6 +46,7 @@ import { NodeActivationRequestComposer } from "../execution/NodeActivationReques
|
|
|
46
46
|
import { PersistedRunStateTerminalBuilder } from "../execution/PersistedRunStateTerminalBuilder";
|
|
47
47
|
import { RunStateSemantics } from "../execution/RunStateSemantics";
|
|
48
48
|
import { WorkflowRunExecutionContextFactory } from "../execution/WorkflowRunExecutionContextFactory";
|
|
49
|
+
import { MissingRuntimeParityGuard } from "../workflowSnapshots/MissingRuntimeParityGuard";
|
|
49
50
|
|
|
50
51
|
export class RunContinuationService {
|
|
51
52
|
constructor(
|
|
@@ -66,6 +67,7 @@ export class RunContinuationService {
|
|
|
66
67
|
private readonly policyErrorServices: WorkflowPolicyErrorServices,
|
|
67
68
|
private readonly terminalPersistence: RunTerminalPersistenceCoordinator,
|
|
68
69
|
private readonly executionLimitsPolicy: EngineExecutionLimitsPolicy,
|
|
70
|
+
private readonly parityGuard: MissingRuntimeParityGuard,
|
|
69
71
|
) {}
|
|
70
72
|
|
|
71
73
|
async markNodeRunning(args: {
|
|
@@ -151,9 +153,6 @@ export class RunContinuationService {
|
|
|
151
153
|
data.setOutputs(args.nodeId, args.outputs);
|
|
152
154
|
const completedAt = new Date().toISOString();
|
|
153
155
|
|
|
154
|
-
// Resolve HITL status from the node's decision output.
|
|
155
|
-
// Only fires when the output carries `item.json.decision.status` written by a
|
|
156
|
-
// defineHumanApprovalNode-based node. Non-HITL nodes never have this field.
|
|
157
156
|
const hitlResolution = this.resolveHitlStatus(args.outputs);
|
|
158
157
|
|
|
159
158
|
const completedSnapshot = this.semantics.createFinishedSnapshot({
|
|
@@ -170,7 +169,6 @@ export class RunContinuationService {
|
|
|
170
169
|
hitlStatus: hitlResolution?.nodeStatus,
|
|
171
170
|
});
|
|
172
171
|
|
|
173
|
-
// Halt the run for HITL rejection / timeout outcomes.
|
|
174
172
|
if (hitlResolution?.halt) {
|
|
175
173
|
const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
176
174
|
state,
|
|
@@ -355,6 +353,7 @@ export class RunContinuationService {
|
|
|
355
353
|
});
|
|
356
354
|
|
|
357
355
|
try {
|
|
356
|
+
this.parityGuard.assertNone(wf, [next.nodeId]);
|
|
358
357
|
const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
359
358
|
runId: state.runId,
|
|
360
359
|
workflowId: state.workflowId,
|
|
@@ -461,9 +460,7 @@ export class RunContinuationService {
|
|
|
461
460
|
nodeId: args.nodeId,
|
|
462
461
|
outputs: recovered,
|
|
463
462
|
});
|
|
464
|
-
} catch {
|
|
465
|
-
// fall through to workflow-level failure
|
|
466
|
-
}
|
|
463
|
+
} catch {}
|
|
467
464
|
}
|
|
468
465
|
}
|
|
469
466
|
|
|
@@ -591,20 +588,6 @@ export class RunContinuationService {
|
|
|
591
588
|
return await this.waiters.waitForWebhookResponse(runId);
|
|
592
589
|
}
|
|
593
590
|
|
|
594
|
-
/**
|
|
595
|
-
* Re-activate a previously suspended run item with a human decision.
|
|
596
|
-
*
|
|
597
|
-
* Called by the HITL resume endpoint. This method:
|
|
598
|
-
* 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
|
|
599
|
-
* 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
|
|
600
|
-
* enqueue flips it to `"pending"`.
|
|
601
|
-
* 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
|
|
602
|
-
* splice `resumeContext` into the node's execution context.
|
|
603
|
-
* 4. Reconstructs the original input from `outputsByNode` of the upstream node and
|
|
604
|
-
* enqueues a new activation via `activationEnqueueService`.
|
|
605
|
-
*
|
|
606
|
-
* @throws if the run is not found, not suspended, or the `taskId` is unknown.
|
|
607
|
-
*/
|
|
608
591
|
async resumeRun(args: { runId: RunId; taskId: string; resumeContext: ResumeContext }): Promise<RunResult> {
|
|
609
592
|
const state = await this.workflowExecutionRepository.load(args.runId);
|
|
610
593
|
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
@@ -626,8 +609,6 @@ export class RunContinuationService {
|
|
|
626
609
|
throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
|
|
627
610
|
}
|
|
628
611
|
|
|
629
|
-
// Reconstruct input: find the parent node that fed this node and use its main output.
|
|
630
|
-
// The single-item input corresponds to `itemIndex` in the original activation batch.
|
|
631
612
|
const data = this.runDataFactory.create(state.outputsByNode);
|
|
632
613
|
const limits = this.resolveEngineLimitsFromState(state);
|
|
633
614
|
const base = this.runExecutionContextFactory.create({
|
|
@@ -644,13 +625,10 @@ export class RunContinuationService {
|
|
|
644
625
|
testContext: state.executionOptions?.testContext,
|
|
645
626
|
});
|
|
646
627
|
|
|
647
|
-
// Find the original input items for this node from upstream outputs.
|
|
648
|
-
// Use the workflow edges to resolve the parent node. If no parent found, fall back to empty.
|
|
649
628
|
const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
|
|
650
629
|
const parentNodeId = parentEdges[0]?.from.nodeId;
|
|
651
630
|
const parentOutputPort = parentEdges[0]?.from.output ?? "main";
|
|
652
631
|
const allParentItems = parentNodeId ? (data.getOutputItems(parentNodeId, parentOutputPort) ?? []) : [];
|
|
653
|
-
// Each suspended item gets its own resume; pass the single item at itemIndex.
|
|
654
632
|
const resumeInput =
|
|
655
633
|
allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]!] : allParentItems;
|
|
656
634
|
|
|
@@ -663,10 +641,6 @@ export class RunContinuationService {
|
|
|
663
641
|
|
|
664
642
|
const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
|
|
665
643
|
|
|
666
|
-
// Thread resumeContext into the execution context so the inline scheduler path
|
|
667
|
-
// (InlineDrivingScheduler) delivers it directly to the node's ctx.resumeContext.
|
|
668
|
-
// On the worker path (BullMQ), NodeExecutionRequestHandlerService re-derives it
|
|
669
|
-
// from state.pendingResume — passing it here is additive and harmless.
|
|
670
644
|
const baseWithResume = { ...base, resumeContext: args.resumeContext };
|
|
671
645
|
|
|
672
646
|
const batchId = `resume_${newActivationId}`;
|
|
@@ -683,6 +657,8 @@ export class RunContinuationService {
|
|
|
683
657
|
input: resumeInput,
|
|
684
658
|
});
|
|
685
659
|
|
|
660
|
+
this.parityGuard.assertNone(wf, [suspensionEntry.nodeId]);
|
|
661
|
+
|
|
686
662
|
const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
687
663
|
runId: state.runId,
|
|
688
664
|
workflowId: state.workflowId,
|
|
@@ -944,6 +920,7 @@ export class RunContinuationService {
|
|
|
944
920
|
});
|
|
945
921
|
|
|
946
922
|
try {
|
|
923
|
+
this.parityGuard.assertNone(args.workflow, [next.nodeId]);
|
|
947
924
|
const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
948
925
|
runId: args.state.runId,
|
|
949
926
|
workflowId: args.state.workflowId,
|
|
@@ -1084,10 +1061,6 @@ export class RunContinuationService {
|
|
|
1084
1061
|
};
|
|
1085
1062
|
}
|
|
1086
1063
|
|
|
1087
|
-
/**
|
|
1088
|
-
* Next activation could not be enqueued (e.g. input contract / mapping failed in the preparer).
|
|
1089
|
-
* Marks the target node failed and terminates the run.
|
|
1090
|
-
*/
|
|
1091
1064
|
private async terminateRunAfterActivationEnqueueRejected(
|
|
1092
1065
|
args: Readonly<{
|
|
1093
1066
|
wf: WorkflowDefinition;
|
|
@@ -1166,11 +1139,6 @@ export class RunContinuationService {
|
|
|
1166
1139
|
return result;
|
|
1167
1140
|
}
|
|
1168
1141
|
|
|
1169
|
-
/**
|
|
1170
|
-
* Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
|
|
1171
|
-
* Returns the first-class HITL node status and halt classification, or `undefined`
|
|
1172
|
-
* when the node is not a HITL approval node.
|
|
1173
|
-
*/
|
|
1174
1142
|
private resolveHitlStatus(outputs: NodeOutputs):
|
|
1175
1143
|
| {
|
|
1176
1144
|
nodeStatus: Extract<
|
|
@@ -277,13 +277,6 @@ export class RunStartService {
|
|
|
277
277
|
currentState: RunCurrentState | undefined,
|
|
278
278
|
mutableState: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["mutableState"],
|
|
279
279
|
): RunCurrentState {
|
|
280
|
-
// Each `ConnectionInvocationRecord` represents a single, auditable LLM/tool call
|
|
281
|
-
// that must belong to exactly one run. Reruns start from a fresh invocation
|
|
282
|
-
// ledger so the new run only records what it actually invokes. The prior run's
|
|
283
|
-
// invocations remain queryable on that run's persisted state (their true owner).
|
|
284
|
-
// Carrying them over here would write duplicate `ExecutionInstance` rows whose
|
|
285
|
-
// primary key (`invocationId`) already exists under the previous run, causing a
|
|
286
|
-
// `Unique constraint failed on the fields: (instance_id)` violation on first save.
|
|
287
280
|
return {
|
|
288
281
|
outputsByNode: { ...(currentState?.outputsByNode ?? {}) },
|
|
289
282
|
nodeSnapshotsByNodeId: { ...(currentState?.nodeSnapshotsByNodeId ?? {}) },
|