@oomol-lab/open-flow 0.1.0-beta.77 → 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 (58) hide show
  1. package/README.md +2 -0
  2. package/dist/browser/{ContentIcon-VZQO8VZU.js → ContentIcon-CMn0c-kl.js} +93 -3
  3. package/dist/browser/{FlowCanvas-BNGDMaWr.js → FlowCanvas-BQGBcfoL.js} +1675 -591
  4. package/dist/browser/{createResourceDialog-BmtakfVg.js → createResourceDialog-DCGxfKFM.js} +3 -3
  5. package/dist/browser/{dialog-BcFBfRB1.js → dialog-WzR4CRFH.js} +11 -81
  6. package/dist/browser/{exportSvg-CG8hEUNk.js → exportSvg-CYwyYiuH.js} +2 -2
  7. package/dist/browser/{flowChanges-BGo_4Ypo.js → flowChanges-Bz4dbDN_.js} +1204 -387
  8. package/dist/browser/{flowWorkspace-BFHGev7s.js → flowWorkspace-BI6L6a4r.js} +557 -597
  9. package/dist/browser/licenses.md +1 -1
  10. package/dist/browser/preview.css +512 -462
  11. package/dist/browser/preview.js +8457 -6732
  12. package/dist/browser/{tooltip-BAWSUexx.js → tooltip-BDcVTNpu.js} +65 -2
  13. package/dist/browser/ui.css +5 -24
  14. package/dist/browser/workbench.css +512 -462
  15. package/dist/browser/workbench.d.ts +2 -3
  16. package/dist/browser/workbench.js +65 -690
  17. package/dist/browser/{workbenchSelect-CjK-xdm3.js → workbenchSelect-DtAd0zc4.js} +2 -2
  18. package/dist/command/index.js +25 -25
  19. package/dist/common/authoring.d.ts +3 -3
  20. package/dist/common/authoringExamples.d.ts +3 -3
  21. package/dist/common/authoringSchema.d.ts +14 -22
  22. package/dist/common/control-api-conformance.js +11 -18
  23. package/dist/common/control-api.d.ts +5 -1
  24. package/dist/common/control-api.js +11 -18
  25. package/dist/common/control-requests.d.ts +4 -3
  26. package/dist/common/control-requests.js +26 -37
  27. package/dist/common/flow-authoring.js +6 -14
  28. package/dist/common/flow-change.d.ts +1 -1
  29. package/dist/common/flow-change.js +6 -14
  30. package/dist/common/flow-encoding.js +5 -13
  31. package/dist/common/flow-semantics.js +15 -21
  32. package/dist/common/flow-state.d.ts +16 -39
  33. package/dist/common/flow-state.js +47 -105
  34. package/dist/common/mcp.d.ts +3 -3
  35. package/dist/common/mcp.js +11 -18
  36. package/dist/common/run-events.js +19 -9
  37. package/dist/common/runtime-contract.js +6 -12
  38. package/dist/common/runtime-nodejs-compat.js +11 -12
  39. package/dist/common/scheduler.d.ts +2 -1
  40. package/dist/common/scheduler.js +56 -50
  41. package/dist/common/state-conformance.d.ts +3 -3
  42. package/package.json +1 -1
  43. package/src/control/common/api.ts +20 -3
  44. package/src/control/common/authoring.ts +6 -5
  45. package/src/control/common/authoringExamples.ts +6 -5
  46. package/src/control/common/authoringSchema.ts +7 -7
  47. package/src/control/common/requests.ts +7 -1
  48. package/src/execution/common/events.ts +22 -10
  49. package/src/execution/common/node-execution.ts +1 -1
  50. package/src/execution/common/scheduler.ts +28 -14
  51. package/src/flow/common/change.ts +1 -1
  52. package/src/flow/common/changeSchema.ts +4 -4
  53. package/src/flow/common/errorHandling.ts +1 -1
  54. package/src/flow/common/semantics.ts +12 -6
  55. package/src/runtime/common/nodejsGuest.generated.json +11 -12
  56. package/src/runtime/common/runtime.ts +6 -7
  57. package/src/state/common/conformance.ts +16 -21
  58. package/src/state/common/state.ts +44 -72
