@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
@@ -0,0 +1,75 @@
1
+ # Licenses
2
+
3
+ The app bundles dependencies which contain the following licenses:
4
+
5
+ ## @wopjs/cast - 0.1.16 (MIT)
6
+
7
+ MIT License
8
+
9
+ Copyright (c) 2026 wopjs
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ ## dequal - 2.0.3 (MIT)
30
+
31
+ The MIT License (MIT)
32
+
33
+ Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy
36
+ of this software and associated documentation files (the "Software"), to deal
37
+ in the Software without restriction, including without limitation the rights
38
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
+ copies of the Software, and to permit persons to whom the Software is
40
+ furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in
43
+ all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
51
+ THE SOFTWARE.
52
+
53
+ ## zod - 4.6.5 (MIT)
54
+
55
+ MIT License
56
+
57
+ Copyright (c) 2025 Colin McDonnell
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining a copy
60
+ of this software and associated documentation files (the "Software"), to deal
61
+ in the Software without restriction, including without limitation the rights
62
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63
+ copies of the Software, and to permit persons to whom the Software is
64
+ furnished to do so, subject to the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be included in all
67
+ copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oomol-lab/open-flow",
3
- "version": "0.1.0-beta.81",
3
+ "version": "0.1.0-beta.83",
4
4
  "description": "TypeScript contracts and Browser runtime for Open Flow.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -141,6 +141,10 @@
141
141
  "types": "./dist/browser/preview.css.d.ts",
142
142
  "default": "./dist/browser/preview.css"
143
143
  },
144
+ "./render": {
145
+ "types": "./dist/render/node/index.d.ts",
146
+ "import": "./dist/render/node/index.js"
147
+ },
144
148
  "./workbench": {
145
149
  "types": "./dist/browser/workbench.d.ts",
146
150
  "import": "./dist/browser/workbench.js"
@@ -174,7 +178,13 @@
174
178
  "effect": "4.0.0-rc.112",
175
179
  "zod": "^4.6.5",
176
180
  "react": "^18.3.1 || ^19.0.0",
177
- "react-dom": "^18.3.1 || ^19.0.0"
181
+ "react-dom": "^18.3.1 || ^19.0.0",
182
+ "playwright-core": "^1.64.1"
183
+ },
184
+ "peerDependenciesMeta": {
185
+ "playwright-core": {
186
+ "optional": true
187
+ }
178
188
  },
