@oomol-lab/open-flow 0.1.0-beta.81 → 0.1.0-beta.83

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 (52) hide show
  1. package/README.md +35 -0
  2. package/dist/browser/{FlowCanvas-CrsiDHK8.js → FlowCanvas-ChbukrDh.js} +879 -535
  3. package/dist/browser/{createResourceDialog-CS3u9ga9.js → createResourceDialog-Bi4fUnbD.js} +5 -4
  4. package/dist/browser/{dialog-CFlvkliO.js → dialog-BzoSxhXQ.js} +1 -1
  5. package/dist/browser/{exportPng-BOPfjeEi.js → exportPng-CrAFfOaD.js} +187 -70
  6. package/dist/browser/{flowChanges-CFj5pL4Y.js → flowChanges-C567g-Pc.js} +2675 -93
  7. package/dist/browser/{flowWorkspace-BeQaeJ3e.js → flowWorkspace-rvcneG4q.js} +980 -2621
  8. package/dist/browser/licenses.md +2 -2
  9. package/dist/browser/{markdownContent-frwDfQQU.js → markdownContent-B-GLj5DU.js} +18 -40
  10. package/dist/browser/{markdownContent-CAG_CQfH.js → markdownContent-D8G_oNOe.js} +18 -41
  11. package/dist/browser/preview.css +125 -80
  12. package/dist/browser/preview.js +1093 -379
  13. package/dist/browser/{tooltip-BDcVTNpu.js → tooltip-DGj5pudg.js} +1 -1
  14. package/dist/browser/ui.css +45 -0
  15. package/dist/browser/{dist-CHwj9BhV.js → videoLink-NAWDmBtf.js} +35 -1
  16. package/dist/browser/workbench-contract.d.ts +6 -0
  17. package/dist/browser/workbench.css +296 -251
  18. package/dist/browser/workbench.d.ts +2 -0
  19. package/dist/browser/workbench.js +294 -141
  20. package/dist/browser/{workbenchSelect-Ci0KCYv9.js → workbenchSelect-BIaEVTx6.js} +2 -2
  21. package/dist/browser/{ContentIcon-CMn0c-kl.js → wrapLayout-C4moMKgb.js} +4400 -4148
  22. package/dist/command/index.js +43 -41
  23. package/dist/common/authoring.d.ts +1 -1
  24. package/dist/common/control-api-conformance.js +306 -2
  25. package/dist/common/control-api.d.ts +20 -0
  26. package/dist/common/control-api.js +306 -4
  27. package/dist/common/control-requests.d.ts +3 -5
  28. package/dist/common/control-requests.js +5 -7
  29. package/dist/common/flow-encoding.d.ts +1 -0
  30. package/dist/common/flow-encoding.js +167 -1
  31. package/dist/common/flow-semantics.js +20 -0
  32. package/dist/common/flow-workflowBundle.d.ts +27 -0
  33. package/dist/common/flow-workflowFile.d.ts +2 -0
  34. package/dist/common/flow-workflowReferences.d.ts +4 -0
  35. package/dist/common/mcp.d.ts +2 -0
  36. package/dist/common/mcp.js +312 -4
  37. package/dist/render/browser/licenses.md +4047 -0
  38. package/dist/render/browser/runtime.css +14914 -0
  39. package/dist/render/browser/runtime.js +267526 -0
  40. package/dist/render/common/png.d.ts +32 -0
  41. package/dist/render/node/index.d.ts +11 -0
  42. package/dist/render/node/index.js +9508 -0
  43. package/dist/render/node/licenses.md +75 -0
  44. package/package.json +12 -2
  45. package/src/control/common/api.ts +50 -0
  46. package/src/control/common/flowDecoders.ts +2 -0
  47. package/src/control/common/mcp.ts +4 -4
  48. package/src/control/common/requests.ts +12 -1
  49. package/src/flow/common/encoding.ts +1 -0
  50. package/src/flow/common/workflowBundle.ts +117 -0
  51. package/src/flow/common/workflowFile.ts +4 -0
  52. package/src/flow/common/workflowReferences.ts +20 -0
@@ -1,8 +1,12 @@
1
1
  import type { GraphNode } from './flow-change.js';
2
2
  import type { WorkflowCapability } from './flow-model.js';
