Wire pipeline DAG execution to manager with events and project context
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { isRecord } from "./types.js";
|
||||
import { isDomainEventType, type DomainEventType } from "./domain-events.js";
|
||||
|
||||
export type ToolClearancePolicy = {
|
||||
allowlist: string[];
|
||||
@@ -45,23 +46,32 @@ export type PipelineConstraint = {
|
||||
maxRetries?: number;
|
||||
};
|
||||
|
||||
export type NodeTopologyKind = "sequential" | "parallel" | "hierarchical" | "retry-unrolled";
|
||||
|
||||
export type PipelineNodeTopology = {
|
||||
kind: NodeTopologyKind;
|
||||
blockId?: string;
|
||||
};
|
||||
|
||||
export type PipelineNode = {
|
||||
id: string;
|
||||
actorId: string;
|
||||
personaId: string;
|
||||
constraints?: PipelineConstraint;
|
||||
topology?: PipelineNodeTopology;
|
||||
};
|
||||
|
||||
export type PipelineEdge = {
|
||||
from: string;
|
||||
to: string;
|
||||
on:
|
||||
on?:
|
||||
| "success"
|
||||
| "validation_fail"
|
||||
| "failure"
|
||||
| "always"
|
||||
| "onTaskComplete"
|
||||
| "onValidationFail";
|
||||
event?: DomainEventType;
|
||||
when?: RouteCondition[];
|
||||
};
|
||||
|
||||
@@ -71,7 +81,7 @@ export type PipelineGraph = {
|
||||
edges: PipelineEdge[];
|
||||
};
|
||||
|
||||
export type TopologyKind = "hierarchical" | "retry-unrolled" | "sequential";
|
||||
export type TopologyKind = "hierarchical" | "parallel" | "retry-unrolled" | "sequential";
|
||||
|
||||
export type TopologyConstraint = {
|
||||
maxDepth: number;
|
||||
@@ -216,6 +226,34 @@ function parsePipelineNode(value: unknown): PipelineNode {
|
||||
throw new Error("Pipeline node must be an object.");
|
||||
}
|
||||
|
||||
const topology = value.topology;
|
||||
let parsedTopology: PipelineNodeTopology | undefined;
|
||||
if (topology !== undefined) {
|
||||
if (!isRecord(topology)) {
|
||||
throw new Error("Pipeline node topology must be an object when provided.");
|
||||
}
|
||||
|
||||
const kind = readString(topology, "kind");
|
||||
if (
|
||||
kind !== "sequential" &&
|
||||
kind !== "parallel" &&
|
||||
kind !== "hierarchical" &&
|
||||
kind !== "retry-unrolled"
|
||||
) {
|
||||
throw new Error(`Pipeline node topology kind "${kind}" is not supported.`);
|
||||
}
|
||||
|
||||
const blockIdRaw = topology.blockId;
|
||||
if (blockIdRaw !== undefined && (typeof blockIdRaw !== "string" || blockIdRaw.trim().length === 0)) {
|
||||
throw new Error("Pipeline node topology blockId must be a non-empty string when provided.");
|
||||
}
|
||||
|
||||
parsedTopology = {
|
||||
kind,
|
||||
...(typeof blockIdRaw === "string" ? { blockId: blockIdRaw.trim() } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
const constraints = isRecord(value.constraints)
|
||||
? {
|
||||
maxRetries: readOptionalInteger(value.constraints, "maxRetries", { min: 0 }),
|
||||
@@ -227,6 +265,7 @@ function parsePipelineNode(value: unknown): PipelineNode {
|
||||
actorId: readString(value, "actorId"),
|
||||
personaId: readString(value, "personaId"),
|
||||
constraints,
|
||||
...(parsedTopology ? { topology: parsedTopology } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -235,8 +274,7 @@ function parsePipelineEdge(value: unknown): PipelineEdge {
|
||||
throw new Error("Pipeline edge must be an object.");
|
||||
}
|
||||
|
||||
const on = readString(value, "on");
|
||||
const validEvents: PipelineEdge["on"][] = [
|
||||
const validEvents: NonNullable<PipelineEdge["on"]>[] = [
|
||||
"success",
|
||||
"validation_fail",
|
||||
"failure",
|
||||
@@ -245,8 +283,29 @@ function parsePipelineEdge(value: unknown): PipelineEdge {
|
||||
"onValidationFail",
|
||||
];
|
||||
|
||||
if (!validEvents.includes(on as PipelineEdge["on"])) {
|
||||
throw new Error(`Pipeline edge field \"on\" has unsupported event \"${on}\".`);
|
||||
const rawOn = value.on;
|
||||
let on: PipelineEdge["on"];
|
||||
if (rawOn !== undefined) {
|
||||
if (typeof rawOn !== "string" || !validEvents.includes(rawOn as NonNullable<PipelineEdge["on"]>)) {
|
||||
throw new Error(`Pipeline edge field "on" has unsupported event "${String(rawOn)}".`);
|
||||
}
|
||||
on = rawOn as NonNullable<PipelineEdge["on"]>;
|
||||
}
|
||||
|
||||
const rawDomainEvent = value.event;
|
||||
let event: DomainEventType | undefined;
|
||||
if (rawDomainEvent !== undefined) {
|
||||
if (typeof rawDomainEvent !== "string" || !isDomainEventType(rawDomainEvent)) {
|
||||
throw new Error(`Pipeline edge field "event" has unsupported domain event "${String(rawDomainEvent)}".`);
|
||||
}
|
||||
event = rawDomainEvent;
|
||||
}
|
||||
|
||||
if (!on && !event) {
|
||||
throw new Error('Pipeline edge must provide either an "on" trigger or an "event" trigger.');
|
||||
}
|
||||
if (on && event) {
|
||||
throw new Error('Pipeline edge cannot define both "on" and "event" triggers simultaneously.');
|
||||
}
|
||||
|
||||
const rawWhen = value.when;
|
||||
@@ -263,7 +322,8 @@ function parsePipelineEdge(value: unknown): PipelineEdge {
|
||||
return {
|
||||
from: readString(value, "from"),
|
||||
to: readString(value, "to"),
|
||||
on: on as PipelineEdge["on"],
|
||||
...(on ? { on } : {}),
|
||||
...(event ? { event } : {}),
|
||||
...(when.length > 0 ? { when } : {}),
|
||||
};
|
||||
}
|
||||
@@ -298,7 +358,7 @@ function parseTopologies(value: unknown): TopologyKind[] {
|
||||
throw new Error("Manifest topologies must be a non-empty array.");
|
||||
}
|
||||
|
||||
const valid = new Set<TopologyKind>(["hierarchical", "retry-unrolled", "sequential"]);
|
||||
const valid = new Set<TopologyKind>(["hierarchical", "parallel", "retry-unrolled", "sequential"]);
|
||||
const result: TopologyKind[] = [];
|
||||
|
||||
for (const item of value) {
|
||||
@@ -498,6 +558,12 @@ export function parseAgentManifest(input: unknown): AgentManifest {
|
||||
if (!personaIds.has(node.personaId)) {
|
||||
throw new Error(`Pipeline node \"${node.id}\" references unknown persona \"${node.personaId}\".`);
|
||||
}
|
||||
|
||||
if (node.topology && !manifest.topologies.includes(node.topology.kind as TopologyKind)) {
|
||||
throw new Error(
|
||||
`Pipeline node "${node.id}" topology "${node.topology.kind}" is not listed in manifest topologies.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assertPipelineDag(manifest.pipeline);
|
||||
|
||||
Reference in New Issue
Block a user