@oomol-lab/open-flow 0.1.0-beta.77 → 0.1.0-beta.79

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.
Files changed (64) hide show
  1. package/README.md +2 -0
  2. package/dist/browser/{ContentIcon-VZQO8VZU.js → ContentIcon-CMn0c-kl.js} +93 -3
  3. package/dist/browser/{FlowCanvas-BNGDMaWr.js → FlowCanvas-BQGBcfoL.js} +1675 -591
  4. package/dist/browser/{createResourceDialog-BmtakfVg.js → createResourceDialog-DkovGogv.js} +3 -3
  5. package/dist/browser/{dialog-BcFBfRB1.js → dialog-CFFuRM8s.js} +37 -86
  6. package/dist/browser/{exportSvg-CG8hEUNk.js → exportSvg-CYwyYiuH.js} +2 -2
  7. package/dist/browser/{flowChanges-BGo_4Ypo.js → flowChanges-CN2R-m1M.js} +1258 -422
  8. package/dist/browser/{flowWorkspace-BFHGev7s.js → flowWorkspace-CXVL1FNv.js} +634 -628
  9. package/dist/browser/licenses.md +1 -1
  10. package/dist/browser/preview.css +512 -462
  11. package/dist/browser/preview.js +8183 -6384
  12. package/dist/browser/{tooltip-BAWSUexx.js → tooltip-BDcVTNpu.js} +65 -2
  13. package/dist/browser/{typeScriptSession-CRoezJPN.js → typeScriptSession-CoEIptNC.js} +1 -1
  14. package/dist/browser/ui.css +5 -24
  15. package/dist/browser/workbench.css +512 -462
  16. package/dist/browser/workbench.d.ts +2 -3
  17. package/dist/browser/workbench.js +65 -690
  18. package/dist/browser/{workbenchSelect-CjK-xdm3.js → workbenchSelect-CkOS95gX.js} +2 -2
  19. package/dist/command/index.js +31 -31
  20. package/dist/common/authoring.d.ts +3 -3
  21. package/dist/common/authoringExamples.d.ts +3 -3
  22. package/dist/common/authoringSchema.d.ts +14 -22
  23. package/dist/common/control-api-conformance.js +91 -25
  24. package/dist/common/control-api.d.ts +8 -3
  25. package/dist/common/control-api.js +51 -25
  26. package/dist/common/control-requests.d.ts +9 -3
  27. package/dist/common/control-requests.js +54 -42
  28. package/dist/common/flow-authoring.js +32 -19
  29. package/dist/common/flow-change.d.ts +1 -1
  30. package/dist/common/flow-change.js +32 -19
  31. package/dist/common/flow-encoding.js +31 -18
  32. package/dist/common/flow-semantics.js +41 -26
  33. package/dist/common/flow-state.d.ts +49 -43
  34. package/dist/common/flow-state.js +95 -110
  35. package/dist/common/mcp.d.ts +35 -4
  36. package/dist/common/mcp.js +51 -26
  37. package/dist/common/run-events.js +19 -9
  38. package/dist/common/runtime-contract.d.ts +4 -1
  39. package/dist/common/runtime-contract.js +66 -17
  40. package/dist/common/runtime-nodejs-compat.js +11 -12
  41. package/dist/common/scheduler.d.ts +2 -1
  42. package/dist/common/scheduler.js +82 -55
  43. package/dist/common/state-conformance.d.ts +3 -3
  44. package/dist/index.d.ts +1 -0
  45. package/package.json +1 -1
  46. package/src/control/common/api.ts +36 -7
  47. package/src/control/common/authoring.ts +6 -5
  48. package/src/control/common/authoringExamples.ts +6 -5
  49. package/src/control/common/authoringSchema.ts +7 -7
  50. package/src/control/common/conformance.ts +31 -0
  51. package/src/control/common/mcp.ts +7 -2
  52. package/src/control/common/requests.ts +10 -1
  53. package/src/execution/common/events.ts +22 -10
  54. package/src/execution/common/node-execution.ts +1 -1
  55. package/src/execution/common/scheduler.ts +28 -14
  56. package/src/flow/common/change.ts +1 -1
  57. package/src/flow/common/changeSchema.ts +4 -4
  58. package/src/flow/common/errorHandling.ts +1 -1
  59. package/src/flow/common/semantics.ts +12 -6
  60. package/src/runtime/common/nodejsGuest.generated.json +11 -12
  61. package/src/runtime/common/runtime.ts +45 -12
  62. package/src/state/common/conformance.ts +32 -21
  63. package/src/state/common/state.ts +85 -80
  64. package/src/types/index.ts +1 -0