@@ -5604,44 +5604,34 @@ function dequal(foo, bar) {
5604
5604
  //#region src/state/common/conformance.ts
5605
5605
  var stateConformanceCases = [
5606
5606
  {
5607
- name: "persists watermarks by instant and resets changed modes atomically",
5607
+ name: "skips composite identities across successful Runs without depending on field order",
5608
5608
  async verify(harness) {
5609
- const config = {
5610
- field: "",
5611
- mode: "increasing",
5612
- space: "node"
5613
- };
5614
- if (!dequal(await harness.dedupe(config, [
5615
- "2026-10-30T09:00:00+08:00",
5616
- "2026-10-30T01:00:00Z",
5617
- "2026-10-30T02:00:00Z"
5618
- ]), {
5619
- items: ["2026-10-30T09:00:00+08:00", "2026-10-30T02:00:00Z"],
5620
- duplicates: ["2026-10-30T01:00:00Z"]
5621
- })) throw new Error("Watermarks must compare instants and preserve item order.");
5622
- let rejected = false;
5623
- try {
5624
- await harness.dedupe(config, [1]);
5625
- } catch {
5626
- rejected = true;
5627
- }
5628
- if (!rejected) throw new Error("Mixed watermark types must fail.");
5629
- if (!dequal(await harness.dedupe(config, ["2026-10-30T02:00:00Z", "2026-10-30T03:00:00Z"]), {
5630
- items: ["2026-10-30T03:00:00Z"],
5631
- duplicates: ["2026-10-30T02:00:00Z"]
5632
- })) throw new Error("A failed batch must preserve the previous watermark.");
5633
- await harness.dedupe({
5634
- ...config,
5635
- mode: "new"
5636
- }, []);
5637
- if (!dequal(await harness.dedupe(config, [
5638
- 5,
5639
- 3,
5640
- 5
5641
- ]), {
5642
- items: [5, 3],
5643
- duplicates: [5]
5644
- })) throw new Error("A mode reset must discard previous watermarks, including on empty batches.");
5609
+ const items = [
5610
+ {
5611
+ company: "a",
5612
+ id: 1
5613
+ },
5614
+ {
5615
+ company: "b",
5616
+ id: 1
5617
+ },
5618
+ {
5619
+ company: "a",
5620
+ id: 1
5621
+ }
5622
+ ];
5623
+ if (!dequal(await harness.skipDuplicates({ fields: ["company", "id"] }, items), {
5624
+ items: items.slice(0, 2),
5625
+ duplicates: [items[2]]
5626
+ })) throw new Error("Composite identities must retain the first item in input order.");
5627
+ if (!dequal(await harness.skipDuplicates({ fields: ["id", "company"] }, items), {
5628
+ items: [],
5629
+ duplicates: items
5630
+ })) throw new Error("Successful Run history must persist independently of field order.");
5631
+ if (!dequal(await harness.skipDuplicates({ fields: ["id"] }, items), {
5632
+ items: [items[0]],
5633
+ duplicates: items.slice(1)
5634
+ })) throw new Error("The field definition must participate in identity.");
5645
5635
  }
5646
5636
  },
5647
5637
  {
@@ -5722,18 +5712,14 @@ var dedupeClearSchema = strictObject({
5722
5712
  scope: _enum(["draft", "live"]),
5723
5713
  space: string().regex(/^(flow|node:.+)$/)
5724
5714
  });
5725
- var dedupeConfigSchema = strictObject({
5726
- field: string().default(""),
5727
- mode: _enum(["new", "increasing"]).default("new"),
5728
- space: _enum(["node", "flow"]).default("node")
5729
- });
5730
- var dedupeExecutorSchema = dedupeConfigSchema.extend({ kind: literal("dedupe") });
5731
- function dedupeTask(config = dedupeConfigSchema.parse({}), name = "Remove Duplicates") {
5715
+ 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([]) });
5716
+ var skipDuplicatesExecutorSchema = skipDuplicatesConfigSchema.extend({ kind: literal("skip-duplicates") });
5717
+ function skipDuplicatesTask(config = skipDuplicatesConfigSchema.parse({}), name = "Skip Duplicates") {
5732
5718
  return {
5733
5719
  name,
5734
5720
  executor: {
5735
5721
  ...config,
5736
- kind: "dedupe"
5722
+ kind: "skip-duplicates"
5737
5723
  },
5738
5724
  inputs: [{
5739
5725
  handle: "items",
@@ -5753,7 +5739,7 @@ var StateError = class extends Error {
5753
5739
  this.retryable = false;
5754
5740
  this.name = "StateError";
5755
5741
  this.code = code;
5756
- this.catchable = code === "dedupe.full";
5742
+ this.catchable = code === "dedupe.full" || code === "node.input-invalid";
5757
5743
  }
5758
5744
  };
5759
5745
  function stateKey(key) {
@@ -5767,58 +5753,18 @@ function dedupeIdentities(value) {
5767
5753
  async function hashIdentities(ids) {
5768
5754
  return Promise.all(ids.map(async (id) => new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${typeof id}:${String(id)}`))).slice(0, 16)));
5769
5755
  }
5770
- function watermarkValue(id) {
5771
- if (typeof id === "number") {
5772
- if (!Number.isFinite(id)) throw new StateError("capability.invalid", "Watermarks must be finite.");
5773
- return id;
5774
- }
5775
- const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})$/.exec(id);
5776
- if (match == null) throw new StateError("capability.invalid", "Watermarks require RFC 3339 timestamps with an explicit timezone.");
5777
- const [, year, month, day, hour, minute, second, , zone] = match;
5778
- const y = Number(year);
5779
- const days = [
5780
- 31,
5781
- y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28,
5782
- 31,
5783
- 30,
5784
- 31,
5785
- 30,
5786
- 31,
5787
- 31,
5788
- 30,
5789
- 31,
5790
- 30,
5791
- 31
5792
- ][Number(month) - 1] ?? 0;
5793
- const result = Date.parse(id);
5794
- if (Number(month) < 1 || Number(month) > 12 || Number(day) < 1 || Number(day) > days || Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59 || zone !== "Z" && (Number(zone.slice(1, 3)) > 23 || Number(zone.slice(4)) > 59) || !Number.isFinite(result)) throw new StateError("capability.invalid", "Watermarks require valid calendar timestamps.");
5795
- return result;
5796
- }
5797
- function increasingIdentities(ids, watermark) {
5798
- const type = typeof (watermark ?? ids[0]);
5799
- if (ids.some((id) => typeof id !== type)) throw new StateError("capability.invalid", "Watermark types must not be mixed.");
5800
- const initial = watermark == null ? -Infinity : watermarkValue(watermark);
5801
- const seen = /* @__PURE__ */ new Set();
5802
- let maximum = initial;
5803
- let next = watermark;
5804
- return {
5805
- inserted: ids.map((id) => {
5806
- const value = watermarkValue(id);
5807
- const fresh = value > initial && !seen.has(value);
5808
- seen.add(value);
5809
- if (value > maximum) {
5810
- maximum = value;
5811
- next = id;
5812
- }
5813
- return fresh;
5814
- }),
5815
- watermark: next
5816
- };
5817
- }
5818
- function itemIdentities(items, field) {
5819
- if (!Array.isArray(items)) throw new StateError("capability.invalid", "Deduplication requires an array of items.");
5820
- const path = field === "" ? [] : field.split(".");
5821
- return dedupeIdentities(items.map((item) => path.reduce((value, key) => value != null && typeof value === "object" && Object.hasOwn(value, key) ? value[key] : void 0, item)));
5756
+ /** Canonical identities include the field definition and preserve scalar types. */
5757
+ function skipDuplicatesIdentities(items, fields) {
5758
+ if (!Array.isArray(items)) throw new StateError("node.input-invalid", "Skip Duplicates requires an array of items.");
5759
+ const paths = fields.toSorted();
5760
+ return items.map((item, index) => {
5761
+ const values = (paths.length === 0 ? [""] : paths).map((field) => {
5762
+ const value = field === "" ? item : field.split(".").reduce((current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? current[key] : void 0, item);
5763
+ if (typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) throw new StateError("node.input-invalid", `Item ${index + 1}, ${field === "" ? "the item itself" : `field ${JSON.stringify(field)}`} must be a string or finite number.`);
5764
+ return value;
5765
+ });
5766
+ return JSON.stringify([paths, values]);
5767
+ });
5822
5768
  }
5823
5769
  var flowStateViewSchema = object({
5824
5770
  version: literal(1),
@@ -5838,15 +5784,11 @@ var flowStateViewSchema = object({
5838
5784
  })),
5839
5785
  spaces: array(object({
5840
5786
  space: string(),
5841
- mode: _enum(["new", "increasing"]),
5842
5787
  count: number().nonnegative(),
5843
- watermark: union([
5844
- string(),
5845
- number(),
5846
- _null()
5847
- ]),
5788
+ pendingCount: number().nonnegative(),
5789
+ reservedCount: number().nonnegative(),
5848
5790
  updatedAt: number()
5849
5791
  }))
5850
5792
  });
5851
5793
  //#endregion
5852
- export { StateError, dedupeClearSchema, dedupeConfigSchema, dedupeExecutorSchema, dedupeIdentities, dedupeTask, flowStateViewSchema, hashIdentities, increasingIdentities, itemIdentities, stateClearSchema, stateConformanceCases, stateKey, stateQuerySchema, watermarkValue };
5794
+ export { StateError, dedupeClearSchema, dedupeIdentities, flowStateViewSchema, hashIdentities, skipDuplicatesConfigSchema, skipDuplicatesExecutorSchema, skipDuplicatesIdentities, skipDuplicatesTask, stateClearSchema, stateConformanceCases, stateKey, stateQuerySchema };
@@ -254,7 +254,7 @@ export declare const mcpTools: {
254
254
  value: {
255
255
  revision?: string | undefined;
256
256
  query: string;
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;
257
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "skip-duplicates" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
258
258
  offset: number;
259
259
  limit: number;
260
260
  flowId: string;
@@ -286,7 +286,7 @@ export declare const mcpTools: {
286
286
  };
287
287
  validate(value: unknown): {
288
288
  value: {
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;
289
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "skip-duplicates" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
290
290
  action?: string | undefined;
291
291
  flowId?: string | undefined;
292
292
  };
@@ -414,7 +414,7 @@ export declare const mcpTools: {
414
414
  edits: ({
415
415
  op: "node.add";
416
416
  as: string;
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";
417
+ type: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "skip-duplicates" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow";
418
418
  name: string;
419
419
  onError?: "error" | "stop" | undefined;
420
420
  retry?: {
@@ -8860,12 +8860,8 @@ var dedupeClearSchema = strictObject({
8860
8860
  scope: _enum(["draft", "live"]),
8861
8861
  space: string$1().regex(/^(flow|node:.+)$/)
8862
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") });
8863
+ var skipDuplicatesConfigSchema = strictObject({ fields: array(string$1().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([]) });
8864
+ var skipDuplicatesExecutorSchema = skipDuplicatesConfigSchema.extend({ kind: literal("skip-duplicates") });
8869
8865
  var flowStateViewSchema = object({
8870
8866
  version: literal(1),
8871
8867
  scope: _enum(["draft", "live"]),
@@ -8884,13 +8880,9 @@ var flowStateViewSchema = object({
8884
8880
  })),
8885
8881
  spaces: array(object({
8886
8882
  space: string$1(),
8887
- mode: _enum(["new", "increasing"]),
8888
8883
  count: number().nonnegative(),
8889
- watermark: union([
8890
- string$1(),
8891
- number(),
8892
- _null()
8893
- ]),
8884
+ pendingCount: number().nonnegative(),
8885
+ reservedCount: number().nonnegative(),
8894
8886
  updatedAt: number()
8895
8887
  }))
8896
8888
  });
@@ -9183,7 +9175,7 @@ var managedFields = object({
9183
9175
  revisionId: text
9184
9176
  }),
9185
9177
  httpRequestExecutorSchema,
9186
- dedupeExecutorSchema
9178
+ skipDuplicatesExecutorSchema
9187
9179
  ])
9188
9180
  });
9189
9181
  var managed = managedFields.transform(limitDecisionTask);
@@ -9655,7 +9647,7 @@ array(union([...new Map(Object.entries(shapes).map(([kind, shape]) => [kind, sha
9655
9647
  ...shape
9656
9648
  })])).values()])).min(1);
9657
9649
  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.");
9650
+ 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.");
9659
9651
  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.");
9660
9652
  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.");
9661
9653
  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.");
@@ -9812,7 +9804,7 @@ var type = _enum([
9812
9804
  "decision",
9813
9805
  "openapi",
9814
9806
  "http-request",
9815
- "dedupe",
9807
+ "skip-duplicates",
9816
9808
  "condition",
9817
9809
  "value",
9818
9810
  "wait",
@@ -9943,7 +9935,7 @@ var authoringConfigurations = {
9943
9935
  "connector": authoringDefinitionSchemas.connector.omit({ outputMode: true }).strict(),
9944
9936
  "openapi": authoringOpenApiSchema,
9945
9937
  "http-request": httpRequestConfigSchema,
9946
- "dedupe": dedupeConfigSchema,
9938
+ "skip-duplicates": skipDuplicatesConfigSchema,
9947
9939
  "agent": authoringDefinitionSchemas.agent.omit({
9948
9940
  prompt: true,
9949
9941
  tools: true
@@ -12057,7 +12049,7 @@ function decodeRunEvent(value) {
12057
12049
  switch (kind) {
12058
12050
  case "node.started": {
12059
12051
  const nodeKind = payload.nodeKind;
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();
12052
+ if (nodeKind !== void 0 && nodeKind !== "approval" && nodeKind !== "condition" && nodeKind !== "connector" && nodeKind !== "javascript" && nodeKind !== "decision" && nodeKind !== "openapi" && nodeKind !== "http-request" && nodeKind !== "skip-duplicates" && nodeKind !== "llm" && nodeKind !== "agent" && nodeKind !== "value" && nodeKind !== "outputs" && nodeKind !== "webhook-response" && nodeKind !== "workflow" && nodeKind !== "wait") return invalidResponse();
12061
12053
  return {
12062
12054
  ...base,
12063
12055
  kind,
@@ -12282,12 +12274,13 @@ var ControlClient = class {
12282
12274
  })
12283
12275
  }));
12284
12276
  }
12285
- async createFlow(name, idempotencyKey = `flow-${randomId()}`, teamId, tagIds) {
12277
+ async createFlow(name, idempotencyKey = `flow-${randomId()}`, teamId, tagIds, initialPresentation) {
12286
12278
  return flow$1(await this.request("/v1/flows", {
12287
12279
  body: JSON.stringify({
12288
12280
  name,
12289
12281
  ...teamId == null ? {} : { teamId },
12290
12282
  ...tagIds == null ? {} : { tagIds },
12283
+ ...initialPresentation == null ? {} : { presentation: initialPresentation },
12291
12284
  version: 1
12292
12285
  }),
12293
12286
  headers: { "idempotency-key": idempotencyKey },
@@ -54,7 +54,7 @@ var nodeKinds = /* @__PURE__ */ new Set([
54
54
  "javascript",
55
55
  "openapi",
56
56
  "http-request",
57
- "dedupe",
57
+ "skip-duplicates",
58
58
  "llm",
59
59
  "value",
60
60
  "wait",
@@ -81,12 +81,12 @@ function nodeKind(value) {
81
81
  function runId(event) {
82
82
  return string(event.runId, "Runtime event runId");
83
83
  }
84
- function nodeContext(event, flows, platformRunId) {
84
+ function nodeContext(event, flows, platformRunId, identify) {
85
85
  const rawRunId = runId(event);
86
86
  const flowId = flows.get(rawRunId);
87
87
  if (flowId == null) throw new TypeError("Runtime node event preceded its run.started event.");
88
88
  return {
89
- executionId: projectExecutionId(platformRunId, rawRunId, string(event.jobId, "Runtime node event jobId")),
89
+ executionId: identify("execution", platformRunId, rawRunId, string(event.jobId, "Runtime node event jobId")),
90
90
  flowId,
91
91
  nodeId: string(event.nodeId, "Runtime node event nodeId"),
92
92
  rawRunId
@@ -139,6 +139,16 @@ function createEventProjector(platformRunId, nodeFailureCodes) {
139
139
  const jobs = /* @__PURE__ */ new Map();
140
140
  const nodeProgressBuckets = /* @__PURE__ */ new Map();
141
141
  const runProgressBuckets = /* @__PURE__ */ new Map();
142
+ const identities = /* @__PURE__ */ new Map();
143
+ const identify = (prefix, ...parts) => {
144
+ const key = JSON.stringify([prefix, ...parts]);
145
+ let identity = identities.get(key);
146
+ if (identity == null) {
147
+ identity = opaqueId(prefix, ...parts);
148
+ identities.set(key, identity);
149
+ }
150
+ return identity;
151
+ };
142
152
  return async (value) => {
143
153
  const event = object(value, "Runtime event");
144
154
  const type = string(event.type, "Runtime event type");
@@ -157,8 +167,8 @@ function createEventProjector(platformRunId, nodeFailureCodes) {
157
167
  kind: "run.started",
158
168
  payload: {
159
169
  flowId,
160
- ...parentRunId === void 0 ? {} : { parentScopeId: await opaqueId("scope", platformRunId, parentRunId) },
161
- scopeId: await opaqueId("scope", platformRunId, rawRunId)
170
+ ...parentRunId === void 0 ? {} : { parentScopeId: await identify("scope", platformRunId, parentRunId) },
171
+ scopeId: await identify("scope", platformRunId, rawRunId)
162
172
  }
163
173
  };
164
174
  }
@@ -175,7 +185,7 @@ function createEventProjector(platformRunId, nodeFailureCodes) {
175
185
  payload: {
176
186
  flowId,
177
187
  progress: event.progress,
178
- scopeId: await opaqueId("scope", platformRunId, rawRunId)
188
+ scopeId: await identify("scope", platformRunId, rawRunId)
179
189
  }
180
190
  };
181
191
  }
@@ -183,7 +193,7 @@ function createEventProjector(platformRunId, nodeFailureCodes) {
183
193
  const rawRunId = runId(event);
184
194
  if (type == "run.failed" && rawRunId == platformRunId && event.failure != null) {
185
195
  const failure = object(event.failure, "Runtime terminal failure");
186
- const context = nodeContext(failure, flows, platformRunId);
196
+ const context = nodeContext(failure, flows, platformRunId, identify);
187
197
  return {
188
198
  kind: "node.failed",
189
199
  payload: {
@@ -201,13 +211,13 @@ function createEventProjector(platformRunId, nodeFailureCodes) {
201
211
  return;
202
212
  }
203
213
  if (type == "node.started" || type == "node.progress" || type == "node.artifact" || type == "node.log" || type == "node.completed" || type == "node.failed") {
204
- const context = nodeContext(event, flows, platformRunId);
214
+ const context = nodeContext(event, flows, platformRunId, identify);
205
215
  const executionId = await context.executionId;
206
216
  const payload = {
207
217
  executionId,
208
218
  flowId: context.flowId,
209
219
  nodeId: context.nodeId,
210
- scopeId: await opaqueId("scope", platformRunId, context.rawRunId),
220
+ scopeId: await identify("scope", platformRunId, context.rawRunId),
211
221
  ...event.systemOutputs === void 0 ? {} : { systemOutputs: object(event.systemOutputs, "Runtime system outputs") },
212
222
  ...event.handled === true ? { handled: true } : {}
213
223
  };
@@ -1036,19 +1036,13 @@ function createBase64() {
1036
1036
  const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
1037
1037
  const bytes = new Uint8Array(text.length / 4 * 3 - padding);
1038
1038
  for (let index = 0, offset = 0; index < text.length; index += 4) {
1039
- const digits = [
1040
- 0,
1041
- 1,
1042
- 2,
1043
- 3
1044
- ].map((position) => {
1039
+ let value = 0;
1040
+ for (let position = 0; position < 4; position++) {
1045
1041
  const code = text.charCodeAt(index + position);
1046
- if (code == 61 && index + position >= text.length - padding) return 0;
1047
- const value = code < 128 ? values[code] : 255;
1048
- if (value == 255) throw new TypeError("Base64 content is invalid.");
1049
- return value;
1050
- });
1051
- const value = digits[0] << 18 | digits[1] << 12 | digits[2] << 6 | digits[3];
1042
+ const digit = code == 61 && index + position >= text.length - padding ? 0 : code < 128 ? values[code] : 255;
1043
+ if (digit == 255) throw new TypeError("Base64 content is invalid.");
1044
+ value = value << 6 | digit;
1045
+ }
1052
1046
  if (offset < bytes.length) bytes[offset++] = value >> 16;
1053
1047
  if (offset < bytes.length) bytes[offset++] = value >> 8 & 255;
1054
1048
  if (offset < bytes.length) bytes[offset++] = value & 255;