@oomol-lab/open-flow 0.1.0-beta.76 → 0.1.0-beta.78

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 (74) hide show
  1. package/README.md +3 -0
  2. package/dist/browser/{ContentIcon-Cm1hbVzW.js → ContentIcon-CMn0c-kl.js} +99 -6
  3. package/dist/browser/{FlowCanvas-NzKtla_c.js → FlowCanvas-BQGBcfoL.js} +1709 -613
  4. package/dist/browser/{createResourceDialog-BgdedQqu.js → createResourceDialog-DCGxfKFM.js} +63 -57
  5. package/dist/browser/{dialog-aSElFFw2.js → dialog-WzR4CRFH.js} +76 -77
  6. package/dist/browser/{exportSvg-Dcz4HTo_.js → exportSvg-CYwyYiuH.js} +2 -2
  7. package/dist/browser/{flowChanges-NZyS7HSp.js → flowChanges-Bz4dbDN_.js} +6875 -11091
  8. package/dist/browser/{flowWorkspace-Bl0SDehy.js → flowWorkspace-BI6L6a4r.js} +1415 -3037
  9. package/dist/browser/licenses.md +1 -1
  10. package/dist/browser/preview.css +619 -462
  11. package/dist/browser/preview.js +9016 -14956
  12. package/dist/browser/theme.css +4 -2
  13. package/dist/browser/{tooltip-3XMK9jkr.js → tooltip-BDcVTNpu.js} +65 -2
  14. package/dist/browser/{typeScriptSession-C9sBGOKZ.js → typeScriptSession-CRoezJPN.js} +1 -1
  15. package/dist/browser/ui.css +112 -24
  16. package/dist/browser/ui.js +3 -2
  17. package/dist/browser/workbench.css +10020 -9842
  18. package/dist/browser/workbench.d.ts +5 -2
  19. package/dist/browser/workbench.js +2382 -2668
  20. package/dist/browser/{workbenchSelect-DBGP7Y4Z.js → workbenchSelect-DtAd0zc4.js} +2 -2
  21. package/dist/command/index.js +48 -48
  22. package/dist/common/authoring.d.ts +4 -4
  23. package/dist/common/authoringExamples.d.ts +3 -2
  24. package/dist/common/authoringSchema.d.ts +15 -4
  25. package/dist/common/control-api-conformance.js +155 -15
  26. package/dist/common/control-api-errors.d.ts +12 -0
  27. package/dist/common/control-api.d.ts +39 -2
  28. package/dist/common/control-api.js +180 -37
  29. package/dist/common/control-requests.d.ts +23 -3
  30. package/dist/common/control-requests.js +141 -14
  31. package/dist/common/engine-contract.d.ts +3 -2
  32. package/dist/common/flow-authoring.js +47 -7
  33. package/dist/common/flow-change-schema.d.ts +1 -1
  34. package/dist/common/flow-change.d.ts +2 -2
  35. package/dist/common/flow-change.js +48 -8
  36. package/dist/common/flow-encoding.js +47 -7
  37. package/dist/common/flow-semantics.js +86 -8
  38. package/dist/common/flow-state.d.ts +100 -0
  39. package/dist/common/flow-state.js +5794 -0
  40. package/dist/common/mcp.d.ts +96 -3
  41. package/dist/common/mcp.js +158 -13
  42. package/dist/common/run-events.js +19 -8
  43. package/dist/common/runtime-contract.d.ts +14 -0
  44. package/dist/common/runtime-contract.js +124 -17
  45. package/dist/common/runtime-nodejs-compat.js +11 -12
  46. package/dist/common/scheduler-node-execution.d.ts +1 -0
  47. package/dist/common/scheduler.d.ts +2 -1
  48. package/dist/common/scheduler.js +95 -40
  49. package/dist/common/state-conformance.d.ts +16 -0
  50. package/dist/common/tags.d.ts +4 -0
  51. package/dist/index.d.ts +11 -0
  52. package/package.json +5 -1
  53. package/src/control/common/api.ts +101 -11
  54. package/src/control/common/authoring.ts +7 -1
  55. package/src/control/common/authoringExamples.ts +9 -0
  56. package/src/control/common/authoringSchema.ts +18 -12
  57. package/src/control/common/errors.ts +6 -0
  58. package/src/control/common/flowDecoders.ts +1 -0
  59. package/src/control/common/mcp.ts +16 -0
  60. package/src/control/common/requests.ts +16 -2
  61. package/src/control/common/tags.ts +20 -0
  62. package/src/execution/common/events.ts +22 -7
  63. package/src/execution/common/node-execution.ts +3 -0
  64. package/src/execution/common/scheduler.ts +29 -13
  65. package/src/flow/common/change.ts +2 -1
  66. package/src/flow/common/changeSchema.ts +11 -1
  67. package/src/flow/common/errorHandling.ts +1 -1
  68. package/src/flow/common/semantics.ts +27 -1
  69. package/src/runtime/common/engineContract.ts +6 -3
  70. package/src/runtime/common/nodejsGuest.generated.json +11 -12
  71. package/src/runtime/common/runtime.ts +122 -7
  72. package/src/state/common/conformance.ts +81 -0
  73. package/src/state/common/state.ts +141 -0
  74. package/src/types/index.ts +11 -0
@@ -12473,6 +12473,63 @@ function httpRequestInputs(mappings, previous, next) {
12473
12473
  const rename = candidate != null && ["query.", "header."].some((prefix) => candidate.every((name) => name.startsWith(prefix))) ? candidate : void 0;
12474
12474
  return remapInputMappings(mappings, next.inputs, rename);
12475
12475
  }
