b0ef9c55a9
CI / check (pull_request) Failing after 1m42s
- 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
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import type { Target } from "@uncaged/workflow-protocol";
|
|
import mustache from "mustache";
|
|
|
|
import type { EvaluateResult, Result } from "./types.js";
|
|
|
|
// Disable HTML escaping — prompts are plain text, not HTML.
|
|
mustache.escape = (text: string) => text;
|
|
|
|
const START_ROLE = "$START";
|
|
const SUSPEND_ROLE = "$SUSPEND";
|
|
const UNIT_STATUS = "_";
|
|
|
|
type LastOutput = Record<string, unknown>;
|
|
|
|
const STATUS_KEY = "$status";
|
|
|
|
export function evaluate(
|
|
graph: Record<string, Record<string, Target>>,
|
|
lastRole: string,
|
|
lastOutput: LastOutput,
|
|
): Result<EvaluateResult, Error> {
|
|
const status =
|
|
lastRole === START_ROLE
|
|
? UNIT_STATUS
|
|
: typeof lastOutput[STATUS_KEY] === "string"
|
|
? (lastOutput[STATUS_KEY] as string)
|
|
: UNIT_STATUS;
|
|
|
|
const roleTargets = graph[lastRole];
|
|
if (roleTargets === undefined) {
|
|
return {
|
|
ok: false,
|
|
error: new Error(`no transitions defined for role "${lastRole}"`),
|
|
};
|
|
}
|
|
|
|
const target = roleTargets[status];
|
|
if (target === undefined) {
|
|
return {
|
|
ok: false,
|
|
error: new Error(`no transition for role "${lastRole}" with status "${status}"`),
|
|
};
|
|
}
|
|
|
|
try {
|
|
const prompt = mustache.render(target.prompt, lastOutput);
|
|
if (prompt.trim() === "") {
|
|
return {
|
|
ok: false,
|
|
error: new Error(
|
|
`edge prompt resolved to empty string for role "${target.role}" (template: "${target.prompt}"). Check that upstream output includes required variables.`,
|
|
),
|
|
};
|
|
}
|
|
if (target.role === SUSPEND_ROLE) {
|
|
return {
|
|
ok: true,
|
|
value: {
|
|
action: "suspend",
|
|
suspendedRole: lastRole,
|
|
prompt,
|
|
},
|
|
};
|
|
}
|
|
|
|
const location = target.location !== null ? mustache.render(target.location, lastOutput) : null;
|
|
return { ok: true, value: { role: target.role, prompt, location } };
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
};
|
|
}
|
|
}
|