179
189
  "sideEffects": [
180
190
  "**/*.css"
@@ -55,10 +55,13 @@ export {
55
55
  type ResultQuery,
56
56
  } from './results.ts'
57
57
  import type { RunStatus } from '../../execution/common/runLifecycle.ts'
58
+ import type { WorkflowBundle, WorkflowExportPreview } from '../../flow/common/workflowBundle.ts'
58
59
  import type { WorkflowFile } from '../../flow/common/workflowFile.ts'
59
60
 
61
+ import { decodeWorkflowExportPreview, workflowBundleFromPreview } from '../../flow/common/workflowBundle.ts'
60
62
  import { decodeWorkflowFile } from '../../flow/common/workflowFile.ts'
61
63
  export type { WorkflowFile, WorkflowCleanupSummary } from '../../flow/common/workflowFile.ts'
64
+ export type { WorkflowBundle, WorkflowExportPreview } from '../../flow/common/workflowBundle.ts'
62
65
  import type { InputPortDefinition, JsonValue, PortDefinition, RevisionContent, TriggerKeySnapshot, WaitAction } from '../../flow/common/change.ts'
63
66
  import type { FlowResourceReferences } from '../../flow/common/semantics.ts'
64
67
 
@@ -109,9 +112,26 @@ export interface FlowResources {
109
112
 
110
113
  export interface FlowUpdate {
111
114
  readonly name?: string
115
+ readonly icon?: string | null
116
+ readonly description?: string | null
112
117
  readonly tagIds?: readonly string[]
113
118
  }
114
119
 
120
+ export interface WorkflowBundleImportResult {
121
+ readonly version: 1
122
+ readonly flow: Flow
123
+ readonly flows: readonly Flow[]
124
+ }
125
+
126
+ export function decodeWorkflowBundleImportResult(value: unknown): WorkflowBundleImportResult {
127
+ const source = record(value)
128
+ if (source.version != 1 || !Array.isArray(source.flows) || source.flows.length == 0) return invalidResponse()
129
+ const entry = flow(source.flow)
130
+ const flows = source.flows.map(flow)
131
+ if (new Set(flows.map((item) => item.flowId)).size != flows.length || !flows.some((item) => item.flowId == entry.flowId)) return invalidResponse()
132
+ return { version: 1, flow: entry, flows }
133
+ }
134
+
115
135
  export interface FlowQuery {
116
136
  readonly query?: string
117
137
  readonly tagIds?: readonly string[]
@@ -139,6 +159,8 @@ export interface Flow {
139
159
  /** Connector Team scope used by the OOMOL resource cache. */
140
160
  readonly connectorTeamId?: string
141
161
  readonly name: string
162
+ readonly icon?: string
163
+ readonly description?: string
142
164
  readonly status: 'active' | 'retiring'
143
165
  readonly updatedAt: string
144
166
  readonly version: 1
@@ -933,6 +955,34 @@ export class ControlClient {
933
955
  }
934
956
  }
935
957
 
958
+ async getFlowExportPreview(flowId: string): Promise<WorkflowExportPreview> {
959
+ const value = await this.request(`/v1/flows/${segment(flowId)}/export-preview`)
960
+ try {
961
+ return decodeWorkflowExportPreview(value)
962
+ } catch {
963
+ return invalidResponse()
964
+ }
965
+ }
966
+
967
+ async exportFlowBundle(flowId: string): Promise<WorkflowBundle> {
968
+ const preview = await this.getFlowExportPreview(flowId)
969
+ if (preview.unavailableFlowIds.length) throw new ApiError(400, 'flow.invalid', 'Some referenced workflows are unavailable.')
970
+ return workflowBundleFromPreview(preview)
971
+ }
972
+
973
+ async importFlowBundle(
974
+ file: WorkflowBundle,
975
+ options: { readonly name?: string; readonly teamId?: string; readonly idempotencyKey?: string; readonly tagIds?: readonly string[] } = {},
976
+ ): Promise<WorkflowBundleImportResult> {
977
+ return decodeWorkflowBundleImportResult(
978
+ await this.request('/v1/flows/import-bundle', {
979
+ method: 'POST',
980
+ headers: { 'Idempotency-Key': options.idempotencyKey ?? `flow-${randomId()}` },
981
+ body: JSON.stringify({ file, name: options.name, teamId: options.teamId, tagIds: options.tagIds, version: 1 }),
982
+ }),
983
+ )
984
+ }
985
+
936
986
  async importFlow(
937
987
  file: WorkflowFile,
938
988
  options: { readonly name?: string; readonly teamId?: string; readonly idempotencyKey?: string; readonly tagIds?: readonly string[] } = {},
@@ -67,6 +67,8 @@ export function flow(value: unknown): Flow {
67
67
  resourceReferences: resourceReferences(source.resourceReferences),
68
68
  flowId: string(source.flowId),
69
69
  name: string(source.name),
70
+ ...(source.icon === undefined ? {} : { icon: string(source.icon) }),
71
+ ...(source.description === undefined ? {} : { description: string(source.description) }),
70
72
  status: status as Flow['status'],
71
73
  updatedAt: string(source.updatedAt),
72
74
  version: 1,
@@ -103,13 +103,13 @@ export const mcpTools = {
103
103
  true,
104
104
  ),
105
105
  flow_export: tool(
106
- '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.',
107
- z.strictObject({ flowId: flow }),
106
+ '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.',
107
+ z.strictObject({ flowId: flow, includeDependencies: z.boolean().optional() }),
108
108
  true,
109
109
  ),
110
110
  flow_import: tool(
111
- 'Import workflow JSON as a new unpublished draft. External references are cleared and must be configured again. Keep identical arguments and idempotencyKey when retrying.',
112
- z.strictObject({ file: json, name: id.optional(), teamId: id.optional(), idempotencyKey: mutationKey }),
111
+ '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.',
112
+ z.strictObject({ file: json, name: id.optional(), teamId: id.optional(), tagIds: z.array(id).optional(), idempotencyKey: mutationKey }),
113
113
  false,
114
114
  ),
115
115
  flow_create: tool(
@@ -45,7 +45,18 @@ const schemas = {
45
45
  }),
46
46
  saveTag: z.strictObject({ name: tagName, version }),
47
47
  setFlowTags: z.strictObject({ tagIds, version }),
48
- updateFlow: z.union([z.strictObject({ name: flowName, tagIds: tagIds.optional(), version }), z.strictObject({ name: flowName.optional(), tagIds, version })]),
48
+ updateFlow: z
49
+ .strictObject({
50
+ name: flowName.optional(),
51
+ icon: z.string().nullable().optional(),
52
+ description: z.string().nullable().optional(),
53
+ tagIds: tagIds.optional(),
54
+ version,
55
+ })
56
+ .refine(
57
+ (value) => value.name !== undefined || value.icon !== undefined || value.description !== undefined || value.tagIds !== undefined,
58
+ 'Flow settings are empty.',
59
+ ),
49
60
  renameFlow: z.strictObject({ name: flowName, version }),
50
61
  changeDraft: z.strictObject({ expectedRevisionId: id, operations: z.array(json).min(1), version }),
51
62
  repairDraft: z.strictObject({ expectedRevisionId: id, version }),
@@ -21,6 +21,7 @@ import type {
21
21
  import { isTriggerNode, currentFlowModelVersion } from '@oomol-lab/open-flow/flow-change'
22
22
  import { decodeRevisionEnvelope, repairRevisionEnvelope } from './changeSchema.ts'
23
23
  export * from './workflowFile.ts'
24
+ export * from './workflowBundle.ts'
24
25
  export { maxJsonDepth } from './json.ts'
25
26
 
26
27
  export { decodeFlowDocument, decodeRevisionContent, upgradeWorkflowInterfaceDocument } from './changeSchema.ts'
@@ -0,0 +1,117 @@
1
+ import type { WorkflowFile } from './workflowFile.ts'
2
+
3
+ import { z } from 'zod'
4
+ import { checkJsonDepth } from './json.ts'
5
+ import { cleanWorkflowFile, decodeWorkflowFile } from './workflowFile.ts'
6
+ import { mapNodeWorkflowTargets, nodeWorkflowReferences } from './workflowReferences.ts'
7
+
8
+ export interface WorkflowBundleEntry {
9
+ readonly flowId: string
10
+ readonly file: WorkflowFile
11
+ }
12
+
13
+ export interface WorkflowBundle {
14
+ readonly kind: 'open-flow-workflow-bundle'
15
+ readonly version: 1
16
+ readonly entryFlowId: string
17
+ readonly workflows: readonly WorkflowBundleEntry[]
18
+ }
19
+
20
+ /** A saved export snapshot; unavailable targets carry no account metadata. */
21
+ export interface WorkflowExportPreview {
22
+ readonly version: 1
23
+ readonly entryFlowId: string
24
+ readonly workflows: readonly WorkflowBundleEntry[]
25
+ readonly unavailableFlowIds: readonly string[]
26
+ }
27
+
28
+ const entries = z.array(z.strictObject({ flowId: z.string().min(1), file: z.unknown() })).min(1)
29
+ const bundleSchema = z.strictObject({ kind: z.literal('open-flow-workflow-bundle'), version: z.literal(1), entryFlowId: z.string().min(1), workflows: entries })
30
+ const previewSchema = z.strictObject({
31
+ version: z.literal(1),
32
+ entryFlowId: z.string().min(1),
33
+ workflows: entries,
34
+ unavailableFlowIds: z.array(z.string().min(1)),
35
+ })
36
+
37
+ function decodeEntries(value: z.infer<typeof entries>, entryFlowId: string, unavailableFlowIds: readonly string[] = []): readonly WorkflowBundleEntry[] {
38
+ const workflows = value.map(({ flowId, file }) => ({ flowId, file: decodeWorkflowFile(file) }))
39
+ const ids = new Set(workflows.map(({ flowId }) => flowId))
40
+ if (ids.size != workflows.length || !ids.has(entryFlowId)) throw new TypeError('Workflow identities must be unique and include the entry workflow.')
41
+ const unavailable = new Set(unavailableFlowIds)
42
+ if (unavailable.size != unavailableFlowIds.length || unavailableFlowIds.some((id) => ids.has(id)))
43
+ throw new TypeError('Invalid unavailable workflow identities.')
44
+ for (const { file } of workflows)
45
+ for (const node of Object.values(file.content.document.graph.nodes))
46
+ for (const { target } of nodeWorkflowReferences(node))
47
+ if (target.flowId && !ids.has(target.flowId) && !unavailable.has(target.flowId))
48
+ throw new TypeError('A referenced workflow is missing from the bundle.')
49
+ return workflows
50
+ }
51
+
52
+ export function decodeWorkflowBundle(value: unknown): WorkflowBundle {
53
+ checkJsonDepth(value, -4)
54
+ const bundle = bundleSchema.parse(value)
55
+ return { ...bundle, workflows: decodeEntries(bundle.workflows, bundle.entryFlowId) }
56
+ }
57
+
58
+ export function decodeWorkflowExportPreview(value: unknown): WorkflowExportPreview {
59
+ checkJsonDepth(value, -4)
60
+ const preview = previewSchema.parse(value)
61
+ return { ...preview, workflows: decodeEntries(preview.workflows, preview.entryFlowId, preview.unavailableFlowIds) }
62
+ }
63
+
64
+ export function cleanWorkflowBundle(value: unknown): WorkflowBundle {
65
+ const bundle = decodeWorkflowBundle(value)
66
+ return { ...bundle, workflows: bundle.workflows.map(({ flowId, file }) => ({ flowId, file: cleanWorkflowFile(file).file })) }
67
+ }
68
+
69
+ export function workflowBundleFromPreview(preview: WorkflowExportPreview): WorkflowBundle {
70
+ if (preview.unavailableFlowIds.length) throw new TypeError('Some referenced workflows are unavailable.')
71
+ return cleanWorkflowBundle({ kind: 'open-flow-workflow-bundle', version: 1, entryFlowId: preview.entryFlowId, workflows: preview.workflows })
72
+ }
73
+
74
+ export function encodeWorkflowBundle(bundle: WorkflowBundle): string {
75
+ return `${JSON.stringify(cleanWorkflowBundle(bundle), null, 2)}\n`
76
+ }
77
+
78
+ export type WorkflowImportFile = WorkflowFile | WorkflowBundle
79
+
80
+ export function parseWorkflowImportFile(text: string): WorkflowImportFile {
81
+ const value: unknown = JSON.parse(text)
82
+ return value != null && typeof value == 'object' && 'kind' in value && value.kind == 'open-flow-workflow-bundle'
83
+ ? decodeWorkflowBundle(value)
84
+ : decodeWorkflowFile(value)
85
+ }
86
+
87
+ /** A bundle creates new drafts, so old published interface provenance must be refreshed. */
88
+ export function remapWorkflowBundle(bundle: WorkflowBundle, flowIds: ReadonlyMap<string, string>): WorkflowBundle {
89
+ const cleaned = cleanWorkflowBundle(bundle)
90
+ function identity(flowId: string): string {
91
+ const mapped = flowIds.get(flowId)
92
+ if (mapped == null || mapped.length == 0) throw new TypeError('Missing imported workflow identity.')
93
+ return mapped
94
+ }
95
+ const workflows = cleaned.workflows.map(({ flowId, file }) => ({
96
+ flowId: identity(flowId),
97
+ file: {
98
+ ...file,
99
+ content: {
100
+ ...file.content,
101
+ document: {
102
+ ...file.content.document,
103
+ graph: {
104
+ ...file.content.document.graph,
105
+ nodes: Object.fromEntries(
106
+ Object.entries(file.content.document.graph.nodes).map(([id, node]) => [
107
+ id,
108
+ mapNodeWorkflowTargets(node, (target) => ({ flowId: target.flowId ? identity(target.flowId) : '', revisionId: '' })),
109
+ ]),
110
+ ),
111
+ },
112
+ },
113
+ },
114
+ },
115
+ }))
116
+ return decodeWorkflowBundle({ ...cleaned, entryFlowId: identity(cleaned.entryFlowId), workflows })
117
+ }
@@ -12,6 +12,8 @@ export interface WorkflowFile {
12
12
  readonly kind: 'open-flow-workflow'
13
13
  readonly version: 1
14
14
  readonly name?: string
15
+ readonly icon?: string
16
+ readonly description?: string
15
17
  readonly content: RevisionContent
16
18
  readonly presentation?: Readonly<Record<string, JsonValue>>
17
19
  }
@@ -25,6 +27,8 @@ const envelope = z.strictObject({
25
27
  kind: z.literal('open-flow-workflow'),
26
28
  version: z.literal(1),
27
29
  name: z.string().optional(),
30
+ icon: z.string().optional(),
31
+ description: z.string().optional(),
28
32
  content: z.unknown(),
29
33
  presentation: z.record(z.string(), z.json()).optional(),
30
34
  })
@@ -1,6 +1,26 @@
1
1
  import type { GraphNode } from './change.ts'
2
2
  import type { WorkflowCapability } from './model.ts'
3
3
 
4
+ type WorkflowTarget = Pick<WorkflowCapability, 'flowId' | 'revisionId'>
5
+
6
+ /** Rewrite only declared workflow identities, preserving business values and saved ports. */
7
+ export function mapNodeWorkflowTargets(node: GraphNode, map: (target: WorkflowTarget) => WorkflowTarget): GraphNode {
8
+ if (node.kind != 'task') return node
9
+ const task = node.task
10
+ if ('moduleId' in task)
11
+ return task.capabilities?.some((item) => item.kind == 'workflow')
12
+ ? { ...node, task: { ...task, capabilities: task.capabilities.map((item) => (item.kind == 'workflow' ? Object.assign({}, item, map(item)) : item)) } }
13
+ : node
14
+ const executor = task.executor
15
+ if (executor.kind == 'workflow') return { ...node, task: { ...task, executor: { ...executor, ...map(executor) } } }
16
+ if (executor.kind == 'agent')
17
+ return {
18
+ ...node,
19
+ task: { ...task, executor: { ...executor, tools: executor.tools.map((tool) => (tool.kind == 'workflow' ? Object.assign({}, tool, map(tool)) : tool)) } },
20
+ }
21
+ return node
22
+ }
23
+
4
24
  /** Workflow declarations share discovery across authoring, admission and resource indexing. */
5
25
  export function nodeWorkflowReferences(node: GraphNode): readonly {
6
26
  readonly target: Omit<WorkflowCapability, 'kind' | 'name'>