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

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 (71) hide show
  1. package/README.md +1 -0
  2. package/dist/browser/{ContentIcon-Cm1hbVzW.js → ContentIcon-VZQO8VZU.js} +8 -5
  3. package/dist/browser/{FlowCanvas-NzKtla_c.js → FlowCanvas-BNGDMaWr.js} +15 -3
  4. package/dist/browser/{createResourceDialog-BgdedQqu.js → createResourceDialog-BmtakfVg.js} +63 -57
  5. package/dist/browser/{dialog-aSElFFw2.js → dialog-BcFBfRB1.js} +84 -15
  6. package/dist/browser/{exportSvg-Dcz4HTo_.js → exportSvg-CG8hEUNk.js} +2 -2
  7. package/dist/browser/{flowChanges-NZyS7HSp.js → flowChanges-BGo_4Ypo.js} +5849 -10882
  8. package/dist/browser/{flowWorkspace-Bl0SDehy.js → flowWorkspace-BFHGev7s.js} +1025 -2607
  9. package/dist/browser/preview.css +112 -5
  10. package/dist/browser/preview.js +1630 -9295
  11. package/dist/browser/theme.css +4 -2
  12. package/dist/browser/{tooltip-3XMK9jkr.js → tooltip-BAWSUexx.js} +1 -1
  13. package/dist/browser/{typeScriptSession-C9sBGOKZ.js → typeScriptSession-CRoezJPN.js} +1 -1
  14. package/dist/browser/ui.css +111 -4
  15. package/dist/browser/ui.js +3 -2
  16. package/dist/browser/workbench.css +9906 -9778
  17. package/dist/browser/workbench.d.ts +6 -2
  18. package/dist/browser/workbench.js +838 -499
  19. package/dist/browser/{workbenchSelect-DBGP7Y4Z.js → workbenchSelect-CjK-xdm3.js} +2 -2
  20. package/dist/command/index.js +48 -48
  21. package/dist/common/authoring.d.ts +4 -4
  22. package/dist/common/authoringExamples.d.ts +3 -2
  23. package/dist/common/authoringSchema.d.ts +23 -4
  24. package/dist/common/control-api-conformance.js +162 -15
  25. package/dist/common/control-api-errors.d.ts +12 -0
  26. package/dist/common/control-api.d.ts +35 -2
  27. package/dist/common/control-api.js +187 -37
  28. package/dist/common/control-requests.d.ts +22 -3
  29. package/dist/common/control-requests.js +152 -14
  30. package/dist/common/engine-contract.d.ts +3 -2
  31. package/dist/common/flow-authoring.js +55 -7
  32. package/dist/common/flow-change-schema.d.ts +1 -1
  33. package/dist/common/flow-change.d.ts +2 -2
  34. package/dist/common/flow-change.js +56 -8
  35. package/dist/common/flow-encoding.js +55 -7
  36. package/dist/common/flow-semantics.js +91 -7
  37. package/dist/common/flow-state.d.ts +123 -0
  38. package/dist/common/flow-state.js +5852 -0
  39. package/dist/common/mcp.d.ts +96 -3
  40. package/dist/common/mcp.js +165 -13
  41. package/dist/common/run-events.js +1 -0
  42. package/dist/common/runtime-contract.d.ts +14 -0
  43. package/dist/common/runtime-contract.js +118 -5
  44. package/dist/common/scheduler-node-execution.d.ts +1 -0
  45. package/dist/common/scheduler.d.ts +1 -1
  46. package/dist/common/scheduler.js +52 -3
  47. package/dist/common/state-conformance.d.ts +16 -0
  48. package/dist/common/tags.d.ts +4 -0
  49. package/dist/index.d.ts +11 -0
  50. package/package.json +5 -1
  51. package/src/control/common/api.ts +84 -11
  52. package/src/control/common/authoring.ts +6 -1
  53. package/src/control/common/authoringExamples.ts +8 -0
  54. package/src/control/common/authoringSchema.ts +18 -12
  55. package/src/control/common/errors.ts +6 -0
  56. package/src/control/common/flowDecoders.ts +1 -0
  57. package/src/control/common/mcp.ts +16 -0
  58. package/src/control/common/requests.ts +10 -2
  59. package/src/control/common/tags.ts +20 -0
  60. package/src/execution/common/events.ts +3 -0
  61. package/src/execution/common/node-execution.ts +3 -0
  62. package/src/execution/common/scheduler.ts +2 -0
  63. package/src/flow/common/change.ts +2 -1
  64. package/src/flow/common/changeSchema.ts +11 -1
  65. package/src/flow/common/errorHandling.ts +1 -1
  66. package/src/flow/common/semantics.ts +20 -0
  67. package/src/runtime/common/engineContract.ts +6 -3
  68. package/src/runtime/common/runtime.ts +116 -0
  69. package/src/state/common/conformance.ts +86 -0
  70. package/src/state/common/state.ts +169 -0
  71. package/src/types/index.ts +11 -0