@@ -23,7 +23,7 @@ const nodeKinds = new Set([
23
23
  'javascript',
24
24
  'openapi',
25
25
  'http-request',
26
- 'dedupe',
26
+ 'skip-duplicates',
27
27
  'llm',
28
28
  'value',
29
29
  'wait',
@@ -57,7 +57,7 @@ function nodeKind(
57
57
  | 'javascript'
58
58
  | 'openapi'
59
59
  | 'http-request'
60
- | 'dedupe'
60
+ | 'skip-duplicates'
61
61
  | 'llm'
62
62
  | 'value'
63
63
  | 'wait'
@@ -78,7 +78,7 @@ function nodeKind(
78
78
  | 'javascript'
79
79
  | 'openapi'
80
80
  | 'http-request'
81
- | 'dedupe'
81
+ | 'skip-duplicates'
82
82
  | 'llm'
83
83
  | 'value'
84
84
  | 'wait'
@@ -95,6 +95,7 @@ function nodeContext(
95
95
  event: Record<string, unknown>,
96
96
  flows: ReadonlyMap<string, string>,
97
97
  platformRunId: string,
98
+ identify: typeof opaqueId,
98
99
  ): {
99
100
  readonly executionId: Promise<string>
100
101
  readonly flowId: string
@@ -105,7 +106,7 @@ function nodeContext(
105
106
  const flowId = flows.get(rawRunId)
106
107
  if (flowId == null) throw new TypeError('Runtime node event preceded its run.started event.')
107
108
  return {
108
- executionId: projectExecutionId(platformRunId, rawRunId, string(event.jobId, 'Runtime node event jobId')),
109
+ executionId: identify('execution', platformRunId, rawRunId, string(event.jobId, 'Runtime node event jobId')),
109
110
  flowId,
110
111
  nodeId: string(event.nodeId, 'Runtime node event nodeId'),
111
112
  rawRunId,
@@ -184,6 +185,17 @@ export function createEventProjector(platformRunId: string, nodeFailureCodes: Re
184
185
  const jobs = new Map<string, string>()
185
186
  const nodeProgressBuckets = new Map<string, number>()
186
187
  const runProgressBuckets = new Map<string, number>()
188
+ // Every event of a scope or execution repeats its identity, so each digest is computed once per projector.
189
+ const identities = new Map<string, Promise<string>>()
190
+ const identify: typeof opaqueId = (prefix, ...parts) => {
191
+ const key = JSON.stringify([prefix, ...parts])
192
+ let identity = identities.get(key)
193
+ if (identity == null) {
194
+ identity = opaqueId(prefix, ...parts)
195
+ identities.set(key, identity)
196
+ }
197
+ return identity
198
+ }
187
199
  return async (value) => {
188
200
  const event = object(value, 'Runtime event')
189
201
  const type = string(event.type, 'Runtime event type')
@@ -205,8 +217,8 @@ export function createEventProjector(platformRunId: string, nodeFailureCodes: Re
205
217
  kind: 'run.started',
206
218
  payload: {
207
219
  flowId,
208
- ...(parentRunId === undefined ? {} : { parentScopeId: await opaqueId('scope', platformRunId, parentRunId) }),
209
- scopeId: await opaqueId('scope', platformRunId, rawRunId),
220
+ ...(parentRunId === undefined ? {} : { parentScopeId: await identify('scope', platformRunId, parentRunId) }),
221
+ scopeId: await identify('scope', platformRunId, rawRunId),
210
222
  },
211
223
  }
212
224
  }
@@ -226,7 +238,7 @@ export function createEventProjector(platformRunId: string, nodeFailureCodes: Re
226
238
  payload: {
227
239
  flowId,
228
240
  progress: event.progress,
229
- scopeId: await opaqueId('scope', platformRunId, rawRunId),
241
+ scopeId: await identify('scope', platformRunId, rawRunId),
230
242
  },
231
243
  }
232
244
  }
@@ -235,7 +247,7 @@ export function createEventProjector(platformRunId: string, nodeFailureCodes: Re
235
247
  const rawRunId = runId(event)
236
248
  if (type == 'run.failed' && rawRunId == platformRunId && event.failure != null) {
237
249
  const failure = object(event.failure, 'Runtime terminal failure')
238
- const context = nodeContext(failure, flows, platformRunId)
250
+ const context = nodeContext(failure, flows, platformRunId, identify)
239
251
  return {
240
252
  kind: 'node.failed',
241
253
  payload: {
@@ -258,13 +270,13 @@ export function createEventProjector(platformRunId: string, nodeFailureCodes: Re
258
270
  type == 'node.completed' ||
259
271
  type == 'node.failed'
260
272
  ) {
261
- const context = nodeContext(event, flows, platformRunId)
273
+ const context = nodeContext(event, flows, platformRunId, identify)
262
274
  const executionId = await context.executionId
263
275
  const payload = {
264
276
  executionId,
265
277
  flowId: context.flowId,
266
278
  nodeId: context.nodeId,
267
- scopeId: await opaqueId('scope', platformRunId, context.rawRunId),
279
+ scopeId: await identify('scope', platformRunId, context.rawRunId),
268
280
  ...(event.systemOutputs === undefined ? {} : { systemOutputs: object(event.systemOutputs, 'Runtime system outputs') }),
269
281
  ...(event.handled === true ? { handled: true } : {}),
270
282
  }
@@ -109,7 +109,7 @@ function nodeKind(
109
109
  | 'javascript'
110
110
  | 'openapi'
111
111
  | 'http-request'
112
- | 'dedupe'
112
+ | 'skip-duplicates'
113
113
  | 'llm'
114
114
  | 'value'
115
115
  | 'wait'
@@ -13,6 +13,7 @@ import * as Effect from 'effect/Effect'
13
13
  import * as FiberSet from 'effect/FiberSet'
14
14
  import * as Queue from 'effect/Queue'
15
15
  import { nanoid } from 'nanoid'
16
+ import { z } from 'zod'
16
17
  import { isTriggerNode } from '../../flow/common/change.ts'
17
18
  import { nodeInputMappings } from '../../flow/common/condition.ts'
18
19
  import { nodeSystemOutputPorts, supportsNodeErrorHandling } from '../../flow/common/errorHandling.ts'
@@ -70,7 +71,7 @@ export type SchedulerEvent =
70
71
  | 'javascript'
71
72
  | 'openapi'
72
73
  | 'http-request'
73
- | 'dedupe'
74
+ | 'skip-duplicates'
74
75
  | 'llm'
75
76
  | 'value'
76
77
  | 'wait'
@@ -217,7 +218,7 @@ export type RunLaunch =
217
218
  readonly resume?: never
218
219
  }
219
220
  | {
220
- readonly resume: { readonly checkpoint: unknown }
221
+ readonly resume: { readonly checkpoint: unknown; readonly readyWorkflowRunIds?: readonly string[] }
221
222
  readonly bindingValues?: never
222
223
  readonly inputs?: never
223
224
  readonly trigger?: never
@@ -250,7 +251,7 @@ function runGraph(
250
251
  runId: string,
251
252
  launchInputs: Readonly<Record<string, Readonly<Record<string, JsonValue>>>> = {},
252
253
  trigger?: TriggerSeed,
253
- resume?: { readonly checkpoint: FlowRunCheckpoint },
254
+ resume?: { readonly checkpoint: FlowRunCheckpoint; readonly readyWorkflowRunIds?: ReadonlySet<string> },
254
255
  ): Effect.Effect<FlowRunOutcome, Error> {
255
256
  return Effect.scoped(
256
257
  Effect.gen(function* () {
@@ -292,6 +293,8 @@ function runGraph(
292
293
  const changes = yield* Queue.unbounded<void>()
293
294
  let remainingMs = context.remainingMs
294
295
  let measuredAt = yield* Clock.currentTimeMillis
296
+ let nextCallCheckAt = measuredAt + (resume == null ? 1000 : 0)
297
+ let readyWorkflowRunIds = resume?.readyWorkflowRunIds
295
298
  let idleSince: number | undefined
296
299
  const agents: Record<string, FlowRunCheckpoint['agents'][string]> = { ...resume?.checkpoint.agents }
297
300
  const resolveInputs = (nodeId: string, node: ExecutableNode) => resolveNodeInputs(nodeId, node, dependencies, context.bindingValues, launch[nodeId])
@@ -546,15 +549,20 @@ function runGraph(
546
549
  ),
547
550
  now,
548
551
  )
549
- for (const call of pendingCalls.values()) {
550
- yield* observe(
551
- call.nodeId,
552
- call.jobId,
553
- Effect.gen(function* () {
554
- const result = yield* execute(call.nodeId, target.graph.nodes[call.nodeId] as ExecutableNode, call.jobId, call.input)
555
- yield* settleNode(call.nodeId, call.jobId, result)
556
- }),
557
- ).pipe(Effect.catch(() => Effect.void))
552
+ if (pendingCalls.size > 0 && now >= nextCallCheckAt) {
553
+ for (const call of pendingCalls.values()) {
554
+ if (readyWorkflowRunIds != null && !readyWorkflowRunIds.has(call.childRunId)) continue
555
+ yield* observe(
556
+ call.nodeId,
557
+ call.jobId,
558
+ Effect.gen(function* () {
559
+ const result = yield* execute(call.nodeId, target.graph.nodes[call.nodeId] as ExecutableNode, call.jobId, call.input)
560
+ yield* settleNode(call.nodeId, call.jobId, result)
561
+ }),
562
+ ).pipe(Effect.catch(() => Effect.void))
563
+ }
564
+ readyWorkflowRunIds = undefined
565
+ nextCallCheckAt = (yield* Clock.currentTimeMillis) + 1000
558
566
  }
559
567
  const ready = dependencies.ready()
560
568
  for (const nodeId of ready) executeReady(nodeId)
@@ -592,7 +600,8 @@ function runGraph(
592
600
  true,
593
601
  )
594
602
  let next = Effect.raceFirst(activity, decision)
595
- if (pendingCalls.size > 0) next = Effect.raceFirst(next, Effect.sleep(1000).pipe(Effect.as({})))
603
+ if (pendingCalls.size > 0)
604
+ next = Effect.raceFirst(next, Effect.sleep(Math.max(0, nextCallCheckAt - (yield* Clock.currentTimeMillis))).pipe(Effect.as({})))
596
605
  if (left != null) next = Effect.raceFirst(next, Effect.sleep(Math.max(0, left)).pipe(Effect.as({})))
597
606
  const resolutions = yield* next
598
607
  applyResolutions(resolutions, yield* Clock.currentTimeMillis)
@@ -631,6 +640,11 @@ export function runFlow(prepared: PreparedFlow, options: FlowRunOptions): Effect
631
640
  return yield* Effect.fail(new Error('A resumed Flow Run cannot accept launch inputs, binding values, or a Trigger seed.'))
632
641
  }
633
642
  const decoded = options.resume == null ? undefined : decodeFlowRunCheckpoint(options.resume.checkpoint)
643
+ const readyWorkflowRunIds =
644
+ options.resume?.readyWorkflowRunIds === undefined ? undefined : new Set(z.array(z.string().min(1)).parse(options.resume.readyWorkflowRunIds))
645
+ if (readyWorkflowRunIds != null && [...readyWorkflowRunIds].some((id) => !decoded?.calls?.some((call) => call.childRunId == id))) {
646
+ return yield* Effect.fail(new Error('Ready workflow Run is not present in the checkpoint.'))
647
+ }
634
648
  const outputs = (nodeId: string, value: Readonly<Record<string, JsonValue>>) =>
635
649
  isResolutionNode(prepared.graph.nodes[nodeId]) && value.pending != null ? { ...value, pending: withWaitNotificationMessage(value.pending) } : value
636
650
  const checkpoint =
@@ -692,7 +706,7 @@ export function runFlow(prepared: PreparedFlow, options: FlowRunOptions): Effect
692
706
  options.runId,
693
707
  options.inputs,
694
708
  trigger,
695
- checkpoint == null ? undefined : { checkpoint },
709
+ checkpoint == null ? undefined : { checkpoint, readyWorkflowRunIds },
696
710
  )
697
711
  })
698
712
  return program
@@ -167,7 +167,7 @@ export interface ConditionNode extends GraphNodeBase {
167
167
 
168
168
  export type ManagedTaskExecutor =
169
169
  | import('../../http/common/httpRequest.ts').HttpRequestExecutor
170
- | import('../../state/common/state.ts').DedupeExecutor
170
+ | import('../../state/common/state.ts').SkipDuplicatesExecutor
171
171
  | WorkflowExecutor
172
172
  | import('../../decision/common/decision.ts').DecisionExecutor
173
173
  | import('../../openapi/common/openapi.ts').OpenApiExecutor
@@ -6,7 +6,7 @@ import { limitDecisionTask } from '../../decision/common/decision.ts'
6
6
  import { assertHttpAuthBindings } from '../../http/common/authBindings.ts'
7
7
  import { isHttpHost, maxHttpHosts } from '../../http/common/hosts.ts'
8
8
  import { httpRequestExecutorSchema } from '../../http/common/httpRequest.ts'
9
- import { dedupeExecutorSchema } from '../../state/common/state.ts'
9
+ import { skipDuplicatesExecutorSchema } from '../../state/common/state.ts'
10
10
  import { checkJsonDepth } from './json.ts'
11
11
  import { retryConfigurationSchema } from './retry.ts'
12
12
  import { triggerScheduleSchema } from './triggerScheduleSchema.ts'
@@ -130,7 +130,7 @@ const managedFields = z.object({
130
130
  }),
131
131
  z.strictObject({ kind: z.literal('workflow'), flowId: text, revisionId: text }),
132
132
  httpRequestExecutorSchema,
133
- dedupeExecutorSchema,
133
+ skipDuplicatesExecutorSchema,
134
134
  ]),
135
135
  })
136
136
 
@@ -729,10 +729,10 @@ function checkModelVersion(content: RevisionContent): void {
729
729
  if (
730
730
  content.modelVersion < 13 &&
731
731
  Object.values(content.document.graph.nodes).some(
732
- (candidateNode) => candidateNode.kind === 'task' && 'executor' in candidateNode.task && candidateNode.task.executor.kind === 'dedupe',
732
+ (candidateNode) => candidateNode.kind === 'task' && 'executor' in candidateNode.task && candidateNode.task.executor.kind === 'skip-duplicates',
733
733
  )
734
734
  )
735
- throw new Error('Deduplication requires Flow model v13.')
735
+ throw new Error('Skip Duplicates requires Flow model v13.')
736
736
  if (
737
737
  content.modelVersion < 12 &&
738
738
  Object.values(content.document.graph.nodes).some(
@@ -26,7 +26,7 @@ export const nodeErrorSchema = {
26
26
  } as const
27
27
 
28
28
  export function supportsNodeErrorHandling(node: GraphNode): boolean {
29
- return supportsNodeRetry(node) || (node.kind === 'task' && 'executor' in node.task && node.task.executor.kind === 'dedupe')
29
+ return supportsNodeRetry(node) || (node.kind === 'task' && 'executor' in node.task && node.task.executor.kind === 'skip-duplicates')
30
30
  }
31
31
 
32
32
  export function nodeSystemOutputPorts(node: GraphNode): Readonly<Record<string, PortDefinition>> {
@@ -2,7 +2,7 @@ import { dequal } from 'dequal/lite'
2
2
  import { decisionTaskIssues } from '../../decision/common/decision.ts'
3
3
  import { httpRequestIssues, httpAuthHandles, httpRequestTask, httpRequestUrl } from '../../http/common/httpRequest.ts'
4
4
  import { openApiIssues, authHandles } from '../../openapi/common/openapi.ts'
5
- import { dedupeTask } from '../../state/common/state.ts'
5
+ import { skipDuplicatesTask } from '../../state/common/state.ts'
6
6
  import { isTriggerNode } from './change.ts'
7
7
  import { nodeWorkflowReferences } from './workflowReferences.ts'
8
8
  export { renderPrompt } from './promptTemplate.ts'
@@ -274,14 +274,14 @@ export async function validateFlow(revision: RevisionContent, engine: EngineCont
274
274
  })
275
275
  for (const message of openApiIssues(task))
276
276
  checked.diagnostics.push({ code: 'openapi.config-invalid', column: 0, line: 1, message, path: `/document/graph/nodes/${nodeId}/task/executor` })
277
- if (task.executor.kind === 'dedupe') {
278
- const expected = dedupeTask(task.executor)
277
+ if (task.executor.kind === 'skip-duplicates') {
278
+ const expected = skipDuplicatesTask(task.executor)
279
279
  if (
280
280
  !dequal(httpPortContracts(task.inputs), httpPortContracts(expected.inputs)) ||
281
281
  !dequal(httpPortContracts(task.outputs), httpPortContracts(expected.outputs))
282
282
  )
283
283
  checked.diagnostics.push({
284
- code: 'dedupe.ports-invalid',
284
+ code: 'skip-duplicates.ports-invalid',
285
285
  column: 0,
286
286
  line: 1,
287
287
  message: 'Deduplication ports must match the fixed contract.',
@@ -457,7 +457,9 @@ export async function prepareFlow(revision: RevisionContent, engineContract: str
457
457
  if (engine == null) return { kind: 'engine-unsupported' }
458
458
  if (
459
459
  !engine.flowState &&
460
- Object.values(revision.document.graph.nodes).some((node) => node.kind === 'task' && 'executor' in node.task && node.task.executor.kind === 'dedupe')
460
+ Object.values(revision.document.graph.nodes).some(
461
+ (node) => node.kind === 'task' && 'executor' in node.task && node.task.executor.kind === 'skip-duplicates',
462
+ )
461
463
  )
462
464
  return { kind: 'engine-unsupported' }
463
465
  if (
@@ -527,11 +529,15 @@ export async function prepareFlow(revision: RevisionContent, engineContract: str
527
529
 
528
530
  export function createRuntimeProgram(prepared: PreparedFlow, entryModuleId: string, engineDigest: string): RuntimeProgram | undefined {
529
531
  if (!Object.hasOwn(prepared.modules, entryModuleId)) return
532
+ const modules = new Set([entryModuleId])
533
+ for (const id of modules) {
534
+ for (const imported of prepared.modules[id]!.imports) modules.add(imported)
535
+ }
530
536
  return {
531
537
  engineContract: prepared.engineContract,
532
538
  engineDigest,
533
539
  entryModuleId,
534
- modules: prepared.modules,
540
+ modules: Object.fromEntries([...modules].map((id) => [id, prepared.modules[id]!])),
535
541
  }
536
542
  }
537
543