12476
+ strictObject({
12477
+ scope: _enum(["draft", "live"]),
12478
+ values: boolean().optional()
12479
+ });
12480
+ strictObject({
12481
+ scope: _enum(["draft", "live"]),
12482
+ key: string().min(1).refine((key) => new TextEncoder().encode(key).length <= 256).optional()
12483
+ });
12484
+ strictObject({
12485
+ scope: _enum(["draft", "live"]),
12486
+ space: string().regex(/^(flow|node:.+)$/)
12487
+ });
12488
+ var skipDuplicatesConfigSchema = strictObject({ fields: array(string().refine((field) => field.length > 0 && field.split(".").every((part) => part.length > 0), "Use a nonempty dot-separated field path.")).refine((fields) => new Set(fields).size === fields.length, "Identity fields must be distinct.").default([]) });
12489
+ var skipDuplicatesExecutorSchema = skipDuplicatesConfigSchema.extend({ kind: literal("skip-duplicates") });
12490
+ function skipDuplicatesTask(config = skipDuplicatesConfigSchema.parse({}), name = "Skip Duplicates") {
12491
+ return {
12492
+ name,
12493
+ executor: {
12494
+ ...config,
12495
+ kind: "skip-duplicates"
12496
+ },
12497
+ inputs: [{
12498
+ handle: "items",
12499
+ jsonSchema: { type: "array" },
12500
+ nullable: false
12501
+ }],
12502
+ outputs: ["items", "duplicates"].map((handle) => ({
12503
+ handle,
12504
+ jsonSchema: { type: "array" },
12505
+ nullable: false
12506
+ }))
12507
+ };
12508
+ }
12509
+ object$1({
12510
+ version: literal(1),
12511
+ scope: _enum(["draft", "live"]),
12512
+ limits: object$1({
12513
+ maxValueBytes: number().positive(),
12514
+ maxValues: number().int().positive(),
12515
+ maxSpaces: number().int().positive(),
12516
+ maxIdentities: number().positive(),
12517
+ maxRunWrites: number().positive()
12518
+ }),
12519
+ values: array(object$1({
12520
+ key: string(),
12521
+ size: number().nonnegative(),
12522
+ updatedAt: number(),
12523
+ value: json$5().optional()
12524
+ })),
12525
+ spaces: array(object$1({
12526
+ space: string(),
12527
+ count: number().nonnegative(),
12528
+ pendingCount: number().nonnegative(),
12529
+ reservedCount: number().nonnegative(),
12530
+ updatedAt: number()
12531
+ }))
12532
+ });
12476
12533
  function checkJsonDepth(value, depth = 0) {
12477
12534
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
12478
12535
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -12792,7 +12849,8 @@ var managedFields = object$1({
12792
12849
  flowId: text$1,
12793
12850
  revisionId: text$1
12794
12851
  }),
12795
- httpRequestExecutorSchema
12852
+ httpRequestExecutorSchema,
12853
+ skipDuplicatesExecutorSchema
12796
12854
  ])
12797
12855
  });
12798
12856
  var managed = managedFields.transform(limitDecisionTask);
@@ -13062,7 +13120,8 @@ var revision = object$1({
13062
13120
  literal(9),
13063
13121
  literal(10),
13064
13122
  literal(11),
13065
- literal(12)
13123
+ literal(12),
13124
+ literal(13)
13066
13125
  ]),
13067
13126
  document,
13068
13127
  modules: record$3(text$1, module)
@@ -13287,6 +13346,7 @@ function changeOperationsSchema(kind) {
13287
13346
  return toJSONSchema(schema, { io: "input" });
13288
13347
  }
13289
13348
  function checkModelVersion(content) {
13349
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "skip-duplicates")) throw new Error("Skip Duplicates requires Flow model v13.");
13290
13350
  if (content.modelVersion < 12 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "moduleId" in candidateNode.task && candidateNode.task.capabilities?.some((item) => item.kind == "http"))) throw new TypeError("Code HTTP access requires Flow model version 12.");
13291
13351
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind == "http-request")) throw new TypeError("HTTP Request requires Flow model version 11.");
13292
13352
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && ("moduleId" in candidateNode.task ? candidateNode.task.capabilities?.some((item) => item.kind == "workflow") : candidateNode.task.executor.kind == "agent" && candidateNode.task.executor.tools.some((item) => item.kind == "workflow")))) throw new TypeError("Workflow tools require Flow model version 11.");
@@ -13308,8 +13368,8 @@ function assertDocumentHttpAuthBindings(content) {
13308
13368
  }
13309
13369
  //#endregion
13310
13370
  //#region src/runtime/common/engineContract.ts
13311
- /** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
13312
- var currentEngineContract = "open-flow-engine/v9";
13371
+ /** v10 adds persistent Flow State to the published v9 Node compatibility and fetch contract. */
13372
+ var currentEngineContract = "open-flow-engine/v10";
13313
13373
  /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
13314
13374
  var nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
