feat: moderator recognizes $SUSPEND as pseudo-role target
- Add GraphPseudoRole type ($END | $SUSPEND) to workflow-protocol - Add 'suspended' to ThreadStatus - evaluate() returns EvaluateSuspendResult for $SUSPEND targets - Thread show/list derive suspended status from moderator evaluation - validate-semantic treats $SUSPEND like $END (valid target, no outgoing edges) - Tests: routing to $SUSPEND, mustache rendering, thread status display Closes #588
This commit is contained in:
@@ -2,7 +2,8 @@ import type { WorkflowPayload } from "@uncaged/workflow-protocol";
|
||||
|
||||
type SchemaObj = Record<string, unknown>;
|
||||
|
||||
const RESERVED_NAMES = new Set(["$START", "$END"]);
|
||||
const RESERVED_NAMES = new Set(["$START", "$END", "$SUSPEND"]);
|
||||
const PSEUDO_TARGETS = new Set(["$END", "$SUSPEND"]);
|
||||
|
||||
/** Extract mustache variable names from a prompt string. */
|
||||
function extractMustacheVars(prompt: string): string[] {
|
||||
@@ -110,9 +111,13 @@ function checkGraphStructure(payload: WorkflowPayload, errors: string[]): void {
|
||||
errors.push("$END must not have outgoing edges");
|
||||
}
|
||||
|
||||
if (graphNodes.has("$SUSPEND")) {
|
||||
errors.push("$SUSPEND must not have outgoing edges");
|
||||
}
|
||||
|
||||
for (const [node, statusMap] of Object.entries(payload.graph)) {
|
||||
for (const [status, target] of Object.entries(statusMap)) {
|
||||
if (target.role !== "$END" && !roleNames.has(target.role)) {
|
||||
if (!PSEUDO_TARGETS.has(target.role) && !roleNames.has(target.role)) {
|
||||
errors.push(`edge ${node}→${status}: unknown target role "${target.role}"`);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +134,7 @@ function collectReachableRoles(graph: WorkflowPayload["graph"]): Set<string> {
|
||||
|
||||
const queue: string[] = [];
|
||||
for (const target of Object.values(startEdges)) {
|
||||
if (target.role !== "$END" && !reachable.has(target.role)) {
|
||||
if (!PSEUDO_TARGETS.has(target.role) && !reachable.has(target.role)) {
|
||||
reachable.add(target.role);
|
||||
queue.push(target.role);
|
||||
}
|
||||
@@ -140,7 +145,7 @@ function collectReachableRoles(graph: WorkflowPayload["graph"]): Set<string> {
|
||||
const edges = graph[current];
|
||||
if (!edges) continue;
|
||||
for (const target of Object.values(edges)) {
|
||||
if (target.role !== "$END" && !reachable.has(target.role)) {
|
||||
if (!PSEUDO_TARGETS.has(target.role) && !reachable.has(target.role)) {
|
||||
reachable.add(target.role);
|
||||
queue.push(target.role);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user