3
+ type WorkflowTarget = Pick<WorkflowCapability, 'flowId' | 'revisionId'>;
4
+ /** Rewrite only declared workflow identities, preserving business values and saved ports. */
5
+ export declare function mapNodeWorkflowTargets(node: GraphNode, map: (target: WorkflowTarget) => WorkflowTarget): GraphNode;
3
6
  /** Workflow declarations share discovery across authoring, admission and resource indexing. */
4
7
  export declare function nodeWorkflowReferences(node: GraphNode): readonly {
5
8
  readonly target: Omit<WorkflowCapability, 'kind' | 'name'>;
6
9
  readonly path: string;
7
10
  readonly tool?: string;
8
11
  }[];
12
+ export {};
@@ -349,6 +349,7 @@ export declare const mcpTools: {
349
349
  validate(value: unknown): {
350
350
  value: {
351
351
  flowId: string;
352
+ includeDependencies?: boolean | undefined;
352
353
  };
353
354
  } | {
354
355
  issues: {
@@ -380,6 +381,7 @@ export declare const mcpTools: {
380
381
  file: JsonValue;
381
382
  name?: string | undefined;
382
383
  teamId?: string | undefined;
384
+ tagIds?: string[] | undefined;
383
385
  idempotencyKey: string;
384
386
  };
385
387
  } | {
@@ -9692,6 +9692,27 @@ function checkModelVersion(content) {
9692
9692
  function assertDocumentHttpAuthBindings(content) {
9693
9693
  for (const graphNode of Object.values(content.graph.nodes)) if (graphNode.kind == "task" && "executor" in graphNode.task && ["openapi", "http-request"].includes(graphNode.task.executor.kind)) assertHttpAuthBindings(graphNode.inputs);
9694
9694
  }
9695
+ //#endregion
9696
+ //#region src/flow/common/condition.ts
9697
+ function clearConditionBinding(operand) {
9698
+ return operand.kind == "source" && operand.source.kind == "binding" ? { kind: "value" } : operand;
9699
+ }
9700
+ function clearConditionBindings(node) {
9701
+ return {
9702
+ ...node,
9703
+ cases: node.cases.map((item) => ({
9704
+ ...item,
9705
+ groups: item.groups.map((group) => ({
9706
+ ...group,
9707
+ expressions: group.expressions.map((expression) => ({
9708
+ ...expression,
9709
+ left: clearConditionBinding(expression.left),
9710
+ ...expression.right == null ? {} : { right: clearConditionBinding(expression.right) }
9711
+ }))
9712
+ }))
9713
+ }))
9714
+ };
9715
+ }
9695
9716
  function resourceNameIssue(value) {
9696
9717
  const name = value.trim();
9697
9718
  if (name.length == 0) return "empty";
@@ -10154,6 +10175,31 @@ var resultQuerySchema = strictObject({
10154
10175
  maxBytes: number().int().min(1).max(1048576).default(15e3)
10155
10176
  });
10156
10177
  //#endregion
10178
+ //#region src/flow/common/workflowReferences.ts
10179
+ /** Workflow declarations share discovery across authoring, admission and resource indexing. */
10180
+ function nodeWorkflowReferences(node) {
10181
+ if (node.kind != "task") return [];
10182
+ if ("moduleId" in node.task) return (node.task.capabilities ?? []).flatMap((item, index) => item.kind == "workflow" ? [{
10183
+ target: item,
10184
+ path: `task/capabilities/${index}`,
10185
+ tool: item.name
10186
+ }] : []);
10187
+ if (node.task.executor.kind == "workflow") return [{
10188
+ target: {
10189
+ ...node.task.executor,
10190
+ inputs: node.task.inputs.filter((port) => "handle" in port),
10191
+ outputs: node.task.outputs.filter((port) => "handle" in port)
10192
+ },
10193
+ path: "task/executor"
10194
+ }];
10195
+ if (node.task.executor.kind == "agent") return node.task.executor.tools.flatMap((item, index) => item.kind == "workflow" ? [{
10196
+ target: item,
10197
+ path: `task/executor/tools/${index}`,
10198
+ tool: item.name
10199
+ }] : []);
10200
+ return [];
10201
+ }
10202
+ //#endregion
10157
10203
  //#region src/workflow/common/workflow.ts
10158
10204
  var portSchema = object({
10159
10205
  handle: string$1().min(1),
@@ -10702,12 +10748,12 @@ function decodeLiveDetails(value) {
10702
10748
  publication: current,
10703
10749
  status,
10704
10750
  enabled: source.enabled,
10705
- entries: decodeEntries(source.entries, current),
10751
+ entries: decodeEntries$1(source.entries, current),
10706
10752
  version: 1
10707
10753
  };
10708
10754
  }
10709
10755
  /** Both producers and consumers enforce the same published-entry invariants. */
10710
- function decodeEntries(values, current) {
10756
+ function decodeEntries$1(values, current) {
10711
10757
  const parsed = array(entrySchema).safeParse(values);
10712
10758
  if (!parsed.success) return invalidResponse();
10713
10759
  const ids = /* @__PURE__ */ new Set();
@@ -10754,11 +10800,109 @@ function providerIconAppearance(metadata, position) {
10754
10800
  };
10755
10801
  }
10756
10802
  //#endregion
10803
+ //#region src/trigger/common/config.ts
10804
+ /** Deployment-bound selections owned by the Trigger configuration contract. */
10805
+ function clearTriggerExternalConfig(key, config) {
10806
+ const fields = key == "jira.on_issue_event" || key == "intercom.on_conversation_event" || key == "sentry.on_issue_alert" || key == "zoom.on_recording_ready" || key == "fireflies.on_meeting_ready" ? ["sourceId", "eventTypes"] : key == "slackbot.on_event" ? [
10807
+ "sourceId",
10808
+ "eventTypes",
10809
+ "channelIds"
10810
+ ] : key == "feishu_app_bot.on_event" ? [
10811
+ "sourceId",
10812
+ "eventTypes",
10813
+ "resource",
10814
+ "chatIds"
10815
+ ] : key == "linear.on_issue_changed" ? ["teamId", "stateIds"] : [];
10816
+ return {
10817
+ ...config,
10818
+ ...Object.fromEntries(fields.map((field) => [field, { kind: "unset" }]))
10819
+ };
10820
+ }
10821
+ //#endregion
10822
+ //#region src/flow/common/connectionUsage.ts
10823
+ function clearNodeConnections(graph, connectionId) {
10824
+ return {
10825
+ ...graph,
10826
+ nodes: Object.fromEntries(Object.entries(graph.nodes).map(([id, node]) => {
10827
+ if ((node.kind == "poll" || node.kind == "integration") && (connectionId == null || node.connectionId == connectionId)) {
10828
+ const { connectionId: _, ...remaining } = node;
10829
+ return [id, remaining];
10830
+ }
10831
+ if (node.kind != "task") return [id, node];
10832
+ if ("executor" in node.task) {
10833
+ const executor = node.task.executor;
10834
+ const clear = (value) => {
10835
+ if (value.connectionId === "") return value;
10836
+ if (connectionId != null && value.connectionId != connectionId) return value;
10837
+ const { connectionId: _, ...rest } = value;
10838
+ return rest;
10839
+ };
10840
+ if (executor.kind == "connector") return [id, {
10841
+ ...node,
10842
+ task: {
10843
+ ...node.task,
10844
+ executor: clear(executor)
10845
+ }
10846
+ }];
10847
+ if (executor.kind == "agent") return [id, {
10848
+ ...node,
10849
+ task: {
10850
+ ...node.task,
10851
+ executor: {
10852
+ ...executor,
10853
+ tools: executor.tools.map(clear),
10854
+ ...executor.notification == null ? {} : { notification: clear(executor.notification) }
10855
+ }
10856
+ }
10857
+ }];
10858
+ return [id, node];
10859
+ }
10860
+ const capabilities = node.task.capabilities?.map((capability) => {
10861
+ if (capability.kind == "workflow" || capability.kind == "http") return capability;
10862
+ if (!("mode" in capability)) {
10863
+ if ("action" in capability) {
10864
+ const { connectionId: selected, ...rest } = capability;
10865
+ return {
10866
+ ...rest,
10867
+ connections: capability.connections.filter((item) => connectionId != null && item.connectionId != connectionId),
10868
+ ...selected != null && connectionId != null && selected != connectionId ? { connectionId: selected } : {}
10869
+ };
10870
+ }
10871
+ return {
10872
+ ...capability,
10873
+ ...connectionId == null && (capability.connectionHints?.length ?? 0) > 0 ? { actionHints: [.../* @__PURE__ */ new Set([...capability.actionHints ?? [], ...capability.connectionHints.map((hint) => hint.action)])] } : {},
10874
+ ...capability.connectionHints == null ? {} : { connectionHints: capability.connectionHints.filter((item) => connectionId != null && item.connectionId != connectionId) }
10875
+ };
10876
+ }
10877
+ if (capability.mode != "independent") return capability;
10878
+ return {
10879
+ ...capability,
10880
+ actions: capability.actions.map((action) => {
10881
+ if (action.connectionId === "") return action;
10882
+ if (connectionId != null && action.connectionId != connectionId) return action;
10883
+ const { connectionId: _, ...remaining } = action;
10884
+ return remaining;
10885
+ })
10886
+ };
10887
+ });
10888
+ return [id, capabilities == null ? node : {
10889
+ ...node,
10890
+ task: {
10891
+ ...node.task,
10892
+ capabilities
10893
+ }
10894
+ }];
10895
+ }))
10896
+ };
10897
+ }
10898
+ //#endregion
10757
10899
  //#region src/flow/common/workflowFile.ts
10758
10900
  var envelope = strictObject({
10759
10901
  kind: literal("open-flow-workflow"),
10760
10902
  version: literal(1),
10761
10903
  name: string$1().optional(),
10904
+ icon: string$1().optional(),
10905
+ description: string$1().optional(),
10762
10906
  content: unknown(),
10763
10907
  presentation: record$2(string$1(), json$4()).optional()
10764
10908
  });
@@ -10771,6 +10915,126 @@ function decodeWorkflowFile(value) {
10771
10915
  content: decodeRevisionContent(file.content)
10772
10916
  };
10773
10917
  }
10918
+ function cleanInput(input) {
10919
+ if (input.kind != "sources" || !input.sources.some((source) => source.kind == "binding")) return input;
10920
+ const sources = input.sources.filter((source) => source.kind != "binding");
10921
+ return sources.length == 0 ? { kind: "unset" } : {
10922
+ kind: "sources",
10923
+ sources
10924
+ };
10925
+ }
10926
+ /** Clear only owned structured references, never strings in code or business values. */
10927
+ function cleanWorkflowFile(value) {
10928
+ const file = decodeWorkflowFile(value);
10929
+ const original = file.content.document.graph;
10930
+ const graph = clearNodeConnections(original);
10931
+ const nodes = Object.fromEntries(Object.entries(graph.nodes).map(([id, source]) => {
10932
+ let node = source;
10933
+ if ("inputs" in node) node = {
10934
+ ...node,
10935
+ inputs: Object.fromEntries(Object.entries(node.inputs).map(([key, input]) => [key, cleanInput(input)]))
10936
+ };
10937
+ if (node.kind == "outputs") node = {
10938
+ ...node,
10939
+ outputMappings: Object.fromEntries(Object.entries(node.outputMappings).map(([key, input]) => [key, cleanInput(input)]))
10940
+ };
10941
+ if (node.kind == "condition") node = clearConditionBindings(node);
10942
+ if (node.kind == "error" && node.sourceFlowIds != null) node = {
10943
+ ...node,
10944
+ sourceFlowIds: []
10945
+ };
10946
+ if (node.kind == "poll" || node.kind == "integration") node = {
10947
+ ...node,
10948
+ config: clearTriggerExternalConfig(node.definition.key, node.config)
10949
+ };
10950
+ return [id, node];
10951
+ }));
10952
+ return {
10953
+ file: {
10954
+ ...file,
10955
+ content: {
10956
+ ...file.content,
10957
+ document: {
10958
+ ...file.content.document,
10959
+ bindings: {},
10960
+ graph: {
10961
+ ...graph,
10962
+ nodes
10963
+ }
10964
+ }
10965
+ }
10966
+ },
10967
+ summary: {
10968
+ changedNodeIds: Object.keys(nodes).filter((id) => !dequal(nodes[id], original.nodes[id])),
10969
+ removedBindings: Object.keys(file.content.document.bindings).length
10970
+ }
10971
+ };
10972
+ }
10973
+ //#endregion
10974
+ //#region src/flow/common/workflowBundle.ts
10975
+ var entries = array(strictObject({
10976
+ flowId: string$1().min(1),
10977
+ file: unknown()
10978
+ })).min(1);
10979
+ var bundleSchema = strictObject({
10980
+ kind: literal("open-flow-workflow-bundle"),
10981
+ version: literal(1),
10982
+ entryFlowId: string$1().min(1),
10983
+ workflows: entries
10984
+ });
10985
+ var previewSchema = strictObject({
10986
+ version: literal(1),
10987
+ entryFlowId: string$1().min(1),
10988
+ workflows: entries,
10989
+ unavailableFlowIds: array(string$1().min(1))
10990
+ });
10991
+ function decodeEntries(value, entryFlowId, unavailableFlowIds = []) {
10992
+ const workflows = value.map(({ flowId, file }) => ({
10993
+ flowId,
10994
+ file: decodeWorkflowFile(file)
10995
+ }));
10996
+ const ids = new Set(workflows.map(({ flowId }) => flowId));
10997
+ if (ids.size != workflows.length || !ids.has(entryFlowId)) throw new TypeError("Workflow identities must be unique and include the entry workflow.");
10998
+ const unavailable = new Set(unavailableFlowIds);
10999
+ if (unavailable.size != unavailableFlowIds.length || unavailableFlowIds.some((id) => ids.has(id))) throw new TypeError("Invalid unavailable workflow identities.");
11000
+ for (const { file } of workflows) for (const node of Object.values(file.content.document.graph.nodes)) for (const { target } of nodeWorkflowReferences(node)) if (target.flowId && !ids.has(target.flowId) && !unavailable.has(target.flowId)) throw new TypeError("A referenced workflow is missing from the bundle.");
11001
+ return workflows;
11002
+ }
11003
+ function decodeWorkflowBundle(value) {
11004
+ checkJsonDepth(value, -4);
11005
+ const bundle = bundleSchema.parse(value);
11006
+ return {
11007
+ ...bundle,
11008
+ workflows: decodeEntries(bundle.workflows, bundle.entryFlowId)
11009
+ };
11010
+ }
11011
+ function decodeWorkflowExportPreview(value) {
11012
+ checkJsonDepth(value, -4);
11013
+ const preview = previewSchema.parse(value);
11014
+ return {
11015
+ ...preview,
11016
+ workflows: decodeEntries(preview.workflows, preview.entryFlowId, preview.unavailableFlowIds)
11017
+ };
11018
+ }
11019
+ function cleanWorkflowBundle(value) {
11020
+ const bundle = decodeWorkflowBundle(value);
11021
+ return {
11022
+ ...bundle,
11023
+ workflows: bundle.workflows.map(({ flowId, file }) => ({
11024
+ flowId,
11025
+ file: cleanWorkflowFile(file).file
11026
+ }))
11027
+ };
11028
+ }
11029
+ function workflowBundleFromPreview(preview) {
11030
+ if (preview.unavailableFlowIds.length) throw new TypeError("Some referenced workflows are unavailable.");
11031
+ return cleanWorkflowBundle({
11032
+ kind: "open-flow-workflow-bundle",
11033
+ version: 1,
11034
+ entryFlowId: preview.entryFlowId,
11035
+ workflows: preview.workflows
11036
+ });
11037
+ }
10774
11038
  new TextEncoder();
10775
11039
  //#endregion
10776
11040
  //#region src/control/common/providerAccess.ts
@@ -11509,6 +11773,8 @@ function flow$1(value) {
11509
11773
  resourceReferences: resourceReferences(source.resourceReferences),
11510
11774
  flowId: string(source.flowId),
11511
11775
  name: string(source.name),
11776
+ ...source.icon === void 0 ? {} : { icon: string(source.icon) },
11777
+ ...source.description === void 0 ? {} : { description: string(source.description) },
11512
11778
  status,
11513
11779
  updatedAt: string(source.updatedAt),
11514
11780
  version: 1
@@ -11984,6 +12250,18 @@ function decodeTagListItem(value) {
11984
12250
  }
11985
12251
  //#endregion
11986
12252
  //#region src/control/common/api.ts
12253
+ function decodeWorkflowBundleImportResult(value) {
12254
+ const source = record(value);
12255
+ if (source.version != 1 || !Array.isArray(source.flows) || source.flows.length == 0) return invalidResponse();
12256
+ const entry = flow$1(source.flow);
12257
+ const flows = source.flows.map(flow$1);
12258
+ if (new Set(flows.map((item) => item.flowId)).size != flows.length || !flows.some((item) => item.flowId == entry.flowId)) return invalidResponse();
12259
+ return {
12260
+ version: 1,
12261
+ flow: entry,
12262
+ flows
12263
+ };
12264
+ }
11987
12265
  function decodeRunEvent(value) {
11988
12266
  const source = record(value);
11989
12267
  const sequence = integer(source.sequence);
@@ -12298,6 +12576,32 @@ var ControlClient = class {
12298
12576
  return invalidResponse();
12299
12577
  }
12300
12578
  }
12579
+ async getFlowExportPreview(flowId) {
12580
+ const value = await this.request(`/v1/flows/${segment(flowId)}/export-preview`);
12581
+ try {
12582
+ return decodeWorkflowExportPreview(value);
12583
+ } catch {
12584
+ return invalidResponse();
12585
+ }
12586
+ }
12587
+ async exportFlowBundle(flowId) {
12588
+ const preview = await this.getFlowExportPreview(flowId);
12589
+ if (preview.unavailableFlowIds.length) throw new ApiError(400, "flow.invalid", "Some referenced workflows are unavailable.");
12590
+ return workflowBundleFromPreview(preview);
12591
+ }
12592
+ async importFlowBundle(file, options = {}) {
12593
+ return decodeWorkflowBundleImportResult(await this.request("/v1/flows/import-bundle", {
12594
+ method: "POST",
12595
+ headers: { "Idempotency-Key": options.idempotencyKey ?? `flow-${randomId()}` },
12596
+ body: JSON.stringify({
12597
+ file,
12598
+ name: options.name,
12599
+ teamId: options.teamId,
12600
+ tagIds: options.tagIds,
12601
+ version: 1
12602
+ })
12603
+ }));
12604
+ }
12301
12605
  async importFlow(file, options = {}) {
12302
12606
  return flow$1(await this.request("/v1/flows/import", {
12303
12607
  method: "POST",
@@ -13206,11 +13510,15 @@ var mcpTools = {
13206
13510
  flow_read: tool("Read an outline, selected node details, or a bounded code/prompt excerpt. Carry the returned revision into subsequent reads and edits.", authoringReadSchema.safeExtend({ flowId: flow }).meta({ ...authoringReadSchema.meta() }), true),
13207
13511
  flow_search: tool("Find nodes by name, configuration or source text in a fixed Revision. Follow nextOffset for more matches.", authoringSearchSchema.extend({ flowId: flow }), true),
13208
13512
  flow_schema: tool("Read node configuration schemas and the edit grammar, or an Action contract. Action lookup requires a flowId.", authoringSchemaQuery.safeExtend({ flowId: flow.optional() }), true),
13209
- flow_export: tool("Export the current draft as a shareable workflow JSON file. Structured external references are cleared. Literal secrets in code, prompts or business JSON remain; review before sharing.", strictObject({ flowId: flow }), true),
13210
- flow_import: tool("Import workflow JSON as a new unpublished draft. External references are cleared and must be configured again. Keep identical arguments and idempotencyKey when retrying.", strictObject({
13513
+ flow_export: tool("Export the current draft as shareable workflow JSON. Set includeDependencies to bundle all referenced drafts. Structured external references are cleared. Literal secrets in code, prompts or business JSON remain; review before sharing.", strictObject({
13514
+ flowId: flow,
13515
+ includeDependencies: boolean().optional()
13516
+ }), true),
13517
+ flow_import: tool("Import workflow JSON or a bundle as new unpublished drafts. tagIds apply to every imported workflow. External references are cleared and must be configured again. Keep identical arguments and idempotencyKey when retrying.", strictObject({
13211
13518
  file: json,
13212
13519
  name: id.optional(),
13213
13520
  teamId: id.optional(),
13521
+ tagIds: array(id).optional(),
13214
13522
  idempotencyKey: mutationKey
13215
13523
  }), false),
13216
13524
  flow_create: tool("Create an empty Flow. If this deployment uses OOMOL Connector, choose a teamId from connector_teams. Keep the key and arguments stable when retrying.", strictObject({