13315
13375
  var platformSource = (contract) => `export const engineContract = ${JSON.stringify(contract)}
@@ -13354,13 +13414,19 @@ var nodejsContract = {
13354
13414
  ].flatMap((name) => [name, `node:${name}`])),
13355
13415
  platformSource: platformSource(nodejsEngineContract)
13356
13416
  };
13357
- var currentContract = {
13417
+ var v9Contract = {
13358
13418
  ...nodejsContract,
13359
13419
  codeFetch: true,
13420
+ platformSource: platformSource("open-flow-engine/v9")
13421
+ };
13422
+ var currentContract = {
13423
+ ...v9Contract,
13424
+ flowState: true,
13360
13425
  platformSource: platformSource(currentEngineContract)
13361
13426
  };
13362
13427
  function findEngineContract(contract) {
13363
- if (contract == "open-flow-engine/v9") return currentContract;
13428
+ if (contract == "open-flow-engine/v10") return currentContract;
13429
+ if (contract == "open-flow-engine/v9") return v9Contract;
13364
13430
  if (contract == "open-flow-engine/v8") return v8Contract;
13365
13431
  if (contract == "open-flow-engine/v8/nodejs-compat-v1") return nodejsContract;
13366
13432
  if (contract == "open-flow-engine/v7" || contract == "open-flow-engine/v7/nodejs-compat-v1") return {
@@ -13965,6 +14031,7 @@ var authoringTypes = [
13965
14031
  "decision",
13966
14032
  "openapi",
13967
14033
  "http-request",
14034
+ "skip-duplicates",
13968
14035
  "condition",
13969
14036
  "value",
13970
14037
  "wait",
@@ -14096,6 +14163,7 @@ var authoringConfigurations = {
14096
14163
  "connector": authoringDefinitionSchemas.connector.omit({ outputMode: true }).strict(),
14097
14164
  "openapi": authoringOpenApiSchema,
14098
14165
  "http-request": httpRequestConfigSchema,
14166
+ "skip-duplicates": skipDuplicatesConfigSchema,
14099
14167
  "agent": authoringDefinitionSchemas.agent.omit({
14100
14168
  prompt: true,
14101
14169
  tools: true
@@ -14315,7 +14383,7 @@ function authoringSchema(kind) {
14315
14383
  guidance: "Read a node type schema before creating or configuring it. Use $alias to reference a preceding node.add in the same batch. Read returns stable node references. Long code and prompt fields use text reads and edits. Changes use the observed baseRevision and a stable requestId."
14316
14384
  } : {
14317
14385
  type: kind,
14318
- onError: retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ["stop", "error"] } : false,
14386
+ onError: kind == "skip-duplicates" || retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ["stop", "error"] } : false,
14319
14387
  retry: retryNodeTypes.some((nodeType) => nodeType == kind) ? toJSONSchema(retryConfigurationSchema) : false,
14320
14388
  inputSources: ["poll", "integration"].includes(kind) ? [
14321
14389
  "value",
@@ -14345,7 +14413,7 @@ function authoringSchema(kind) {
14345
14413
  "wait",
14346
14414
  "approval"
14347
14415
  ].includes(kind),
14348
- guidance: kind == "webhook-response" ? "Prepare an HTTP response using fixed inputs body (any JSON, default empty text), statusCode (number, default 200) and headers (scalar-valued object, default {}). Select this node in Webhook options.responseBody as {kind: \"webhook-response\", nodeId: \"node reference\", timeoutMs: 30000}. Its first completion responds immediately while the Run continues. It has no business outputs. Multiple response nodes are allowed; each Webhook selects one reachable node." : kind == "outputs" ? "Declare output fields through config.outputDefs. Set their values through node.add.inputs or input.set. Outputs is a singleton execution sink; it waits for its predecessors and produces workflow business outputs independently of node logs. No downstream edges or data references to Outputs are allowed." : kind == "webhook" ? "Use options.responseBody: {kind: \"webhook-response\", nodeId: \"node ID or $alias\", timeoutMs: 30000} to respond on the selected node’s first completion while the Run continues. Body, statusCode and headers come exclusively from that node; clear workflow-output-only status/header references when switching. Alternatively configure options.responseBody as {kind: \"workflow-outputs\", output?: \"top-level output name\", timeoutMs?: 30000} to respond after the Run succeeds. Omit output for the complete object. Connect Webhook to Outputs. Timeout is 1000–60000 ms; HTTP timeout does not cancel the Run. Strings return text; other values return JSON. Only in this mode, options.responseStatusCodeOutput can select a numeric top-level workflow output as the HTTP status, overriding the preserved fixed responseStatusCode (default 200). Its runtime value must be an integer from 200 to 599, excluding 204, 205, 304; missing or invalid values return 500. Use responseHeaders for fixed text headers. In workflow-output mode, responseHeaderOutputs maps header names to top-level output names; values can be string, number or boolean and are converted to text. Alternatively responseHeadersSource: {kind: \"workflow-outputs\", output?: \"object field\"} replaces the entire header object (omit output for all outputs), preserving but ignoring per-header settings. Missing, null, complex or invalid header values return 500 without fallback. Header restrictions and Content-Type policy still apply. Omit responseBody for immediate responseData. Clear options.responseBody, options.responseStatusCodeOutput, options.responseHeadersSource and options.responseHeaderOutputs explicitly to switch back." : kind == "start" ? "Declare workflow inputs through config.inputDefs; read them in Start node details. REST calls and manual runs supply them as trigger.outputs. Downstream nodes bind these parameters as Start outputs. Start has no input sources or defaults; non-nullable parameters are required, and omitted nullable parameters become null." : kind == "condition" ? "Edit branch operands through config.branches. A branch can test one expression, all expressions, or any alternatives. Unmatched execution uses branch otherwise." : ["poll", "integration"].includes(kind) ? "Configure static parameters with node.add.inputs or input.set. Definitions come from the selected Trigger key; execution output and variable sources are unavailable." : kind == "llm" ? "Configure model settings, message templates and history as defaults. Input bindings with the same names override these defaults; named business inputs can be bound directly. Template strings interpolate {{name}}." : kind == "http-request" ? "Configure method, query/headers modes and names, bodyFormat, authentication, responseFormat and statusPolicy. Bind url, query.<name>/header.<name> or whole query/headers objects, body and auth.token (Basic: auth.username/auth.password). Credentials accept variables or upstream outputs only. Outputs: body, statusCode, headers. Authentication is replaced as a complete choice." : kind == "openapi" ? "Select an operation; the tool resolves its ports and authentication. Read authenticationChoices from node details. authentication is replaced as a complete choice. Bind advertised credentials through inputs." : kind == "workflow" ? "Select another workflow with config.flowId. A workflow cannot call itself, including in a disconnected node. Owned unpublished or unavailable targets may be saved as incomplete drafts. Use node.refresh to load the current published interface." : kind == "agent" ? "Tools accept Connector Actions or {kind: \"workflow\", flowId, name, description, approval, inputs}. Workflow parameter sources use model, input or value. The platform derives the interface. Refresh a workflow tool with node.refresh and tool: its name." : kind == "code" ? codeGuidance : "Supply business configuration and input data. Fixed definitions are assembled by the tool. Existing constraints and defaults are preserved on partial updates.",
14416
+ guidance: kind == "webhook-response" ? "Prepare an HTTP response using fixed inputs body (any JSON, default empty text), statusCode (number, default 200) and headers (scalar-valued object, default {}). Select this node in Webhook options.responseBody as {kind: \"webhook-response\", nodeId: \"node reference\", timeoutMs: 30000}. Its first completion responds immediately while the Run continues. It has no business outputs. Multiple response nodes are allowed; each Webhook selects one reachable node." : kind == "outputs" ? "Declare output fields through config.outputDefs. Set their values through node.add.inputs or input.set. Outputs is a singleton execution sink; it waits for its predecessors and produces workflow business outputs independently of node logs. No downstream edges or data references to Outputs are allowed." : kind == "webhook" ? "Use options.responseBody: {kind: \"webhook-response\", nodeId: \"node ID or $alias\", timeoutMs: 30000} to respond on the selected node’s first completion while the Run continues. Body, statusCode and headers come exclusively from that node; clear workflow-output-only status/header references when switching. Alternatively configure options.responseBody as {kind: \"workflow-outputs\", output?: \"top-level output name\", timeoutMs?: 30000} to respond after the Run succeeds. Omit output for the complete object. Connect Webhook to Outputs. Timeout is 1000–60000 ms; HTTP timeout does not cancel the Run. Strings return text; other values return JSON. Only in this mode, options.responseStatusCodeOutput can select a numeric top-level workflow output as the HTTP status, overriding the preserved fixed responseStatusCode (default 200). Its runtime value must be an integer from 200 to 599, excluding 204, 205, 304; missing or invalid values return 500. Use responseHeaders for fixed text headers. In workflow-output mode, responseHeaderOutputs maps header names to top-level output names; values can be string, number or boolean and are converted to text. Alternatively responseHeadersSource: {kind: \"workflow-outputs\", output?: \"object field\"} replaces the entire header object (omit output for all outputs), preserving but ignoring per-header settings. Missing, null, complex or invalid header values return 500 without fallback. Header restrictions and Content-Type policy still apply. Omit responseBody for immediate responseData. Clear options.responseBody, options.responseStatusCodeOutput, options.responseHeadersSource and options.responseHeaderOutputs explicitly to switch back." : kind == "start" ? "Declare workflow inputs through config.inputDefs; read them in Start node details. REST calls and manual runs supply them as trigger.outputs. Downstream nodes bind these parameters as Start outputs. Start has no input sources or defaults; non-nullable parameters are required, and omitted nullable parameters become null." : kind == "condition" ? "Edit branch operands through config.branches. A branch can test one expression, all expressions, or any alternatives. Unmatched execution uses branch otherwise." : ["poll", "integration"].includes(kind) ? "Configure static parameters with node.add.inputs or input.set. Definitions come from the selected Trigger key; execution output and variable sources are unavailable." : kind == "llm" ? "Configure model settings, message templates and history as defaults. Input bindings with the same names override these defaults; named business inputs can be bound directly. Template strings interpolate {{name}}." : kind == "skip-duplicates" ? "Bind an items array. Configure fields as dot-separated identity paths; an empty list uses each scalar item. Outputs are items and duplicates. History belongs to this node and commits only when the Run completes successfully. Fields accept strings or finite numbers; missing or complex values reject the batch." : kind == "http-request" ? "Configure method, query/headers modes and names, bodyFormat, authentication, responseFormat and statusPolicy. Bind url, query.<name>/header.<name> or whole query/headers objects, body and auth.token (Basic: auth.username/auth.password). Credentials accept variables or upstream outputs only. Outputs: body, statusCode, headers. Authentication is replaced as a complete choice." : kind == "openapi" ? "Select an operation; the tool resolves its ports and authentication. Read authenticationChoices from node details. authentication is replaced as a complete choice. Bind advertised credentials through inputs." : kind == "workflow" ? "Select another workflow with config.flowId. A workflow cannot call itself, including in a disconnected node. Owned unpublished or unavailable targets may be saved as incomplete drafts. Use node.refresh to load the current published interface." : kind == "agent" ? "Tools accept Connector Actions or {kind: \"workflow\", flowId, name, description, approval, inputs}. Workflow parameter sources use model, input or value. The platform derives the interface. Refresh a workflow tool with node.refresh and tool: its name." : kind == "code" ? codeGuidance : "Supply business configuration and input data. Fixed definitions are assembled by the tool. Existing constraints and defaults are preserved on partial updates.",
14349
14417
  example: {
14350
14418
  op: "node.add",
14351
14419
  as: "step",
@@ -14368,6 +14436,13 @@ function authoringSchema(kind) {
14368
14436
  }]
14369
14437
  } },
14370
14438
  "integration": { config: { key: "<Trigger key>" } },
14439
+ "skip-duplicates": {
14440
+ config: { fields: ["id"] },
14441
+ inputs: { items: {
14442
+ kind: "value",
14443
+ value: [{ id: "a" }, { id: "b" }]
14444
+ } }
14445
+ },
14371
14446
  "http-request": {
14372
14447
  config: {},
14373
14448
  inputs: { url: {
@@ -14627,7 +14702,7 @@ var nodeErrorSchema = {
14627
14702
  }
14628
14703
  };
14629
14704
  function supportsNodeErrorHandling(node) {
14630
- return supportsNodeRetry(node);
14705
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "skip-duplicates";
14631
14706
  }
14632
14707
  function nodeSystemOutputPorts(node) {
14633
14708
  return supportsNodeErrorHandling(node) && node.onError == "error" ? { error: {
@@ -14802,10 +14877,10 @@ function upgradeRevisionContent(content) {
14802
14877
  function upgradeDecodedRevisionContent(content) {
14803
14878
  const graph = content.document.graph;
14804
14879
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
14805
- if (!hasManual && content.modelVersion == 12) return content;
14880
+ if (!hasManual && content.modelVersion == 13) return content;
14806
14881
  return {
14807
14882
  ...content,
14808
- modelVersion: 12,
14883
+ modelVersion: 13,
14809
14884
  document: {
14810
14885
  ...content.document,
14811
14886
  graph: hasManual ? {
@@ -14840,7 +14915,7 @@ function invalid(message) {
14840
14915
  throw new FlowChangeError(message);
14841
14916
  }
14842
14917
  function applyFlowChanges(content, operations) {
14843
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
14918
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
14844
14919
  const document = { ...source.document };
14845
14920
  const modules = { ...source.modules };
14846
14921
  for (const operation of operations) switch (operation.kind) {
@@ -15288,7 +15363,7 @@ function applyFlowChanges(content, operations) {
15288
15363
  assertDocumentHttpAuthBindings(document);
15289
15364
  return upgradeDecodedRevisionContent({
15290
15365
  document,
15291
- modelVersion: 12,
15366
+ modelVersion: 13,
15292
15367
  modules
15293
15368
  });
15294
15369
  }
@@ -16513,7 +16588,7 @@ function authoringNode(content, ref) {
16513
16588
  inputs: fieldsView([...task.inputs, ...node.kind == "task" ? node.additionalInputs ?? [] : []].filter((port) => !("handle" in port) || !llmReservedInputs.includes(port.handle)))
16514
16589
  };
16515
16590
  else if (task.executor.kind == "workflow") config = { flowId: task.executor.flowId };
16516
- else if (task.executor.kind == "http-request") {
16591
+ else if (task.executor.kind == "http-request" || task.executor.kind == "skip-duplicates") {
16517
16592
  const { kind: _kind, ...settings } = task.executor;
16518
16593
  config = settings;
16519
16594
  } else if (task.executor.kind == "openapi") config = { ...openApiConfigView(task.executor) };
@@ -16903,6 +16978,14 @@ async function compileAuthoring(base, request, host) {
16903
16978
  };
16904
16979
  } else task = await connectorTask(config, String(update.name));
16905
16980
  } else if (type == "decision") task = decisionTask(config.questions, String(update.name));
16981
+ else if (type == "skip-duplicates") task = {
16982
+ ...before,
16983
+ name: String(update.name),
16984
+ executor: {
16985
+ kind: "skip-duplicates",
16986
+ ...skipDuplicatesConfigSchema.parse(config)
16987
+ }
16988
+ };
16906
16989
  else if (type == "http-request") {
16907
16990
  task = httpRequestTask(httpRequestConfigSchema.parse(config), String(update.name), before);
16908
16991
  next = {
@@ -17250,6 +17333,9 @@ async function compileAuthoring(base, request, host) {
17250
17333
  case "connector":
17251
17334
  changes = createManagedTask(identity, await connectorTask(config, name));
17252
17335
  break;
17336
+ case "skip-duplicates":
17337
+ changes = createManagedTask(identity, skipDuplicatesTask(skipDuplicatesConfigSchema.parse(config), name));
17338
+ break;
17253
17339
  case "http-request":
17254
17340
  changes = createManagedTask(identity, httpRequestTask(httpRequestConfigSchema.parse(config), name));
17255
17341
  break;
@@ -17471,6 +17557,7 @@ var descriptions = {
17471
17557
  "poll": "Gmail polling by key; replace CONNECTION_ID with an active Gmail connection.",
17472
17558
  "integration": "Telegram webhook integration by key; replace CONNECTION_ID with an active Telegram connection.",
17473
17559
  "decision": "AI Decision evaluates named questions against target and returns a complete answer object per output. Configure target before running.",
17560
+ "skip-duplicates": "Skip items already recorded by successful Runs. Configure identity fields; an empty list uses each string or numeric item. History is private to the node and only commits after Run success.",
17474
17561
  "http-request": "A single HTTP request. Bind url and request inputs; outputs are body, statusCode and headers.",
17475
17562
  "openapi": "A fixed JSON API operation. Replace the example document and URL before running.",
17476
17563
  "connector": "A Connector Task and its node. Replace ACTION_ID, CONNECTION_ID and port definitions using connector_get / connector show. Set connectionId to an empty string to defer account configuration; execution will require an account.",
@@ -17515,6 +17602,21 @@ function authoringExample(name) {
17515
17602
  instructions: "Does the customer need support?"
17516
17603
  }]);
17517
17604
  break;
17605
+ case "skip-duplicates":
17606
+ operations = [...createManagedTask({ nodeId: "skip-duplicates" }, skipDuplicatesTask()), {
17607
+ kind: "graph.node.input.set",
17608
+ nodeId: "skip-duplicates",
17609
+ handle: "items",
17610
+ value: {
17611
+ kind: "value",
17612
+ value: [
17613
+ "a",
17614
+ "b",
17615
+ "a"
17616
+ ]
17617
+ }
17618
+ }];
17619
+ break;
17518
17620
  case "http-request":
17519
17621
  operations = [...createManagedTask({ nodeId: "http" }, httpRequestTask()), {
17520
17622
  kind: "graph.node.input.set",
@@ -17758,6 +17860,8 @@ var json = json$5();
17758
17860
  var id = string().min(1);
17759
17861
  var version = literal(1);
17760
17862
  var flowName = id.refine((value) => value == value.trim() && resourceNameIssue(value) == null, "Flow name is invalid.");
17863
+ var tagIds = array(id).transform((ids) => [...new Set(ids)].toSorted());
17864
+ var tagName = id.transform((name) => name.trim()).refine((name) => resourceNameIssue(name) == null, "Tag name is invalid.");
17761
17865
  var inputs = record$3(string(), record$3(string(), json));
17762
17866
  var trigger = strictObject({
17763
17867
  nodeId: id,
@@ -17784,13 +17888,33 @@ var schemas = {
17784
17888
  file: json,
17785
17889
  name: flowName.optional(),
17786
17890
  teamId: id.optional(),
17891
+ tagIds: tagIds.optional(),
17787
17892
  version
17788
17893
  }),
17789
17894
  createFlow: strictObject({
17790
17895
  name: flowName,
17791
17896
  teamId: id.optional(),
17897
+ tagIds: tagIds.optional(),
17898
+ presentation: record$3(string(), json).optional(),
17899
+ version
17900
+ }),
17901
+ saveTag: strictObject({
17902
+ name: tagName,
17903
+ version
17904
+ }),
17905
+ setFlowTags: strictObject({
17906
+ tagIds,
17792
17907
  version
17793
17908
  }),
17909
+ updateFlow: union([strictObject({
17910
+ name: flowName,
17911
+ tagIds: tagIds.optional(),
17912
+ version
17913
+ }), strictObject({
17914
+ name: flowName.optional(),
17915
+ tagIds,
17916
+ version
17917
+ })]),
17794
17918
  renameFlow: strictObject({
17795
17919
  name: flowName,
17796
17920
  version
@@ -17895,6 +18019,9 @@ var controlRequests = {
17895
18019
  eventSourceRevision: decoder(schemas.eventSourceRevision),
17896
18020
  importFlow: decoder(schemas.importFlow),
17897
18021
  createFlow: decoder(schemas.createFlow),
18022
+ saveTag: decoder(schemas.saveTag),
18023
+ setFlowTags: decoder(schemas.setFlowTags),
18024
+ updateFlow: decoder(schemas.updateFlow),
17898
18025
  renameFlow: decoder(schemas.renameFlow),
17899
18026
  changeDraft(value) {
17900
18027
  const body = schemas.changeDraft.parse(value);
@@ -9,9 +9,10 @@ export interface EngineContract {
9
9
  readonly httpRequests?: boolean;
10
10
  /** Code may call `context.fetch` for hosts declared by its `http` Capability. */
11
11
  readonly codeFetch?: boolean;
12
+ readonly flowState?: boolean;
12
13
  }
13
- /** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
14
- export declare const currentEngineContract = "open-flow-engine/v9";
14
+ /** v10 adds persistent Flow State to the published v9 Node compatibility and fetch contract. */
15
+ export declare const currentEngineContract = "open-flow-engine/v10";
15
16
  /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
16
17
  export declare const nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
17
18
  export declare function findEngineContract(contract: string): EngineContract | undefined;
@@ -8760,6 +8760,43 @@ var httpRequestExecutorSchema = strictObject({
8760
8760
  ]).default("auto"),
8761
8761
  statusPolicy: _enum(["success", "all"]).default("success")
8762
8762
  }).extend({ kind: literal("http-request") });
8763
+ strictObject({
8764
+ scope: _enum(["draft", "live"]),
8765
+ values: boolean().optional()
8766
+ });
8767
+ strictObject({
8768
+ scope: _enum(["draft", "live"]),
8769
+ key: string().min(1).refine((key) => new TextEncoder().encode(key).length <= 256).optional()
8770
+ });
8771
+ strictObject({
8772
+ scope: _enum(["draft", "live"]),
8773
+ space: string().regex(/^(flow|node:.+)$/)
8774
+ });
8775
+ var skipDuplicatesExecutorSchema = strictObject({ fields: array(string().refine((field) => field.length > 0 && field.split(".").every((part) => part.length > 0), "Use a nonempty dot-separated field path.")).refine((fields) => new Set(fields).size === fields.length, "Identity fields must be distinct.").default([]) }).extend({ kind: literal("skip-duplicates") });
8776
+ object({
8777
+ version: literal(1),
8778
+ scope: _enum(["draft", "live"]),
8779
+ limits: object({
8780
+ maxValueBytes: number().positive(),
8781
+ maxValues: number().int().positive(),
8782
+ maxSpaces: number().int().positive(),
8783
+ maxIdentities: number().positive(),
8784
+ maxRunWrites: number().positive()
8785
+ }),
8786
+ values: array(object({
8787
+ key: string(),
8788
+ size: number().nonnegative(),
8789
+ updatedAt: number(),
8790
+ value: json$1().optional()
8791
+ })),
8792
+ spaces: array(object({
8793
+ space: string(),
8794
+ count: number().nonnegative(),
8795
+ pendingCount: number().nonnegative(),
8796
+ reservedCount: number().nonnegative(),
8797
+ updatedAt: number()
8798
+ }))
8799
+ });
8763
8800
  function checkJsonDepth(value, depth = 0) {
8764
8801
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8765
8802
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -9055,7 +9092,8 @@ var managedFields = object({
9055
9092
  flowId: text,
9056
9093
  revisionId: text
9057
9094
  }),
9058
- httpRequestExecutorSchema
9095
+ httpRequestExecutorSchema,
9096
+ skipDuplicatesExecutorSchema
9059
9097
  ])
9060
9098
  });
9061
9099
  var managed = managedFields.transform(limitDecisionTask);
@@ -9325,7 +9363,8 @@ var revision = object({
9325
9363
  literal(9),
9326
9364
  literal(10),
9327
9365
  literal(11),
9328
- literal(12)
9366
+ literal(12),
9367
+ literal(13)
9329
9368
  ]),
9330
9369
  document,
9331
9370
  modules: record$1(text, module)
@@ -9526,6 +9565,7 @@ array(union([...new Map(Object.entries(shapes).map(([kind, shape]) => [kind, sha
9526
9565
  ...shape
9527
9566
  })])).values()])).min(1);
9528
9567
  function checkModelVersion(content) {
9568
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "skip-duplicates")) throw new Error("Skip Duplicates requires Flow model v13.");
9529
9569
  if (content.modelVersion < 12 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "moduleId" in candidateNode.task && candidateNode.task.capabilities?.some((item) => item.kind == "http"))) throw new TypeError("Code HTTP access requires Flow model version 12.");
9530
9570
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind == "http-request")) throw new TypeError("HTTP Request requires Flow model version 11.");
9531
9571
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && ("moduleId" in candidateNode.task ? candidateNode.task.capabilities?.some((item) => item.kind == "workflow") : candidateNode.task.executor.kind == "agent" && candidateNode.task.executor.tools.some((item) => item.kind == "workflow")))) throw new TypeError("Workflow tools require Flow model version 11.");
@@ -9604,7 +9644,7 @@ function setConditionInput(node, handle, mapping) {
9604
9644
  //#endregion
9605
9645
  //#region src/flow/common/errorHandling.ts
9606
9646
  function supportsNodeErrorHandling(node) {
9607
- return supportsNodeRetry(node);
9647
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "skip-duplicates";
9608
9648
  }
9609
9649
  //#endregion
9610
9650
  //#region src/flow/common/change.ts
@@ -9755,10 +9795,10 @@ function isTriggerNode(node) {
9755
9795
  function upgradeDecodedRevisionContent(content) {
9756
9796
  const graph = content.document.graph;
9757
9797
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
9758
- if (!hasManual && content.modelVersion == 12) return content;
9798
+ if (!hasManual && content.modelVersion == 13) return content;
9759
9799
  return {
9760
9800
  ...content,
9761
- modelVersion: 12,
9801
+ modelVersion: 13,
9762
9802
  document: {
9763
9803
  ...content.document,
9764
9804
  graph: hasManual ? {
@@ -9793,7 +9833,7 @@ function invalid(message) {
9793
9833
  throw new FlowChangeError(message);
9794
9834
  }
9795
9835
  function applyFlowChanges(content, operations) {
9796
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9836
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9797
9837
  const document = { ...source.document };
9798
9838
  const modules = { ...source.modules };
9799
9839
  for (const operation of operations) switch (operation.kind) {
@@ -10241,7 +10281,7 @@ function applyFlowChanges(content, operations) {
10241
10281
  assertDocumentHttpAuthBindings(document);
10242
10282
  return upgradeDecodedRevisionContent({
10243
10283
  document,
10244
- modelVersion: 12,
10284
+ modelVersion: 13,
10245
10285
  modules
10246
10286
  });
10247
10287
  }
@@ -1,6 +1,6 @@
1
1
  import type { ChangeOperation, FlowDocument, JsonValue, RevisionContent } from './flow-change.js';
2
2
  import { z } from 'zod';
3
- export declare const currentFlowModelVersion = 12;
3
+ export declare const currentFlowModelVersion = 13;
4
4
  export declare const connectorCapabilitySchema: z.ZodUnion<readonly [z.ZodObject<{
5
5
  kind: z.ZodLiteral<"connector">;
6
6
  action: z.ZodString;
@@ -71,7 +71,7 @@ export interface ConditionNode extends GraphNodeBase {
71
71
  readonly matchMode: 'first' | 'all';
72
72
  readonly kind: 'condition';
73
73
  }
74
- export type ManagedTaskExecutor = import('./http-request.js').HttpRequestExecutor | WorkflowExecutor | import('./decision.js').DecisionExecutor | import('./openapi.js').OpenApiExecutor | {
74
+ export type ManagedTaskExecutor = import('./http-request.js').HttpRequestExecutor | import('./flow-state.js').SkipDuplicatesExecutor | WorkflowExecutor | import('./decision.js').DecisionExecutor | import('./openapi.js').OpenApiExecutor | {
75
75
  readonly kind: 'connector';
76
76
  readonly action: string;
77
77
  readonly connectionId?: string;
@@ -160,7 +160,7 @@ export interface FlowDocument {
160
160
  }
161
161
  export interface RevisionContent {
162
162
  readonly document: FlowDocument;
163
- readonly modelVersion: 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | typeof currentFlowModelVersion;
163
+ readonly modelVersion: 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | typeof currentFlowModelVersion;
164
164
  readonly modules: Readonly<Record<string, CodeModule>>;
165
165
  }
166
166
  export interface GraphEdge {
@@ -8833,6 +8833,43 @@ var httpRequestExecutorSchema = strictObject({
8833
8833
  ]).default("auto"),
8834
8834
  statusPolicy: _enum(["success", "all"]).default("success")
8835
8835
  }).extend({ kind: literal("http-request") });
8836
+ strictObject({
8837
+ scope: _enum(["draft", "live"]),
8838
+ values: boolean().optional()
8839
+ });
8840
+ strictObject({
8841
+ scope: _enum(["draft", "live"]),
8842
+ key: string().min(1).refine((key) => new TextEncoder().encode(key).length <= 256).optional()
8843
+ });
8844
+ strictObject({
8845
+ scope: _enum(["draft", "live"]),
8846
+ space: string().regex(/^(flow|node:.+)$/)
8847
+ });
8848
+ var skipDuplicatesExecutorSchema = strictObject({ fields: array(string().refine((field) => field.length > 0 && field.split(".").every((part) => part.length > 0), "Use a nonempty dot-separated field path.")).refine((fields) => new Set(fields).size === fields.length, "Identity fields must be distinct.").default([]) }).extend({ kind: literal("skip-duplicates") });
8849
+ object({
8850
+ version: literal(1),
8851
+ scope: _enum(["draft", "live"]),
8852
+ limits: object({
8853
+ maxValueBytes: number().positive(),
8854
+ maxValues: number().int().positive(),
8855
+ maxSpaces: number().int().positive(),
8856
+ maxIdentities: number().positive(),
8857
+ maxRunWrites: number().positive()
8858
+ }),
8859
+ values: array(object({
8860
+ key: string(),
8861
+ size: number().nonnegative(),
8862
+ updatedAt: number(),
8863
+ value: json$1().optional()
8864
+ })),
8865
+ spaces: array(object({
8866
+ space: string(),
8867
+ count: number().nonnegative(),
8868
+ pendingCount: number().nonnegative(),
8869
+ reservedCount: number().nonnegative(),
8870
+ updatedAt: number()
8871
+ }))
8872
+ });
8836
8873
  function checkJsonDepth(value, depth = 0) {
8837
8874
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8838
8875
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -8933,7 +8970,7 @@ var webhookResponseTimeout = {
8933
8970
  }).jsonSchema;
8934
8971
  //#endregion
8935
8972
  //#region src/flow/common/changeSchema.ts
8936
- var currentFlowModelVersion = 12;
8973
+ var currentFlowModelVersion = 13;
8937
8974
  var text = string();
8938
8975
  var actionConnection = text.describe("Account selection: omit while unconfigured; use an account ID to select it; use \"\" to explicitly defer configuration. Deferred Actions fail if called; deferred Agent tools are excluded from the callable tool set. Not supported by Triggers.");
8939
8976
  var json = json$1();
@@ -9131,7 +9168,8 @@ var managedFields = object({
9131
9168
  flowId: text,
9132
9169
  revisionId: text
9133
9170
  }),
9134
- httpRequestExecutorSchema
9171
+ httpRequestExecutorSchema,
9172
+ skipDuplicatesExecutorSchema
9135
9173
  ])
9136
9174
  });
9137
9175
  var managed = managedFields.transform(limitDecisionTask);
@@ -9401,7 +9439,8 @@ var revision = object({
9401
9439
  literal(9),
9402
9440
  literal(10),
9403
9441
  literal(11),
9404
- literal(12)
9442
+ literal(12),
9443
+ literal(13)
9405
9444
  ]),
9406
9445
  document,
9407
9446
  modules: record$1(text, module)
@@ -9626,6 +9665,7 @@ function changeOperationsSchema(kind) {
9626
9665
  return toJSONSchema(schema, { io: "input" });
9627
9666
  }
9628
9667
  function checkModelVersion(content) {
9668
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "skip-duplicates")) throw new Error("Skip Duplicates requires Flow model v13.");
9629
9669
  if (content.modelVersion < 12 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "moduleId" in candidateNode.task && candidateNode.task.capabilities?.some((item) => item.kind == "http"))) throw new TypeError("Code HTTP access requires Flow model version 12.");
9630
9670
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind == "http-request")) throw new TypeError("HTTP Request requires Flow model version 11.");
9631
9671
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && ("moduleId" in candidateNode.task ? candidateNode.task.capabilities?.some((item) => item.kind == "workflow") : candidateNode.task.executor.kind == "agent" && candidateNode.task.executor.tools.some((item) => item.kind == "workflow")))) throw new TypeError("Workflow tools require Flow model version 11.");
@@ -9724,7 +9764,7 @@ var nodeErrorSchema = {
9724
9764
  }
9725
9765
  };
9726
9766
  function supportsNodeErrorHandling(node) {
9727
- return supportsNodeRetry(node);
9767
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "skip-duplicates";
9728
9768
  }
9729
9769
  function nodeSystemOutputPorts(node) {
9730
9770
  return supportsNodeErrorHandling(node) && node.onError == "error" ? { error: {
@@ -9905,10 +9945,10 @@ function upgradeRevisionContent(content) {
9905
9945
  function upgradeDecodedRevisionContent(content) {
9906
9946
  const graph = content.document.graph;
9907
9947
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
9908
- if (!hasManual && content.modelVersion == 12) return content;
9948
+ if (!hasManual && content.modelVersion == 13) return content;
9909
9949
  return {
9910
9950
  ...content,
9911
- modelVersion: 12,
9951
+ modelVersion: 13,
9912
9952
  document: {
9913
9953
  ...content.document,
9914
9954
  graph: hasManual ? {
@@ -9955,7 +9995,7 @@ function invalid(message) {
9955
9995
  throw new FlowChangeError(message);
9956
9996
  }
9957
9997
  function applyFlowChanges(content, operations) {
9958
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9998
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9959
9999
  const document = { ...source.document };
9960
10000
  const modules = { ...source.modules };
9961
10001
  for (const operation of operations) switch (operation.kind) {
@@ -10403,7 +10443,7 @@ function applyFlowChanges(content, operations) {
10403
10443
  assertDocumentHttpAuthBindings(document);
10404
10444
  return upgradeDecodedRevisionContent({
10405
10445
  document,
10406
- modelVersion: 12,
10446
+ modelVersion: 13,
10407
10447
  modules
10408
10448
  });
10409
10449
  }