feat(wizard): produce fully startable EA2 flows
Reverse-engineered the EA2 runtime startability contract by diffing
pr_to_po_def (startable) against wizard-created drafts (500). Minimum
shape required:
Parent flow (kind=definition, status=published)
+ Step flow (kind=definition, status=published, dispatch_kind=human)
+ View doc (kind=definition, status=active, layout={fields,layout:form})
+ Version doc (kind=definition, status=active)
+ defines edge: parent -> step (role=child)
+ defines edge: step -> step (role=dispatch self-edge)
+ defines edge: step -> view (role=presentation)
+ governs edge collection 'governs': version -> parent (role=governs)
wizardApi.ops gains: createView, createVersion, childEdge, dispatchEdge,
presentationEdge, governsEdge. createStepEdge renamed to childEdge to
reflect the actual EA2 role. Steps now create with status=published
(was draft) so they're visible to the runtime.
Wizard.handleStructureSave emits all of the above in two apply-batches
(creates then wires). Verified live: posted a probe flow with this
contract, /api/runtime/transactions returned 200 + a running
transaction with step_run_id.
This commit is contained in:
+1
-1
@@ -215,7 +215,7 @@ export const chatApi = {
|
|||||||
if (!created.ok) throw new Error(`sendMessage create ${created.status}`);
|
if (!created.ok) throw new Error(`sendMessage create ${created.status}`);
|
||||||
const msg = await created.json();
|
const msg = await created.json();
|
||||||
|
|
||||||
const linkOps: BatchOperation[] = [wizardApi.ops.createStepEdge(threadKey, msg._key)];
|
const linkOps: BatchOperation[] = [wizardApi.ops.childEdge(threadKey, msg._key)];
|
||||||
await fetch(`${api.config.baseUrl}/api/ea2/apply-batch`, {
|
await fetch(`${api.config.baseUrl}/api/ea2/apply-batch`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: authHeaders(),
|
headers: authHeaders(),
|
||||||
|
|||||||
+69
-3
@@ -130,7 +130,7 @@ export const wizardApi = {
|
|||||||
kind: "definition",
|
kind: "definition",
|
||||||
name,
|
name,
|
||||||
display_name: node.display_name,
|
display_name: node.display_name,
|
||||||
status: "draft",
|
status: "published",
|
||||||
description: node.description || node.display_name,
|
description: node.description || node.display_name,
|
||||||
source_context: "EA2_WIZARD_STEP",
|
source_context: "EA2_WIZARD_STEP",
|
||||||
dispatch_kind: node.dispatch_kind,
|
dispatch_kind: node.dispatch_kind,
|
||||||
@@ -138,13 +138,79 @@ export const wizardApi = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
createStepEdge(parentFlowKey: string, childFlowKey: string): CreateEdgeOp {
|
createView(node: { display_name: string }): CreateOp {
|
||||||
|
const name = `${node.display_name.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40) || "view"}_view`;
|
||||||
|
return {
|
||||||
|
op: "create",
|
||||||
|
coll: "view",
|
||||||
|
data: {
|
||||||
|
kind: "definition",
|
||||||
|
name,
|
||||||
|
label: `${node.display_name} · form`,
|
||||||
|
status: "active",
|
||||||
|
description: null,
|
||||||
|
source_context: null,
|
||||||
|
channel: "web",
|
||||||
|
layout: {
|
||||||
|
layout: "form",
|
||||||
|
fields: [
|
||||||
|
{ name: "notes", label: "Notes", type: "textarea", required: false, description: "Notes" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
actions: [{ label: "Submit", action: "submit" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
createVersion(parentDisplayName: string): CreateOp {
|
||||||
|
const name = `${parentDisplayName.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40) || "process"}_v1`;
|
||||||
|
return {
|
||||||
|
op: "create",
|
||||||
|
coll: "version",
|
||||||
|
data: {
|
||||||
|
kind: "definition",
|
||||||
|
name,
|
||||||
|
label: "v1",
|
||||||
|
status: "active",
|
||||||
|
change_description: `Initial publish of ${parentDisplayName}`,
|
||||||
|
legal_entity: "canvas",
|
||||||
|
source_context: "EA2_WIZARD_VERSION",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
childEdge(parentFlowKey: string, childFlowKey: string): CreateEdgeOp {
|
||||||
return {
|
return {
|
||||||
op: "create_edge",
|
op: "create_edge",
|
||||||
edge_coll: "defines",
|
edge_coll: "defines",
|
||||||
from: `flow/${parentFlowKey}`,
|
from: `flow/${parentFlowKey}`,
|
||||||
to: `flow/${childFlowKey}`,
|
to: `flow/${childFlowKey}`,
|
||||||
role: "next",
|
role: "child",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
dispatchEdge(stepFlowKey: string): CreateEdgeOp {
|
||||||
|
return {
|
||||||
|
op: "create_edge",
|
||||||
|
edge_coll: "defines",
|
||||||
|
from: `flow/${stepFlowKey}`,
|
||||||
|
to: `flow/${stepFlowKey}`,
|
||||||
|
role: "dispatch",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
presentationEdge(stepFlowKey: string, viewKey: string): CreateEdgeOp {
|
||||||
|
return {
|
||||||
|
op: "create_edge",
|
||||||
|
edge_coll: "defines",
|
||||||
|
from: `flow/${stepFlowKey}`,
|
||||||
|
to: `view/${viewKey}`,
|
||||||
|
role: "presentation",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
governsEdge(versionKey: string, parentFlowKey: string): CreateEdgeOp {
|
||||||
|
return {
|
||||||
|
op: "create_edge",
|
||||||
|
edge_coll: "governs",
|
||||||
|
from: `version/${versionKey}`,
|
||||||
|
to: `flow/${parentFlowKey}`,
|
||||||
|
role: "governs",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
publishFlow(flowKey: string): UpdateOp {
|
publishFlow(flowKey: string): UpdateOp {
|
||||||
|
|||||||
+20
-11
@@ -103,27 +103,36 @@ export default function Wizard() {
|
|||||||
if (!draft.flowKey || !actor) return;
|
if (!draft.flowKey || !actor) return;
|
||||||
setWorking(true);
|
setWorking(true);
|
||||||
try {
|
try {
|
||||||
const createOps: BatchOperation[] = draft.nodes.map(n =>
|
const stepCreateOps: BatchOperation[] = draft.nodes.map(n =>
|
||||||
wizardApi.ops.createStep({
|
wizardApi.ops.createStep({
|
||||||
display_name: n.display_name,
|
display_name: n.display_name,
|
||||||
dispatch_kind: n.dispatch_kind,
|
dispatch_kind: n.dispatch_kind,
|
||||||
agent_capability: n.agent_capability,
|
agent_capability: n.agent_capability,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const createRes = await wizardApi.applyBatch(draft.flowKey, createOps, actor);
|
const viewCreateOps: BatchOperation[] = draft.nodes.map(n =>
|
||||||
const createdKeys: string[] = (createRes?.result?.ops || []).map((o: any) => o.key);
|
wizardApi.ops.createView({ display_name: n.display_name })
|
||||||
const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: createdKeys[i] || n._key }));
|
);
|
||||||
|
const versionCreateOp: BatchOperation = wizardApi.ops.createVersion(draft.name || "Process");
|
||||||
|
const allCreateOps = [...stepCreateOps, ...viewCreateOps, versionCreateOp];
|
||||||
|
|
||||||
const linkOps: BatchOperation[] = [
|
const createRes = await wizardApi.applyBatch(draft.flowKey, allCreateOps, actor);
|
||||||
wizardApi.ops.createStepEdge(draft.flowKey!, nodesWithKeys[0]._key),
|
const createdOps: any[] = createRes?.result?.ops || [];
|
||||||
...nodesWithKeys.slice(0, -1).map((n, i) =>
|
const stepKeys = createdOps.slice(0, draft.nodes.length).map(o => o.key);
|
||||||
wizardApi.ops.createStepEdge(n._key, nodesWithKeys[i + 1]._key)
|
const viewKeys = createdOps.slice(draft.nodes.length, draft.nodes.length * 2).map(o => o.key);
|
||||||
),
|
const versionKey = createdOps[createdOps.length - 1]?.key;
|
||||||
|
const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: stepKeys[i] || n._key, viewKey: viewKeys[i] }));
|
||||||
|
|
||||||
|
const wireOps: BatchOperation[] = [
|
||||||
|
...nodesWithKeys.map((n, _i) => wizardApi.ops.childEdge(draft.flowKey!, n._key)),
|
||||||
|
...nodesWithKeys.map(n => wizardApi.ops.dispatchEdge(n._key)),
|
||||||
|
...nodesWithKeys.map(n => wizardApi.ops.presentationEdge(n._key, n.viewKey)),
|
||||||
];
|
];
|
||||||
if (linkOps.length) await wizardApi.applyBatch(draft.flowKey, linkOps, actor);
|
if (versionKey) wireOps.push(wizardApi.ops.governsEdge(versionKey, draft.flowKey!));
|
||||||
|
if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor);
|
||||||
|
|
||||||
updateDraft({ step: "Generate", nodes: nodesWithKeys });
|
updateDraft({ step: "Generate", nodes: nodesWithKeys });
|
||||||
pushToast("ok", `Saved ${createdKeys.length} steps to EA2`);
|
pushToast("ok", `Saved ${stepKeys.length} steps with views and version to EA2`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
pushToast("err", `Save failed: ${err.message}`);
|
pushToast("err", `Save failed: ${err.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user