@@ -12473,6 +12473,71 @@ 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 dedupeConfigSchema = strictObject({
12489
+ field: string().default(""),
12490
+ mode: _enum(["new", "increasing"]).default("new"),
12491
+ space: _enum(["node", "flow"]).default("node")
12492
+ });
12493
+ var dedupeExecutorSchema = dedupeConfigSchema.extend({ kind: literal("dedupe") });
12494
+ function dedupeTask(config = dedupeConfigSchema.parse({}), name = "Remove Duplicates") {
12495
+ return {
12496
+ name,
12497
+ executor: {
12498
+ ...config,
12499
+ kind: "dedupe"
12500
+ },
12501
+ inputs: [{
12502
+ handle: "items",
12503
+ jsonSchema: { type: "array" },
12504
+ nullable: false
12505
+ }],
12506
+ outputs: ["items", "duplicates"].map((handle) => ({
12507
+ handle,
12508
+ jsonSchema: { type: "array" },
12509
+ nullable: false
12510
+ }))
12511
+ };
12512
+ }
12513
+ object$1({
12514
+ version: literal(1),
12515
+ scope: _enum(["draft", "live"]),
12516
+ limits: object$1({
12517
+ maxValueBytes: number().positive(),
12518
+ maxValues: number().int().positive(),
12519
+ maxSpaces: number().int().positive(),
12520
+ maxIdentities: number().positive(),
12521
+ maxRunWrites: number().positive()
12522
+ }),
12523
+ values: array(object$1({
12524
+ key: string(),
12525
+ size: number().nonnegative(),
12526
+ updatedAt: number(),
12527
+ value: json$5().optional()
12528
+ })),
12529
+ spaces: array(object$1({
12530
+ space: string(),
12531
+ mode: _enum(["new", "increasing"]),
12532
+ count: number().nonnegative(),
12533
+ watermark: union([
12534
+ string(),
12535
+ number(),
12536
+ _null()
12537
+ ]),
12538
+ updatedAt: number()
12539
+ }))
12540
+ });
12476
12541
  function checkJsonDepth(value, depth = 0) {
12477
12542
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
12478
12543
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -12792,7 +12857,8 @@ var managedFields = object$1({
12792
12857
  flowId: text$1,
12793
12858
  revisionId: text$1
12794
12859
  }),
12795
- httpRequestExecutorSchema
12860
+ httpRequestExecutorSchema,
12861
+ dedupeExecutorSchema
12796
12862
  ])
12797
12863
  });
12798
12864
  var managed = managedFields.transform(limitDecisionTask);
@@ -13062,7 +13128,8 @@ var revision = object$1({
13062
13128
  literal(9),
13063
13129
  literal(10),
13064
13130
  literal(11),
13065
- literal(12)
13131
+ literal(12),
13132
+ literal(13)
13066
13133
  ]),
13067
13134
  document,
13068
13135
  modules: record$3(text$1, module)
@@ -13287,6 +13354,7 @@ function changeOperationsSchema(kind) {
13287
13354
  return toJSONSchema(schema, { io: "input" });
13288
13355
  }
13289
13356
  function checkModelVersion(content) {
13357
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "dedupe")) throw new Error("Deduplication requires Flow model v13.");
13290
13358
  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
13359
  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
13360
  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 +13376,8 @@ function assertDocumentHttpAuthBindings(content) {
13308
13376
  }
13309
13377
  //#endregion
13310
13378
  //#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";
13379
+ /** v10 adds persistent Flow State to the published v9 Node compatibility and fetch contract. */
13380
+ var currentEngineContract = "open-flow-engine/v10";
13313
13381
  /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
