@rigkit/engine 0.2.7 → 0.2.9
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/package.json +1 -1
- package/src/authoring.ts +177 -28
- package/src/authoring.typecheck.ts +27 -0
- package/src/console-intercept.test.ts +121 -0
- package/src/console-intercept.ts +75 -0
- package/src/engine.test.ts +161 -3
- package/src/engine.ts +515 -67
- package/src/index.ts +7 -0
- package/src/state.ts +36 -0
- package/src/types.ts +80 -18
- package/src/version.ts +1 -1
package/src/engine.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { isRigkitConfig, isProviderDefinition, isWorkflowNode } from "./authoring.ts";
|
|
5
|
+
import { runWithStepConsole, type ConsoleLevel, type StepConsoleSink } from "./console-intercept.ts";
|
|
5
6
|
import { loadDotEnv } from "./env-file.ts";
|
|
6
7
|
import { hash } from "./hash.ts";
|
|
7
8
|
import {
|
|
@@ -36,8 +37,10 @@ import type {
|
|
|
36
37
|
ProviderRuntimeMap,
|
|
37
38
|
WorkflowInputFieldDefinition,
|
|
38
39
|
WorkflowDefinition,
|
|
40
|
+
WorkflowCacheScope,
|
|
39
41
|
WorkflowEvent,
|
|
40
42
|
WorkflowLogStream,
|
|
43
|
+
WorkflowNodeKind,
|
|
41
44
|
WorkflowNodeDefinition,
|
|
42
45
|
WorkflowOperationDefinition,
|
|
43
46
|
WorkflowPlan,
|
|
@@ -59,6 +62,7 @@ export type CreateDevMachineEngineOptions = {
|
|
|
59
62
|
providers?: BaseProviderPlugin[];
|
|
60
63
|
providerFactory?: ProviderFactory;
|
|
61
64
|
stateFactory?: StateServiceFactory;
|
|
65
|
+
globalFragmentStateLocator?: GlobalFragmentStateLocator;
|
|
62
66
|
hostStorageDir?: string;
|
|
63
67
|
hostStorageFactory?: ProviderHostStorageFactory;
|
|
64
68
|
interaction?: {
|
|
@@ -69,6 +73,18 @@ export type CreateDevMachineEngineOptions = {
|
|
|
69
73
|
|
|
70
74
|
export type { InteractionPresenter, InteractionPresentationRequest };
|
|
71
75
|
|
|
76
|
+
export type GlobalFragmentStateLocator = (fragment: GlobalFragmentStateLocationInput) =>
|
|
77
|
+
| string
|
|
78
|
+
| { statePath: string };
|
|
79
|
+
|
|
80
|
+
export type GlobalFragmentStateLocationInput = {
|
|
81
|
+
hash: string;
|
|
82
|
+
workflow: string;
|
|
83
|
+
nodePath: string;
|
|
84
|
+
nodeName: string;
|
|
85
|
+
nodeKind: WorkflowNodeKind;
|
|
86
|
+
};
|
|
87
|
+
|
|
72
88
|
export type EngineLoadResult = {
|
|
73
89
|
workflow: LoadedWorkflow;
|
|
74
90
|
workflows: LoadedWorkflow[];
|
|
@@ -85,6 +101,30 @@ export type EngineProjectInfo = {
|
|
|
85
101
|
workflow?: WorkflowSummary;
|
|
86
102
|
};
|
|
87
103
|
|
|
104
|
+
export type EngineCacheScope = WorkflowCacheScope;
|
|
105
|
+
|
|
106
|
+
export type EngineCacheEntry = {
|
|
107
|
+
scope: EngineCacheScope;
|
|
108
|
+
workflow: string;
|
|
109
|
+
nodePath: string;
|
|
110
|
+
nodeName: string;
|
|
111
|
+
nodeKind: string;
|
|
112
|
+
runId: string;
|
|
113
|
+
invalidated: boolean;
|
|
114
|
+
createdAt: string;
|
|
115
|
+
fragmentHash?: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type EngineCacheList = {
|
|
119
|
+
entries: EngineCacheEntry[];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type EngineCacheClearScope = EngineCacheScope | "all";
|
|
123
|
+
|
|
124
|
+
export type EngineCacheClearResult = {
|
|
125
|
+
deleted: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
88
128
|
export type WorkflowSummary = {
|
|
89
129
|
name: string;
|
|
90
130
|
providers: string[];
|
|
@@ -165,6 +205,15 @@ type EvaluationState = {
|
|
|
165
205
|
type EvaluationPreviousTask = {
|
|
166
206
|
name: string;
|
|
167
207
|
path: string;
|
|
208
|
+
cache: EvaluationCacheTarget;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
type EvaluationCacheTarget = {
|
|
212
|
+
scope: WorkflowCacheScope;
|
|
213
|
+
workflow: string;
|
|
214
|
+
nodePath: string;
|
|
215
|
+
state: StateService;
|
|
216
|
+
fragmentHash?: string;
|
|
168
217
|
};
|
|
169
218
|
|
|
170
219
|
type EvaluationResult = EvaluationState & {
|
|
@@ -177,10 +226,14 @@ type EvaluateNodeInput = {
|
|
|
177
226
|
providers: ProviderControllers;
|
|
178
227
|
providerFingerprint: string;
|
|
179
228
|
mode: EvaluationMode;
|
|
229
|
+
cache: EvaluationCacheTarget;
|
|
230
|
+
configStack: JsonObject[];
|
|
180
231
|
state: EvaluationState;
|
|
181
232
|
prefix: string[];
|
|
233
|
+
cachePrefix: string[];
|
|
182
234
|
root: boolean;
|
|
183
235
|
suppressSequenceName?: string;
|
|
236
|
+
suppressCacheSequenceName?: string;
|
|
184
237
|
planNodes: WorkflowPlanNode[];
|
|
185
238
|
index: { value: number };
|
|
186
239
|
};
|
|
@@ -221,6 +274,8 @@ class StepInvalidationRestart extends Error {
|
|
|
221
274
|
|
|
222
275
|
let configImportCounter = 0;
|
|
223
276
|
|
|
277
|
+
// The engine owns the workflow graph, cache, and event emission for one
|
|
278
|
+
// project. The runtime daemon hosts a single long-lived instance per project.
|
|
224
279
|
export class DevMachineEngine {
|
|
225
280
|
private readonly projectDir: string;
|
|
226
281
|
private readonly configPath: string;
|
|
@@ -229,6 +284,9 @@ export class DevMachineEngine {
|
|
|
229
284
|
private providers: BaseProviderPlugin[];
|
|
230
285
|
private readonly providerFactory: ProviderFactory;
|
|
231
286
|
private readonly stateFactory: StateServiceFactory;
|
|
287
|
+
private readonly globalFragmentStateLocator: GlobalFragmentStateLocator;
|
|
288
|
+
private readonly globalFragmentStates = new Map<string, { input: GlobalFragmentStateLocationInput; state: StateService }>();
|
|
289
|
+
private evaluationFragmentHashes: Set<string> | undefined;
|
|
232
290
|
private readonly hostStorageDir: string;
|
|
233
291
|
private readonly hostStorageFactory: ProviderHostStorageFactory;
|
|
234
292
|
private readonly providerHostStorage = new Map<string, ReturnType<ProviderHostStorageFactory>>();
|
|
@@ -247,6 +305,9 @@ export class DevMachineEngine {
|
|
|
247
305
|
this.providers = options.providers ?? [];
|
|
248
306
|
this.providerFactory = options.providerFactory ?? ((input) => this.createProviderFromPlugin(input));
|
|
249
307
|
this.stateFactory = options.stateFactory ?? createStateStore;
|
|
308
|
+
this.globalFragmentStateLocator = options.globalFragmentStateLocator ?? ((fragment) => ({
|
|
309
|
+
statePath: join(this.projectDir, ".rigkit", "fragments", fragment.hash, "state.sqlite"),
|
|
310
|
+
}));
|
|
250
311
|
this.hostStorageDir = options.hostStorageDir ? resolve(options.hostStorageDir) : defaultProviderHostStorageDir();
|
|
251
312
|
this.hostStorageFactory = options.hostStorageFactory ?? createFileProviderHostStorage;
|
|
252
313
|
this.interactionPresenter = options.interaction?.present ?? defaultInteractionPresenter;
|
|
@@ -268,7 +329,7 @@ export class DevMachineEngine {
|
|
|
268
329
|
|
|
269
330
|
if (!existsSync(this.configPath)) {
|
|
270
331
|
throw new Error(
|
|
271
|
-
`No Rigkit config found at ${this.configPath}. Create one with "rig init" or pass
|
|
332
|
+
`No Rigkit config found at ${this.configPath}. Create one with "rig init" or pass -config=<file>.`,
|
|
272
333
|
);
|
|
273
334
|
}
|
|
274
335
|
|
|
@@ -534,6 +595,9 @@ export class DevMachineEngine {
|
|
|
534
595
|
stringField({ name: "name", required: true }),
|
|
535
596
|
],
|
|
536
597
|
cli: {
|
|
598
|
+
positionals: [
|
|
599
|
+
{ name: "name", index: 0 },
|
|
600
|
+
],
|
|
537
601
|
options: [
|
|
538
602
|
{ name: "workflow", flag: "--workflow" },
|
|
539
603
|
{ name: "name", flag: "--name", required: true },
|
|
@@ -590,6 +654,124 @@ export class DevMachineEngine {
|
|
|
590
654
|
return this.getStateService().listNodeRuns();
|
|
591
655
|
}
|
|
592
656
|
|
|
657
|
+
async listCache(input: {
|
|
658
|
+
workflow?: string;
|
|
659
|
+
machine?: string;
|
|
660
|
+
includeUnreachable?: boolean;
|
|
661
|
+
} = {}): Promise<EngineCacheList> {
|
|
662
|
+
const workflow = this.getWorkflow(input.workflow ?? input.machine);
|
|
663
|
+
const providers = await this.createProviders(workflow);
|
|
664
|
+
const evaluated = await this.evaluate({
|
|
665
|
+
workflow,
|
|
666
|
+
providers,
|
|
667
|
+
mode: "plan",
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// The plan tells us which row (by runId) would satisfy each cached node
|
|
671
|
+
// under the *current* code. Those are the only cache rows that matter.
|
|
672
|
+
const reachableRunIds = new Set<string>();
|
|
673
|
+
for (const node of evaluated.plan.nodes) {
|
|
674
|
+
if (node.status === "cached" && node.runId) reachableRunIds.add(node.runId);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (input.includeUnreachable) {
|
|
678
|
+
const entries: EngineCacheEntry[] = [
|
|
679
|
+
...this.getStateService()
|
|
680
|
+
.listNodeRuns()
|
|
681
|
+
.filter((run) => run.workflow === workflow.name)
|
|
682
|
+
.map((run) => cacheEntryForRun(run, "local")),
|
|
683
|
+
];
|
|
684
|
+
for (const fragmentHash of evaluated.fragments) {
|
|
685
|
+
const fragment = this.globalFragmentStates.get(fragmentHash);
|
|
686
|
+
if (!fragment) continue;
|
|
687
|
+
entries.push(
|
|
688
|
+
...fragment.state
|
|
689
|
+
.listNodeRuns()
|
|
690
|
+
.map((run) => cacheEntryForRun(run, "global", fragmentHash, workflow.name)),
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
entries.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
694
|
+
return { entries };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Local state is per-project — anything not reachable is dead weight, so
|
|
698
|
+
// prune as a side effect. Global fragments are shared across projects;
|
|
699
|
+
// another project might still reach the row we'd consider unreachable
|
|
700
|
+
// here, so we only filter the display for globals — never delete.
|
|
701
|
+
const localRuns = this.getStateService().listNodeRuns()
|
|
702
|
+
.filter((run) => run.workflow === workflow.name);
|
|
703
|
+
const localStaleIds = localRuns
|
|
704
|
+
.filter((run) => !reachableRunIds.has(run.id))
|
|
705
|
+
.map((run) => run.id);
|
|
706
|
+
if (localStaleIds.length > 0) {
|
|
707
|
+
this.getStateService().deleteNodeRunsById(localStaleIds);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const entries: EngineCacheEntry[] = localRuns
|
|
711
|
+
.filter((run) => reachableRunIds.has(run.id))
|
|
712
|
+
.map((run) => cacheEntryForRun(run, "local"));
|
|
713
|
+
|
|
714
|
+
for (const fragmentHash of evaluated.fragments) {
|
|
715
|
+
const fragment = this.globalFragmentStates.get(fragmentHash);
|
|
716
|
+
if (!fragment) continue;
|
|
717
|
+
entries.push(
|
|
718
|
+
...fragment.state
|
|
719
|
+
.listNodeRuns()
|
|
720
|
+
.filter((run) => reachableRunIds.has(run.id))
|
|
721
|
+
.map((run) => cacheEntryForRun(run, "global", fragmentHash, workflow.name)),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
entries.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
726
|
+
return { entries };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async invalidateCache(input: {
|
|
730
|
+
workflow?: string;
|
|
731
|
+
machine?: string;
|
|
732
|
+
nodePaths?: readonly string[];
|
|
733
|
+
} = {}): Promise<{ invalidated: number }> {
|
|
734
|
+
const workflow = this.getWorkflow(input.workflow ?? input.machine);
|
|
735
|
+
const state = this.getStateService();
|
|
736
|
+
// If no explicit paths, invalidate every cached node for this workflow.
|
|
737
|
+
const paths = input.nodePaths && input.nodePaths.length > 0
|
|
738
|
+
? [...input.nodePaths]
|
|
739
|
+
: [...new Set(state.listNodeRuns()
|
|
740
|
+
.filter((run) => run.workflow === workflow.name && !run.invalidated)
|
|
741
|
+
.map((run) => run.nodePath))];
|
|
742
|
+
if (paths.length === 0) return { invalidated: 0 };
|
|
743
|
+
const ids = state.invalidateNodeRuns({ workflow: workflow.name, nodePaths: paths });
|
|
744
|
+
return { invalidated: ids.length };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async clearCache(input: {
|
|
748
|
+
workflow?: string;
|
|
749
|
+
machine?: string;
|
|
750
|
+
scope?: EngineCacheClearScope;
|
|
751
|
+
} = {}): Promise<EngineCacheClearResult> {
|
|
752
|
+
const scope = input.scope ?? "all";
|
|
753
|
+
const workflow = this.getWorkflow(input.workflow ?? input.machine);
|
|
754
|
+
const providers = await this.createProviders(workflow);
|
|
755
|
+
const evaluated = await this.evaluate({
|
|
756
|
+
workflow,
|
|
757
|
+
providers,
|
|
758
|
+
mode: "plan",
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
let deleted = 0;
|
|
762
|
+
if (scope === "all" || scope === "local") {
|
|
763
|
+
deleted += this.getStateService().clearNodeRuns({ workflow: workflow.name });
|
|
764
|
+
}
|
|
765
|
+
if (scope === "all" || scope === "global") {
|
|
766
|
+
for (const fragmentHash of evaluated.fragments) {
|
|
767
|
+
const fragment = this.globalFragmentStates.get(fragmentHash);
|
|
768
|
+
if (!fragment) continue;
|
|
769
|
+
deleted += fragment.state.clearNodeRuns();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return { deleted };
|
|
773
|
+
}
|
|
774
|
+
|
|
593
775
|
hasOperation(operationId: string): boolean {
|
|
594
776
|
return this.listWorkflows().some((workflow) => workflow.operations.some((operation) => operation.id === operationId));
|
|
595
777
|
}
|
|
@@ -620,14 +802,15 @@ export class DevMachineEngine {
|
|
|
620
802
|
metadata,
|
|
621
803
|
});
|
|
622
804
|
const operationInput = this.resolveOperationInput(workflow, operation, input.input ?? {});
|
|
623
|
-
const
|
|
805
|
+
const operationNodePath = `operation.${operation.id}`;
|
|
806
|
+
const result = await this.withStepConsole(operationNodePath, () => operation.run({
|
|
624
807
|
...runtime,
|
|
625
808
|
input: Object.freeze(operationInput),
|
|
626
809
|
providers: runtime,
|
|
627
810
|
local: this.local,
|
|
628
811
|
workflow: workflow.name,
|
|
629
|
-
step: this.createStepRuntime(workflow.name,
|
|
630
|
-
});
|
|
812
|
+
step: this.createStepRuntime(workflow.name, operationNodePath, metadata),
|
|
813
|
+
}));
|
|
631
814
|
if (result !== undefined) assertJsonValue(result, `Operation ${operation.id} result`);
|
|
632
815
|
return result ?? null;
|
|
633
816
|
}
|
|
@@ -694,6 +877,8 @@ export class DevMachineEngine {
|
|
|
694
877
|
}> {
|
|
695
878
|
const workflow = this.getWorkflow(input.workflow ?? input.machine);
|
|
696
879
|
const providers = await this.createProviders(workflow);
|
|
880
|
+
const startedAt = Date.now();
|
|
881
|
+
this.emit({ type: "workflow.apply.started", workflow: workflow.name });
|
|
697
882
|
let result: { context: Record<string, JsonValue>; plan: WorkflowPlan } | undefined;
|
|
698
883
|
const maxRestarts = 8;
|
|
699
884
|
for (let attempt = 0; attempt <= maxRestarts; attempt++) {
|
|
@@ -716,6 +901,14 @@ export class DevMachineEngine {
|
|
|
716
901
|
}
|
|
717
902
|
if (!result) throw new Error(`Workflow ${workflow.name} did not produce an apply result`);
|
|
718
903
|
|
|
904
|
+
this.emit({
|
|
905
|
+
type: "workflow.apply.completed",
|
|
906
|
+
workflow: workflow.name,
|
|
907
|
+
nodeCount: result.plan.nodeCount,
|
|
908
|
+
cachedNodeCount: result.plan.cachedNodeCount,
|
|
909
|
+
durationMs: Date.now() - startedAt,
|
|
910
|
+
});
|
|
911
|
+
|
|
719
912
|
return {
|
|
720
913
|
context: result.context,
|
|
721
914
|
plan: result.plan,
|
|
@@ -751,6 +944,7 @@ export class DevMachineEngine {
|
|
|
751
944
|
};
|
|
752
945
|
|
|
753
946
|
this.getStateService().saveWorkspace(workspace);
|
|
947
|
+
this.emit({ type: "workspace.create.started", workspaceName: input.name });
|
|
754
948
|
try {
|
|
755
949
|
await this.runWorkspaceCreate({
|
|
756
950
|
workflow,
|
|
@@ -802,7 +996,10 @@ export class DevMachineEngine {
|
|
|
802
996
|
const draft = cloneWorkspace(workspace);
|
|
803
997
|
const workspaceRuntime = this.createWorkspaceRuntime(draft);
|
|
804
998
|
|
|
805
|
-
|
|
999
|
+
const removeNodePath = `workspace.${workspace.name}.remove`;
|
|
1000
|
+
const workspaceDef = workflow.workspace;
|
|
1001
|
+
this.emit({ type: "workspace.remove.started", workspaceName: workspace.name });
|
|
1002
|
+
await this.withStepConsole(removeNodePath, () => workspaceDef.remove({
|
|
806
1003
|
...runtime,
|
|
807
1004
|
workflow: {
|
|
808
1005
|
name: workflow.name,
|
|
@@ -811,10 +1008,11 @@ export class DevMachineEngine {
|
|
|
811
1008
|
workspace: workspaceRuntime,
|
|
812
1009
|
providers: runtime,
|
|
813
1010
|
local: this.local,
|
|
814
|
-
step: this.createStepRuntime(workflow.name,
|
|
815
|
-
});
|
|
1011
|
+
step: this.createStepRuntime(workflow.name, removeNodePath, metadata),
|
|
1012
|
+
}));
|
|
816
1013
|
|
|
817
1014
|
this.getStateService().deleteWorkspace(input.workspace);
|
|
1015
|
+
this.emit({ type: "workspace.remove.completed", workspaceName: workspace.name });
|
|
818
1016
|
return workspace;
|
|
819
1017
|
}
|
|
820
1018
|
|
|
@@ -822,26 +1020,42 @@ export class DevMachineEngine {
|
|
|
822
1020
|
workflow: LoadedWorkflow;
|
|
823
1021
|
providers: ProviderControllers;
|
|
824
1022
|
mode: EvaluationMode;
|
|
825
|
-
}): Promise<{ context: Record<string, JsonValue>; plan: WorkflowPlan }> {
|
|
1023
|
+
}): Promise<{ context: Record<string, JsonValue>; plan: WorkflowPlan; fragments: Set<string> }> {
|
|
826
1024
|
const providerFingerprint = providerFingerprintFor(input.workflow);
|
|
827
1025
|
const planNodes: WorkflowPlanNode[] = [];
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1026
|
+
const previousEvaluationFragmentHashes = this.evaluationFragmentHashes;
|
|
1027
|
+
const fragments = new Set<string>();
|
|
1028
|
+
this.evaluationFragmentHashes = fragments;
|
|
1029
|
+
let result!: EvaluationResult;
|
|
1030
|
+
try {
|
|
1031
|
+
result = await this.evaluateNode({
|
|
1032
|
+
workflow: input.workflow,
|
|
1033
|
+
providers: input.providers,
|
|
1034
|
+
providerFingerprint,
|
|
1035
|
+
mode: input.mode,
|
|
1036
|
+
node: input.workflow.root,
|
|
1037
|
+
cache: {
|
|
1038
|
+
scope: "local",
|
|
1039
|
+
workflow: input.workflow.name,
|
|
1040
|
+
nodePath: "",
|
|
1041
|
+
state: this.getStateService(),
|
|
1042
|
+
},
|
|
1043
|
+
configStack: [],
|
|
1044
|
+
state: {
|
|
1045
|
+
context: {},
|
|
1046
|
+
upstreamRunIds: [],
|
|
1047
|
+
previousTasks: [],
|
|
1048
|
+
known: true,
|
|
1049
|
+
},
|
|
1050
|
+
prefix: [],
|
|
1051
|
+
cachePrefix: [],
|
|
1052
|
+
root: true,
|
|
1053
|
+
planNodes,
|
|
1054
|
+
index: { value: 0 },
|
|
1055
|
+
});
|
|
1056
|
+
} finally {
|
|
1057
|
+
this.evaluationFragmentHashes = previousEvaluationFragmentHashes;
|
|
1058
|
+
}
|
|
845
1059
|
const cachedNodeCount = planNodes.filter((node) => node.status === "cached").length;
|
|
846
1060
|
const plan: WorkflowPlan = {
|
|
847
1061
|
workflow: input.workflow.name,
|
|
@@ -855,29 +1069,79 @@ export class DevMachineEngine {
|
|
|
855
1069
|
return {
|
|
856
1070
|
context: result.context,
|
|
857
1071
|
plan,
|
|
1072
|
+
fragments,
|
|
858
1073
|
};
|
|
859
1074
|
}
|
|
860
1075
|
|
|
861
1076
|
private async evaluateNode(input: EvaluateNodeInput): Promise<EvaluationResult> {
|
|
862
|
-
if (input.node.
|
|
863
|
-
|
|
1077
|
+
if (input.node.cacheScope === "global" && input.cache.scope !== "global") {
|
|
1078
|
+
const nodePath = nodeDisplayPath(input.node, input.prefix, input.root, input.suppressSequenceName);
|
|
1079
|
+
const fragmentHash = globalFragmentHashFor({
|
|
1080
|
+
node: input.node,
|
|
1081
|
+
providerFingerprint: input.providerFingerprint,
|
|
1082
|
+
});
|
|
1083
|
+
const fragmentState = await this.getGlobalFragmentState({
|
|
1084
|
+
hash: fragmentHash,
|
|
1085
|
+
workflow: input.workflow.name,
|
|
1086
|
+
nodePath,
|
|
1087
|
+
nodeName: input.node.name,
|
|
1088
|
+
nodeKind: input.node.nodeKind,
|
|
1089
|
+
});
|
|
1090
|
+
return await this.evaluateNode({
|
|
1091
|
+
...input,
|
|
1092
|
+
cache: {
|
|
1093
|
+
scope: "global",
|
|
1094
|
+
workflow: `fragment:${fragmentHash}`,
|
|
1095
|
+
nodePath: "",
|
|
1096
|
+
state: fragmentState,
|
|
1097
|
+
fragmentHash,
|
|
1098
|
+
},
|
|
1099
|
+
cachePrefix: [],
|
|
1100
|
+
suppressCacheSequenceName: undefined,
|
|
1101
|
+
});
|
|
864
1102
|
}
|
|
865
1103
|
|
|
866
|
-
if (input.node.
|
|
867
|
-
return await this.
|
|
1104
|
+
if (input.node.cacheScope === "local" && input.cache.scope !== "local") {
|
|
1105
|
+
return await this.evaluateNode({
|
|
1106
|
+
...input,
|
|
1107
|
+
cache: {
|
|
1108
|
+
scope: "local",
|
|
1109
|
+
workflow: input.workflow.name,
|
|
1110
|
+
nodePath: "",
|
|
1111
|
+
state: this.getStateService(),
|
|
1112
|
+
},
|
|
1113
|
+
cachePrefix: input.prefix,
|
|
1114
|
+
suppressCacheSequenceName: input.suppressSequenceName,
|
|
1115
|
+
});
|
|
868
1116
|
}
|
|
869
1117
|
|
|
870
|
-
const
|
|
871
|
-
? input.
|
|
872
|
-
:
|
|
873
|
-
let state = input.state;
|
|
1118
|
+
const configuredInput = input.node.config
|
|
1119
|
+
? { ...input, configStack: [...input.configStack, input.node.config] }
|
|
1120
|
+
: input;
|
|
874
1121
|
|
|
875
|
-
|
|
1122
|
+
if (configuredInput.node.nodeKind === "task") {
|
|
1123
|
+
return await this.evaluateTask(configuredInput as EvaluateNodeInput & { node: WorkflowTaskNode<any, any, any> });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (configuredInput.node.nodeKind === "parallel") {
|
|
1127
|
+
return await this.evaluateParallel(configuredInput);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const sequencePrefix = configuredInput.root || configuredInput.suppressSequenceName === configuredInput.node.name
|
|
1131
|
+
? configuredInput.prefix
|
|
1132
|
+
: [...configuredInput.prefix, configuredInput.node.name];
|
|
1133
|
+
const cacheSequencePrefix = configuredInput.root || configuredInput.suppressCacheSequenceName === configuredInput.node.name
|
|
1134
|
+
? configuredInput.cachePrefix
|
|
1135
|
+
: [...configuredInput.cachePrefix, configuredInput.node.name];
|
|
1136
|
+
let state = configuredInput.state;
|
|
1137
|
+
|
|
1138
|
+
for (const child of sequenceChildren(configuredInput.node)) {
|
|
876
1139
|
const result = await this.evaluateNode({
|
|
877
|
-
...
|
|
1140
|
+
...configuredInput,
|
|
878
1141
|
node: child,
|
|
879
1142
|
state,
|
|
880
1143
|
prefix: sequencePrefix,
|
|
1144
|
+
cachePrefix: cacheSequencePrefix,
|
|
881
1145
|
root: false,
|
|
882
1146
|
});
|
|
883
1147
|
state = {
|
|
@@ -889,7 +1153,7 @@ export class DevMachineEngine {
|
|
|
889
1153
|
};
|
|
890
1154
|
}
|
|
891
1155
|
|
|
892
|
-
return { ...state, planNodes:
|
|
1156
|
+
return { ...state, planNodes: configuredInput.planNodes };
|
|
893
1157
|
}
|
|
894
1158
|
|
|
895
1159
|
private async evaluateParallel(input: EvaluateNodeInput): Promise<EvaluationResult> {
|
|
@@ -916,8 +1180,10 @@ export class DevMachineEngine {
|
|
|
916
1180
|
blockedReason: input.state.blockedReason,
|
|
917
1181
|
},
|
|
918
1182
|
prefix: [...input.prefix, branchName],
|
|
1183
|
+
cachePrefix: [...input.cachePrefix, branchName],
|
|
919
1184
|
root: false,
|
|
920
1185
|
suppressSequenceName: branchName,
|
|
1186
|
+
suppressCacheSequenceName: branchName,
|
|
921
1187
|
});
|
|
922
1188
|
|
|
923
1189
|
if (branchState.known) {
|
|
@@ -942,13 +1208,14 @@ export class DevMachineEngine {
|
|
|
942
1208
|
|
|
943
1209
|
private async evaluateTask(input: EvaluateNodeInput & { node: WorkflowTaskNode<any, any, any> }): Promise<EvaluationResult> {
|
|
944
1210
|
const nodePath = [...input.prefix, input.node.name].join(".");
|
|
1211
|
+
const cacheNodePath = [...input.cachePrefix, input.node.name].join(".");
|
|
945
1212
|
const upstreamRunIds = [...input.state.upstreamRunIds];
|
|
946
1213
|
const nodeKey = hash({
|
|
947
|
-
cache: "task-
|
|
1214
|
+
cache: "task-v4",
|
|
948
1215
|
kind: "task",
|
|
949
|
-
path:
|
|
1216
|
+
path: cacheNodePath,
|
|
950
1217
|
name: input.node.name,
|
|
951
|
-
|
|
1218
|
+
config: input.configStack,
|
|
952
1219
|
handler: functionFingerprintFor(input.node.handler),
|
|
953
1220
|
output: input.node.options?.output ?? null,
|
|
954
1221
|
});
|
|
@@ -974,8 +1241,10 @@ export class DevMachineEngine {
|
|
|
974
1241
|
}
|
|
975
1242
|
|
|
976
1243
|
const cached = await this.findReusableTaskRun({
|
|
977
|
-
|
|
978
|
-
|
|
1244
|
+
state: input.cache.state,
|
|
1245
|
+
workflow: input.cache.workflow,
|
|
1246
|
+
nodePath: cacheNodePath,
|
|
1247
|
+
displayNodePath: nodePath,
|
|
979
1248
|
nodeKey,
|
|
980
1249
|
providerFingerprint: input.providerFingerprint,
|
|
981
1250
|
upstreamRunIds,
|
|
@@ -988,6 +1257,10 @@ export class DevMachineEngine {
|
|
|
988
1257
|
const previousTasks = appendPreviousTask(input.state.previousTasks, {
|
|
989
1258
|
name: input.node.name,
|
|
990
1259
|
path: nodePath,
|
|
1260
|
+
cache: {
|
|
1261
|
+
...input.cache,
|
|
1262
|
+
nodePath: cacheNodePath,
|
|
1263
|
+
},
|
|
991
1264
|
});
|
|
992
1265
|
this.emit({ type: "node.cached", nodePath, runId: cached.id });
|
|
993
1266
|
input.planNodes.push({
|
|
@@ -1035,6 +1308,7 @@ export class DevMachineEngine {
|
|
|
1035
1308
|
nodePath,
|
|
1036
1309
|
metadata,
|
|
1037
1310
|
});
|
|
1311
|
+
const config = Object.freeze(mergeConfigStack(input.configStack));
|
|
1038
1312
|
const step = this.createStepRuntime(
|
|
1039
1313
|
input.workflow.name,
|
|
1040
1314
|
nodePath,
|
|
@@ -1042,16 +1316,26 @@ export class DevMachineEngine {
|
|
|
1042
1316
|
input.state.context,
|
|
1043
1317
|
input.state.previousTasks,
|
|
1044
1318
|
);
|
|
1045
|
-
const result = await input.node.handler({
|
|
1319
|
+
const result = await this.withStepConsole(nodePath, () => input.node.handler({
|
|
1046
1320
|
...runtime,
|
|
1047
1321
|
providers: runtime,
|
|
1048
1322
|
step,
|
|
1049
|
-
|
|
1323
|
+
config,
|
|
1324
|
+
}));
|
|
1050
1325
|
if (isStepInvalidation(result)) {
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1326
|
+
const targetTask = input.state.previousTasks.find((task) => task.path === result.targetNodePath);
|
|
1327
|
+
const invalidatedRunIds = targetTask
|
|
1328
|
+
? this.invalidateTaskCaches([
|
|
1329
|
+
targetTask.cache,
|
|
1330
|
+
{
|
|
1331
|
+
...input.cache,
|
|
1332
|
+
nodePath: cacheNodePath,
|
|
1333
|
+
},
|
|
1334
|
+
])
|
|
1335
|
+
: this.invalidateTaskCaches([{
|
|
1336
|
+
...input.cache,
|
|
1337
|
+
nodePath: cacheNodePath,
|
|
1338
|
+
}]);
|
|
1055
1339
|
throw new StepInvalidationRestart({
|
|
1056
1340
|
workflow: input.workflow.name,
|
|
1057
1341
|
target: result.target,
|
|
@@ -1073,8 +1357,8 @@ export class DevMachineEngine {
|
|
|
1073
1357
|
const artifacts = collectArtifacts(output);
|
|
1074
1358
|
const record: WorkflowNodeRunRecord = {
|
|
1075
1359
|
id: crypto.randomUUID(),
|
|
1076
|
-
workflow: input.workflow
|
|
1077
|
-
nodePath,
|
|
1360
|
+
workflow: input.cache.workflow,
|
|
1361
|
+
nodePath: cacheNodePath,
|
|
1078
1362
|
nodeName: input.node.name,
|
|
1079
1363
|
nodeKind: input.node.nodeKind,
|
|
1080
1364
|
nodeKey,
|
|
@@ -1087,7 +1371,7 @@ export class DevMachineEngine {
|
|
|
1087
1371
|
metadata,
|
|
1088
1372
|
};
|
|
1089
1373
|
|
|
1090
|
-
|
|
1374
|
+
input.cache.state.saveNodeRun(record);
|
|
1091
1375
|
for (const artifact of artifacts) {
|
|
1092
1376
|
const providerId = providerIdOf(artifact);
|
|
1093
1377
|
this.emit({
|
|
@@ -1103,6 +1387,10 @@ export class DevMachineEngine {
|
|
|
1103
1387
|
const previousTasks = appendPreviousTask(input.state.previousTasks, {
|
|
1104
1388
|
name: input.node.name,
|
|
1105
1389
|
path: nodePath,
|
|
1390
|
+
cache: {
|
|
1391
|
+
...input.cache,
|
|
1392
|
+
nodePath: cacheNodePath,
|
|
1393
|
+
},
|
|
1106
1394
|
});
|
|
1107
1395
|
|
|
1108
1396
|
return {
|
|
@@ -1115,8 +1403,10 @@ export class DevMachineEngine {
|
|
|
1115
1403
|
}
|
|
1116
1404
|
|
|
1117
1405
|
private async findReusableTaskRun(input: {
|
|
1406
|
+
state: StateService;
|
|
1118
1407
|
workflow: string;
|
|
1119
1408
|
nodePath: string;
|
|
1409
|
+
displayNodePath: string;
|
|
1120
1410
|
nodeKey: string;
|
|
1121
1411
|
providerFingerprint: string;
|
|
1122
1412
|
upstreamRunIds: readonly string[];
|
|
@@ -1124,11 +1414,11 @@ export class DevMachineEngine {
|
|
|
1124
1414
|
outputSchema?: OutputSchema;
|
|
1125
1415
|
cacheTTL?: WorkflowTaskCacheTTL;
|
|
1126
1416
|
}): Promise<WorkflowNodeRunRecord | undefined> {
|
|
1127
|
-
const cached =
|
|
1417
|
+
const cached = input.state.findReusableNodeRun(input);
|
|
1128
1418
|
if (!cached) return undefined;
|
|
1129
1419
|
if (!isCacheFresh(cached.createdAt, input.cacheTTL)) return undefined;
|
|
1130
1420
|
|
|
1131
|
-
const parsed = normalizeTaskOutput(input.
|
|
1421
|
+
const parsed = normalizeTaskOutput(input.displayNodePath, cached.output, input.outputSchema, "cached");
|
|
1132
1422
|
if (!parsed) return undefined;
|
|
1133
1423
|
|
|
1134
1424
|
for (const artifact of cached.artifacts) {
|
|
@@ -1223,8 +1513,10 @@ export class DevMachineEngine {
|
|
|
1223
1513
|
metadata,
|
|
1224
1514
|
});
|
|
1225
1515
|
|
|
1516
|
+
const createNodePath = `workspace.${input.name}.create`;
|
|
1517
|
+
const workspaceDef = input.workflow.workspace;
|
|
1226
1518
|
try {
|
|
1227
|
-
const data = await
|
|
1519
|
+
const data = await this.withStepConsole(createNodePath, () => workspaceDef.create({
|
|
1228
1520
|
...providers,
|
|
1229
1521
|
workflow: {
|
|
1230
1522
|
name: input.workflow.name,
|
|
@@ -1235,8 +1527,8 @@ export class DevMachineEngine {
|
|
|
1235
1527
|
},
|
|
1236
1528
|
providers,
|
|
1237
1529
|
local: this.local,
|
|
1238
|
-
step: this.createStepRuntime(input.workflow.name,
|
|
1239
|
-
});
|
|
1530
|
+
step: this.createStepRuntime(input.workflow.name, createNodePath, metadata),
|
|
1531
|
+
}));
|
|
1240
1532
|
assertJsonValue(data, `Workflow ${input.workflow.name} workspace create result`);
|
|
1241
1533
|
if (!isPlainObject(data)) {
|
|
1242
1534
|
throw new Error(`Workflow ${input.workflow.name} workspace create result must be an object`);
|
|
@@ -1266,7 +1558,13 @@ export class DevMachineEngine {
|
|
|
1266
1558
|
const workspace = this.createWorkspaceRuntime(draft);
|
|
1267
1559
|
const operationInput = this.resolveOperationInput(input.workflow, input.operation, input.rawInput ?? {});
|
|
1268
1560
|
|
|
1269
|
-
const
|
|
1561
|
+
const workspaceOperationNodePath = `workspace.${input.workspace.name}.${input.operation.id}`;
|
|
1562
|
+
this.emit({
|
|
1563
|
+
type: "workspace.operation.started",
|
|
1564
|
+
workspaceName: input.workspace.name,
|
|
1565
|
+
operationId: input.operation.id,
|
|
1566
|
+
});
|
|
1567
|
+
const result = await this.withStepConsole(workspaceOperationNodePath, () => input.operation.run({
|
|
1270
1568
|
...providers,
|
|
1271
1569
|
workflow: {
|
|
1272
1570
|
name: input.workflow.name,
|
|
@@ -1278,11 +1576,16 @@ export class DevMachineEngine {
|
|
|
1278
1576
|
local: this.local,
|
|
1279
1577
|
step: this.createStepRuntime(
|
|
1280
1578
|
input.workflow.name,
|
|
1281
|
-
|
|
1579
|
+
workspaceOperationNodePath,
|
|
1282
1580
|
metadata,
|
|
1283
1581
|
),
|
|
1284
|
-
});
|
|
1582
|
+
}));
|
|
1285
1583
|
if (result !== undefined) assertJsonValue(result, `Workspace operation ${input.operation.id} result`);
|
|
1584
|
+
this.emit({
|
|
1585
|
+
type: "workspace.operation.completed",
|
|
1586
|
+
workspaceName: input.workspace.name,
|
|
1587
|
+
operationId: input.operation.id,
|
|
1588
|
+
});
|
|
1286
1589
|
return result ?? null;
|
|
1287
1590
|
}
|
|
1288
1591
|
|
|
@@ -1293,6 +1596,22 @@ export class DevMachineEngine {
|
|
|
1293
1596
|
}) as WorkspaceRuntimeRecord<Data>;
|
|
1294
1597
|
}
|
|
1295
1598
|
|
|
1599
|
+
// Wraps a user step handler in an AsyncLocalStorage scope so that any
|
|
1600
|
+
// console.log / debug / warn / error invoked inside (transitively, through
|
|
1601
|
+
// any helper or third-party SDK) is captured and emitted as a log.output
|
|
1602
|
+
// event tied to this step's node path.
|
|
1603
|
+
private withStepConsole<T>(nodePath: string, fn: () => Promise<T> | T): Promise<T> | T {
|
|
1604
|
+
const sink: StepConsoleSink = ({ level, message }) => {
|
|
1605
|
+
this.emit({
|
|
1606
|
+
type: "log.output",
|
|
1607
|
+
nodePath,
|
|
1608
|
+
stream: consoleLevelToLogStream(level),
|
|
1609
|
+
data: message,
|
|
1610
|
+
});
|
|
1611
|
+
};
|
|
1612
|
+
return runWithStepConsole(sink, fn);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1296
1615
|
private createStepRuntime<Context extends JsonObject = JsonObject>(
|
|
1297
1616
|
workflow: string,
|
|
1298
1617
|
nodePath: string,
|
|
@@ -1307,15 +1626,6 @@ export class DevMachineEngine {
|
|
|
1307
1626
|
metadata: (value: JsonObject) => {
|
|
1308
1627
|
Object.assign(metadata, value);
|
|
1309
1628
|
},
|
|
1310
|
-
log: (data: string, options: { stream?: WorkflowLogStream; label?: string } = {}) => {
|
|
1311
|
-
this.emit({
|
|
1312
|
-
type: "log.output",
|
|
1313
|
-
nodePath,
|
|
1314
|
-
stream: options.stream ?? "info",
|
|
1315
|
-
label: options.label,
|
|
1316
|
-
data,
|
|
1317
|
-
});
|
|
1318
|
-
},
|
|
1319
1629
|
invalidate: <Target extends string>(target: Target) => {
|
|
1320
1630
|
const matches = previousTasks.filter((task) => task.name === target || task.path === target);
|
|
1321
1631
|
if (matches.length === 0) {
|
|
@@ -1459,6 +1769,51 @@ export class DevMachineEngine {
|
|
|
1459
1769
|
return this.state;
|
|
1460
1770
|
}
|
|
1461
1771
|
|
|
1772
|
+
private async getGlobalFragmentState(input: GlobalFragmentStateLocationInput): Promise<StateService> {
|
|
1773
|
+
this.evaluationFragmentHashes?.add(input.hash);
|
|
1774
|
+
const existing = this.globalFragmentStates.get(input.hash);
|
|
1775
|
+
if (existing) return existing.state;
|
|
1776
|
+
|
|
1777
|
+
const located = this.globalFragmentStateLocator(input);
|
|
1778
|
+
const statePath = typeof located === "string" ? located : located.statePath;
|
|
1779
|
+
const state = this.stateFactory({
|
|
1780
|
+
projectDir: this.projectDir,
|
|
1781
|
+
configPath: this.configPath,
|
|
1782
|
+
statePath,
|
|
1783
|
+
source: {
|
|
1784
|
+
kind: "global-fragment",
|
|
1785
|
+
hash: input.hash,
|
|
1786
|
+
workflow: input.workflow,
|
|
1787
|
+
nodePath: input.nodePath,
|
|
1788
|
+
nodeName: input.nodeName,
|
|
1789
|
+
nodeKind: input.nodeKind,
|
|
1790
|
+
},
|
|
1791
|
+
});
|
|
1792
|
+
await state.syncSchema();
|
|
1793
|
+
this.globalFragmentStates.set(input.hash, { input, state });
|
|
1794
|
+
return state;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
private invalidateTaskCaches(targets: EvaluationCacheTarget[]): string[] {
|
|
1798
|
+
const grouped = new Map<string, { target: EvaluationCacheTarget; nodePaths: Set<string> }>();
|
|
1799
|
+
for (const target of targets) {
|
|
1800
|
+
const key = `${target.state.path}\0${target.workflow}`;
|
|
1801
|
+
const existing = grouped.get(key);
|
|
1802
|
+
if (existing) {
|
|
1803
|
+
existing.nodePaths.add(target.nodePath);
|
|
1804
|
+
} else {
|
|
1805
|
+
grouped.set(key, { target, nodePaths: new Set([target.nodePath]) });
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
return [...grouped.values()].flatMap(({ target, nodePaths }) =>
|
|
1810
|
+
target.state.invalidateNodeRuns({
|
|
1811
|
+
workflow: target.workflow,
|
|
1812
|
+
nodePaths: [...nodePaths],
|
|
1813
|
+
})
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1462
1817
|
private async createProviders(workflow: LoadedWorkflow): Promise<ProviderControllers> {
|
|
1463
1818
|
const entries = await Promise.all(
|
|
1464
1819
|
Object.entries(workflow.providers).map(async ([name, provider]) => {
|
|
@@ -1703,6 +2058,88 @@ function providerPluginFingerprint(plugin: unknown): unknown {
|
|
|
1703
2058
|
};
|
|
1704
2059
|
}
|
|
1705
2060
|
|
|
2061
|
+
function globalFragmentHashFor(input: {
|
|
2062
|
+
node: WorkflowNodeDefinition<any, any, any>;
|
|
2063
|
+
providerFingerprint: string;
|
|
2064
|
+
}): string {
|
|
2065
|
+
return `sha256-${hash({
|
|
2066
|
+
cache: "fragment-v1",
|
|
2067
|
+
graph: graphFingerprintFor(input.node),
|
|
2068
|
+
providerFingerprint: input.providerFingerprint,
|
|
2069
|
+
})}`;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function graphFingerprintFor(node: WorkflowNodeDefinition<any, any, any>): unknown {
|
|
2073
|
+
if (node.nodeKind === "task") {
|
|
2074
|
+
const task = node as WorkflowTaskNode<any, any, any>;
|
|
2075
|
+
return {
|
|
2076
|
+
kind: "task",
|
|
2077
|
+
name: task.name,
|
|
2078
|
+
scope: task.cacheScope ?? null,
|
|
2079
|
+
config: task.config ?? null,
|
|
2080
|
+
handler: functionFingerprintFor(task.handler),
|
|
2081
|
+
output: task.options?.output ?? null,
|
|
2082
|
+
cacheTTL: task.options?.cacheTTL ?? null,
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
if (node.nodeKind === "parallel") {
|
|
2087
|
+
return {
|
|
2088
|
+
kind: "parallel",
|
|
2089
|
+
name: node.name,
|
|
2090
|
+
scope: node.cacheScope ?? null,
|
|
2091
|
+
config: node.config ?? null,
|
|
2092
|
+
branches: Object.fromEntries(
|
|
2093
|
+
Object.entries(parallelBranches(node)).map(([name, branch]) => [name, graphFingerprintFor(branch)]),
|
|
2094
|
+
),
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
return {
|
|
2099
|
+
kind: "sequence",
|
|
2100
|
+
name: node.name,
|
|
2101
|
+
scope: node.cacheScope ?? null,
|
|
2102
|
+
config: node.config ?? null,
|
|
2103
|
+
children: sequenceChildren(node).map((child) => graphFingerprintFor(child)),
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function nodeDisplayPath(
|
|
2108
|
+
node: WorkflowNodeDefinition<any, any, any>,
|
|
2109
|
+
prefix: string[],
|
|
2110
|
+
root: boolean,
|
|
2111
|
+
suppressSequenceName?: string,
|
|
2112
|
+
): string {
|
|
2113
|
+
if (node.nodeKind === "task") return [...prefix, node.name].join(".");
|
|
2114
|
+
if (node.nodeKind === "sequence") {
|
|
2115
|
+
return (root || suppressSequenceName === node.name ? prefix : [...prefix, node.name]).join(".");
|
|
2116
|
+
}
|
|
2117
|
+
return [...prefix, node.name].join(".");
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function cacheEntryForRun(
|
|
2121
|
+
run: WorkflowNodeRunRecord,
|
|
2122
|
+
scope: WorkflowCacheScope,
|
|
2123
|
+
fragmentHash?: string,
|
|
2124
|
+
workflow?: string,
|
|
2125
|
+
): EngineCacheEntry {
|
|
2126
|
+
return {
|
|
2127
|
+
scope,
|
|
2128
|
+
workflow: workflow ?? run.workflow,
|
|
2129
|
+
nodePath: run.nodePath,
|
|
2130
|
+
nodeName: run.nodeName,
|
|
2131
|
+
nodeKind: run.nodeKind,
|
|
2132
|
+
runId: run.id,
|
|
2133
|
+
invalidated: run.invalidated,
|
|
2134
|
+
createdAt: run.createdAt,
|
|
2135
|
+
...(fragmentHash ? { fragmentHash } : {}),
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function mergeConfigStack(configStack: readonly JsonObject[]): JsonObject {
|
|
2140
|
+
return Object.assign({}, ...configStack);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1706
2143
|
function functionFingerprintFor(fn: Function): { name: string; length: number; source: string } {
|
|
1707
2144
|
return {
|
|
1708
2145
|
name: fn.name,
|
|
@@ -2154,3 +2591,14 @@ async function defaultInteractionPresenter(request: InteractionPresentationReque
|
|
|
2154
2591
|
if (request.instructions) console.error(request.instructions);
|
|
2155
2592
|
console.error(`Open ${request.url}`);
|
|
2156
2593
|
}
|
|
2594
|
+
|
|
2595
|
+
function consoleLevelToLogStream(level: ConsoleLevel): WorkflowLogStream {
|
|
2596
|
+
switch (level) {
|
|
2597
|
+
case "debug": return "debug";
|
|
2598
|
+
case "warn": return "warn";
|
|
2599
|
+
case "error": return "stderr";
|
|
2600
|
+
case "info":
|
|
2601
|
+
case "log":
|
|
2602
|
+
default: return "info";
|
|
2603
|
+
}
|
|
2604
|
+
}
|