@oomol-lab/open-flow 0.1.0-beta.85 → 0.1.0-beta.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/{FlowCanvas-CZoHhB1y.js → FlowCanvas-CHIaMaBv.js} +3 -3
- package/dist/browser/{createResourceDialog-Bi4fUnbD.js → createResourceDialog-C0E5yPe6.js} +3 -3
- package/dist/browser/{dialog-BzoSxhXQ.js → dialog-b3hsdu8i.js} +291 -143
- package/dist/browser/{exportPng-CXVWVlLc.js → exportPng-DGHcejgy.js} +2 -2
- package/dist/browser/{flowChanges-C567g-Pc.js → flowChanges-BvSWezEb.js} +375 -83
- package/dist/browser/{flowWorkspace-BNdBKtla.js → flowWorkspace-BKnMRClO.js} +1619 -475
- package/dist/browser/fr-CNTSedYU.js +593 -0
- package/dist/browser/ja-CxQBHr2J.js +593 -0
- package/dist/browser/ko-D1Exiv1h.js +593 -0
- package/dist/browser/licenses.md +1 -1
- package/dist/browser/{markdownContent-DF39sd9T.js → markdownContent-BR9-1ikU.js} +1 -1
- package/dist/browser/preview.css +181 -181
- package/dist/browser/preview.js +1994 -623
- package/dist/browser/ru-BzLZv0B7.js +593 -0
- package/dist/browser/{tooltip-DGj5pudg.js → tooltip-DSBF3bRw.js} +55 -23
- package/dist/browser/workbench.css +181 -181
- package/dist/browser/workbench.js +86 -17
- package/dist/browser/{workbenchSelect-BIaEVTx6.js → workbenchSelect-Di1Bm_-d.js} +2 -2
- package/dist/browser/{wrapLayout-C4moMKgb.js → wrapLayout-B0AT8_-i.js} +1 -1
- package/dist/browser/zh-CN-Dg_xSGde.js +593 -0
- package/dist/browser/zh-TW-oxuLwsV0.js +593 -0
- package/dist/command/index.js +61 -57
- package/dist/command/licenses.md +28 -4
- package/dist/common/authoring.d.ts +5 -5
- package/dist/common/authoringExamples.d.ts +3 -2
- package/dist/common/authoringSchema.d.ts +41 -4
- package/dist/common/catalogCache.d.ts +46 -0
- package/dist/common/connector-action.js +13 -10
- package/dist/common/connector-proxy.d.ts +96 -0
- package/dist/common/connector-proxy.js +5209 -0
- package/dist/common/control-api-conformance.js +312 -46
- package/dist/common/control-api.d.ts +12 -1
- package/dist/common/control-api.js +303 -41
- package/dist/common/control-requests.d.ts +4 -3
- package/dist/common/control-requests.js +14111 -171
- package/dist/common/engine-contract.d.ts +3 -2
- package/dist/common/event-source.js +55 -23
- package/dist/common/flow-authoring-node.d.ts +2 -0
- package/dist/common/flow-authoring.js +6936 -6781
- package/dist/common/flow-change-schema.d.ts +1 -1
- package/dist/common/flow-change.d.ts +2 -2
- package/dist/common/flow-change.js +91 -36
- package/dist/common/flow-connectionUsage.d.ts +1 -1
- package/dist/common/flow-encoding.js +93 -38
- package/dist/common/flow-requestParameters.d.ts +34 -0
- package/dist/common/flow-semantics.js +9429 -9236
- package/dist/common/flow-state.js +55 -23
- package/dist/common/flow-workflowFile.d.ts +3 -1
- package/dist/common/http-request.d.ts +6 -11
- package/dist/common/http-request.js +192 -141
- package/dist/common/licenses.md +1 -1
- package/dist/common/mcp.d.ts +3 -3
- package/dist/common/mcp.js +303 -41
- package/dist/common/run-events.js +1 -0
- package/dist/common/runtime-contract.js +10 -4
- package/dist/common/runtime-nodejs-compat.js +55 -23
- package/dist/common/scheduler-checkpoint.d.ts +3 -3
- package/dist/common/scheduler-dependencies.d.ts +22 -28
- package/dist/common/scheduler-node-execution.d.ts +1 -0
- package/dist/common/scheduler.d.ts +8 -2
- package/dist/common/scheduler.js +274 -95
- package/dist/common/workflow.js +51 -19
- package/dist/common/workflowAuthoring.d.ts +411 -0
- package/dist/render/browser/licenses.md +1 -1
- package/dist/render/browser/runtime.js +180 -48
- package/dist/render/node/index.js +85 -30
- package/dist/render/node/licenses.md +1 -1
- package/package.json +1 -1
- package/skills/open-flow/references/flow-authoring.md +14 -13
- package/src/base/common/jsonSchema.ts +11 -0
- package/src/connector/common/actionSchema.ts +4 -11
- package/src/connector/common/proxy.ts +152 -0
- package/src/control/common/api.ts +99 -3
- package/src/control/common/authoring.ts +23 -9
- package/src/control/common/authoringExamples.ts +9 -0
- package/src/control/common/authoringSchema.ts +21 -12
- package/src/control/common/catalogCache.ts +145 -0
- package/src/control/common/conformance.ts +9 -3
- package/src/control/common/connectorQueries.ts +17 -2
- package/src/control/common/requests.ts +1 -0
- package/src/control/common/workflowAuthoring.ts +120 -0
- package/src/execution/common/checkpoint.ts +18 -12
- package/src/execution/common/dependencies.ts +132 -15
- package/src/execution/common/events.ts +3 -0
- package/src/execution/common/node-execution.ts +22 -15
- package/src/execution/common/scheduler.ts +20 -31
- package/src/flow/common/change.ts +2 -1
- package/src/flow/common/changeSchema.ts +11 -1
- package/src/flow/common/connectionUsage.ts +4 -2
- package/src/flow/common/errorHandling.ts +3 -1
- package/src/flow/common/nodeChanges.ts +35 -18
- package/src/flow/common/requestParameters.ts +120 -0
- package/src/flow/common/semantics.ts +51 -1
- package/src/flow/common/sourceField.ts +3 -2
- package/src/flow/common/workflowFile.ts +2 -2
- package/src/http/common/httpRequest.ts +28 -78
- package/src/runtime/common/engineContract.ts +6 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { JsonValue } from '../../flow/common/change.ts'
|
|
2
|
+
import type { WorkflowFile } from '../../flow/common/workflowFile.ts'
|
|
3
|
+
import type { ControlClient } from './api.ts'
|
|
4
|
+
import type { AuthoringHost, AuthoringRead, AuthoringRequest, AuthoringSearch, AuthoringType } from './authoring.ts'
|
|
5
|
+
|
|
6
|
+
import { currentFlowModelVersion } from '../../flow/common/changeSchema.ts'
|
|
7
|
+
import { canonicalJsonBytes, digestBytes } from '../../flow/common/encoding.ts'
|
|
8
|
+
import { validateFlow } from '../../flow/common/semantics.ts'
|
|
9
|
+
import { decodeWorkflowFile } from '../../flow/common/workflowFile.ts'
|
|
10
|
+
import { currentEngineContract, findEngineContract } from '../../runtime/common/engineContract.ts'
|
|
11
|
+
import { workflowCandidate } from '../../workflow/common/workflow.ts'
|
|
12
|
+
import { AuthoringError, authoringDiagnostics, compileAuthoring, readAuthoring, searchAuthoring } from './authoring.ts'
|
|
13
|
+
import { authoringRequestSchema, authoringSchema, authoringSchemaQuery } from './authoringSchema.ts'
|
|
14
|
+
|
|
15
|
+
export interface WorkflowAuthoringOptions {
|
|
16
|
+
/** Flow API metadata is already normalized; no Connector catalog adapter runs here. */
|
|
17
|
+
readonly client?: ControlClient
|
|
18
|
+
readonly locale?: string
|
|
19
|
+
readonly engineContract?: string
|
|
20
|
+
/** Override only for callers that own their deterministic node/module identity scope. */
|
|
21
|
+
readonly id?: AuthoringHost['id']
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function revision(file: WorkflowFile): Promise<string> {
|
|
25
|
+
return digestBytes(canonicalJsonBytes(JSON.parse(JSON.stringify(file)) as JsonValue))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function checkedRevision(file: WorkflowFile, expected?: string): Promise<string> {
|
|
29
|
+
const actual = await revision(file)
|
|
30
|
+
if (expected != null && expected != actual)
|
|
31
|
+
throw new AuthoringError('flow.revision-conflict', 'The workflow file changed. Read it before submitting another edit.', { revision: actual })
|
|
32
|
+
return actual
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** File objects in, file objects out. Persistence and transport belong to the caller. */
|
|
36
|
+
export function createWorkflowAuthoring(options: WorkflowAuthoringOptions = {}) {
|
|
37
|
+
function client(): ControlClient {
|
|
38
|
+
if (options.client == null) throw new AuthoringError('catalog.unavailable', 'A Control client is required to discover this capability.')
|
|
39
|
+
return options.client
|
|
40
|
+
}
|
|
41
|
+
const engineContract = options.engineContract ?? currentEngineContract
|
|
42
|
+
const engine = findEngineContract(engineContract)
|
|
43
|
+
if (engine == null) throw new AuthoringError('engine.unsupported', 'The Engine contract is not supported.', { engineContract })
|
|
44
|
+
|
|
45
|
+
async function check(file: WorkflowFile) {
|
|
46
|
+
const decoded = decodeWorkflowFile(file)
|
|
47
|
+
const validation = await validateFlow(decoded.content, engine!)
|
|
48
|
+
return {
|
|
49
|
+
revision: await revision(decoded),
|
|
50
|
+
valid: validation.valid,
|
|
51
|
+
diagnostics: authoringDiagnostics(decoded.content, validation.diagnostics),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
create(metadata: Omit<WorkflowFile, 'kind' | 'version' | 'content'> = {}): WorkflowFile {
|
|
57
|
+
return decodeWorkflowFile({
|
|
58
|
+
...metadata,
|
|
59
|
+
kind: 'open-flow-workflow',
|
|
60
|
+
version: 1,
|
|
61
|
+
content: { modelVersion: currentFlowModelVersion, document: { graph: { nodes: {}, edges: [] }, bindings: {} }, modules: {} },
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async read(file: WorkflowFile, query: AuthoringRead = {}) {
|
|
66
|
+
const decoded = decodeWorkflowFile(file)
|
|
67
|
+
return { revision: await checkedRevision(decoded, query.revision), data: readAuthoring(decoded.content, query) }
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async search(file: WorkflowFile, query: AuthoringSearch) {
|
|
71
|
+
const decoded = decodeWorkflowFile(file)
|
|
72
|
+
return { revision: await checkedRevision(decoded, query.revision), data: searchAuthoring(decoded.content, query) }
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async schema(query: { readonly type?: AuthoringType; readonly action?: string } = {}) {
|
|
76
|
+
const parsed = authoringSchemaQuery.parse(query)
|
|
77
|
+
if (parsed.action != null) {
|
|
78
|
+
const action = await client().getConnectorActionMetadata(parsed.action, undefined, undefined, options.locale ?? 'en')
|
|
79
|
+
return { action: action.actionId, name: action.name, inputs: action.inputs, outputs: action.outputs, version: 1 as const }
|
|
80
|
+
}
|
|
81
|
+
return { ...authoringSchema(parsed.type), version: 1 as const }
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async edit(file: WorkflowFile, request: AuthoringRequest) {
|
|
85
|
+
const decoded = decodeWorkflowFile(file)
|
|
86
|
+
const parsed = authoringRequestSchema.parse(request)
|
|
87
|
+
const baseRevision = await checkedRevision(decoded, parsed.baseRevision)
|
|
88
|
+
const prefix = (await digestBytes(canonicalJsonBytes([baseRevision, parsed.requestId]))).slice(7, 31)
|
|
89
|
+
const ids = new Map<string, string>()
|
|
90
|
+
const compiled = await compileAuthoring(decoded.content, parsed, {
|
|
91
|
+
id: (label) => {
|
|
92
|
+
if (options.id != null) return options.id(label)
|
|
93
|
+
let id = ids.get(label)
|
|
94
|
+
if (id == null) {
|
|
95
|
+
id = `${prefix}_${ids.size}`
|
|
96
|
+
ids.set(label, id)
|
|
97
|
+
}
|
|
98
|
+
return id
|
|
99
|
+
},
|
|
100
|
+
action: (action) => client().getConnectorActionMetadata(action, undefined, undefined, options.locale ?? 'en'),
|
|
101
|
+
trigger: (key) => client().getTriggerKey(key),
|
|
102
|
+
openapi: (url) => client().loadOpenApiDocument(url),
|
|
103
|
+
workflow: async (flowId) => {
|
|
104
|
+
const candidate = workflowCandidate(await client().getFlow(flowId))
|
|
105
|
+
return candidate.problem == null ? client().getWorkflowContract(flowId) : candidate
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
const next = { ...decoded, content: compiled.content }
|
|
109
|
+
const validation = await check(next)
|
|
110
|
+
return {
|
|
111
|
+
file: next,
|
|
112
|
+
revision: validation.revision,
|
|
113
|
+
nodes: compiled.nodes,
|
|
114
|
+
validation: { status: validation.valid ? ('valid' as const) : ('invalid' as const), diagnostics: validation.diagnostics },
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
check,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Graph, JsonValue } from '../../flow/common/change.ts'
|
|
2
2
|
import type { AgentCheckpoint } from '../../runtime/common/runtime.ts'
|
|
3
|
-
import type { DependenciesCheckpoint } from './dependencies.ts'
|
|
3
|
+
import type { Dependencies, DependenciesCheckpoint } from './dependencies.ts'
|
|
4
4
|
import type { ExecutableNode } from './node-execution.ts'
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod'
|
|
@@ -9,7 +9,7 @@ import { nodeErrorSchema, nodeSystemOutputPorts } from '../../flow/common/errorH
|
|
|
9
9
|
import { nodeInputPorts as nodePorts, isResolutionNode, resolutionActions } from '../../flow/common/graph.ts'
|
|
10
10
|
import { matchesSchema } from '../../flow/common/schema.ts'
|
|
11
11
|
import { matchesTriggerOutputs } from '../../trigger/common/contract.ts'
|
|
12
|
-
import {
|
|
12
|
+
import { decodeDependenciesCheckpoint } from './dependencies.ts'
|
|
13
13
|
import { agentCheckpointSchema, agentConfig, agentApproval, jsonEqual, jsonValue, outputRecord, usesPending, validateOutputs } from './node-execution.ts'
|
|
14
14
|
|
|
15
15
|
export interface PendingWait {
|
|
@@ -44,7 +44,7 @@ export interface FlowRunCheckpoint {
|
|
|
44
44
|
>
|
|
45
45
|
readonly counts: Readonly<Record<string, Readonly<Record<string, number>>>>
|
|
46
46
|
readonly dependencies: DependenciesCheckpoint
|
|
47
|
-
readonly version:
|
|
47
|
+
readonly version: 7
|
|
48
48
|
readonly waits: readonly PendingWait[]
|
|
49
49
|
readonly agents: Readonly<
|
|
50
50
|
Record<
|
|
@@ -81,7 +81,8 @@ export function decodeFlowRunCheckpoint(input: unknown): FlowRunCheckpoint {
|
|
|
81
81
|
['agents', 'bindingValues', 'counts', 'dependencies', 'inputs', 'results', 'version', 'waits', ...(source.calls === undefined ? [] : ['calls'])],
|
|
82
82
|
'Flow Run checkpoint',
|
|
83
83
|
)
|
|
84
|
-
|
|
84
|
+
const version = source.version
|
|
85
|
+
if (version !== 6 && version !== 7) throw new Error('Flow Run checkpoint version is unsupported.')
|
|
85
86
|
const bindingValues = Object.fromEntries(
|
|
86
87
|
Object.entries(checkpointRecord(source.bindingValues, 'Checkpoint bindings')).map(([id, value]) => {
|
|
87
88
|
if (typeof value != 'string') throw new Error('Checkpoint binding must be a string.')
|
|
@@ -119,7 +120,7 @@ export function decodeFlowRunCheckpoint(input: unknown): FlowRunCheckpoint {
|
|
|
119
120
|
}),
|
|
120
121
|
)
|
|
121
122
|
const counts = z.record(z.string(), z.record(z.string(), z.number().int().positive().max(Number.MAX_SAFE_INTEGER))).parse(source.counts)
|
|
122
|
-
const dependencies =
|
|
123
|
+
const dependencies = decodeDependenciesCheckpoint(source.dependencies, version)
|
|
123
124
|
const savedWaitFields = {
|
|
124
125
|
jobId: z.string().min(1),
|
|
125
126
|
nodeId: z.string().min(1),
|
|
@@ -171,12 +172,13 @@ export function decodeFlowRunCheckpoint(input: unknown): FlowRunCheckpoint {
|
|
|
171
172
|
if (Object.values(dependencies.nodes).some((state) => state.status == 'running' && !jobs.has(state.jobId!)))
|
|
172
173
|
throw new Error('Checkpoint dependency has no waiting execution.')
|
|
173
174
|
if (Object.keys(agents).some((id) => !jobs.has(id))) throw new Error('Checkpoint Agent has no waiting execution.')
|
|
174
|
-
return { agents, bindingValues, counts, dependencies, inputs, results, version:
|
|
175
|
+
return { agents, bindingValues, counts, dependencies, inputs, results, version: 7, waits: pendingWaits, ...(source.calls === undefined ? {} : { calls }) }
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
export function validateCheckpoint(
|
|
178
179
|
target: { readonly graph: Graph },
|
|
179
180
|
checkpoint: FlowRunCheckpoint,
|
|
181
|
+
dependencies: Dependencies,
|
|
180
182
|
resolveInputs: (nodeId: string, node: ExecutableNode) => Readonly<Record<string, JsonValue>>,
|
|
181
183
|
): void {
|
|
182
184
|
const launch = checkpoint.inputs
|
|
@@ -249,12 +251,16 @@ export function validateCheckpoint(
|
|
|
249
251
|
throw new Error('Checkpoint launch inputs do not match the prepared graph.')
|
|
250
252
|
}
|
|
251
253
|
|
|
252
|
-
for (const
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
for (const result of checkpoint.dependencies.results) validateResults({ [result.nodeId]: result.outputs }, true, { [result.nodeId]: result })
|
|
255
|
+
const graphTriggers = Object.keys(target.graph.nodes).filter((id) => isTriggerNode(target.graph.nodes[id]!))
|
|
256
|
+
for (const [nodeId, state] of Object.entries(dependencies.state.nodes)) {
|
|
255
257
|
if (!isTriggerNode(target.graph.nodes[nodeId]!) && (state.status == 'running' || state.status == 'completed')) {
|
|
256
|
-
const triggers =
|
|
257
|
-
if (
|
|
258
|
+
const triggers = graphTriggers.filter((id) => dependencies.input(nodeId, id) != null)
|
|
259
|
+
if (
|
|
260
|
+
triggers.length != 1 ||
|
|
261
|
+
triggers[0] != triggerIds[0] ||
|
|
262
|
+
!jsonEqual(dependencies.input(nodeId, triggers[0]!)!.outputs, checkpoint.results[triggers[0]!]!.outputs)
|
|
263
|
+
)
|
|
258
264
|
throw new Error('Checkpoint input Trigger does not match the Run.')
|
|
259
265
|
}
|
|
260
266
|
if (state.output != null) validateResults({ [nodeId]: state.output }, state.status == 'running')
|
|
@@ -284,7 +290,7 @@ export function validateCheckpoint(
|
|
|
284
290
|
for (const waiting of checkpoint.waits) {
|
|
285
291
|
const node = target.graph.nodes[waiting.nodeId]
|
|
286
292
|
if (node == null || isTriggerNode(node)) throw new Error('Checkpoint waiting node is missing.')
|
|
287
|
-
const state =
|
|
293
|
+
const state = dependencies.state.nodes[waiting.nodeId]!
|
|
288
294
|
const expectedOutput = waiting.pending == null ? undefined : { pending: waiting.pending }
|
|
289
295
|
if ((state.output == null) != (expectedOutput == null) || (state.output != null && !jsonEqual(state.output, expectedOutput!)))
|
|
290
296
|
throw new Error('Checkpoint waiting dependency output is inconsistent.')
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import type { Graph, JsonValue } from '../../flow/common/change.ts'
|
|
2
2
|
|
|
3
|
+
import { dequal } from 'dequal/lite'
|
|
3
4
|
import { z } from 'zod'
|
|
4
5
|
import { isTriggerNode } from '../../flow/common/change.ts'
|
|
5
6
|
|
|
6
7
|
export interface DependenciesCheckpoint {
|
|
8
|
+
nextId: number
|
|
9
|
+
iterations: Record<string, number>
|
|
10
|
+
nodes: Record<string, Omit<DependencyState, 'input' | 'output' | 'systemOutputs'> & { input: number; output?: number }>
|
|
11
|
+
frames: readonly { readonly parent?: number; readonly input: Readonly<Record<string, number>> }[]
|
|
12
|
+
results: readonly {
|
|
13
|
+
readonly nodeId: string
|
|
14
|
+
readonly execution: number
|
|
15
|
+
readonly outputs: Readonly<Record<string, JsonValue>>
|
|
16
|
+
readonly systemOutputs?: Readonly<Record<string, JsonValue>>
|
|
17
|
+
}[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DependenciesState {
|
|
7
21
|
nextId: number
|
|
8
22
|
iterations: Record<string, number>
|
|
9
23
|
nodes: Record<string, DependencyState>
|
|
@@ -29,15 +43,11 @@ const inputSchema = z.record(
|
|
|
29
43
|
z.string(),
|
|
30
44
|
z.strictObject({ execution: z.number().int().nonnegative(), outputs: outputsSchema, systemOutputs: outputsSchema.optional() }),
|
|
31
45
|
)
|
|
32
|
-
type InputSnapshot =
|
|
46
|
+
type InputSnapshot = DependencyState['input']
|
|
33
47
|
type InputFrame =
|
|
34
48
|
| { values: InputSnapshot }
|
|
35
49
|
| { values?: InputSnapshot; source: string; result: InputSnapshot[string]; parent: InputFrame; cache: Map<string, InputSnapshot[string] | undefined> }
|
|
36
50
|
|
|
37
|
-
export function dependencyValues(input: InputSnapshot): Readonly<Record<string, Readonly<Record<string, JsonValue>>>> {
|
|
38
|
-
return Object.fromEntries(Object.entries(input).map(([id, result]) => [id, result.outputs]))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
51
|
const stateSchema = z.strictObject({
|
|
42
52
|
id: z.number().int().nonnegative(),
|
|
43
53
|
status: z.enum(['pending', 'running', 'completed', 'skipped']),
|
|
@@ -49,11 +59,97 @@ const stateSchema = z.strictObject({
|
|
|
49
59
|
jobId: z.string().optional(),
|
|
50
60
|
seeded: z.boolean(),
|
|
51
61
|
})
|
|
52
|
-
|
|
62
|
+
const legacyDependenciesSchema = z.strictObject({
|
|
53
63
|
nextId: z.number().int().nonnegative(),
|
|
54
64
|
iterations: z.record(z.string(), z.number().int().nonnegative()),
|
|
55
65
|
nodes: z.record(z.string(), stateSchema),
|
|
56
66
|
})
|
|
67
|
+
|
|
68
|
+
const referenceSchema = z.number().int().nonnegative()
|
|
69
|
+
const dependenciesSchema = z.strictObject({
|
|
70
|
+
nextId: referenceSchema,
|
|
71
|
+
iterations: z.record(z.string(), referenceSchema),
|
|
72
|
+
nodes: z.record(
|
|
73
|
+
z.string(),
|
|
74
|
+
stateSchema.omit({ input: true, output: true, systemOutputs: true }).extend({ input: referenceSchema, output: referenceSchema.optional() }),
|
|
75
|
+
),
|
|
76
|
+
frames: z.array(z.strictObject({ parent: referenceSchema.optional(), input: z.record(z.string(), referenceSchema) })),
|
|
77
|
+
results: z.array(z.strictObject({ nodeId: z.string(), execution: referenceSchema, outputs: outputsSchema, systemOutputs: outputsSchema.optional() })),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
export function decodeDependenciesCheckpoint(input: unknown, version: 6 | 7): DependenciesCheckpoint {
|
|
81
|
+
const saved = version == 6 ? saveDependencies(legacyDependenciesSchema.parse(input)) : dependenciesSchema.parse(input)
|
|
82
|
+
for (const [index, frame] of saved.frames.entries()) {
|
|
83
|
+
if (frame.parent != null && (frame.parent >= index || Object.keys(frame.input).length != 1)) throw new Error('Checkpoint input frame parent is invalid.')
|
|
84
|
+
for (const [nodeId, reference] of Object.entries(frame.input)) {
|
|
85
|
+
const result = saved.results[reference]
|
|
86
|
+
if (result == null || result.nodeId != nodeId) throw new Error('Checkpoint input result reference is invalid.')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const [nodeId, state] of Object.entries(saved.nodes)) {
|
|
90
|
+
if (saved.frames[state.input] == null) throw new Error('Checkpoint input frame reference is invalid.')
|
|
91
|
+
if (state.output != null) {
|
|
92
|
+
const result = saved.results[state.output]
|
|
93
|
+
if (result == null || result.nodeId != nodeId || result.execution != state.id) throw new Error('Checkpoint output result reference is invalid.')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const result of saved.results) {
|
|
97
|
+
if (saved.nodes[result.nodeId] == null || result.execution > saved.nodes[result.nodeId]!.id || result.execution >= saved.nextId)
|
|
98
|
+
throw new Error('Checkpoint dependency result identity is invalid.')
|
|
99
|
+
}
|
|
100
|
+
return saved
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function saveDependencies(state: DependenciesState, frames?: ReadonlyMap<string, InputFrame>): DependenciesCheckpoint {
|
|
104
|
+
const results: DependenciesCheckpoint['results'][number][] = []
|
|
105
|
+
const references = new Map<string, number>()
|
|
106
|
+
const saveResult = (nodeId: string, result: InputSnapshot[string]) => {
|
|
107
|
+
const key = JSON.stringify([nodeId, result.execution, Object.keys(result.outputs).toSorted()])
|
|
108
|
+
let reference = references.get(key)
|
|
109
|
+
if (reference == null) {
|
|
110
|
+
reference = results.length
|
|
111
|
+
references.set(key, reference)
|
|
112
|
+
results.push({ nodeId, ...result })
|
|
113
|
+
} else if (!dequal(results[reference]!.outputs, result.outputs) || !dequal(results[reference]!.systemOutputs, result.systemOutputs))
|
|
114
|
+
throw new Error('Checkpoint invocation results conflict.')
|
|
115
|
+
return reference
|
|
116
|
+
}
|
|
117
|
+
const savedFrames: DependenciesCheckpoint['frames'][number][] = []
|
|
118
|
+
const frameIds = new Map<InputFrame, number>()
|
|
119
|
+
const saveFrame = (frame: InputFrame): number => {
|
|
120
|
+
const existing = frameIds.get(frame)
|
|
121
|
+
if (existing != null) return existing
|
|
122
|
+
const saved =
|
|
123
|
+
'source' in frame
|
|
124
|
+
? { parent: saveFrame(frame.parent), input: { [frame.source]: saveResult(frame.source, frame.result) } }
|
|
125
|
+
: { input: Object.fromEntries(Object.entries(frame.values).map(([id, result]) => [id, saveResult(id, result)])) }
|
|
126
|
+
const id = savedFrames.length
|
|
127
|
+
savedFrames.push(saved)
|
|
128
|
+
frameIds.set(frame, id)
|
|
129
|
+
return id
|
|
130
|
+
}
|
|
131
|
+
const nodes = Object.fromEntries(
|
|
132
|
+
Object.entries(state.nodes).map(([id, current]) => {
|
|
133
|
+
const { id: execution, status, previous, released, jobId, seeded, output, systemOutputs } = current
|
|
134
|
+
return [
|
|
135
|
+
id,
|
|
136
|
+
{
|
|
137
|
+
id: execution,
|
|
138
|
+
status,
|
|
139
|
+
previous,
|
|
140
|
+
released,
|
|
141
|
+
seeded,
|
|
142
|
+
...(jobId == null ? {} : { jobId }),
|
|
143
|
+
input: saveFrame(frames?.get(id) ?? { values: current.input }),
|
|
144
|
+
...(output == null
|
|
145
|
+
? {}
|
|
146
|
+
: { output: saveResult(id, { execution: current.id, outputs: output, ...(systemOutputs == null ? {} : { systemOutputs }) }) }),
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
}),
|
|
150
|
+
)
|
|
151
|
+
return { nextId: state.nextId, iterations: { ...state.iterations }, nodes, frames: savedFrames, results }
|
|
152
|
+
}
|
|
57
153
|
type Edge = Graph['edges'][number]
|
|
58
154
|
interface Loop {
|
|
59
155
|
readonly header: string
|
|
@@ -64,7 +160,7 @@ interface Loop {
|
|
|
64
160
|
|
|
65
161
|
/** Dependency completion belongs to an invocation, never to a Run-wide latest-result table. */
|
|
66
162
|
export class Dependencies {
|
|
67
|
-
readonly state:
|
|
163
|
+
readonly state: DependenciesState
|
|
68
164
|
private readonly forward: Edge[] = []
|
|
69
165
|
private readonly loops = new Map<string, Loop>()
|
|
70
166
|
private readonly innerLoopsFirst: readonly Loop[]
|
|
@@ -135,15 +231,40 @@ export class Dependencies {
|
|
|
135
231
|
this.loops.set(header, { header, feedback: edges, body, continuation: descendants })
|
|
136
232
|
}
|
|
137
233
|
this.innerLoopsFirst = [...this.loops.values()].toSorted((a, b) => a.body.size - b.body.size || a.header.localeCompare(b.header))
|
|
138
|
-
this.state =
|
|
234
|
+
this.state = { nextId: saved?.nextId ?? 0, iterations: { ...saved?.iterations }, nodes: {} }
|
|
139
235
|
if (saved == null) for (const id of ids) this.state.nodes[id] = this.fresh()
|
|
140
|
-
else
|
|
236
|
+
else {
|
|
237
|
+
const frames: InputFrame[] = []
|
|
238
|
+
for (const frame of saved.frames) {
|
|
239
|
+
if (frame.parent == null)
|
|
240
|
+
frames.push({ values: Object.fromEntries(Object.entries(frame.input).map(([id, reference]) => [id, saved.results[reference]!])) })
|
|
241
|
+
else {
|
|
242
|
+
const [source, reference] = Object.entries(frame.input)[0]!
|
|
243
|
+
frames.push({ source, result: saved.results[reference]!, parent: frames[frame.parent]!, cache: new Map() })
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const [id, current] of Object.entries(saved.nodes)) {
|
|
247
|
+
const { input, output, ...node } = current
|
|
248
|
+
const result = output == null ? undefined : saved.results[output]!
|
|
249
|
+
const frame = frames[input]!
|
|
250
|
+
this.frames.set(id, frame)
|
|
251
|
+
const restored: DependencyState = { ...node, input: {}, ...(result == null ? {} : { output: result.outputs, systemOutputs: result.systemOutputs }) }
|
|
252
|
+
if (this.loops.size == 0) Object.defineProperty(restored, 'input', { enumerable: true, configurable: true, get: () => this.snapshot(frame) })
|
|
253
|
+
else restored.input = this.snapshot(frame)
|
|
254
|
+
this.state.nodes[id] = restored
|
|
255
|
+
}
|
|
256
|
+
this.validate()
|
|
257
|
+
}
|
|
141
258
|
for (const id of ids) {
|
|
142
259
|
if (this.state.nodes[id]!.status == 'pending') this.candidates.add(id)
|
|
143
|
-
this.frames.set(id, { values: this.state.nodes[id]!.input })
|
|
260
|
+
if (saved == null) this.frames.set(id, { values: this.state.nodes[id]!.input })
|
|
144
261
|
}
|
|
145
262
|
}
|
|
146
263
|
|
|
264
|
+
checkpoint(): DependenciesCheckpoint {
|
|
265
|
+
return saveDependencies(this.state, this.loops.size == 0 ? this.frames : undefined)
|
|
266
|
+
}
|
|
267
|
+
|
|
147
268
|
private fresh(): DependencyState {
|
|
148
269
|
return { id: this.state.nextId++, status: 'pending', input: {}, previous: [], released: [], seeded: false }
|
|
149
270
|
}
|
|
@@ -170,11 +291,7 @@ export class Dependencies {
|
|
|
170
291
|
throw new Error('Checkpoint predecessor does not match the graph.')
|
|
171
292
|
if (state.previous.length != new Set(state.previous).size) throw new Error('Checkpoint predecessors are duplicated.')
|
|
172
293
|
if (state.status == 'completed' && state.output == null) throw new Error('Checkpoint completed dependency has no output.')
|
|
173
|
-
if (state.previous.some((source) =>
|
|
174
|
-
for (const [source, result] of Object.entries(state.input)) {
|
|
175
|
-
if (nodes[source] == null || result.execution > nodes[source]!.id || result.execution >= nextId)
|
|
176
|
-
throw new Error('Checkpoint dependency result identity is invalid.')
|
|
177
|
-
}
|
|
294
|
+
if (state.previous.some((source) => this.input(id, source) == null)) throw new Error('Checkpoint predecessor has no input snapshot.')
|
|
178
295
|
}
|
|
179
296
|
if (Object.keys(iterations).some((id) => !this.loops.has(id))) throw new Error('Checkpoint loop scope is invalid.')
|
|
180
297
|
}
|
|
@@ -19,6 +19,7 @@ const nodeKinds = new Set([
|
|
|
19
19
|
'approval',
|
|
20
20
|
'condition',
|
|
21
21
|
'connector',
|
|
22
|
+
'connector-proxy',
|
|
22
23
|
'decision',
|
|
23
24
|
'javascript',
|
|
24
25
|
'openapi',
|
|
@@ -53,6 +54,7 @@ function nodeKind(
|
|
|
53
54
|
| 'approval'
|
|
54
55
|
| 'condition'
|
|
55
56
|
| 'connector'
|
|
57
|
+
| 'connector-proxy'
|
|
56
58
|
| 'decision'
|
|
57
59
|
| 'javascript'
|
|
58
60
|
| 'openapi'
|
|
@@ -74,6 +76,7 @@ function nodeKind(
|
|
|
74
76
|
| 'approval'
|
|
75
77
|
| 'condition'
|
|
76
78
|
| 'connector'
|
|
79
|
+
| 'connector-proxy'
|
|
77
80
|
| 'decision'
|
|
78
81
|
| 'javascript'
|
|
79
82
|
| 'openapi'
|
|
@@ -20,6 +20,7 @@ interface NodeExecutionContext {
|
|
|
20
20
|
readonly createId: FlowRunOptions['createId']
|
|
21
21
|
readonly emit: (event: SchedulerEvent) => Effect.Effect<void, Error>
|
|
22
22
|
readonly invokeTask: FlowRunOptions['invokeTask']
|
|
23
|
+
readonly admitTask?: FlowRunOptions['admitTask']
|
|
23
24
|
readonly projectFailure: FlowRunOptions['projectFailure']
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -105,6 +106,7 @@ function nodeKind(
|
|
|
105
106
|
| 'approval'
|
|
106
107
|
| 'condition'
|
|
107
108
|
| 'connector'
|
|
109
|
+
| 'connector-proxy'
|
|
108
110
|
| 'decision'
|
|
109
111
|
| 'javascript'
|
|
110
112
|
| 'openapi'
|
|
@@ -217,7 +219,11 @@ export function validateOutputs(nodeId: string, node: ExecutableNode, value: unk
|
|
|
217
219
|
: {}
|
|
218
220
|
const outputs = outputRecord(
|
|
219
221
|
jsonValue(
|
|
220
|
-
Object.fromEntries(
|
|
222
|
+
Object.fromEntries(
|
|
223
|
+
Object.entries(raw)
|
|
224
|
+
.filter(([handle]) => !discardUndeclared || Object.hasOwn(ports, handle))
|
|
225
|
+
.map(([handle, item]) => [handle, Object.hasOwn(ports, handle) && item === undefined ? null : item]),
|
|
226
|
+
),
|
|
221
227
|
`Node "${nodeId}" outputs`,
|
|
222
228
|
),
|
|
223
229
|
nodeId,
|
|
@@ -227,11 +233,7 @@ export function validateOutputs(nodeId: string, node: ExecutableNode, value: unk
|
|
|
227
233
|
const validated: [string, JsonValue][] = []
|
|
228
234
|
for (const [handle, output] of Object.entries(complete)) {
|
|
229
235
|
const port = ports[handle]
|
|
230
|
-
if (port == null)
|
|
231
|
-
if (discardUndeclared) continue
|
|
232
|
-
throw new PortValidationError(`Node "${nodeId}" output "${handle}" does not match its declaration.`)
|
|
233
|
-
}
|
|
234
|
-
if (!(output === null && port.nullable) && !matchesSchema(output, port.jsonSchema))
|
|
236
|
+
if (port == null || (!(output === null && port.nullable) && !matchesSchema(output, port.jsonSchema)))
|
|
235
237
|
throw new PortValidationError(`Node "${nodeId}" output "${handle}" does not match its declaration.`)
|
|
236
238
|
validated.push([handle, output])
|
|
237
239
|
}
|
|
@@ -316,8 +318,9 @@ export function executeNode(
|
|
|
316
318
|
const { nodeId, node, jobId, nodeInputs, flowId, runId, graph, dependencies, previousIds, saved, workflowRunId, resolution } = input
|
|
317
319
|
const config = agentConfig(node)
|
|
318
320
|
const nodeRemainingMs = saved?.remainingMs ?? node.timeoutMs
|
|
319
|
-
|
|
321
|
+
let startedAt = 0
|
|
320
322
|
const execution = Effect.gen(function* () {
|
|
323
|
+
startedAt = performance.now()
|
|
321
324
|
const kind = nodeKind(node)
|
|
322
325
|
const title = nodeTitle(node)
|
|
323
326
|
const projectedInputs = Object.fromEntries(
|
|
@@ -461,12 +464,16 @@ export function executeNode(
|
|
|
461
464
|
catch: (error) => new NodeValidationError('node.output-invalid', error),
|
|
462
465
|
})
|
|
463
466
|
})
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
467
|
+
const timed =
|
|
468
|
+
nodeRemainingMs == null
|
|
469
|
+
? execution
|
|
470
|
+
: execution.pipe(
|
|
471
|
+
Effect.timeoutOrElse({
|
|
472
|
+
duration: nodeRemainingMs,
|
|
473
|
+
orElse: () => Effect.fail(new Error(`Node "${nodeId}" timed out.`)),
|
|
474
|
+
}),
|
|
475
|
+
)
|
|
476
|
+
// Waiting for deployment admission is queueing, not node execution time.
|
|
477
|
+
// Pending workflow calls only read child state and do not consume Task capacity.
|
|
478
|
+
return node.kind == 'task' && workflowRunId == null && context.admitTask != null ? Effect.andThen(context.admitTask({ runId, nodeId, jobId }), timed) : timed
|
|
472
479
|
}
|
|
@@ -67,6 +67,7 @@ export type SchedulerEvent =
|
|
|
67
67
|
| 'approval'
|
|
68
68
|
| 'condition'
|
|
69
69
|
| 'connector'
|
|
70
|
+
| 'connector-proxy'
|
|
70
71
|
| 'decision'
|
|
71
72
|
| 'javascript'
|
|
72
73
|
| 'openapi'
|
|
@@ -157,8 +158,6 @@ export interface WaitHost {
|
|
|
157
158
|
readonly resolutions: (waitIds: readonly string[], block: boolean) => Effect.Effect<Readonly<Record<string, WaitResolution>>, Error>
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
export const waitRetentionMs = 120_000
|
|
161
|
-
|
|
162
161
|
export type FlowRunOutcome =
|
|
163
162
|
| FlowRunResult
|
|
164
163
|
| {
|
|
@@ -199,11 +198,19 @@ export interface SchedulerFailure {
|
|
|
199
198
|
readonly message: string
|
|
200
199
|
}
|
|
201
200
|
|
|
201
|
+
export interface TaskAdmission {
|
|
202
|
+
readonly runId: string
|
|
203
|
+
readonly nodeId: string
|
|
204
|
+
readonly jobId: string
|
|
205
|
+
}
|
|
206
|
+
|
|
202
207
|
export type FlowRunOptions = {
|
|
203
208
|
readonly createId: () => string
|
|
204
209
|
readonly emit?: (event: SchedulerEvent) => Effect.Effect<void, Error>
|
|
205
210
|
readonly flowId: string
|
|
206
211
|
readonly invokeTask: (invocation: TaskInvocation) => Effect.Effect<unknown, Error>
|
|
212
|
+
/** Deployment admission for each Task invocation; it completes before the node timeout starts. */
|
|
213
|
+
readonly admitTask?: (admission: TaskAdmission) => Effect.Effect<void, Error>
|
|
207
214
|
readonly projectFailure?: (error: unknown) => SchedulerFailure
|
|
208
215
|
readonly runId: string
|
|
209
216
|
readonly waits?: WaitHost
|
|
@@ -239,6 +246,7 @@ interface RunContext {
|
|
|
239
246
|
readonly createId: FlowRunOptions['createId']
|
|
240
247
|
readonly emit: (event: SchedulerEvent) => Effect.Effect<void, Error>
|
|
241
248
|
readonly invokeTask: FlowRunOptions['invokeTask']
|
|
249
|
+
readonly admitTask: FlowRunOptions['admitTask']
|
|
242
250
|
readonly prepared: PreparedFlow
|
|
243
251
|
readonly projectFailure: FlowRunOptions['projectFailure']
|
|
244
252
|
readonly waits: WaitHost | undefined
|
|
@@ -295,7 +303,6 @@ function runGraph(
|
|
|
295
303
|
let measuredAt = yield* Clock.currentTimeMillis
|
|
296
304
|
let nextCallCheckAt = measuredAt + (resume == null ? 1000 : 0)
|
|
297
305
|
let readyWorkflowRunIds = resume?.readyWorkflowRunIds
|
|
298
|
-
let idleSince: number | undefined
|
|
299
306
|
const agents: Record<string, FlowRunCheckpoint['agents'][string]> = { ...resume?.checkpoint.agents }
|
|
300
307
|
const resolveInputs = (nodeId: string, node: ExecutableNode) => resolveNodeInputs(nodeId, node, dependencies, context.bindingValues, launch[nodeId])
|
|
301
308
|
const commit = (
|
|
@@ -479,7 +486,7 @@ function runGraph(
|
|
|
479
486
|
}
|
|
480
487
|
if (resume != null) {
|
|
481
488
|
yield* Effect.try({
|
|
482
|
-
try: () => validateCheckpoint(target, resume.checkpoint, resolveInputs),
|
|
489
|
+
try: () => validateCheckpoint(target, resume.checkpoint, dependencies, resolveInputs),
|
|
483
490
|
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
|
484
491
|
})
|
|
485
492
|
}
|
|
@@ -487,15 +494,11 @@ function runGraph(
|
|
|
487
494
|
if (trigger != null) dependencies.start(trigger.nodeId, trigger.outputs)
|
|
488
495
|
}
|
|
489
496
|
|
|
490
|
-
const applyResolutions = (resolutions: Readonly<Record<string, WaitResolution
|
|
497
|
+
const applyResolutions = (resolutions: Readonly<Record<string, WaitResolution>>) => {
|
|
491
498
|
for (const saved of pendingWaits.values()) {
|
|
492
499
|
const resolution = resolutions[saved.waitId]
|
|
493
500
|
if (resolution == null) continue
|
|
494
501
|
const { action, resolvedAt, comment } = resolution
|
|
495
|
-
if (idleSince != null) {
|
|
496
|
-
idleSince = undefined
|
|
497
|
-
measuredAt = now
|
|
498
|
-
}
|
|
499
502
|
pendingWaits.delete(saved.jobId)
|
|
500
503
|
const node = target.graph.nodes[saved.nodeId] as ExecutableNode
|
|
501
504
|
runNode(
|
|
@@ -526,7 +529,7 @@ function runGraph(
|
|
|
526
529
|
}
|
|
527
530
|
while (true) {
|
|
528
531
|
const now = yield* Clock.currentTimeMillis
|
|
529
|
-
if (
|
|
532
|
+
if (remainingMs != null) remainingMs -= now - measuredAt
|
|
530
533
|
measuredAt = now
|
|
531
534
|
if (remainingMs != null && remainingMs <= 0) return yield* Effect.fail(new Error('Run exceeded its execution deadline.'))
|
|
532
535
|
if (firstCause != null) {
|
|
@@ -547,7 +550,6 @@ function runGraph(
|
|
|
547
550
|
[...pendingWaits.values()].map((wait) => wait.waitId),
|
|
548
551
|
false,
|
|
549
552
|
),
|
|
550
|
-
now,
|
|
551
553
|
)
|
|
552
554
|
if (pendingCalls.size > 0 && now >= nextCallCheckAt) {
|
|
553
555
|
for (const call of pendingCalls.values()) {
|
|
@@ -572,17 +574,14 @@ function runGraph(
|
|
|
572
574
|
if (dependencies.incomplete) return yield* Effect.fail(new Error('Execution graph contains unresolved dependencies.'))
|
|
573
575
|
break
|
|
574
576
|
}
|
|
575
|
-
if (running
|
|
576
|
-
else idleSince ??= now
|
|
577
|
-
const left = idleSince == null ? remainingMs : waitRetentionMs - (now - idleSince)
|
|
578
|
-
if (idleSince != null && (left! <= 0 || pendingCalls.size > 0)) {
|
|
577
|
+
if (running == 0 && ready.length == 0) {
|
|
579
578
|
const checkpoint: FlowRunCheckpoint = {
|
|
580
579
|
bindingValues: context.bindingValues,
|
|
581
580
|
inputs: launch,
|
|
582
581
|
results: Object.fromEntries(completed),
|
|
583
582
|
counts: context.counts,
|
|
584
|
-
dependencies: dependencies.
|
|
585
|
-
version:
|
|
583
|
+
dependencies: dependencies.checkpoint(),
|
|
584
|
+
version: 7,
|
|
586
585
|
agents,
|
|
587
586
|
waits: [...pendingWaits.values()],
|
|
588
587
|
...(pendingCalls.size == 0 ? {} : { calls: [...pendingCalls.values()] }),
|
|
@@ -602,9 +601,9 @@ function runGraph(
|
|
|
602
601
|
let next = Effect.raceFirst(activity, decision)
|
|
603
602
|
if (pendingCalls.size > 0)
|
|
604
603
|
next = Effect.raceFirst(next, Effect.sleep(Math.max(0, nextCallCheckAt - (yield* Clock.currentTimeMillis))).pipe(Effect.as({})))
|
|
605
|
-
if (
|
|
604
|
+
if (remainingMs != null) next = Effect.raceFirst(next, Effect.sleep(Math.max(0, remainingMs)).pipe(Effect.as({})))
|
|
606
605
|
const resolutions = yield* next
|
|
607
|
-
applyResolutions(resolutions
|
|
606
|
+
applyResolutions(resolutions)
|
|
608
607
|
}
|
|
609
608
|
yield* context.emit({ progress: 100, runId, type: 'run.progress' })
|
|
610
609
|
const outputNodeId = order.find((nodeId) => target.graph.nodes[nodeId]?.kind == 'outputs')
|
|
@@ -655,18 +654,7 @@ export function runFlow(prepared: PreparedFlow, options: FlowRunOptions): Effect
|
|
|
655
654
|
results: Object.fromEntries(Object.entries(decoded.results).map(([id, result]) => [id, { ...result, outputs: outputs(id, result.outputs) }])),
|
|
656
655
|
dependencies: {
|
|
657
656
|
...decoded.dependencies,
|
|
658
|
-
|
|
659
|
-
Object.entries(decoded.dependencies.nodes).map(([id, state]) => [
|
|
660
|
-
id,
|
|
661
|
-
{
|
|
662
|
-
...state,
|
|
663
|
-
...(state.output == null ? {} : { output: outputs(id, state.output) }),
|
|
664
|
-
input: Object.fromEntries(
|
|
665
|
-
Object.entries(state.input).map(([source, result]) => [source, { ...result, outputs: outputs(source, result.outputs) }]),
|
|
666
|
-
),
|
|
667
|
-
},
|
|
668
|
-
]),
|
|
669
|
-
),
|
|
657
|
+
results: decoded.dependencies.results.map((result) => ({ ...result, outputs: outputs(result.nodeId, result.outputs) })),
|
|
670
658
|
},
|
|
671
659
|
waits: decoded.waits.map((wait) => (wait.pending == null ? wait : Object.assign({}, wait, { pending: withWaitNotificationMessage(wait.pending) }))),
|
|
672
660
|
}
|
|
@@ -691,6 +679,7 @@ export function runFlow(prepared: PreparedFlow, options: FlowRunOptions): Effect
|
|
|
691
679
|
createId: options.createId,
|
|
692
680
|
emit,
|
|
693
681
|
invokeTask: options.invokeTask,
|
|
682
|
+
admitTask: options.admitTask,
|
|
694
683
|
prepared,
|
|
695
684
|
projectFailure: options.projectFailure,
|
|
696
685
|
waits: options.waits,
|