@hexabot-ai/agentic 3.0.1-alpha.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/LICENSE.md +120 -0
- package/README.md +231 -0
- package/dist/cjs/action/abstract-action.js +141 -0
- package/dist/cjs/action/action.js +47 -0
- package/dist/cjs/action/action.types.js +7 -0
- package/dist/cjs/bindings/base-binding.js +278 -0
- package/dist/cjs/context.js +79 -0
- package/dist/cjs/dsl.types.js +318 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/runner-runtime-control.js +343 -0
- package/dist/cjs/step-executors/conditional-executor.js +63 -0
- package/dist/cjs/step-executors/loop-executor.js +187 -0
- package/dist/cjs/step-executors/parallel-executor.js +51 -0
- package/dist/cjs/step-executors/skip-helpers.js +29 -0
- package/dist/cjs/step-executors/task-executor.js +155 -0
- package/dist/cjs/step-executors/types.js +7 -0
- package/dist/cjs/suspension-rebuilder.js +301 -0
- package/dist/cjs/utils/deferred.js +17 -0
- package/dist/cjs/utils/naming.js +48 -0
- package/dist/cjs/utils/timeout.js +42 -0
- package/dist/cjs/utils/workflow-definition.js +102 -0
- package/dist/cjs/workflow-compiler.js +212 -0
- package/dist/cjs/workflow-event-emitter.js +40 -0
- package/dist/cjs/workflow-runner.js +448 -0
- package/dist/cjs/workflow-types.js +7 -0
- package/dist/cjs/workflow-values.js +99 -0
- package/dist/cjs/workflow.js +243 -0
- package/dist/esm/action/abstract-action.js +137 -0
- package/dist/esm/action/action.js +44 -0
- package/dist/esm/action/action.types.js +6 -0
- package/dist/esm/bindings/base-binding.js +273 -0
- package/dist/esm/context.js +75 -0
- package/dist/esm/dsl.types.js +309 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/runner-runtime-control.js +338 -0
- package/dist/esm/step-executors/conditional-executor.js +60 -0
- package/dist/esm/step-executors/loop-executor.js +182 -0
- package/dist/esm/step-executors/parallel-executor.js +48 -0
- package/dist/esm/step-executors/skip-helpers.js +25 -0
- package/dist/esm/step-executors/task-executor.js +152 -0
- package/dist/esm/step-executors/types.js +6 -0
- package/dist/esm/suspension-rebuilder.js +296 -0
- package/dist/esm/utils/deferred.js +14 -0
- package/dist/esm/utils/naming.js +42 -0
- package/dist/esm/utils/timeout.js +37 -0
- package/dist/esm/utils/workflow-definition.js +98 -0
- package/dist/esm/workflow-compiler.js +208 -0
- package/dist/esm/workflow-event-emitter.js +36 -0
- package/dist/esm/workflow-runner.js +444 -0
- package/dist/esm/workflow-types.js +6 -0
- package/dist/esm/workflow-values.js +89 -0
- package/dist/esm/workflow.js +236 -0
- package/dist/types/action/abstract-action.d.ts +54 -0
- package/dist/types/action/abstract-action.d.ts.map +1 -0
- package/dist/types/action/action.d.ts +23 -0
- package/dist/types/action/action.d.ts.map +1 -0
- package/dist/types/action/action.types.d.ts +55 -0
- package/dist/types/action/action.types.d.ts.map +1 -0
- package/dist/types/bindings/base-binding.d.ts +52 -0
- package/dist/types/bindings/base-binding.d.ts.map +1 -0
- package/dist/types/context.d.ts +105 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/dsl.types.d.ts +220 -0
- package/dist/types/dsl.types.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/runner-runtime-control.d.ts +64 -0
- package/dist/types/runner-runtime-control.d.ts.map +1 -0
- package/dist/types/step-executors/conditional-executor.d.ts +13 -0
- package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
- package/dist/types/step-executors/loop-executor.d.ts +33 -0
- package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
- package/dist/types/step-executors/parallel-executor.d.ts +14 -0
- package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
- package/dist/types/step-executors/skip-helpers.d.ts +12 -0
- package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
- package/dist/types/step-executors/task-executor.d.ts +13 -0
- package/dist/types/step-executors/task-executor.d.ts.map +1 -0
- package/dist/types/step-executors/types.d.ts +24 -0
- package/dist/types/step-executors/types.d.ts.map +1 -0
- package/dist/types/suspension-rebuilder.d.ts +67 -0
- package/dist/types/suspension-rebuilder.d.ts.map +1 -0
- package/dist/types/utils/deferred.d.ts +7 -0
- package/dist/types/utils/deferred.d.ts.map +1 -0
- package/dist/types/utils/naming.d.ts +24 -0
- package/dist/types/utils/naming.d.ts.map +1 -0
- package/dist/types/utils/timeout.d.ts +17 -0
- package/dist/types/utils/timeout.d.ts.map +1 -0
- package/dist/types/utils/workflow-definition.d.ts +3 -0
- package/dist/types/utils/workflow-definition.d.ts.map +1 -0
- package/dist/types/workflow-compiler.d.ts +12 -0
- package/dist/types/workflow-compiler.d.ts.map +1 -0
- package/dist/types/workflow-event-emitter.d.ts +70 -0
- package/dist/types/workflow-event-emitter.d.ts.map +1 -0
- package/dist/types/workflow-runner.d.ts +179 -0
- package/dist/types/workflow-runner.d.ts.map +1 -0
- package/dist/types/workflow-types.d.ts +195 -0
- package/dist/types/workflow-types.d.ts.map +1 -0
- package/dist/types/workflow-values.d.ts +34 -0
- package/dist/types/workflow-values.d.ts.map +1 -0
- package/dist/types/workflow.d.ts +75 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
- package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
- package/examples/defs-bindings-agent/actions/index.ts +15 -0
- package/examples/defs-bindings-agent/bindings.ts +20 -0
- package/examples/defs-bindings-agent/context.ts +32 -0
- package/examples/defs-bindings-agent/workflow.ts +67 -0
- package/examples/defs-bindings-agent/workflow.yml +28 -0
- package/examples/full/actions/await-user-input.ts +46 -0
- package/examples/full/actions/call-llm.ts +133 -0
- package/examples/full/actions/create-ticket.ts +46 -0
- package/examples/full/actions/decision-router.ts +83 -0
- package/examples/full/actions/get-calendar-events.ts +55 -0
- package/examples/full/actions/get-user-profile.ts +72 -0
- package/examples/full/actions/index.ts +33 -0
- package/examples/full/actions/query-memory.ts +45 -0
- package/examples/full/actions/search-web.ts +53 -0
- package/examples/full/actions/send-email.ts +50 -0
- package/examples/full/context.ts +41 -0
- package/examples/full/workflow.ts +75 -0
- package/examples/full/workflow.yml +359 -0
- package/examples/loop/actions/await-reply.ts +47 -0
- package/examples/loop/actions/index.ts +19 -0
- package/examples/loop/actions/send-text-message.ts +40 -0
- package/examples/loop/context.ts +32 -0
- package/examples/loop/workflow.ts +66 -0
- package/examples/loop/workflow.yml +66 -0
- package/examples/suspend-resume/actions/format-reply.ts +43 -0
- package/examples/suspend-resume/actions/index.ts +13 -0
- package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
- package/examples/suspend-resume/context.ts +32 -0
- package/examples/suspend-resume/workflow.ts +79 -0
- package/examples/suspend-resume/workflow.yml +29 -0
- package/package.json +60 -0
- package/src/__tests__/parser.test.ts +95 -0
- package/src/__tests__/suspension-rebuilder.test.ts +361 -0
- package/src/__tests__/test-helpers.ts +36 -0
- package/src/__tests__/validation.test.ts +526 -0
- package/src/__tests__/workflow-compiler.test.ts +715 -0
- package/src/__tests__/workflow-definition-path.test.ts +381 -0
- package/src/__tests__/workflow-event-emitter.test.ts +50 -0
- package/src/__tests__/workflow-runner.test.ts +1397 -0
- package/src/__tests__/workflow-values.test.ts +132 -0
- package/src/__tests__/workflow.test.ts +320 -0
- package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
- package/src/action/__tests__/action.test.ts +473 -0
- package/src/action/abstract-action.ts +215 -0
- package/src/action/action.ts +83 -0
- package/src/action/action.types.ts +93 -0
- package/src/bindings/base-binding.ts +508 -0
- package/src/context.ts +179 -0
- package/src/dsl.types.ts +473 -0
- package/src/index.ts +114 -0
- package/src/runner-runtime-control.ts +544 -0
- package/src/step-executors/conditional-executor.test.ts +192 -0
- package/src/step-executors/conditional-executor.ts +83 -0
- package/src/step-executors/loop-executor.test.ts +303 -0
- package/src/step-executors/loop-executor.ts +299 -0
- package/src/step-executors/parallel-executor.test.ts +182 -0
- package/src/step-executors/parallel-executor.ts +77 -0
- package/src/step-executors/skip-helpers.ts +68 -0
- package/src/step-executors/task-executor.test.ts +257 -0
- package/src/step-executors/task-executor.ts +248 -0
- package/src/step-executors/types.ts +70 -0
- package/src/suspension-rebuilder.ts +504 -0
- package/src/utils/deferred.ts +23 -0
- package/src/utils/naming.ts +55 -0
- package/src/utils/timeout.ts +48 -0
- package/src/utils/workflow-definition.ts +173 -0
- package/src/workflow-compiler.ts +317 -0
- package/src/workflow-event-emitter.ts +86 -0
- package/src/workflow-runner.ts +603 -0
- package/src/workflow-types.ts +207 -0
- package/src/workflow-values.ts +159 -0
- package/src/workflow.ts +387 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { AbstractAction } from './action/abstract-action';
|
|
8
|
+
|
|
9
|
+
export { defineAction } from './action/action';
|
|
10
|
+
|
|
11
|
+
export type { DefineActionParams } from './action/action';
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
Action,
|
|
15
|
+
ActionExecutionArgs,
|
|
16
|
+
ActionExecutionOutcome,
|
|
17
|
+
ActionMetadata,
|
|
18
|
+
Actions,
|
|
19
|
+
InferActionArgs,
|
|
20
|
+
InferActionBindings,
|
|
21
|
+
InferActionContext,
|
|
22
|
+
InferActionInput,
|
|
23
|
+
InferActionOutput,
|
|
24
|
+
InferActionSettings,
|
|
25
|
+
SuspensionNotice,
|
|
26
|
+
} from './action/action.types';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
BaseWorkflowContext,
|
|
30
|
+
type ActionSnapshot,
|
|
31
|
+
type ActionStatus,
|
|
32
|
+
type StepExecutionRecord,
|
|
33
|
+
type SuspensionOptions,
|
|
34
|
+
type WorkflowRunStatus,
|
|
35
|
+
type WorkflowRuntimeControl,
|
|
36
|
+
type WorkflowSnapshot,
|
|
37
|
+
} from './context';
|
|
38
|
+
|
|
39
|
+
export * from './dsl.types';
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
BindingKindDescriptor,
|
|
43
|
+
BindingKindSchemas,
|
|
44
|
+
InferMountedBindingValue,
|
|
45
|
+
InferWorkflowBindings,
|
|
46
|
+
} from './bindings/base-binding';
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
compileWorkflow,
|
|
50
|
+
Workflow as Workflow,
|
|
51
|
+
WorkflowEventEmitter,
|
|
52
|
+
WorkflowRunner,
|
|
53
|
+
type FlowStepPath,
|
|
54
|
+
type WorkflowCompileOptions,
|
|
55
|
+
type WorkflowResumeResult,
|
|
56
|
+
type WorkflowRunOptions,
|
|
57
|
+
type WorkflowStartResult,
|
|
58
|
+
} from './workflow';
|
|
59
|
+
|
|
60
|
+
export { StepType } from './workflow-event-emitter';
|
|
61
|
+
|
|
62
|
+
export type {
|
|
63
|
+
StepInfo,
|
|
64
|
+
WorkflowEventEmitterLike,
|
|
65
|
+
WorkflowEventMap,
|
|
66
|
+
} from './workflow-event-emitter';
|
|
67
|
+
|
|
68
|
+
export type {
|
|
69
|
+
BaseStep as CompiledBaseStep,
|
|
70
|
+
ConditionalBranch as CompiledConditionalBranch,
|
|
71
|
+
ConditionalStep as CompiledConditionalStep,
|
|
72
|
+
LoopStep as CompiledLoopStep,
|
|
73
|
+
CompiledMapping,
|
|
74
|
+
ParallelStep as CompiledParallelStep,
|
|
75
|
+
CompiledStep,
|
|
76
|
+
CompiledTask,
|
|
77
|
+
CompiledTaskBindings,
|
|
78
|
+
TaskStep as CompiledTaskStep,
|
|
79
|
+
CompiledValue,
|
|
80
|
+
CompiledWorkflow,
|
|
81
|
+
EvaluationScope,
|
|
82
|
+
ExecutionState,
|
|
83
|
+
PersistedSuspension,
|
|
84
|
+
RunnerResumeArgs,
|
|
85
|
+
RunnerStartArgs,
|
|
86
|
+
Suspension,
|
|
87
|
+
ResumeResult as WorkflowResumeOutcome,
|
|
88
|
+
StartResult as WorkflowStartOutcome,
|
|
89
|
+
} from './workflow-types';
|
|
90
|
+
|
|
91
|
+
export { NonDeterministicWorkflowError } from './runner-runtime-control';
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
compileValue,
|
|
95
|
+
evaluateMapping,
|
|
96
|
+
evaluateValue,
|
|
97
|
+
mergeSettings,
|
|
98
|
+
type CompileValueOptions,
|
|
99
|
+
type JsonataFunctionConfig,
|
|
100
|
+
type JsonataFunctionImplementation,
|
|
101
|
+
type JsonataFunctionRegistry,
|
|
102
|
+
} from './workflow-values';
|
|
103
|
+
|
|
104
|
+
export { createDeferred } from './utils/deferred';
|
|
105
|
+
|
|
106
|
+
export type { Deferred } from './utils/deferred';
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
assertSnakeCaseName,
|
|
110
|
+
isSnakeCaseName,
|
|
111
|
+
toSnakeCase,
|
|
112
|
+
} from './utils/naming';
|
|
113
|
+
|
|
114
|
+
export { sleep, withTimeout } from './utils/timeout';
|
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
SuspensionOptions,
|
|
9
|
+
WorkflowRunStatus,
|
|
10
|
+
WorkflowRuntimeControl,
|
|
11
|
+
} from './context';
|
|
12
|
+
import type { Deferred } from './utils/deferred';
|
|
13
|
+
import { createDeferred } from './utils/deferred';
|
|
14
|
+
import type { WorkflowRunner } from './workflow-runner';
|
|
15
|
+
|
|
16
|
+
const INDEX_KEY_PREFIX = 'index:';
|
|
17
|
+
const USER_KEY_PREFIX = 'key:';
|
|
18
|
+
|
|
19
|
+
type ReplayExpectation = {
|
|
20
|
+
suspendIndex?: number;
|
|
21
|
+
suspendKey: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
matched: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type StepExecutionState = {
|
|
27
|
+
stepId: string;
|
|
28
|
+
stepExecId: string;
|
|
29
|
+
suspendCursor: number;
|
|
30
|
+
awaitResults: Map<string, unknown>;
|
|
31
|
+
replayExpectation?: ReplayExpectation;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type StoredReplaySeed = {
|
|
35
|
+
stepExecId: string;
|
|
36
|
+
awaitResults: Record<string, unknown>;
|
|
37
|
+
activeSuspension?: {
|
|
38
|
+
suspendIndex?: number;
|
|
39
|
+
suspendKey?: string;
|
|
40
|
+
reason?: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type RuntimeSuspensionRequest = {
|
|
45
|
+
stepId: string;
|
|
46
|
+
stepExecId: string;
|
|
47
|
+
suspendIndex: number;
|
|
48
|
+
suspendKey: string;
|
|
49
|
+
reason?: string;
|
|
50
|
+
data?: unknown;
|
|
51
|
+
awaitResults: Record<string, unknown>;
|
|
52
|
+
resume: Deferred<unknown>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type RuntimeStepReplaySeed = {
|
|
56
|
+
stepId: string;
|
|
57
|
+
stepExecId: string;
|
|
58
|
+
awaitResults?: Record<string, unknown>;
|
|
59
|
+
activeSuspension?: {
|
|
60
|
+
suspendIndex?: number;
|
|
61
|
+
suspendKey?: string;
|
|
62
|
+
reason?: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type RuntimeResolvedSuspension = {
|
|
67
|
+
stepId: string;
|
|
68
|
+
resumeData: unknown;
|
|
69
|
+
stepExecId?: string;
|
|
70
|
+
suspendIndex?: number;
|
|
71
|
+
suspendKey?: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export class NonDeterministicWorkflowError extends Error {
|
|
75
|
+
constructor(message: string) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = 'NonDeterministicWorkflowError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Minimal wrapper that exposes runner controls to actions via the context. */
|
|
82
|
+
export class RunnerRuntimeControl implements WorkflowRuntimeControl {
|
|
83
|
+
private readonly runner: WorkflowRunner;
|
|
84
|
+
|
|
85
|
+
private readonly pendingSuspensions = new Map<
|
|
86
|
+
string,
|
|
87
|
+
RuntimeSuspensionRequest[]
|
|
88
|
+
>();
|
|
89
|
+
|
|
90
|
+
private readonly suspensionWaiters = new Map<
|
|
91
|
+
string,
|
|
92
|
+
Array<(request: RuntimeSuspensionRequest) => void>
|
|
93
|
+
>();
|
|
94
|
+
|
|
95
|
+
private readonly primedResumeData = new Map<string, unknown[]>();
|
|
96
|
+
|
|
97
|
+
private readonly stepAttempts = new Map<string, number>();
|
|
98
|
+
|
|
99
|
+
private readonly activeStepExecutions = new Map<string, StepExecutionState>();
|
|
100
|
+
|
|
101
|
+
private readonly replaySeeds = new Map<string, StoredReplaySeed>();
|
|
102
|
+
|
|
103
|
+
constructor(runner: WorkflowRunner) {
|
|
104
|
+
this.runner = runner;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get status(): WorkflowRunStatus {
|
|
108
|
+
return this.runner.getStatus();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get resumeData(): unknown {
|
|
112
|
+
return this.runner.getLastResumeData();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
suspend<T = unknown>(options?: SuspensionOptions): Promise<T> {
|
|
116
|
+
const currentStep = this.runner.getCurrentStep();
|
|
117
|
+
if (!currentStep) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'workflow.suspend() can only be called while a workflow step is running.',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const execution = this.ensureStepExecution(currentStep.id);
|
|
124
|
+
|
|
125
|
+
execution.suspendCursor += 1;
|
|
126
|
+
const suspendIndex = execution.suspendCursor;
|
|
127
|
+
const suspendKey = buildSuspendKey(suspendIndex, options?.key);
|
|
128
|
+
|
|
129
|
+
this.assertReplayExpectation(execution, {
|
|
130
|
+
suspendIndex,
|
|
131
|
+
suspendKey,
|
|
132
|
+
reason: options?.reason,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (execution.awaitResults.has(suspendKey)) {
|
|
136
|
+
return Promise.resolve(execution.awaitResults.get(suspendKey) as T);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const primed = this.dequeuePrimedResumeData(currentStep.id);
|
|
140
|
+
if (primed.found) {
|
|
141
|
+
return Promise.resolve(primed.value as T);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const request: RuntimeSuspensionRequest = {
|
|
145
|
+
stepId: currentStep.id,
|
|
146
|
+
stepExecId: execution.stepExecId,
|
|
147
|
+
suspendIndex,
|
|
148
|
+
suspendKey,
|
|
149
|
+
reason: options?.reason,
|
|
150
|
+
data: options?.data,
|
|
151
|
+
awaitResults: this.serializeAwaitResults(execution.awaitResults),
|
|
152
|
+
resume: createDeferred<unknown>(),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
this.enqueueSuspension(request);
|
|
156
|
+
|
|
157
|
+
return request.resume.promise as Promise<T>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
resume(data?: unknown): void {
|
|
161
|
+
void this.runner.resume({ resumeData: data });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
waitForStepSuspension(stepId: string): Promise<RuntimeSuspensionRequest> {
|
|
165
|
+
const queued = this.dequeueStepSuspension(stepId);
|
|
166
|
+
if (queued) {
|
|
167
|
+
return Promise.resolve(queued);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
const waiters = this.suspensionWaiters.get(stepId) ?? [];
|
|
172
|
+
|
|
173
|
+
waiters.push(resolve);
|
|
174
|
+
this.suspensionWaiters.set(stepId, waiters);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
beginStepExecution(stepId: string): string {
|
|
179
|
+
const existing = this.activeStepExecutions.get(stepId);
|
|
180
|
+
if (existing) {
|
|
181
|
+
return existing.stepExecId;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const seeded = this.replaySeeds.get(stepId);
|
|
185
|
+
if (seeded) {
|
|
186
|
+
const execution: StepExecutionState = {
|
|
187
|
+
stepId,
|
|
188
|
+
stepExecId: seeded.stepExecId,
|
|
189
|
+
suspendCursor: 0,
|
|
190
|
+
awaitResults: this.normalizeAwaitResults(seeded.awaitResults),
|
|
191
|
+
replayExpectation: seeded.activeSuspension
|
|
192
|
+
? {
|
|
193
|
+
suspendIndex: seeded.activeSuspension.suspendIndex,
|
|
194
|
+
suspendKey:
|
|
195
|
+
normalizeSuspendKey(
|
|
196
|
+
seeded.activeSuspension.suspendIndex,
|
|
197
|
+
seeded.activeSuspension.suspendKey,
|
|
198
|
+
) ?? buildSuspendKey(seeded.activeSuspension.suspendIndex ?? 1),
|
|
199
|
+
reason: seeded.activeSuspension.reason,
|
|
200
|
+
matched: false,
|
|
201
|
+
}
|
|
202
|
+
: undefined,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
this.activeStepExecutions.set(stepId, execution);
|
|
206
|
+
this.replaySeeds.delete(stepId);
|
|
207
|
+
this.bumpStepAttemptCounter(stepId, execution.stepExecId);
|
|
208
|
+
|
|
209
|
+
return execution.stepExecId;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const attempt = (this.stepAttempts.get(stepId) ?? 0) + 1;
|
|
213
|
+
const stepExecId = `${stepId}#${attempt}`;
|
|
214
|
+
this.stepAttempts.set(stepId, attempt);
|
|
215
|
+
this.activeStepExecutions.set(stepId, {
|
|
216
|
+
stepId,
|
|
217
|
+
stepExecId,
|
|
218
|
+
suspendCursor: 0,
|
|
219
|
+
awaitResults: new Map<string, unknown>(),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return stepExecId;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
prepareStepReplay(seed: RuntimeStepReplaySeed): void {
|
|
226
|
+
const normalizedAwaitResults = this.serializeAwaitResults(
|
|
227
|
+
this.normalizeAwaitResults(seed.awaitResults ?? {}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
this.replaySeeds.set(seed.stepId, {
|
|
231
|
+
stepExecId: seed.stepExecId,
|
|
232
|
+
awaitResults: normalizedAwaitResults,
|
|
233
|
+
activeSuspension: seed.activeSuspension
|
|
234
|
+
? {
|
|
235
|
+
suspendIndex: seed.activeSuspension.suspendIndex,
|
|
236
|
+
suspendKey: normalizeSuspendKey(
|
|
237
|
+
seed.activeSuspension.suspendIndex,
|
|
238
|
+
seed.activeSuspension.suspendKey,
|
|
239
|
+
),
|
|
240
|
+
reason: seed.activeSuspension.reason,
|
|
241
|
+
}
|
|
242
|
+
: undefined,
|
|
243
|
+
});
|
|
244
|
+
this.bumpStepAttemptCounter(seed.stepId, seed.stepExecId);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
recordStepSuspendResult(params: RuntimeResolvedSuspension): void {
|
|
248
|
+
const suspendKey = normalizeSuspendKey(
|
|
249
|
+
params.suspendIndex,
|
|
250
|
+
params.suspendKey,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (!suspendKey) {
|
|
254
|
+
this.primeStepResumeData(params.stepId, params.resumeData);
|
|
255
|
+
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const activeExecution = this.activeStepExecutions.get(params.stepId);
|
|
260
|
+
if (
|
|
261
|
+
activeExecution &&
|
|
262
|
+
(!params.stepExecId || params.stepExecId === activeExecution.stepExecId)
|
|
263
|
+
) {
|
|
264
|
+
if (!activeExecution.awaitResults.has(suspendKey)) {
|
|
265
|
+
activeExecution.awaitResults.set(suspendKey, params.resumeData);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const replaySeed = this.replaySeeds.get(params.stepId);
|
|
272
|
+
if (replaySeed) {
|
|
273
|
+
if (params.stepExecId && replaySeed.stepExecId !== params.stepExecId) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
replaySeed.awaitResults[suspendKey] =
|
|
278
|
+
replaySeed.awaitResults[suspendKey] ?? params.resumeData;
|
|
279
|
+
this.replaySeeds.set(params.stepId, replaySeed);
|
|
280
|
+
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const fallbackExecId = params.stepExecId ?? `${params.stepId}#1`;
|
|
285
|
+
this.replaySeeds.set(params.stepId, {
|
|
286
|
+
stepExecId: fallbackExecId,
|
|
287
|
+
awaitResults: { [suspendKey]: params.resumeData },
|
|
288
|
+
});
|
|
289
|
+
this.bumpStepAttemptCounter(params.stepId, fallbackExecId);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
clearStepSuspensions(stepId: string, error?: unknown): void {
|
|
293
|
+
const queued = this.pendingSuspensions.get(stepId);
|
|
294
|
+
if (queued) {
|
|
295
|
+
for (const request of queued) {
|
|
296
|
+
request.resume.reject(
|
|
297
|
+
error ?? new Error(`Suspension for step "${stepId}" was cancelled.`),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const execution = this.activeStepExecutions.get(stepId);
|
|
303
|
+
|
|
304
|
+
this.pendingSuspensions.delete(stepId);
|
|
305
|
+
this.suspensionWaiters.delete(stepId);
|
|
306
|
+
this.primedResumeData.delete(stepId);
|
|
307
|
+
this.activeStepExecutions.delete(stepId);
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
!error &&
|
|
311
|
+
execution?.replayExpectation &&
|
|
312
|
+
!execution.replayExpectation.matched
|
|
313
|
+
) {
|
|
314
|
+
throw new NonDeterministicWorkflowError(
|
|
315
|
+
`Replay for step "${stepId}" did not reach expected suspension ` +
|
|
316
|
+
`"${execution.replayExpectation.suspendKey}".`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
primeStepResumeData(stepId: string, resumeData: unknown): void {
|
|
322
|
+
const queued = this.primedResumeData.get(stepId) ?? [];
|
|
323
|
+
|
|
324
|
+
queued.push(resumeData);
|
|
325
|
+
this.primedResumeData.set(stepId, queued);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
getSnapshot() {
|
|
329
|
+
return this.runner.getSnapshot();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private enqueueSuspension(request: RuntimeSuspensionRequest): void {
|
|
333
|
+
const waiters = this.suspensionWaiters.get(request.stepId);
|
|
334
|
+
if (waiters && waiters.length > 0) {
|
|
335
|
+
const resolve = waiters.shift();
|
|
336
|
+
if (!resolve) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (waiters.length === 0) {
|
|
341
|
+
this.suspensionWaiters.delete(request.stepId);
|
|
342
|
+
} else {
|
|
343
|
+
this.suspensionWaiters.set(request.stepId, waiters);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
resolve(request);
|
|
347
|
+
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const queued = this.pendingSuspensions.get(request.stepId) ?? [];
|
|
352
|
+
|
|
353
|
+
queued.push(request);
|
|
354
|
+
this.pendingSuspensions.set(request.stepId, queued);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private dequeueStepSuspension(
|
|
358
|
+
stepId: string,
|
|
359
|
+
): RuntimeSuspensionRequest | undefined {
|
|
360
|
+
const queued = this.pendingSuspensions.get(stepId);
|
|
361
|
+
if (!queued || queued.length === 0) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const request = queued.shift();
|
|
366
|
+
if (queued.length === 0) {
|
|
367
|
+
this.pendingSuspensions.delete(stepId);
|
|
368
|
+
} else {
|
|
369
|
+
this.pendingSuspensions.set(stepId, queued);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return request;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private dequeuePrimedResumeData(stepId: string): {
|
|
376
|
+
found: boolean;
|
|
377
|
+
value?: unknown;
|
|
378
|
+
} {
|
|
379
|
+
const queued = this.primedResumeData.get(stepId);
|
|
380
|
+
if (!queued || queued.length === 0) {
|
|
381
|
+
return { found: false };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const value = queued.shift();
|
|
385
|
+
if (queued.length === 0) {
|
|
386
|
+
this.primedResumeData.delete(stepId);
|
|
387
|
+
} else {
|
|
388
|
+
this.primedResumeData.set(stepId, queued);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { found: true, value };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private ensureStepExecution(stepId: string): StepExecutionState {
|
|
395
|
+
const existing = this.activeStepExecutions.get(stepId);
|
|
396
|
+
if (existing) {
|
|
397
|
+
return existing;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.beginStepExecution(stepId);
|
|
401
|
+
|
|
402
|
+
const created = this.activeStepExecutions.get(stepId);
|
|
403
|
+
if (!created) {
|
|
404
|
+
throw new Error(`Unable to create runtime state for step "${stepId}".`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return created;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private assertReplayExpectation(
|
|
411
|
+
execution: StepExecutionState,
|
|
412
|
+
encountered: { suspendIndex: number; suspendKey: string; reason?: string },
|
|
413
|
+
): void {
|
|
414
|
+
const expectation = execution.replayExpectation;
|
|
415
|
+
if (!expectation || expectation.matched) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (encountered.suspendKey === expectation.suspendKey) {
|
|
420
|
+
if (
|
|
421
|
+
expectation.reason !== undefined &&
|
|
422
|
+
encountered.reason !== expectation.reason
|
|
423
|
+
) {
|
|
424
|
+
throw new NonDeterministicWorkflowError(
|
|
425
|
+
`Replay for step "${execution.stepId}" reached suspend ` +
|
|
426
|
+
`"${encountered.suspendKey}" with reason "${encountered.reason ?? ''}", ` +
|
|
427
|
+
`expected reason "${expectation.reason}".`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
expectation.matched = true;
|
|
432
|
+
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (execution.awaitResults.has(encountered.suspendKey)) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
throw new NonDeterministicWorkflowError(
|
|
441
|
+
`Replay for step "${execution.stepId}" reached unexpected suspend ` +
|
|
442
|
+
`"${encountered.suspendKey}" before expected ` +
|
|
443
|
+
`"${expectation.suspendKey}".`,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private normalizeAwaitResults(
|
|
448
|
+
awaitResults: Record<string, unknown>,
|
|
449
|
+
): Map<string, unknown> {
|
|
450
|
+
const normalized = new Map<string, unknown>();
|
|
451
|
+
|
|
452
|
+
for (const [rawKey, value] of Object.entries(awaitResults)) {
|
|
453
|
+
const normalizedKey = normalizeStoredSuspendKey(rawKey);
|
|
454
|
+
normalized.set(normalizedKey, value);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return normalized;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private serializeAwaitResults(
|
|
461
|
+
awaitResults: Map<string, unknown>,
|
|
462
|
+
): Record<string, unknown> {
|
|
463
|
+
return Object.fromEntries(awaitResults.entries());
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private bumpStepAttemptCounter(stepId: string, stepExecId: string): void {
|
|
467
|
+
const parsedAttempt = parseStepExecAttempt(stepId, stepExecId);
|
|
468
|
+
if (parsedAttempt === null) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const previous = this.stepAttempts.get(stepId) ?? 0;
|
|
473
|
+
if (parsedAttempt > previous) {
|
|
474
|
+
this.stepAttempts.set(stepId, parsedAttempt);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const buildSuspendKey = (suspendIndex: number, key?: string): string => {
|
|
480
|
+
if (key) {
|
|
481
|
+
return `${USER_KEY_PREFIX}${key}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return `${INDEX_KEY_PREFIX}${suspendIndex}`;
|
|
485
|
+
};
|
|
486
|
+
const normalizeSuspendKey = (
|
|
487
|
+
suspendIndex?: number,
|
|
488
|
+
suspendKey?: string,
|
|
489
|
+
): string | undefined => {
|
|
490
|
+
if (typeof suspendKey === 'string' && suspendKey.length > 0) {
|
|
491
|
+
if (
|
|
492
|
+
suspendKey.startsWith(INDEX_KEY_PREFIX) ||
|
|
493
|
+
suspendKey.startsWith(USER_KEY_PREFIX)
|
|
494
|
+
) {
|
|
495
|
+
return suspendKey;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (/^\d+$/.test(suspendKey)) {
|
|
499
|
+
return `${INDEX_KEY_PREFIX}${suspendKey}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return `${USER_KEY_PREFIX}${suspendKey}`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (
|
|
506
|
+
typeof suspendIndex === 'number' &&
|
|
507
|
+
Number.isInteger(suspendIndex) &&
|
|
508
|
+
suspendIndex > 0
|
|
509
|
+
) {
|
|
510
|
+
return `${INDEX_KEY_PREFIX}${suspendIndex}`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return undefined;
|
|
514
|
+
};
|
|
515
|
+
const normalizeStoredSuspendKey = (rawKey: string): string => {
|
|
516
|
+
if (
|
|
517
|
+
rawKey.startsWith(INDEX_KEY_PREFIX) ||
|
|
518
|
+
rawKey.startsWith(USER_KEY_PREFIX)
|
|
519
|
+
) {
|
|
520
|
+
return rawKey;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (/^\d+$/.test(rawKey)) {
|
|
524
|
+
return `${INDEX_KEY_PREFIX}${rawKey}`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return `${USER_KEY_PREFIX}${rawKey}`;
|
|
528
|
+
};
|
|
529
|
+
const parseStepExecAttempt = (
|
|
530
|
+
stepId: string,
|
|
531
|
+
stepExecId: string,
|
|
532
|
+
): number | null => {
|
|
533
|
+
const prefix = `${stepId}#`;
|
|
534
|
+
if (!stepExecId.startsWith(prefix)) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const numeric = Number.parseInt(stepExecId.slice(prefix.length), 10);
|
|
539
|
+
if (Number.isNaN(numeric) || numeric < 1) {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return numeric;
|
|
544
|
+
};
|