@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
@@ -3,6 +3,99 @@ import { z } from 'zod';
3
3
  export declare const mcpProtocolVersion = "2026-07-28";
4
4
  export declare const mcpInstructions: string;
5
5
  export declare const mcpTools: {
6
+ flow_state: {
7
+ description: string;
8
+ annotations: {
9
+ readOnlyHint: boolean;
10
+ destructiveHint: boolean;
11
+ idempotentHint: boolean;
12
+ openWorldHint: boolean;
13
+ };
14
+ inputSchema: {
15
+ '~standard': {
16
+ version: 1;
17
+ vendor: string;
18
+ jsonSchema: {
19
+ input: () => Record<string, unknown>;
20
+ output: () => Record<string, unknown>;
21
+ };
22
+ validate(value: unknown): {
23
+ value: {
24
+ scope: "draft" | "live";
25
+ values?: boolean | undefined;
26
+ flowId: string;
27
+ };
28
+ } | {
29
+ issues: {
30
+ message: string;
31
+ path: string[];
32
+ }[];
33
+ };
34
+ };
35
+ };
36
+ };
37
+ flow_state_clear: {
38
+ description: string;
39
+ annotations: {
40
+ readOnlyHint: boolean;
41
+ destructiveHint: boolean;
42
+ idempotentHint: boolean;
43
+ openWorldHint: boolean;
44
+ };
45
+ inputSchema: {
46
+ '~standard': {
47
+ version: 1;
48
+ vendor: string;
49
+ jsonSchema: {
50
+ input: () => Record<string, unknown>;
51
+ output: () => Record<string, unknown>;
52
+ };
53
+ validate(value: unknown): {
54
+ value: {
55
+ scope: "draft" | "live";
56
+ key?: string | undefined;
57
+ flowId: string;
58
+ };
59
+ } | {
60
+ issues: {
61
+ message: string;
62
+ path: string[];
63
+ }[];
64
+ };
65
+ };
66
+ };
67
+ };
68
+ flow_dedupe_clear: {
69
+ description: string;
70
+ annotations: {
71
+ readOnlyHint: boolean;
72
+ destructiveHint: boolean;
73
+ idempotentHint: boolean;
74
+ openWorldHint: boolean;
75
+ };
76
+ inputSchema: {
77
+ '~standard': {
78
+ version: 1;
79
+ vendor: string;
80
+ jsonSchema: {
81
+ input: () => Record<string, unknown>;
82
+ output: () => Record<string, unknown>;
83
+ };
84
+ validate(value: unknown): {
85
+ value: {
86
+ scope: "draft" | "live";
87
+ space: string;
88
+ flowId: string;
89
+ };
90
+ } | {
91
+ issues: {
92
+ message: string;
93
+ path: string[];
94
+ }[];
95
+ };
96
+ };
97
+ };
98
+ };
6
99
  flow_canvas: {
7
100
  description: string;
8
101
  annotations: {
@@ -161,7 +254,7 @@ export declare const mcpTools: {
161
254
  value: {
162
255
  revision?: string | undefined;
163
256
  query: string;
164
- type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
257
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "dedupe" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
165
258
  offset: number;
166
259
  limit: number;
167
260
  flowId: string;
@@ -193,7 +286,7 @@ export declare const mcpTools: {
193
286
  };
194
287
  validate(value: unknown): {
195
288
  value: {
196
- type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
289
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "dedupe" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
197
290
  action?: string | undefined;
198
291
  flowId?: string | undefined;
199
292
  };
@@ -321,7 +414,7 @@ export declare const mcpTools: {
321
414
  edits: ({
322
415
  op: "node.add";
323
416
  as: string;
324
- type: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow";
417
+ type: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "dedupe" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow";
325
418
  name: string;
326
419
  onError?: "error" | "stop" | undefined;
327
420
  retry?: {
@@ -8846,6 +8846,54 @@ var httpRequestConfigSchema = strictObject({
8846
8846
  statusPolicy: _enum(["success", "all"]).default("success")
8847
8847
  });
8848
8848
  var httpRequestExecutorSchema = httpRequestConfigSchema.extend({ kind: literal("http-request") });
8849
+ //#endregion
8850
+ //#region src/state/common/state.ts
8851
+ var stateQuerySchema = strictObject({
8852
+ scope: _enum(["draft", "live"]),
8853
+ values: boolean().optional()
8854
+ });
8855
+ var stateClearSchema = strictObject({
8856
+ scope: _enum(["draft", "live"]),
8857
+ key: string$1().min(1).refine((key) => new TextEncoder().encode(key).length <= 256).optional()
8858
+ });
8859
+ var dedupeClearSchema = strictObject({
8860
+ scope: _enum(["draft", "live"]),
8861
+ space: string$1().regex(/^(flow|node:.+)$/)
8862
+ });
8863
+ var dedupeConfigSchema = strictObject({
8864
+ field: string$1().default(""),
8865
+ mode: _enum(["new", "increasing"]).default("new"),
8866
+ space: _enum(["node", "flow"]).default("node")
8867
+ });
8868
+ var dedupeExecutorSchema = dedupeConfigSchema.extend({ kind: literal("dedupe") });
8869
+ var flowStateViewSchema = object({
8870
+ version: literal(1),
8871
+ scope: _enum(["draft", "live"]),
8872
+ limits: object({
8873
+ maxValueBytes: number().positive(),
8874
+ maxValues: number().int().positive(),
8875
+ maxSpaces: number().int().positive(),
8876
+ maxIdentities: number().positive(),
8877
+ maxRunWrites: number().positive()
8878
+ }),
8879
+ values: array(object({
8880
+ key: string$1(),
8881
+ size: number().nonnegative(),
8882
+ updatedAt: number(),
8883
+ value: json$4().optional()
8884
+ })),
8885
+ spaces: array(object({
8886
+ space: string$1(),
8887
+ mode: _enum(["new", "increasing"]),
8888
+ count: number().nonnegative(),
8889
+ watermark: union([
8890
+ string$1(),
8891
+ number(),
8892
+ _null()
8893
+ ]),
8894
+ updatedAt: number()
8895
+ }))
8896
+ });
8849
8897
  function checkJsonDepth(value, depth = 0) {
8850
8898
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8851
8899
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -9134,7 +9182,8 @@ var managedFields = object({
9134
9182
  flowId: text,
9135
9183
  revisionId: text
9136
9184
  }),
9137
- httpRequestExecutorSchema
9185
+ httpRequestExecutorSchema,
9186
+ dedupeExecutorSchema
9138
9187
  ])
9139
9188
  });
9140
9189
  var managed = managedFields.transform(limitDecisionTask);
@@ -9404,7 +9453,8 @@ var revision = object({
9404
9453
  literal(9),
9405
9454
  literal(10),
9406
9455
  literal(11),
9407
- literal(12)
9456
+ literal(12),
9457
+ literal(13)
9408
9458
  ]),
9409
9459
  document,
9410
9460
  modules: record$2(text, module)
@@ -9605,6 +9655,7 @@ array(union([...new Map(Object.entries(shapes).map(([kind, shape]) => [kind, sha
9605
9655
  ...shape
9606
9656
  })])).values()])).min(1);
9607
9657
  function checkModelVersion(content) {
9658
+ 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.");
9608
9659
  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.");
9609
9660
  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.");
9610
9661
  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.");
@@ -9636,8 +9687,8 @@ function resourceNameIssue(value) {
9636
9687
  }
9637
9688
  //#endregion
9638
9689
  //#region src/runtime/common/engineContract.ts
9639
- /** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
9640
- var currentEngineContract = "open-flow-engine/v9";
9690
+ /** v10 adds persistent Flow State to the published v9 Node compatibility and fetch contract. */
9691
+ var currentEngineContract = "open-flow-engine/v10";
9641
9692
  /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
9642
9693
  var nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
9643
9694
  var platformSource = (contract) => `export const engineContract = ${JSON.stringify(contract)}
@@ -9682,13 +9733,19 @@ var nodejsContract = {
9682
9733
  ].flatMap((name) => [name, `node:${name}`])),
9683
9734
  platformSource: platformSource(nodejsEngineContract)
9684
9735
  };
9685
- var currentContract = {
9736
+ var v9Contract = {
9686
9737
  ...nodejsContract,
9687
9738
  codeFetch: true,
9739
+ platformSource: platformSource("open-flow-engine/v9")
9740
+ };
9741
+ var currentContract = {
9742
+ ...v9Contract,
9743
+ flowState: true,
9688
9744
  platformSource: platformSource(currentEngineContract)
9689
9745
  };
9690
9746
  function findEngineContract(contract) {
9691
- if (contract == "open-flow-engine/v9") return currentContract;
9747
+ if (contract == "open-flow-engine/v10") return currentContract;
9748
+ if (contract == "open-flow-engine/v9") return v9Contract;
9692
9749
  if (contract == "open-flow-engine/v8") return v8Contract;
9693
9750
  if (contract == "open-flow-engine/v8/nodejs-compat-v1") return nodejsContract;
9694
9751
  if (contract == "open-flow-engine/v7" || contract == "open-flow-engine/v7/nodejs-compat-v1") return {
@@ -9755,6 +9812,7 @@ var type = _enum([
9755
9812
  "decision",
9756
9813
  "openapi",
9757
9814
  "http-request",
9815
+ "dedupe",
9758
9816
  "condition",
9759
9817
  "value",
9760
9818
  "wait",
@@ -9885,6 +9943,7 @@ var authoringConfigurations = {
9885
9943
  "connector": authoringDefinitionSchemas.connector.omit({ outputMode: true }).strict(),
9886
9944
  "openapi": authoringOpenApiSchema,
9887
9945
  "http-request": httpRequestConfigSchema,
9946
+ "dedupe": dedupeConfigSchema,
9888
9947
  "agent": authoringDefinitionSchemas.agent.omit({
9889
9948
  prompt: true,
9890
9949
  tools: true
@@ -10160,11 +10219,14 @@ var controlErrorCode = {
10160
10219
  triggerKeyInvalid: "trigger-key.invalid",
10161
10220
  triggerKeyNotFound: "trigger-key.not-found",
10162
10221
  triggerNotFound: "trigger.not-found",
10222
+ tagInvalid: "tag.invalid",
10223
+ tagConflict: "tag.conflict",
10224
+ tagNotFound: "tag.not-found",
10163
10225
  variableInvalid: "variable.invalid",
10164
10226
  variableLimitReached: "variable.limit-reached",
10165
10227
  variableNotFound: "variable.not-found"
10166
10228
  };
10167
- controlErrorCode.eventSourceInvalid, controlErrorCode.eventSourceIdentityUnavailable, controlErrorCode.eventSourceNotFound, controlErrorCode.eventSourceConflict, controlErrorCode.authenticationRequired, controlErrorCode.authorizationDenied, controlErrorCode.bindingUnresolved, controlErrorCode.connectorActionNotFound, controlErrorCode.connectorAccessConflict, controlErrorCode.connectorAccessInvalid, controlErrorCode.connectorAccessRequired, controlErrorCode.connectorAccessUnsupported, controlErrorCode.connectorUnconfigured, controlErrorCode.connectorUnavailable, controlErrorCode.connectorConsoleUnconfigured, controlErrorCode.engineUnavailable, controlErrorCode.engineUnsupported, controlErrorCode.flowInvalid, controlErrorCode.flowRepairRequired, controlErrorCode.flowUpgradeRequired, controlErrorCode.flowNotFound, controlErrorCode.flowBusy, controlErrorCode.flowConflict, controlErrorCode.flowPresentationConflict, controlErrorCode.flowRevisionConflict, controlErrorCode.liveConflict, controlErrorCode.liveNotFound, controlErrorCode.pageInvalidCursor, controlErrorCode.publicationConflict, controlErrorCode.publicationNotFound, controlErrorCode.publicationUnsupported, controlErrorCode.publishOperationNotFound, controlErrorCode.routeNotFound, controlErrorCode.runConflict, controlErrorCode.runEventsExpired, controlErrorCode.runInvalid, controlErrorCode.runNotFound, controlErrorCode.runNotTerminal, controlErrorCode.runOverloaded, controlErrorCode.runWaitNotFound, controlErrorCode.triggerKeyInvalid, controlErrorCode.triggerKeyNotFound, controlErrorCode.triggerNotFound, controlErrorCode.variableInvalid, controlErrorCode.variableLimitReached, controlErrorCode.variableNotFound;
10229
+ controlErrorCode.eventSourceInvalid, controlErrorCode.eventSourceIdentityUnavailable, controlErrorCode.eventSourceNotFound, controlErrorCode.eventSourceConflict, controlErrorCode.authenticationRequired, controlErrorCode.authorizationDenied, controlErrorCode.bindingUnresolved, controlErrorCode.connectorActionNotFound, controlErrorCode.connectorAccessConflict, controlErrorCode.connectorAccessInvalid, controlErrorCode.connectorAccessRequired, controlErrorCode.connectorAccessUnsupported, controlErrorCode.connectorUnconfigured, controlErrorCode.connectorUnavailable, controlErrorCode.connectorConsoleUnconfigured, controlErrorCode.engineUnavailable, controlErrorCode.engineUnsupported, controlErrorCode.flowInvalid, controlErrorCode.flowRepairRequired, controlErrorCode.flowUpgradeRequired, controlErrorCode.flowNotFound, controlErrorCode.flowBusy, controlErrorCode.flowConflict, controlErrorCode.flowPresentationConflict, controlErrorCode.flowRevisionConflict, controlErrorCode.liveConflict, controlErrorCode.liveNotFound, controlErrorCode.pageInvalidCursor, controlErrorCode.publicationConflict, controlErrorCode.publicationNotFound, controlErrorCode.publicationUnsupported, controlErrorCode.publishOperationNotFound, controlErrorCode.routeNotFound, controlErrorCode.runConflict, controlErrorCode.runEventsExpired, controlErrorCode.runInvalid, controlErrorCode.runNotFound, controlErrorCode.runNotTerminal, controlErrorCode.runOverloaded, controlErrorCode.runWaitNotFound, controlErrorCode.triggerKeyInvalid, controlErrorCode.triggerKeyNotFound, controlErrorCode.triggerNotFound, controlErrorCode.tagInvalid, controlErrorCode.tagConflict, controlErrorCode.tagNotFound, controlErrorCode.variableInvalid, controlErrorCode.variableLimitReached, controlErrorCode.variableNotFound;
10168
10230
  var ApiError = class extends Error {
10169
10231
  constructor(status, code, message, details) {
10170
10232
  super(message);
@@ -11430,6 +11492,7 @@ function flow$1(value) {
11430
11492
  publicationId: string(target.publicationId),
11431
11493
  revisionId: string(target.revisionId)
11432
11494
  } },
11495
+ tagIds: source.tagIds === void 0 ? [] : strings(source.tagIds),
11433
11496
  createdAt: string(source.createdAt),
11434
11497
  ...source.connectorTeamId == null ? {} : { connectorTeamId: string(source.connectorTeamId) },
11435
11498
  draftRevisionId: string(source.draftRevisionId),
@@ -11864,6 +11927,33 @@ function randomId() {
11864
11927
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
11865
11928
  }
11866
11929
  //#endregion
11930
+ //#region src/control/common/tags.ts
11931
+ function normalizeFlowQuery(query = {}) {
11932
+ return {
11933
+ query: query.query?.trim().toLowerCase() ?? "",
11934
+ tagIds: [...new Set(query.tagIds ?? [])].toSorted(),
11935
+ untagged: query.untagged ?? false
11936
+ };
11937
+ }
11938
+ function decodeTag(value) {
11939
+ const source = record(value);
11940
+ if (source.version != 1) return invalidResponse();
11941
+ return {
11942
+ tagId: string(source.tagId),
11943
+ name: string(source.name),
11944
+ version: 1
11945
+ };
11946
+ }
11947
+ function decodeTagListItem(value) {
11948
+ const source = record(value);
11949
+ const referenceCount = integer(source.referenceCount);
11950
+ if (referenceCount < 0) return invalidResponse();
11951
+ return {
11952
+ ...decodeTag(source),
11953
+ referenceCount
11954
+ };
11955
+ }
11956
+ //#endregion
11867
11957
  //#region src/control/common/api.ts
11868
11958
  function decodeRunEvent(value) {
11869
11959
  const source = record(value);
@@ -11967,7 +12057,7 @@ function decodeRunEvent(value) {
11967
12057
  switch (kind) {
11968
12058
  case "node.started": {
11969
12059
  const nodeKind = payload.nodeKind;
11970
- if (nodeKind !== void 0 && nodeKind !== "approval" && nodeKind !== "condition" && nodeKind !== "connector" && nodeKind !== "javascript" && nodeKind !== "decision" && nodeKind !== "openapi" && nodeKind !== "http-request" && nodeKind !== "llm" && nodeKind !== "agent" && nodeKind !== "value" && nodeKind !== "outputs" && nodeKind !== "webhook-response" && nodeKind !== "workflow" && nodeKind !== "wait") return invalidResponse();
12060
+ if (nodeKind !== void 0 && nodeKind !== "approval" && nodeKind !== "condition" && nodeKind !== "connector" && nodeKind !== "javascript" && nodeKind !== "decision" && nodeKind !== "openapi" && nodeKind !== "http-request" && nodeKind !== "dedupe" && nodeKind !== "llm" && nodeKind !== "agent" && nodeKind !== "value" && nodeKind !== "outputs" && nodeKind !== "webhook-response" && nodeKind !== "workflow" && nodeKind !== "wait") return invalidResponse();
11971
12061
  return {
11972
12062
  ...base,
11973
12063
  kind,
@@ -12094,6 +12184,10 @@ var ControlClient = class {
12094
12184
  }
12095
12185
  async listFlows(options = {}) {
12096
12186
  const parameters = new URLSearchParams();
12187
+ const filter = normalizeFlowQuery(options);
12188
+ if (filter.query) parameters.set("query", filter.query);
12189
+ if (filter.tagIds.length) parameters.set("tagIds", filter.tagIds.join(","));
12190
+ if (filter.untagged) parameters.set("untagged", "true");
12097
12191
  if (options.cursor != null) parameters.set("cursor", options.cursor);
12098
12192
  if (options.limit != null) parameters.set("limit", String(options.limit));
12099
12193
  if (options.includeTotal != null) parameters.set("includeTotal", String(options.includeTotal));
@@ -12183,15 +12277,17 @@ var ControlClient = class {
12183
12277
  file,
12184
12278
  name: options.name,
12185
12279
  teamId: options.teamId,
12280
+ tagIds: options.tagIds,
12186
12281
  version: 1
12187
12282
  })
12188
12283
  }));
12189
12284
  }
12190
- async createFlow(name, idempotencyKey = `flow-${randomId()}`, teamId) {
12285
+ async createFlow(name, idempotencyKey = `flow-${randomId()}`, teamId, tagIds) {
12191
12286
  return flow$1(await this.request("/v1/flows", {
12192
12287
  body: JSON.stringify({
12193
12288
  name,
12194
12289
  ...teamId == null ? {} : { teamId },
12290
+ ...tagIds == null ? {} : { tagIds },
12195
12291
  version: 1
12196
12292
  }),
12197
12293
  headers: { "idempotency-key": idempotencyKey },
@@ -12226,15 +12322,68 @@ var ControlClient = class {
12226
12322
  if (source.version != 1 || !Array.isArray(source.listeners)) return invalidResponse();
12227
12323
  return source.listeners.map(errorListener);
12228
12324
  }
12229
- async renameFlow(flowId, name) {
12230
- return flow$1(await this.request(`/v1/flows/${segment(flowId)}`, {
12325
+ async listTags(signal) {
12326
+ const source = record(await this.request("/v1/tags", { signal }));
12327
+ if (source.version != 1 || !Array.isArray(source.tags)) return invalidResponse();
12328
+ return {
12329
+ tags: source.tags.map(decodeTagListItem),
12330
+ version: 1
12331
+ };
12332
+ }
12333
+ async createTag(name) {
12334
+ return decodeTag(await this.request("/v1/tags", {
12335
+ method: "POST",
12231
12336
  body: JSON.stringify({
12232
12337
  name,
12233
12338
  version: 1
12234
- }),
12235
- method: "PATCH"
12339
+ })
12340
+ }));
12341
+ }
12342
+ async renameTag(tagId, name) {
12343
+ return decodeTag(await this.request(`/v1/tags/${segment(tagId)}`, {
12344
+ method: "PATCH",
12345
+ body: JSON.stringify({
12346
+ name,
12347
+ version: 1
12348
+ })
12236
12349
  }));
12237
12350
  }
12351
+ async deleteTag(tagId) {
12352
+ if (record(await this.request(`/v1/tags/${segment(tagId)}`, { method: "DELETE" })).version != 1) invalidResponse();
12353
+ }
12354
+ async setFlowTags(flowId, tagIds) {
12355
+ return flow$1(await this.request(`/v1/flows/${segment(flowId)}/tags`, {
12356
+ method: "PUT",
12357
+ body: JSON.stringify({
12358
+ tagIds,
12359
+ version: 1
12360
+ })
12361
+ }));
12362
+ }
12363
+ async updateFlow(flowId, changes) {
12364
+ return flow$1(await this.request(`/v1/flows/${segment(flowId)}`, {
12365
+ method: "PATCH",
12366
+ body: JSON.stringify({
12367
+ ...changes,
12368
+ version: 1
12369
+ })
12370
+ }));
12371
+ }
12372
+ async getFlowState(flowId, scope, values = false) {
12373
+ const result = flowStateViewSchema.safeParse(await this.request(`/v1/flows/${segment(flowId)}/state?scope=${scope}&values=${values}`));
12374
+ return result.success && result.data.scope === scope ? result.data : invalidResponse();
12375
+ }
12376
+ async clearFlowState(flowId, scope, key) {
12377
+ const source = record(await this.request(`/v1/flows/${segment(flowId)}/state?scope=${scope}${key == null ? "" : `&key=${encodeURIComponent(key)}`}`, { method: "DELETE" }));
12378
+ if (source.version !== 1 || source.cleared !== true) invalidResponse();
12379
+ }
12380
+ async clearFlowDedupe(flowId, scope, space) {
12381
+ const source = record(await this.request(`/v1/flows/${segment(flowId)}/dedupe?scope=${scope}&space=${encodeURIComponent(space)}`, { method: "DELETE" }));
12382
+ if (source.version !== 1 || source.cleared !== true) invalidResponse();
12383
+ }
12384
+ async renameFlow(flowId, name) {
12385
+ return this.updateFlow(flowId, { name });
12386
+ }
12238
12387
  async deleteFlow(flowId) {
12239
12388
  return flow$1(await this.request(`/v1/flows/${segment(flowId)}`, { method: "DELETE" }));
12240
12389
  }
@@ -12986,6 +13135,9 @@ function tool(description, schema, readOnly, idempotent = true) {
12986
13135
  };
12987
13136
  }
12988
13137
  var mcpTools = {
13138
+ flow_state: tool("Inspect Flow state usage, business keys and deduplication spaces. Values are omitted unless requested. Scope is required.", stateQuerySchema.extend({ flowId: flow }), true),
13139
+ flow_state_clear: tool("Delete one business state key or clear all business values. Explicit draft/live scope is required; live changes production state immediately.", stateClearSchema.extend({ flowId: flow }), false),
13140
+ flow_dedupe_clear: tool("Clear one deduplication space (flow or node:<id>). Explicit draft/live scope is required; live changes production history immediately.", dedupeClearSchema.extend({ flowId: flow }), false),
12989
13141
  flow_canvas: tool("Broadcast a transient canvas command to connected Workbenches viewing this Flow. Success only confirms Server broadcast, not delivery or execution. No replay, session selection or Presentation save. Pan uses screen pixels (positive right/down); zoom is an absolute ratio (0.1–3); fit includes all nodes. Repeating pan moves again.", strictObject({
12990
13142
  flowId: flow,
12991
13143
  command: canvasCommandSchema
@@ -54,6 +54,7 @@ var nodeKinds = /* @__PURE__ */ new Set([
54
54
  "javascript",
55
55
  "openapi",
56
56
  "http-request",
57
+ "dedupe",
57
58
  "llm",
58
59
  "value",
59
60
  "wait",
@@ -1,5 +1,19 @@
1
1
  import type { CodeModule, ConnectorCapability, TaskCapability, JsonValue } from './flow-model.js';
2
2
  export { currentEngineContract, nodejsEngineContract, findEngineContract, type EngineContract } from './engine-contract.js';
3
+ /** Serialized into the guest; keep validation and helpers self-contained. */
4
+ export declare function createState(invoke: (kind: string, payload: JsonValue) => Promise<RuntimeCapabilityResponse>): {
5
+ state: Readonly<{
6
+ get(key: unknown): Promise<JsonValue>;
7
+ set(key: unknown, value: unknown): Promise<void>;
8
+ insert(key: unknown, value: unknown): Promise<boolean>;
9
+ delete(key: unknown): Promise<void>;
10
+ list(): Promise<string[]>;
11
+ }>;
12
+ dedupe: Readonly<{
13
+ insert(ids: unknown): Promise<boolean[]>;
14
+ has(ids: unknown): Promise<boolean[]>;
15
+ }>;
16
+ };
3
17
  export type RuntimeModule = Pick<CodeModule, 'imports' | 'source'>;
4
18
  /** Snapshot a Task result before JSON transport can discard or coerce invalid values. */
5
19
  export declare function snapshotRuntimeResult(value: unknown): JsonValue | undefined;
@@ -23,8 +23,8 @@ function dequal(foo, bar) {
23
23
  }
24
24
  //#endregion
25
25
  //#region src/runtime/common/engineContract.ts
26
- /** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
27
- var currentEngineContract = "open-flow-engine/v9";
26
+ /** v10 adds persistent Flow State to the published v9 Node compatibility and fetch contract. */
27
+ var currentEngineContract = "open-flow-engine/v10";
28
28
  /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
29
29
  var nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
30
30
  var platformSource = (contract) => `export const engineContract = ${JSON.stringify(contract)}
@@ -69,13 +69,19 @@ var nodejsContract = {
69
69
  ].flatMap((name) => [name, `node:${name}`])),
70
70
  platformSource: platformSource(nodejsEngineContract)
71
71
  };
72
- var currentContract = {
72
+ var v9Contract = {
73
73
  ...nodejsContract,
74
74
  codeFetch: true,
75
+ platformSource: platformSource("open-flow-engine/v9")
76
+ };
77
+ var currentContract = {
78
+ ...v9Contract,
79
+ flowState: true,
75
80
  platformSource: platformSource(currentEngineContract)
76
81
  };
77
82
  function findEngineContract(contract) {
78
- if (contract == "open-flow-engine/v9") return currentContract;
83
+ if (contract == "open-flow-engine/v10") return currentContract;
84
+ if (contract == "open-flow-engine/v9") return v9Contract;
79
85
  if (contract == "open-flow-engine/v8") return v8Contract;
80
86
  if (contract == "open-flow-engine/v8/nodejs-compat-v1") return nodejsContract;
81
87
  if (contract == "open-flow-engine/v7" || contract == "open-flow-engine/v7/nodejs-compat-v1") return {
@@ -95,6 +101,60 @@ function findEngineContract(contract) {
95
101
  }
96
102
  //#endregion
97
103
  //#region src/runtime/common/runtime.ts
104
+ /** Serialized into the guest; keep validation and helpers self-contained. */
105
+ function createState(invoke) {
106
+ const invalid = (message) => Object.assign(new TypeError(message), { code: "capability.invalid" });
107
+ const keyOf = (key) => {
108
+ if (typeof key !== "string" || key.length === 0 || new TextEncoder().encode(key).length > 256) throw invalid("State keys require 1 to 256 UTF-8 bytes.");
109
+ return key;
110
+ };
111
+ const valueOf = (value) => {
112
+ if (value === void 0) throw invalid("State values must be JSON.");
113
+ try {
114
+ return snapshotRuntimeResult({ value }).value;
115
+ } catch {
116
+ throw invalid("State values must be JSON.");
117
+ }
118
+ };
119
+ const idsOf = (ids) => {
120
+ if (!Array.isArray(ids) || !Array.from(ids).every((id) => typeof id === "string" || typeof id === "number" && Number.isFinite(id))) throw invalid("Identities must be strings or finite numbers.");
121
+ return ids;
122
+ };
123
+ return {
124
+ state: Object.freeze({
125
+ async get(key) {
126
+ return (await invoke("state.get", { key: keyOf(key) })).body.value;
127
+ },
128
+ async set(key, value) {
129
+ await invoke("state.set", {
130
+ key: keyOf(key),
131
+ value: valueOf(value)
132
+ });
133
+ },
134
+ async insert(key, value) {
135
+ if (value === null) throw invalid("State insert does not accept null.");
136
+ return (await invoke("state.insert", {
137
+ key: keyOf(key),
138
+ value: valueOf(value)
139
+ })).body.inserted;
140
+ },
141
+ async delete(key) {
142
+ await invoke("state.delete", { key: keyOf(key) });
143
+ },
144
+ async list() {
145
+ return (await invoke("state.list", {})).body.keys;
146
+ }
147
+ }),
148
+ dedupe: Object.freeze({
149
+ async insert(ids) {
150
+ return (await invoke("dedupe.insert", { ids: idsOf(ids) })).body.inserted;
151
+ },
152
+ async has(ids) {
153
+ return (await invoke("dedupe.has", { ids: idsOf(ids) })).body.present;
154
+ }
155
+ })
156
+ };
157
+ }
98
158
  /** Snapshot a Task result before JSON transport can discard or coerce invalid values. */
99
159
  function snapshotRuntimeResult(value) {
100
160
  const parents = /* @__PURE__ */ new Set();
@@ -394,6 +454,59 @@ async function rejects(operation, pattern, message) {
394
454
  throw new Error(`${message}: operation unexpectedly succeeded.`);
395
455
  }
396
456
  var runtimeConformanceCases = [
457
+ {
458
+ name: "cancels detached State work when its Task finishes",
459
+ async verify(harness) {
460
+ let aborted = false;
461
+ equal(await harness.invoke({
462
+ input: null,
463
+ invocationId: "state-detached",
464
+ capability: async ({ signal }) => await new Promise((resolve) => {
465
+ signal.addEventListener("abort", () => {
466
+ aborted = true;
467
+ resolve({
468
+ status: 200,
469
+ body: null
470
+ });
471
+ }, { once: true });
472
+ }),
473
+ program: program(harness, `export default async (_, context) => {
474
+ void context.state.set('detached', 1);
475
+ await new Promise(resolve => setTimeout(resolve, 20));
476
+ return null;
477
+ }`)
478
+ }), null, "Detached State Task result");
479
+ equal(aborted, true, "Detached State cancellation");
480
+ }
481
+ },
482
+ {
483
+ name: "rejects non-JSON State values and invalid identities before Host dispatch",
484
+ async verify(harness) {
485
+ let calls = 0;
486
+ equal(await harness.invoke({
487
+ input: null,
488
+ invocationId: "state-invalid",
489
+ capability: async () => {
490
+ calls += 1;
491
+ return {
492
+ status: 200,
493
+ body: null
494
+ };
495
+ },
496
+ program: program(harness, `export default async (_, context) => {
497
+ const cyclic = {}; cyclic.self = cyclic;
498
+ const invalid = [undefined, NaN, Infinity, new Date(), { x: undefined }, cyclic, [undefined], { x: () => 1 }];
499
+ const errors = [];
500
+ for (const value of invalid) { try { await context.state.set('key', value); errors.push('accepted'); } catch (e) { errors.push(e.code); } }
501
+ for (const call of [() => context.state.get(''), () => context.state.get('x'.repeat(257)), () => context.state.insert('x', null), () => context.dedupe.insert([NaN]), () => context.dedupe.has([null]), () => context.dedupe.insert(new Array(1))]) {
502
+ try { await call(); errors.push('accepted'); } catch (e) { errors.push(e.code); }
503
+ }
504
+ return errors;
505
+ }`)
506
+ }), Array.from({ length: 14 }, () => "capability.invalid"), "State boundary validation");
507
+ equal(calls, 0, "State invalid dispatch");
508
+ }
509
+ },
397
510
  {
398
511
  name: "rejects Task results that JSON transport would discard or coerce",
399
512
  async verify(harness) {
@@ -1098,4 +1211,4 @@ function createWorkflows(invoke) {
1098
1211
  } });
1099
1212
  }
1100
1213
  //#endregion
1101
- export { createActions, createArtifacts, createBase64, createFetch, createWorkflows, currentEngineContract, findEngineContract, nodejsEngineContract, nodejsRuntimeConformanceCases, resolveAction, runtimeConformanceCases, snapshotRuntimeResult };
1214
+ export { createActions, createArtifacts, createBase64, createFetch, createState, createWorkflows, currentEngineContract, findEngineContract, nodejsEngineContract, nodejsRuntimeConformanceCases, resolveAction, runtimeConformanceCases, snapshotRuntimeResult };
@@ -63,6 +63,7 @@ export declare function executeNode(context: NodeExecutionContext, input: {
63
63
  readonly node: ExecutableNode;
64
64
  readonly jobId: string;
65
65
  readonly nodeInputs: Readonly<Record<string, JsonValue>>;
66
+ readonly launchInputs?: Readonly<Record<string, JsonValue>>;
66
67
  readonly flowId: string;
67
68
  readonly runId: string;
68
69
  readonly graph: Graph;
@@ -33,7 +33,7 @@ export type SchedulerEvent = {
33
33
  readonly inputs: Readonly<Record<string, JsonValue>>;
34
34
  readonly jobId: string;
35
35
  readonly nodeId: string;
36
- readonly nodeKind: 'agent' | 'approval' | 'condition' | 'connector' | 'decision' | 'javascript' | 'openapi' | 'http-request' | 'llm' | 'value' | 'wait' | 'outputs' | 'workflow' | 'webhook-response';
36
+ readonly nodeKind: 'agent' | 'approval' | 'condition' | 'connector' | 'decision' | 'javascript' | 'openapi' | 'http-request' | 'dedupe' | 'llm' | 'value' | 'wait' | 'outputs' | 'workflow' | 'webhook-response';
37
37
  readonly nodeTitle?: string;
38
38
  readonly runId: string;
39
39
  readonly type: 'node.started';