13314
13382
  var nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
13315
13383
  var platformSource = (contract) => `export const engineContract = ${JSON.stringify(contract)}
@@ -13354,13 +13422,19 @@ var nodejsContract = {
13354
13422
  ].flatMap((name) => [name, `node:${name}`])),
13355
13423
  platformSource: platformSource(nodejsEngineContract)
13356
13424
  };
13357
- var currentContract = {
13425
+ var v9Contract = {
13358
13426
  ...nodejsContract,
13359
13427
  codeFetch: true,
13428
+ platformSource: platformSource("open-flow-engine/v9")
13429
+ };
13430
+ var currentContract = {
13431
+ ...v9Contract,
13432
+ flowState: true,
13360
13433
  platformSource: platformSource(currentEngineContract)
13361
13434
  };
13362
13435
  function findEngineContract(contract) {
13363
- if (contract == "open-flow-engine/v9") return currentContract;
13436
+ if (contract == "open-flow-engine/v10") return currentContract;
13437
+ if (contract == "open-flow-engine/v9") return v9Contract;
13364
13438
  if (contract == "open-flow-engine/v8") return v8Contract;
13365
13439
  if (contract == "open-flow-engine/v8/nodejs-compat-v1") return nodejsContract;
13366
13440
  if (contract == "open-flow-engine/v7" || contract == "open-flow-engine/v7/nodejs-compat-v1") return {
@@ -13965,6 +14039,7 @@ var authoringTypes = [
13965
14039
  "decision",
13966
14040
  "openapi",
13967
14041
  "http-request",
14042
+ "dedupe",
13968
14043
  "condition",
13969
14044
  "value",
13970
14045
  "wait",
@@ -14096,6 +14171,7 @@ var authoringConfigurations = {
14096
14171
  "connector": authoringDefinitionSchemas.connector.omit({ outputMode: true }).strict(),
14097
14172
  "openapi": authoringOpenApiSchema,
14098
14173
  "http-request": httpRequestConfigSchema,
14174
+ "dedupe": dedupeConfigSchema,
14099
14175
  "agent": authoringDefinitionSchemas.agent.omit({
14100
14176
  prompt: true,
14101
14177
  tools: true
@@ -14315,7 +14391,7 @@ function authoringSchema(kind) {
14315
14391
  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
14392
  } : {
14317
14393
  type: kind,
14318
- onError: retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ["stop", "error"] } : false,
14394
+ onError: kind == "dedupe" || retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ["stop", "error"] } : false,
14319
14395
  retry: retryNodeTypes.some((nodeType) => nodeType == kind) ? toJSONSchema(retryConfigurationSchema) : false,
14320
14396
  inputSources: ["poll", "integration"].includes(kind) ? [
14321
14397
  "value",
@@ -14345,7 +14421,7 @@ function authoringSchema(kind) {
14345
14421
  "wait",
14346
14422
  "approval"
14347
14423
  ].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.",
14424
+ 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 == "dedupe" ? "Bind an items array. Configure field (dot-separated identity path, empty for scalar items), mode (new or increasing), and space (node or flow). Outputs are items and duplicates. History persists immediately; downstream failures do not roll it back. Increasing mode accepts finite numbers or RFC 3339 timestamps, never mixed types." : 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
14425
  example: {
14350
14426
  op: "node.add",
14351
14427
  as: "step",
@@ -14368,6 +14444,17 @@ function authoringSchema(kind) {
14368
14444
  }]
14369
14445
  } },
14370
14446
  "integration": { config: { key: "<Trigger key>" } },
14447
+ "dedupe": {
14448
+ config: {
14449
+ field: "id",
14450
+ mode: "new",
14451
+ space: "node"
14452
+ },
14453
+ inputs: { items: {
14454
+ kind: "value",
14455
+ value: [{ id: "a" }, { id: "b" }]
14456
+ } }
14457
+ },
14371
14458
  "http-request": {
14372
14459
  config: {},
14373
14460
  inputs: { url: {
@@ -14627,7 +14714,7 @@ var nodeErrorSchema = {
14627
14714
  }
14628
14715
  };
14629
14716
  function supportsNodeErrorHandling(node) {
14630
- return supportsNodeRetry(node);
14717
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "dedupe";
14631
14718
  }
14632
14719
  function nodeSystemOutputPorts(node) {
14633
14720
  return supportsNodeErrorHandling(node) && node.onError == "error" ? { error: {
@@ -14802,10 +14889,10 @@ function upgradeRevisionContent(content) {
14802
14889
  function upgradeDecodedRevisionContent(content) {
14803
14890
  const graph = content.document.graph;
14804
14891
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
14805
- if (!hasManual && content.modelVersion == 12) return content;
14892
+ if (!hasManual && content.modelVersion == 13) return content;
14806
14893
  return {
14807
14894
  ...content,
14808
- modelVersion: 12,
14895
+ modelVersion: 13,
14809
14896
  document: {
14810
14897
  ...content.document,
14811
14898
  graph: hasManual ? {
@@ -14840,7 +14927,7 @@ function invalid(message) {
14840
14927
  throw new FlowChangeError(message);
14841
14928
  }
14842
14929
  function applyFlowChanges(content, operations) {
14843
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
14930
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
14844
14931
  const document = { ...source.document };
14845
14932
  const modules = { ...source.modules };
14846
14933
  for (const operation of operations) switch (operation.kind) {
@@ -15288,7 +15375,7 @@ function applyFlowChanges(content, operations) {
15288
15375
  assertDocumentHttpAuthBindings(document);
15289
15376
  return upgradeDecodedRevisionContent({
15290
15377
  document,
15291
- modelVersion: 12,
15378
+ modelVersion: 13,
15292
15379
  modules
15293
15380
  });
15294
15381
  }
@@ -16513,7 +16600,7 @@ function authoringNode(content, ref) {
16513
16600
  inputs: fieldsView([...task.inputs, ...node.kind == "task" ? node.additionalInputs ?? [] : []].filter((port) => !("handle" in port) || !llmReservedInputs.includes(port.handle)))
16514
16601
  };
16515
16602
  else if (task.executor.kind == "workflow") config = { flowId: task.executor.flowId };
16516
- else if (task.executor.kind == "http-request") {
16603
+ else if (task.executor.kind == "http-request" || task.executor.kind == "dedupe") {
16517
16604
  const { kind: _kind, ...settings } = task.executor;
16518
16605
  config = settings;
16519
16606
  } else if (task.executor.kind == "openapi") config = { ...openApiConfigView(task.executor) };
@@ -16903,6 +16990,14 @@ async function compileAuthoring(base, request, host) {
16903
16990
  };
16904
16991
  } else task = await connectorTask(config, String(update.name));
16905
16992
  } else if (type == "decision") task = decisionTask(config.questions, String(update.name));
16993
+ else if (type == "dedupe") task = {
16994
+ ...before,
16995
+ name: String(update.name),
16996
+ executor: {
16997
+ kind: "dedupe",
16998
+ ...dedupeConfigSchema.parse(config)
16999
+ }
17000
+ };
16906
17001
  else if (type == "http-request") {
16907
17002
  task = httpRequestTask(httpRequestConfigSchema.parse(config), String(update.name), before);
16908
17003
  next = {
@@ -17250,6 +17345,9 @@ async function compileAuthoring(base, request, host) {
17250
17345
  case "connector":
17251
17346
  changes = createManagedTask(identity, await connectorTask(config, name));
17252
17347
  break;
17348
+ case "dedupe":
17349
+ changes = createManagedTask(identity, dedupeTask(dedupeConfigSchema.parse(config), name));
17350
+ break;
17253
17351
  case "http-request":
17254
17352
  changes = createManagedTask(identity, httpRequestTask(httpRequestConfigSchema.parse(config), name));
17255
17353
  break;
@@ -17471,6 +17569,7 @@ var descriptions = {
17471
17569
  "poll": "Gmail polling by key; replace CONNECTION_ID with an active Gmail connection.",
17472
17570
  "integration": "Telegram webhook integration by key; replace CONNECTION_ID with an active Telegram connection.",
17473
17571
  "decision": "AI Decision evaluates named questions against target and returns a complete answer object per output. Configure target before running.",
17572
+ "dedupe": "Filter items using persistent history. Records immediately, including when downstream work fails. Empty field uses each scalar item as identity.",
17474
17573
  "http-request": "A single HTTP request. Bind url and request inputs; outputs are body, statusCode and headers.",
17475
17574
  "openapi": "A fixed JSON API operation. Replace the example document and URL before running.",
17476
17575
  "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 +17614,21 @@ function authoringExample(name) {
17515
17614
  instructions: "Does the customer need support?"
17516
17615
  }]);
17517
17616
  break;
17617
+ case "dedupe":
17618
+ operations = [...createManagedTask({ nodeId: "dedupe" }, dedupeTask()), {
17619
+ kind: "graph.node.input.set",
17620
+ nodeId: "dedupe",
17621
+ handle: "items",
17622
+ value: {
17623
+ kind: "value",
17624
+ value: [
17625
+ "a",
17626
+ "b",
17627
+ "a"
17628
+ ]
17629
+ }
17630
+ }];
17631
+ break;
17518
17632
  case "http-request":
17519
17633
  operations = [...createManagedTask({ nodeId: "http" }, httpRequestTask()), {
17520
17634
  kind: "graph.node.input.set",
@@ -17758,6 +17872,8 @@ var json = json$5();
17758
17872
  var id = string().min(1);
17759
17873
  var version = literal(1);
17760
17874
  var flowName = id.refine((value) => value == value.trim() && resourceNameIssue(value) == null, "Flow name is invalid.");
17875
+ var tagIds = array(id).transform((ids) => [...new Set(ids)].toSorted());
17876
+ var tagName = id.transform((name) => name.trim()).refine((name) => resourceNameIssue(name) == null, "Tag name is invalid.");
17761
17877
  var inputs = record$3(string(), record$3(string(), json));
17762
17878
  var trigger = strictObject({
17763
17879
  nodeId: id,
@@ -17784,13 +17900,32 @@ var schemas = {
17784
17900
  file: json,
17785
17901
  name: flowName.optional(),
17786
17902
  teamId: id.optional(),
17903
+ tagIds: tagIds.optional(),
17787
17904
  version
17788
17905
  }),
17789
17906
  createFlow: strictObject({
17790
17907
  name: flowName,
17791
17908
  teamId: id.optional(),
17909
+ tagIds: tagIds.optional(),
17792
17910
  version
17793
17911
  }),
17912
+ saveTag: strictObject({
17913
+ name: tagName,
17914
+ version
17915
+ }),
17916
+ setFlowTags: strictObject({
17917
+ tagIds,
17918
+ version
17919
+ }),
17920
+ updateFlow: union([strictObject({
17921
+ name: flowName,
17922
+ tagIds: tagIds.optional(),
17923
+ version
17924
+ }), strictObject({
17925
+ name: flowName.optional(),
17926
+ tagIds,
17927
+ version
17928
+ })]),
17794
17929
  renameFlow: strictObject({
17795
17930
  name: flowName,
17796
17931
  version
@@ -17895,6 +18030,9 @@ var controlRequests = {
17895
18030
  eventSourceRevision: decoder(schemas.eventSourceRevision),
17896
18031
  importFlow: decoder(schemas.importFlow),
17897
18032
  createFlow: decoder(schemas.createFlow),
18033
+ saveTag: decoder(schemas.saveTag),
18034
+ setFlowTags: decoder(schemas.setFlowTags),
18035
+ updateFlow: decoder(schemas.updateFlow),
17898
18036
  renameFlow: decoder(schemas.renameFlow),
17899
18037
  changeDraft(value) {
17900
18038
  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,51 @@ 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 dedupeExecutorSchema = strictObject({
8776
+ field: string().default(""),
8777
+ mode: _enum(["new", "increasing"]).default("new"),
8778
+ space: _enum(["node", "flow"]).default("node")
8779
+ }).extend({ kind: literal("dedupe") });
8780
+ object({
8781
+ version: literal(1),
8782
+ scope: _enum(["draft", "live"]),
8783
+ limits: object({
8784
+ maxValueBytes: number().positive(),
8785
+ maxValues: number().int().positive(),
8786
+ maxSpaces: number().int().positive(),
8787
+ maxIdentities: number().positive(),
8788
+ maxRunWrites: number().positive()
8789
+ }),
8790
+ values: array(object({
8791
+ key: string(),
8792
+ size: number().nonnegative(),
8793
+ updatedAt: number(),
8794
+ value: json$1().optional()
8795
+ })),
8796
+ spaces: array(object({
8797
+ space: string(),
8798
+ mode: _enum(["new", "increasing"]),
8799
+ count: number().nonnegative(),
8800
+ watermark: union([
8801
+ string(),
8802
+ number(),
8803
+ _null()
8804
+ ]),
8805
+ updatedAt: number()
8806
+ }))
8807
+ });
8763
8808
  function checkJsonDepth(value, depth = 0) {
8764
8809
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8765
8810
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -9055,7 +9100,8 @@ var managedFields = object({
9055
9100
  flowId: text,
9056
9101
  revisionId: text
9057
9102
  }),
9058
- httpRequestExecutorSchema
9103
+ httpRequestExecutorSchema,
9104
+ dedupeExecutorSchema
9059
9105
  ])
9060
9106
  });
9061
9107
  var managed = managedFields.transform(limitDecisionTask);
@@ -9325,7 +9371,8 @@ var revision = object({
9325
9371
  literal(9),
9326
9372
  literal(10),
9327
9373
  literal(11),
9328
- literal(12)
9374
+ literal(12),
9375
+ literal(13)
9329
9376
  ]),
9330
9377
  document,
9331
9378
  modules: record$1(text, module)
@@ -9526,6 +9573,7 @@ array(union([...new Map(Object.entries(shapes).map(([kind, shape]) => [kind, sha
9526
9573
  ...shape
9527
9574
  })])).values()])).min(1);
9528
9575
  function checkModelVersion(content) {
9576
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "dedupe")) throw new Error("Deduplication requires Flow model v13.");
9529
9577
  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
9578
  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
9579
  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 +9652,7 @@ function setConditionInput(node, handle, mapping) {
9604
9652
  //#endregion
9605
9653
  //#region src/flow/common/errorHandling.ts
9606
9654
  function supportsNodeErrorHandling(node) {
9607
- return supportsNodeRetry(node);
9655
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "dedupe";
9608
9656
  }
9609
9657
  //#endregion
9610
9658
  //#region src/flow/common/change.ts
@@ -9755,10 +9803,10 @@ function isTriggerNode(node) {
9755
9803
  function upgradeDecodedRevisionContent(content) {
9756
9804
  const graph = content.document.graph;
9757
9805
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
9758
- if (!hasManual && content.modelVersion == 12) return content;
9806
+ if (!hasManual && content.modelVersion == 13) return content;
9759
9807
  return {
9760
9808
  ...content,
9761
- modelVersion: 12,
9809
+ modelVersion: 13,
9762
9810
  document: {
9763
9811
  ...content.document,
9764
9812
  graph: hasManual ? {
@@ -9793,7 +9841,7 @@ function invalid(message) {
9793
9841
  throw new FlowChangeError(message);
9794
9842
  }
9795
9843
  function applyFlowChanges(content, operations) {
9796
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9844
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9797
9845
  const document = { ...source.document };
9798
9846
  const modules = { ...source.modules };
9799
9847
  for (const operation of operations) switch (operation.kind) {
@@ -10241,7 +10289,7 @@ function applyFlowChanges(content, operations) {
10241
10289
  assertDocumentHttpAuthBindings(document);
10242
10290
  return upgradeDecodedRevisionContent({
10243
10291
  document,
10244
- modelVersion: 12,
10292
+ modelVersion: 13,
10245
10293
  modules
10246
10294
  });
10247
10295
  }
@@ -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').DedupeExecutor | 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,51 @@ 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 dedupeExecutorSchema = strictObject({
8849
+ field: string().default(""),
8850
+ mode: _enum(["new", "increasing"]).default("new"),
8851
+ space: _enum(["node", "flow"]).default("node")
8852
+ }).extend({ kind: literal("dedupe") });
8853
+ object({
8854
+ version: literal(1),
8855
+ scope: _enum(["draft", "live"]),
8856
+ limits: object({
8857
+ maxValueBytes: number().positive(),
8858
+ maxValues: number().int().positive(),
8859
+ maxSpaces: number().int().positive(),
8860
+ maxIdentities: number().positive(),
8861
+ maxRunWrites: number().positive()
8862
+ }),
8863
+ values: array(object({
8864
+ key: string(),
8865
+ size: number().nonnegative(),
8866
+ updatedAt: number(),
8867
+ value: json$1().optional()
8868
+ })),
8869
+ spaces: array(object({
8870
+ space: string(),
8871
+ mode: _enum(["new", "increasing"]),
8872
+ count: number().nonnegative(),
8873
+ watermark: union([
8874
+ string(),
8875
+ number(),
8876
+ _null()
8877
+ ]),
8878
+ updatedAt: number()
8879
+ }))
8880
+ });
8836
8881
  function checkJsonDepth(value, depth = 0) {
8837
8882
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8838
8883
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -8933,7 +8978,7 @@ var webhookResponseTimeout = {
8933
8978
  }).jsonSchema;
8934
8979
  //#endregion
8935
8980
  //#region src/flow/common/changeSchema.ts
8936
- var currentFlowModelVersion = 12;
8981
+ var currentFlowModelVersion = 13;
8937
8982
  var text = string();
8938
8983
  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
8984
  var json = json$1();
@@ -9131,7 +9176,8 @@ var managedFields = object({
9131
9176
  flowId: text,
9132
9177
  revisionId: text
9133
9178
  }),
9134
- httpRequestExecutorSchema
9179
+ httpRequestExecutorSchema,
9180
+ dedupeExecutorSchema
9135
9181
  ])
9136
9182
  });
9137
9183
  var managed = managedFields.transform(limitDecisionTask);
@@ -9401,7 +9447,8 @@ var revision = object({
9401
9447
  literal(9),
9402
9448
  literal(10),
9403
9449
  literal(11),
9404
- literal(12)
9450
+ literal(12),
9451
+ literal(13)
9405
9452
  ]),
9406
9453
  document,
9407
9454
  modules: record$1(text, module)
@@ -9626,6 +9673,7 @@ function changeOperationsSchema(kind) {
9626
9673
  return toJSONSchema(schema, { io: "input" });
9627
9674
  }
9628
9675
  function checkModelVersion(content) {
9676
+ if (content.modelVersion < 13 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind === "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind === "dedupe")) throw new Error("Deduplication requires Flow model v13.");
9629
9677
  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
9678
  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
9679
  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 +9772,7 @@ var nodeErrorSchema = {
9724
9772
  }
9725
9773
  };
9726
9774
  function supportsNodeErrorHandling(node) {
9727
- return supportsNodeRetry(node);
9775
+ return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "dedupe";
9728
9776
  }
9729
9777
  function nodeSystemOutputPorts(node) {
9730
9778
  return supportsNodeErrorHandling(node) && node.onError == "error" ? { error: {
@@ -9905,10 +9953,10 @@ function upgradeRevisionContent(content) {
9905
9953
  function upgradeDecodedRevisionContent(content) {
9906
9954
  const graph = content.document.graph;
9907
9955
  const hasManual = Object.values(graph.nodes).some((node) => node.kind == "manual");
9908
- if (!hasManual && content.modelVersion == 12) return content;
9956
+ if (!hasManual && content.modelVersion == 13) return content;
9909
9957
  return {
9910
9958
  ...content,
9911
- modelVersion: 12,
9959
+ modelVersion: 13,
9912
9960
  document: {
9913
9961
  ...content.document,
9914
9962
  graph: hasManual ? {
@@ -9955,7 +10003,7 @@ function invalid(message) {
9955
10003
  throw new FlowChangeError(message);
9956
10004
  }
9957
10005
  function applyFlowChanges(content, operations) {
9958
- const source = content.modelVersion != 12 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
10006
+ const source = content.modelVersion != 13 || Object.values(content.document.graph.nodes).some((node) => node.kind == "manual") ? decodeRevisionContent(content) : content;
9959
10007
  const document = { ...source.document };
9960
10008
  const modules = { ...source.modules };
9961
10009
  for (const operation of operations) switch (operation.kind) {
@@ -10403,7 +10451,7 @@ function applyFlowChanges(content, operations) {
10403
10451
  assertDocumentHttpAuthBindings(document);
10404
10452
  return upgradeDecodedRevisionContent({
10405
10453
  document,
10406
- modelVersion: 12,
10454
+ modelVersion: 13,
10407
10455
  modules
10408
10456
  });
10409
10457
  }