@oomol-lab/open-flow 0.1.0-beta.80 → 0.1.0-beta.82
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.
- package/README.md +35 -0
- package/dist/browser/{FlowCanvas-CrsiDHK8.js → FlowCanvas-Dew2fmoT.js} +55 -274
- package/dist/browser/{createResourceDialog-CS3u9ga9.js → createResourceDialog-bdOJa5Zk.js} +2 -1
- package/dist/browser/{exportPng-BOPfjeEi.js → exportPng-Cjdb6COo.js} +2 -2
- package/dist/browser/{flowChanges-pFjPHU5k.js → flowChanges-URBEd4DI.js} +2719 -78
- package/dist/browser/{flowWorkspace-CRlqc403.js → flowWorkspace-BlXx-yAn.js} +445 -2699
- package/dist/browser/{markdownContent-CAG_CQfH.js → markdownContent-D8G_oNOe.js} +18 -41
- package/dist/browser/{markdownContent-frwDfQQU.js → markdownContent-IS8xXDoc.js} +18 -40
- package/dist/browser/preview.css +88 -80
- package/dist/browser/preview.js +291 -188
- package/dist/browser/ui.css +8 -0
- package/dist/browser/{dist-CHwj9BhV.js → videoLink-NAWDmBtf.js} +35 -1
- package/dist/browser/workbench-contract.d.ts +6 -0
- package/dist/browser/workbench.css +259 -251
- package/dist/browser/workbench.d.ts +2 -0
- package/dist/browser/workbench.js +311 -143
- package/dist/browser/{ContentIcon-CMn0c-kl.js → wrapLayout-DZvW9dBs.js} +4397 -4152
- package/dist/command/index.js +43 -41
- package/dist/common/authoring.d.ts +1 -1
- package/dist/common/control-api-conformance.js +306 -2
- package/dist/common/control-api.d.ts +20 -0
- package/dist/common/control-api.js +306 -4
- package/dist/common/control-requests.d.ts +3 -5
- package/dist/common/control-requests.js +24 -10
- package/dist/common/flow-authoring.js +19 -3
- package/dist/common/flow-encoding.d.ts +1 -0
- package/dist/common/flow-encoding.js +167 -1
- package/dist/common/flow-semantics.js +44 -7
- package/dist/common/flow-workflowBundle.d.ts +27 -0
- package/dist/common/flow-workflowFile.d.ts +2 -0
- package/dist/common/flow-workflowReferences.d.ts +4 -0
- package/dist/common/mcp.d.ts +2 -0
- package/dist/common/mcp.js +312 -4
- package/dist/render/browser/licenses.md +4047 -0
- package/dist/render/browser/runtime.css +14877 -0
- package/dist/render/browser/runtime.js +266765 -0
- package/dist/render/common/png.d.ts +24 -0
- package/dist/render/node/index.d.ts +11 -0
- package/dist/render/node/index.js +9491 -0
- package/dist/render/node/licenses.md +75 -0
- package/package.json +12 -2
- package/src/connector/common/actionContract.ts +26 -0
- package/src/control/common/api.ts +50 -0
- package/src/control/common/flowDecoders.ts +2 -0
- package/src/control/common/mcp.ts +4 -4
- package/src/control/common/requests.ts +12 -1
- package/src/flow/common/edgeChanges.ts +9 -6
- package/src/flow/common/encoding.ts +1 -0
- package/src/flow/common/semantics.ts +11 -8
- package/src/flow/common/workflowBundle.ts +117 -0
- package/src/flow/common/workflowFile.ts +4 -0
- 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.
|
|
3
|
+
"version": "0.1.0-beta.82",
|
|
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"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { JsonValue } from '../../types/index.ts'
|
|
2
|
+
|
|
3
|
+
interface ConnectorActionContract {
|
|
4
|
+
readonly inputConstraints?: readonly {
|
|
5
|
+
readonly handle: string
|
|
6
|
+
readonly jsonSchema: JsonValue
|
|
7
|
+
readonly code: string
|
|
8
|
+
readonly message: string
|
|
9
|
+
}[]
|
|
10
|
+
readonly pendingMessageInput?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** These rules also apply to existing Revision snapshots without a catalog lookup. */
|
|
14
|
+
export const connectorActionContracts: Readonly<Record<string, ConnectorActionContract | undefined>> = {
|
|
15
|
+
'leina.send_notification': {
|
|
16
|
+
inputConstraints: [
|
|
17
|
+
{
|
|
18
|
+
handle: 'leinaId',
|
|
19
|
+
jsonSchema: { type: 'string', pattern: '\\S' },
|
|
20
|
+
code: 'leina.robot-required',
|
|
21
|
+
message: 'Select a Leina robot or bind its identifier to an input source.',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
pendingMessageInput: 'message',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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 }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChangeOperation, GraphEdge, RevisionContent } from './change.ts'
|
|
2
2
|
|
|
3
3
|
import { dequal } from 'dequal/lite'
|
|
4
|
+
import { connectorActionContracts } from '../../connector/common/actionContract.ts'
|
|
4
5
|
|
|
5
6
|
export function connect(content: RevisionContent, edge: GraphEdge): readonly ChangeOperation[] {
|
|
6
7
|
const selected = content.document.graph
|
|
@@ -8,20 +9,22 @@ export function connect(content: RevisionContent, edge: GraphEdge): readonly Cha
|
|
|
8
9
|
const changes: ChangeOperation[] = [{ edge, kind: 'graph.edge.connect' }]
|
|
9
10
|
const source = selected.nodes[edge.source]
|
|
10
11
|
const target = selected.nodes[edge.target]
|
|
12
|
+
const messageInput =
|
|
13
|
+
target?.kind == 'task' && 'executor' in target.task && target.task.executor.kind == 'connector'
|
|
14
|
+
? connectorActionContracts[target.task.executor.action]?.pendingMessageInput
|
|
15
|
+
: undefined
|
|
11
16
|
if (
|
|
12
17
|
edge.sourceHandle == 'pending' &&
|
|
13
18
|
(source?.kind == 'approval' || source?.kind == 'wait') &&
|
|
14
19
|
target?.kind == 'task' &&
|
|
15
|
-
|
|
16
|
-
target.
|
|
17
|
-
target.task.executor.action == 'leina.send_notification' &&
|
|
18
|
-
(target.inputs.message == null || target.inputs.message.kind == 'unset')
|
|
20
|
+
messageInput != null &&
|
|
21
|
+
(target.inputs[messageInput] == null || target.inputs[messageInput]?.kind == 'unset')
|
|
19
22
|
) {
|
|
20
23
|
changes.push({
|
|
21
24
|
kind: 'graph.node.input.set',
|
|
22
25
|
nodeId: edge.target,
|
|
23
|
-
handle:
|
|
24
|
-
before: target.inputs
|
|
26
|
+
handle: messageInput,
|
|
27
|
+
before: target.inputs[messageInput],
|
|
25
28
|
value: { kind: 'sources', sources: [{ kind: 'node', nodeId: edge.source, output: 'pending', field: 'message' }] },
|
|
26
29
|
})
|
|
27
30
|
}
|
|
@@ -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'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dequal } from 'dequal/lite'
|
|
2
|
+
import { connectorActionContracts } from '../../connector/common/actionContract.ts'
|
|
2
3
|
import { decisionTaskIssues } from '../../decision/common/decision.ts'
|
|
3
4
|
import { httpRequestIssues, httpAuthHandles, httpRequestTask, httpRequestUrl } from '../../http/common/httpRequest.ts'
|
|
4
5
|
import { openApiIssues, authHandles } from '../../openapi/common/openapi.ts'
|
|
@@ -22,7 +23,7 @@ import { canonicalJsonBytes, canonicalModule, canonicalRevisionGraph, digestByte
|
|
|
22
23
|
import { nodeInputPorts, validateFlowGraph } from './graph.ts'
|
|
23
24
|
import { inputValue } from './inputValue.ts'
|
|
24
25
|
import { compareDiagnostics, validateModuleGraph } from './modules.ts'
|
|
25
|
-
import { hasRetiredRef } from './schema.ts'
|
|
26
|
+
import { hasRetiredRef, matchesSchema } from './schema.ts'
|
|
26
27
|
export { availableOutputs, graphOrder, nodeInputPorts } from './graph.ts'
|
|
27
28
|
export { validateModules } from './modules.ts'
|
|
28
29
|
export { matchesSchema, schemaValidationErrors, variableInputCompatible } from './schema.ts'
|
|
@@ -349,16 +350,18 @@ export async function validateFlow(revision: RevisionContent, engine: EngineCont
|
|
|
349
350
|
for (const message of agentConfigIssues(task)) {
|
|
350
351
|
checked.diagnostics.push({ code: 'agent.config-invalid', column: 0, line: 1, message, path: `/document/graph/nodes/${nodeId}/task/executor` })
|
|
351
352
|
}
|
|
352
|
-
if (task.executor.kind == 'connector'
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
if (task.executor.kind == 'connector') {
|
|
354
|
+
for (const constraint of connectorActionContracts[task.executor.action]?.inputConstraints ?? []) {
|
|
355
|
+
const mapping = node.inputs[constraint.handle]
|
|
356
|
+
if (mapping?.kind == 'sources' && mapping.sources.length > 0) continue
|
|
357
|
+
const value = inputValue(mapping, nodeInputPorts(node)[constraint.handle]?.value)
|
|
358
|
+
if (value !== undefined && matchesSchema(value, constraint.jsonSchema)) continue
|
|
356
359
|
checked.diagnostics.push({
|
|
357
|
-
code:
|
|
360
|
+
code: constraint.code,
|
|
358
361
|
column: 0,
|
|
359
362
|
line: 1,
|
|
360
|
-
message:
|
|
361
|
-
path: `/document/graph/nodes/${nodeId}/inputs
|
|
363
|
+
message: constraint.message,
|
|
364
|
+
path: `/document/graph/nodes/${nodeId}/inputs/${constraint.handle}`,
|
|
362
365
|
})
|
|
363
366
|
}
|
|
364
367
|
}
|
|
@@ -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'>
|