agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
1,447 lines (1,407 loc) • 61.9 kB
JavaScript
import {
createGuardrailHooksFromRules,
pluginRegistry
} from "./chunk-MGRZBGQL.js";
import {
buildGuardrailContext
} from "./chunk-LTZA6QWC.js";
// src/lib/task-runner.ts
import { z } from "zod";
import * as yaml from "yaml";
// src/lib/candidate-agents.ts
function renderAgentSystemPrompt(agent) {
const sections = [];
sections.push(`# Role: ${agent.role_name}
${agent.purpose}`);
if (agent.responsibilities.length > 0) {
sections.push(
`## Responsibilities
${agent.responsibilities.map((r) => `- ${r}`).join("\n")}`
);
}
if (agent.constraints.length > 0) {
sections.push(
`## Constraints
${agent.constraints.map((c) => `- ${c}`).join("\n")}`
);
}
if (agent.rules && agent.rules.length > 0) {
const ruleLines = agent.rules.map(
(r) => `- **${r.id}** [${r.severity}]: ${r.description}`
);
sections.push(`## Rules
${ruleLines.join("\n")}`);
}
return sections.join("\n\n");
}
function deriveDescription(purpose) {
const firstLine = purpose.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
const sentenceEnd = firstLine.search(/[.!?](\s|$)/);
if (sentenceEnd !== -1) {
return firstLine.slice(0, sentenceEnd + 1).trim();
}
return firstLine;
}
function buildCandidateAgents(agentRegistry, opts) {
const entryId = opts?.entryAgentId;
const allowed = opts?.allowedAgentIds;
const allowSet = allowed ? new Set(allowed) : void 0;
const candidates = [];
for (const [id, agent] of Object.entries(agentRegistry)) {
if (id === entryId) continue;
if (allowSet && !allowSet.has(id)) continue;
const tools = agent.can_execute_tools;
candidates.push({
name: id,
description: deriveDescription(agent.purpose),
prompt: renderAgentSystemPrompt(agent),
...tools && tools.length > 0 ? { tools: [...tools] } : {}
});
}
return candidates;
}
// src/lib/task-runner.ts
function getZodChecks(schema) {
const def = schema._def;
return def?.checks ?? [];
}
function extractZodConstraints(schema) {
const inner = unwrapOptional(schema);
const parts = [];
if (inner instanceof z.ZodString) {
const str = inner;
if (str.minLength != null) parts.push(`min: ${str.minLength}`);
if (str.maxLength != null) parts.push(`max: ${str.maxLength}`);
for (const check of getZodChecks(inner)) {
if (check.kind === "min") parts.push(`min: ${check.value}`);
if (check.kind === "max") parts.push(`max: ${check.value}`);
if (check.kind === "regex" && check.regex) parts.push(`regex: ${check.regex}`);
const def = check._zod?.def;
if (def?.format === "regex" && def.pattern instanceof RegExp) {
parts.push(`regex: ${def.pattern}`);
}
}
} else if (inner instanceof z.ZodNumber) {
let hasInt = false;
for (const check of getZodChecks(inner)) {
if (check.kind === "int") hasInt = true;
if (check.kind === "min") parts.push(`min: ${check.value}`);
if (check.kind === "max") parts.push(`max: ${check.value}`);
if (check.isInt) hasInt = true;
const def = check._zod?.def;
if (def?.check === "number_format" && def.format === "safeint") hasInt = true;
if (def?.check === "greater_than") parts.push(`min: ${def.value}`);
if (def?.check === "less_than") parts.push(`max: ${def.value}`);
}
if (hasInt) parts.unshift("int");
} else if (inner instanceof z.ZodArray) {
for (const check of getZodChecks(inner)) {
if (check.kind === "min") parts.push(`min: ${check.value}`);
if (check.kind === "max") parts.push(`max: ${check.value}`);
const def = check._zod?.def;
if (def?.check === "min_length") parts.push(`min: ${def.minimum}`);
if (def?.check === "max_length") parts.push(`max: ${def.maximum}`);
}
}
return parts.length > 0 ? parts.join(", ") : void 0;
}
function zodTypeToString(schema) {
if (schema instanceof z.ZodString) return "string";
if (schema instanceof z.ZodNumber) return "number";
if (schema instanceof z.ZodBoolean) return "boolean";
if (schema instanceof z.ZodEnum) return `enum(${schema.options.join(", ")})`;
if (schema instanceof z.ZodLiteral) return `"${String(schema._def.values[0])}"`;
if (schema instanceof z.ZodArray) {
const element = schema._def.element;
const inner = zodTypeToString(element);
return `${inner}[]`;
}
if (schema instanceof z.ZodObject) return "object";
if (schema instanceof z.ZodOptional) {
return zodTypeToString(schema._def.innerType);
}
if (schema instanceof z.ZodRecord) return "Record<string, unknown>";
return "unknown";
}
function unwrapOptional(schema) {
if (schema instanceof z.ZodOptional) {
return schema._def.innerType;
}
return schema;
}
function getArrayElement(schema) {
const inner = unwrapOptional(schema);
if (inner instanceof z.ZodArray) {
return inner._def.element;
}
return null;
}
function extractFieldDescriptors(schema) {
let shape = null;
if (schema instanceof z.ZodObject) {
shape = schema.shape;
}
if (!shape) return null;
return Object.entries(shape).map(([name, field]) => {
const isOptional = field instanceof z.ZodOptional;
const innerField = isOptional ? field._def.innerType : field;
const description = innerField.description ?? field.description;
return {
name,
type: zodTypeToString(field),
required: !isOptional,
description,
constraints: extractZodConstraints(innerField)
};
});
}
function zodSchemaToPromptDescription(schema) {
const tables = [];
collectFieldTables(schema, "", tables);
return tables.join("\n\n");
}
function collectFieldTables(schema, pathPrefix, tables) {
const fields = extractFieldDescriptors(schema);
if (!fields || fields.length === 0) return;
const title = pathPrefix ? `**${pathPrefix}**
` : "";
const header = "| Field | Type | Required | Constraints | Description |";
const sep = "|-------|------|----------|-------------|-------------|";
const rows = fields.map(
(f) => `| ${f.name} | ${f.type} | ${f.required ? "Yes" : "No"} | ${f.constraints ?? ""} | ${f.description ?? ""} |`
);
tables.push(`${title}${[header, sep, ...rows].join("\n")}`);
const shape = schema instanceof z.ZodObject ? schema.shape : null;
if (!shape) return;
for (const f of fields) {
const rawField = shape[f.name];
const inner = unwrapOptional(rawField);
if (inner instanceof z.ZodObject) {
collectFieldTables(inner, pathPrefix ? `${pathPrefix}.${f.name}` : f.name, tables);
} else if (inner instanceof z.ZodArray) {
const element = inner._def.element;
if (element instanceof z.ZodObject) {
collectFieldTables(element, pathPrefix ? `${pathPrefix}.${f.name}[]` : `${f.name}[]`, tables);
}
}
}
}
function zodSchemaToYamlExample(schema, maxDepth = 3) {
return renderYamlObject(schema, 0, maxDepth);
}
function renderYamlObject(schema, indent, maxDepth) {
const fields = extractFieldDescriptors(schema);
if (!fields || fields.length === 0) return "";
const prefix = " ".repeat(indent);
const lines = [];
for (const f of fields) {
const shape = schema instanceof z.ZodObject ? schema.shape : null;
const rawField = shape?.[f.name];
if (f.type.endsWith("[]")) {
lines.push(`${prefix}${f.name}:`);
const element = rawField ? getArrayElement(rawField) : null;
if (element && element instanceof z.ZodObject && indent < maxDepth) {
const nested = renderYamlObject(element, indent + 2, maxDepth);
if (nested) {
const basePrefix = " ".repeat(indent + 2);
const nestedLines = nested.split("\n");
lines.push(`${prefix} - ${nestedLines[0].slice(basePrefix.length)}`);
for (let i = 1; i < nestedLines.length; i++) {
lines.push(`${prefix} ${nestedLines[i].slice(basePrefix.length)}`);
}
} else {
lines.push(`${prefix} - key: "value"`);
}
} else if (f.type === "string[]") {
lines.push(`${prefix} - "example"`);
} else {
lines.push(`${prefix} - key: "value"`);
}
} else if (f.type === "object" && indent < maxDepth) {
const inner = rawField ? unwrapOptional(rawField) : null;
if (inner && inner instanceof z.ZodObject) {
lines.push(`${prefix}${f.name}:`);
const nested = renderYamlObject(inner, indent + 1, maxDepth);
if (nested) {
lines.push(nested);
} else {
lines.push(`${prefix} key: "value"`);
}
} else {
lines.push(`${prefix}${f.name}:`);
lines.push(`${prefix} key: "value"`);
}
} else if (f.type === "string") {
lines.push(`${prefix}${f.name}: "..."`);
} else if (f.type === "number") {
lines.push(`${prefix}${f.name}: 0`);
} else if (f.type === "boolean") {
lines.push(`${prefix}${f.name}: false`);
} else if (f.type.startsWith("enum(")) {
const enumVals = f.type.slice(5, -1).split(", ");
lines.push(`${prefix}${f.name}: "${enumVals[0]}"`);
} else {
lines.push(`${prefix}${f.name}: "..."`);
}
}
return lines.join("\n");
}
function buildSplitTaskPrompt(agent, task, context, options) {
const system = [];
system.push(renderAgentSystemPrompt(agent));
system.push(`## Task: ${task.id}
${task.description}`);
if (task.completion_criteria.length > 0) {
system.push(
`## Completion Criteria
${task.completion_criteria.map((c) => `- ${c}`).join("\n")}`
);
}
const handoffId = task.result_handoff;
const schema = options?.handoffSchemas?.[handoffId];
let outputSection = `## Required Output Format
Return your result as a YAML block matching the "${handoffId}" schema.`;
if (schema) {
const fieldTable = zodSchemaToPromptDescription(schema);
if (fieldTable) {
outputSection += `
${fieldTable}`;
}
const yamlExample = zodSchemaToYamlExample(schema);
if (yamlExample) {
outputSection += `
Example:
\`\`\`yaml
${yamlExample}
\`\`\``;
}
} else {
outputSection += ` Include all required fields.`;
}
system.push(outputSection);
if (options?.retry) {
const r = options.retry;
let recoverySection = `## Recovery Instructions
If the task fails due to: "${r.condition}"
\u2192 Apply the recovery approach from task "${r.fix_task}"`;
if (r.revalidate_task) {
recoverySection += `
\u2192 Then revalidate using task "${r.revalidate_task}"`;
}
system.push(recoverySection);
}
const userSections = [];
if (context.handoff_input) {
const hi = context.handoff_input;
const payloadStr = typeof hi.payload === "string" ? hi.payload : yaml.stringify(hi.payload);
userSections.push(`## User Request
${context.user_request}`);
userSections.push(`## Handoff Input (${hi.type})
\`\`\`yaml
${payloadStr}\`\`\``);
} else {
userSections.push(`## User Request
${context.user_request}`);
}
if (context.prior_context) {
userSections.push(`## Prior Context
${context.prior_context}`);
}
if (context.relevant_paths && context.relevant_paths.length > 0) {
userSections.push(
`## Relevant Files
${context.relevant_paths.map((p) => `- ${p}`).join("\n")}`
);
}
if (context.acceptance_criteria && context.acceptance_criteria.length > 0) {
userSections.push(
`## Acceptance Criteria
${context.acceptance_criteria.map((c) => `- ${c}`).join("\n")}`
);
}
return {
system,
user: userSections.join("\n\n---\n\n")
};
}
function joinSplitPrompt(split) {
return [...split.system, split.user].join("\n\n---\n\n");
}
function buildTaskPrompt(agent, task, context, options) {
return joinSplitPrompt(buildSplitTaskPrompt(agent, task, context, options));
}
function extractStructuredResult(raw) {
const yamlMatch = raw.match(/```ya?ml\n([\s\S]*?)```/);
if (yamlMatch) {
try {
return yaml.parse(yamlMatch[1]);
} catch {
}
}
const jsonMatch = raw.match(/```json\n([\s\S]*?)```/);
if (jsonMatch) {
try {
return JSON.parse(jsonMatch[1]);
} catch {
}
}
try {
return JSON.parse(raw);
} catch {
}
return null;
}
function validateOutput(fullOutput, task, schemas) {
if (!fullOutput.trim()) {
return { status: "error", message: "Agent returned empty output" };
}
const parsed = extractStructuredResult(fullOutput);
if (!parsed) {
return {
status: "validation_error",
errors: new z.ZodError([{ code: "custom", message: "Could not extract structured result from agent output", path: [] }]),
raw: fullOutput
};
}
const schema = schemas[task.result_handoff];
if (!schema) {
return { status: "success", data: parsed, raw: fullOutput };
}
const validation = schema.safeParse(parsed);
if (!validation.success) {
return { status: "validation_error", errors: validation.error, raw: fullOutput };
}
const data = validation.data;
if (data.escalation_needed === true) {
return {
status: "escalation",
reason: data.escalation_reasons ?? "Agent requested escalation",
raw: fullOutput
};
}
return { status: "success", data: validation.data, raw: fullOutput };
}
function attachMemoryRef(outcome, adapter) {
if (outcome.status !== "success") return outcome;
const ref = adapter.getLastMemoryRef?.();
if (!ref) return outcome;
return { ...outcome, memoryRef: ref };
}
function buildRetryFollowUp(task, lastOutcome, options) {
const sections = [];
sections.push(
"Your previous response could not be validated. The task work itself is fine \u2014 please re-output only the structured result block in the correct format."
);
if (lastOutcome.status === "validation_error") {
sections.push(`Validation errors:
${lastOutcome.errors.message}`);
} else if (lastOutcome.status === "error") {
sections.push(`Error: ${lastOutcome.message}`);
}
const handoffId = task.result_handoff;
const schema = options?.handoffSchemas?.[handoffId];
let formatSection = `Required format: Return your result as a YAML block matching the "${handoffId}" schema.`;
if (schema) {
const fieldTable = zodSchemaToPromptDescription(schema);
if (fieldTable) {
formatSection += `
${fieldTable}`;
}
const yamlExample = zodSchemaToYamlExample(schema);
if (yamlExample) {
formatSection += `
Example:
\`\`\`yaml
${yamlExample}
\`\`\``;
}
} else {
formatSection += ` Include all required fields.`;
}
sections.push(formatSection);
return sections.join("\n\n");
}
function defaultDecideRetryStrategy(outcome, _counts) {
if (outcome.status === "validation_error") {
return "follow_up";
}
if (outcome.status === "error") {
if (outcome.message === "Agent returned empty output") return "retry";
return "abort";
}
return "abort";
}
function summarizeToolInput(input) {
if (!input) return "";
const path = input.file_path ?? input.path ?? input.filename ?? input.file;
if (typeof path === "string") return ` \u2014 ${path}`;
const cmd = input.command ?? input.cmd;
if (typeof cmd === "string") return ` \u2014 ${cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd}`;
const query = input.query ?? input.search ?? input.pattern;
if (typeof query === "string") return ` \u2014 "${query}"`;
return "";
}
function timestamp() {
return (/* @__PURE__ */ new Date()).toISOString();
}
function formatProgressPrefix(event, taskId, agentId) {
const ts = timestamp();
const sid = event.session_id ? event.session_id.slice(0, 8) : "-";
const agent = agentId ?? "-";
return `[${ts},${sid},${agent},${taskId}]`;
}
function formatProgressEvent(event, taskId, agentId) {
const prefix = formatProgressPrefix(event, taskId, agentId);
switch (event.type) {
case "tool_use":
return `${prefix} \u2699 ${event.tool_name ?? "tool"}${summarizeToolInput(event.input)}
`;
case "tool_result":
return "";
case "text": {
if (!event.message) return "";
const trimmed = event.message.trim();
if (!trimmed) return "";
const firstLine = trimmed.split("\n")[0];
const preview = firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine;
return `${prefix} \u{1F4AD} ${preview}
`;
}
case "status":
return event.message ? `${prefix} \u25CF ${event.message}
` : "";
default:
return "";
}
}
async function runTask(adapter, taskId, context, options) {
const taskReg = options?.taskRegistry ?? {};
const agentReg = options?.agentRegistry ?? {};
const schemas = options?.handoffSchemas ?? {};
const task = taskReg[taskId];
if (!task) return { outcome: { status: "error", message: `Unknown task: ${taskId}` }, follow_ups_used: 0, retries_used: 0 };
const agentContract = agentReg[task.target_agent];
if (!agentContract) return { outcome: { status: "error", message: `Unknown agent: ${task.target_agent}` }, follow_ups_used: 0, retries_used: 0 };
const modifiedContext = await pluginRegistry.runBeforeTask(taskId, context);
if (modifiedContext === null) {
return { outcome: { status: "error", message: `Task "${taskId}" skipped by plugin` }, follow_ups_used: 0, retries_used: 0 };
}
const enrichedContext = pluginRegistry.applyContextEnhancers(taskId, modifiedContext);
const promptOpts = { handoffSchemas: schemas, retry: options?.retry };
const builderArgs = { agent: agentContract, task, context: enrichedContext, options: promptOpts };
let prompt;
let splitPrompt;
const pluginPrompt = pluginRegistry.applyPromptBuilder(builderArgs);
if (pluginPrompt) {
prompt = pluginPrompt;
} else {
const split = buildSplitTaskPrompt(agentContract, task, enrichedContext, promptOpts);
splitPrompt = split;
prompt = joinSplitPrompt(split);
}
const enhancedPrompt = pluginRegistry.applyPromptEnhancers(taskId, prompt, enrichedContext);
if (enhancedPrompt !== prompt) {
splitPrompt = void 0;
prompt = enhancedPrompt;
}
const isReadonly = agentContract.mode === "read-only";
const allowDynamicWorkflow = task.allow_dynamic_workflow ?? false;
const candidateAgents = Object.keys(agentReg).length > 0 ? buildCandidateAgents(agentReg, {
entryAgentId: agentContract.id,
allowedAgentIds: agentContract.can_invoke_agents
}) : [];
const agents = candidateAgents.length > 0 ? candidateAgents : void 0;
const maxFollowUps = options?.maxFollowUps ?? 2;
const maxRetries = options?.maxRetries ?? 0;
const decide = options?.decideRetryStrategy ?? defaultDecideRetryStrategy;
const onProgress = options?.progressOutput ? (event) => {
const line = formatProgressEvent(event, taskId, agentContract.id);
if (line) options.progressOutput.write(line);
} : void 0;
let followUpsUsed = 0;
let retriesUsed = 0;
let memoryRef = enrichedContext.memoryRef;
if (memoryRef && adapter.isCompatible && !adapter.isCompatible(memoryRef.compat)) {
console.warn(
`Memory ref compat mismatch: ${memoryRef.compat} is not compatible with this adapter`
);
memoryRef = void 0;
}
let fullOutput;
try {
if (adapter.sendExecution) {
fullOutput = await adapter.sendExecution({
agentId: agentContract.id,
taskId,
prompt,
handoff: schemas[task.result_handoff] ? { type: task.result_handoff, payload: void 0 } : void 0,
schema: schemas[task.result_handoff] ? {
handoffType: task.result_handoff,
zodSchema: schemas[task.result_handoff],
promptDescription: zodSchemaToPromptDescription(schemas[task.result_handoff])
} : void 0,
context: enrichedContext,
memoryRef,
splitPrompt,
agents,
options: { readonly: isReadonly, onProgress, splitPrompt, agents, allowDynamicWorkflow, maxFollowUps, attempt: 0 }
});
} else {
fullOutput = await adapter.send(prompt, { readonly: isReadonly, onProgress, splitPrompt, agents, allowDynamicWorkflow });
}
} catch (err) {
return { outcome: { status: "error", message: `SDK error: ${err.message}` }, follow_ups_used: 0, retries_used: 0 };
}
let outcome = attachMemoryRef(validateOutput(fullOutput, task, schemas), adapter);
outcome = await pluginRegistry.runAfterTask(taskId, outcome);
while (outcome.status === "validation_error" || outcome.status === "error") {
const decision = await decide(outcome, { followUps: followUpsUsed, retries: retriesUsed });
if (decision === "abort") break;
let effectiveDecision = decision;
if (effectiveDecision === "follow_up" && followUpsUsed >= maxFollowUps) {
effectiveDecision = "retry";
}
if (effectiveDecision === "retry" && retriesUsed >= maxRetries) {
break;
}
if (effectiveDecision === "follow_up") {
followUpsUsed++;
const followUpPrompt = buildRetryFollowUp(task, outcome, promptOpts);
try {
if (adapter.followUp) {
fullOutput = await adapter.followUp(followUpPrompt);
} else {
const retryContext = {
...modifiedContext,
prior_context: (modifiedContext.prior_context ?? "") + `
--- FollowUp ${followUpsUsed} ---
${followUpPrompt}`
};
let retryFullPrompt = buildTaskPrompt(agentContract, task, retryContext, promptOpts);
retryFullPrompt = pluginRegistry.applyPromptEnhancers(taskId, retryFullPrompt, retryContext);
fullOutput = await adapter.send(retryFullPrompt, { readonly: isReadonly, agents });
}
} catch (err) {
outcome = { status: "error", message: `SDK error on followUp ${followUpsUsed}: ${err.message}` };
continue;
}
} else {
retriesUsed++;
const retryContext = {
...modifiedContext,
prior_context: (modifiedContext.prior_context ?? "") + `
--- Full Retry ${retriesUsed} ---
Previous attempt failed: ` + (outcome.status === "validation_error" ? `Validation errors: ${outcome.errors.message}` : outcome.status === "error" ? outcome.message : "unknown") + `
Please re-execute the entire task.`
};
let retryFullPrompt = buildTaskPrompt(agentContract, task, retryContext);
retryFullPrompt = pluginRegistry.applyPromptEnhancers(taskId, retryFullPrompt, retryContext);
try {
fullOutput = await adapter.send(retryFullPrompt, { readonly: isReadonly, agents });
} catch (err) {
outcome = { status: "error", message: `SDK error on retry ${retriesUsed}: ${err.message}` };
continue;
}
}
outcome = attachMemoryRef(validateOutput(fullOutput, task, schemas), adapter);
outcome = await pluginRegistry.runAfterTask(taskId, outcome);
}
return { outcome, follow_ups_used: followUpsUsed, retries_used: retriesUsed };
}
// src/lib/workflow-runner.ts
function isAdapterFactory(v) {
return typeof v === "function" && !("send" in v);
}
function resolveAdapter(adapterOrFactory) {
if (isAdapterFactory(adapterOrFactory)) {
const result = adapterOrFactory();
if (result && typeof result.then === "function") {
throw new Error("ModelAwareSdkAdapterFactory returned a Promise for the default adapter. Use a synchronous factory or pre-resolve the default adapter.");
}
return { adapter: result, factory: adapterOrFactory };
}
return { adapter: adapterOrFactory, factory: void 0 };
}
function resolveInvocation(invocation) {
let userRequest;
if (invocation.handoff) {
const h = invocation.handoff;
userRequest = invocation.user_request ? invocation.user_request : typeof h.payload === "string" ? h.payload : JSON.stringify(h.payload, null, 2);
} else {
userRequest = invocation.user_request ?? "";
}
const ctx = invocation.context;
const variables = { ...ctx?.variables };
if (ctx?.cwd) variables.cwd = ctx.cwd;
if (ctx?.environment) variables.environment = ctx.environment;
if (ctx?.artifacts) variables.artifacts = ctx.artifacts;
return {
workflowId: invocation.workflow,
options: {
user_request: userRequest,
maxFollowUps: invocation.runtime?.maxFollowUps,
maxRetries: invocation.runtime?.maxRetries,
variables: Object.keys(variables).length > 0 ? variables : void 0,
onGate: invocation.hooks?.onGate,
onStepComplete: invocation.hooks?.onStepComplete,
onOptionalStep: invocation.hooks?.onOptionalStep,
progressOutput: invocation.progressOutput
}
};
}
async function runWorkflow(adapterOrFactory, workflowIdOrInvocation, optionsOrRegistries, maybeRegistries) {
const { adapter: primaryAdapter, factory: adapterFactory } = resolveAdapter(adapterOrFactory);
let workflowId;
let options;
let registries;
let invocationHandoff;
if (typeof workflowIdOrInvocation === "string") {
workflowId = workflowIdOrInvocation;
options = optionsOrRegistries;
registries = maybeRegistries;
} else {
const resolved = resolveInvocation(workflowIdOrInvocation);
workflowId = resolved.workflowId;
options = resolved.options;
registries = optionsOrRegistries;
invocationHandoff = workflowIdOrInvocation.handoff;
}
if (invocationHandoff && registries?.handoffSchemas) {
const inputSchema = registries.handoffSchemas[invocationHandoff.type];
if (inputSchema) {
const validation = inputSchema.safeParse(invocationHandoff.payload);
if (!validation.success) {
return {
workflow_id: workflowId,
status: "error",
steps: [],
total_elapsed_ms: 0,
error_message: `Input handoff validation failed for "${invocationHandoff.type}": ${validation.error.message}`
};
}
}
}
const wfReg = registries?.workflowRegistry ?? {};
const workflow = wfReg[workflowId];
if (!workflow) {
return {
workflow_id: workflowId,
status: "error",
steps: [],
total_elapsed_ms: 0,
error_message: `Unknown workflow: ${workflowId}`
};
}
await pluginRegistry.runBeforeWorkflow(workflowId, options.user_request);
const workflowStart = Date.now();
const stepId = (i) => {
const s = workflow.steps[i];
return s.type === "delegate" ? s.task : `gate:${s.gate_kind}`;
};
const depsByIndex = workflow.steps.map((step, i) => {
if (step.depends_on !== void 0) {
return step.depends_on.map((dep) => {
const idx = workflow.steps.findIndex((s, j) => j < i && stepId(j) === dep);
if (idx === -1) {
throw new Error(`depends_on "${dep}" in step ${i} (${stepId(i)}) not found among preceding steps`);
}
return idx;
});
}
return i === 0 ? [] : Array.from({ length: i }, (_, k) => k);
});
const completed = /* @__PURE__ */ new Set();
const stepResults = new Array(workflow.steps.length);
const stepContexts = new Array(workflow.steps.length).fill("");
let abortResult;
function contextFor(stepIndex) {
const deps = depsByIndex[stepIndex];
if (deps.length === 0) return "";
return deps.filter((d) => completed.has(d)).map((d) => stepContexts[d]).filter(Boolean).join("");
}
async function executeStep(i, stepAdapter) {
const step = workflow.steps[i];
const stepStart = Date.now();
if (step.type === "gate") {
return handleGateStep(step, Object.values(stepResults).filter(Boolean), options, i, stepAdapter);
}
if (step.optional) {
const ctx = { user_request: options.user_request, prior_context: contextFor(i), variables: options.variables };
const shouldRun = options.onOptionalStep ? await options.onOptionalStep(step, ctx) : false;
if (!shouldRun) {
return {
step_index: i,
task_id: step.task,
outcome: { status: "skipped" },
follow_ups_used: 0,
retries_used: 0,
elapsed_ms: Date.now() - stepStart
};
}
}
const context = {
user_request: options.user_request,
prior_context: contextFor(i),
variables: options.variables,
...invocationHandoff ? { handoff_input: invocationHandoff } : {}
};
const taskResult = await runTask(stepAdapter, step.task, context, {
maxFollowUps: options.maxFollowUps ?? step.max_follow_ups ?? 2,
maxRetries: options.maxRetries ?? step.max_retries ?? 0,
taskRegistry: registries?.taskRegistry,
agentRegistry: registries?.agentRegistry,
handoffSchemas: registries?.handoffSchemas,
retry: step.retry,
progressOutput: options.progressOutput
});
return {
step_index: i,
task_id: step.task,
outcome: taskResult.outcome,
follow_ups_used: taskResult.follow_ups_used,
retries_used: taskResult.retries_used,
elapsed_ms: Date.now() - stepStart
};
}
while (completed.size < workflow.steps.length) {
const ready = [];
for (let i = 0; i < workflow.steps.length; i++) {
if (completed.has(i)) continue;
if (depsByIndex[i].every((d) => completed.has(d))) {
ready.push(i);
}
}
if (ready.length === 0) {
abortResult = {
workflow_id: workflow.id,
status: "error",
steps: stepResults.filter(Boolean),
total_elapsed_ms: Date.now() - workflowStart,
error_message: "Dependency cycle detected: no steps are ready to execute"
};
break;
}
const batchResults = await Promise.all(ready.map(async (i) => {
const step = workflow.steps[i];
const modelClass = step.type === "delegate" ? registries?.taskRegistry?.[step.task]?.model_class : void 0;
let stepAdapter;
if (ready.length > 1 && adapterFactory) {
const result = adapterFactory(modelClass);
stepAdapter = result && typeof result.then === "function" ? await result : result;
} else if (modelClass && adapterFactory) {
const result = adapterFactory(modelClass);
stepAdapter = result && typeof result.then === "function" ? await result : result;
} else {
stepAdapter = primaryAdapter;
}
return executeStep(i, stepAdapter);
}));
for (let b = 0; b < ready.length; b++) {
const i = ready[b];
const result = batchResults[b];
const step = workflow.steps[i];
stepResults[i] = result;
completed.add(i);
emitStepEvent(options, workflow.id, i, step, result);
if (result.outcome.status === "success") {
const data = result.outcome.data;
stepContexts[i] = `
--- Step ${i + 1}: ${stepId(i)} ---
` + (typeof data === "string" ? data : JSON.stringify(data, null, 2));
}
if (result.outcome.status === "gate_rejected") {
abortResult = {
workflow_id: workflow.id,
status: "gate_rejected",
steps: stepResults.filter(Boolean),
total_elapsed_ms: Date.now() - workflowStart
};
} else if (result.outcome.status === "escalation") {
abortResult = {
workflow_id: workflow.id,
status: "escalated",
steps: stepResults.filter(Boolean),
total_elapsed_ms: Date.now() - workflowStart,
escalation_reason: result.outcome.reason
};
} else if (result.outcome.status === "error" || result.outcome.status === "validation_error") {
const taskId = step.type === "delegate" ? step.task : step.gate_kind;
abortResult = {
workflow_id: workflow.id,
status: "escalated",
steps: stepResults.filter(Boolean),
total_elapsed_ms: Date.now() - workflowStart,
escalation_reason: `Task "${taskId}" failed after ${result.follow_ups_used} follow-ups and ${result.retries_used} retries`
};
}
}
if (abortResult) break;
}
const wfResult = abortResult ?? {
workflow_id: workflow.id,
status: "completed",
steps: stepResults.filter(Boolean),
total_elapsed_ms: Date.now() - workflowStart
};
await pluginRegistry.runAfterWorkflow(workflow.id, wfResult);
return wfResult;
}
async function handleGateStep(step, priorResults, options, stepIndex, adapter) {
const start = Date.now();
if (options.onGate) {
const approved = await options.onGate(step.gate_kind, step.description, priorResults);
return {
step_index: stepIndex,
gate_kind: step.gate_kind,
outcome: { status: approved ? "gate_approved" : "gate_rejected" },
follow_ups_used: 0,
retries_used: 0,
elapsed_ms: Date.now() - start
};
}
if (adapter && step.description) {
const approved = await evaluateGateWithLlm(adapter, step, priorResults);
return {
step_index: stepIndex,
gate_kind: step.gate_kind,
outcome: { status: approved ? "gate_approved" : "gate_rejected" },
follow_ups_used: 0,
retries_used: 0,
elapsed_ms: Date.now() - start
};
}
return {
step_index: stepIndex,
gate_kind: step.gate_kind,
outcome: { status: "gate_approved" },
follow_ups_used: 0,
retries_used: 0,
elapsed_ms: Date.now() - start
};
}
async function evaluateGateWithLlm(adapter, step, priorResults) {
const priorContext = priorResults.map((r) => {
const id = r.task_id ?? r.gate_kind ?? `step_${r.step_index}`;
const data = "data" in r.outcome ? r.outcome.data : void 0;
return `- ${id}: status=${r.outcome.status}${data ? `, data=${typeof data === "string" ? data : JSON.stringify(data)}` : ""}`;
}).join("\n");
const prompt = buildGateEvaluationPrompt(step, priorContext);
try {
const response = await adapter.send(prompt, { readonly: true });
return parseGateEvaluationResponse(response);
} catch {
return false;
}
}
function buildGateEvaluationPrompt(step, priorContext) {
return `You are a gate evaluator in a workflow pipeline. Your job is to decide whether the workflow should proceed based on prior step results.
## Gate
- kind: ${step.gate_kind}
- condition: ${step.description}
## Prior Step Results
${priorContext || "(no prior results)"}
## Instructions
Evaluate whether the gate condition is satisfied based on the prior step results.
Respond with ONLY a JSON object (no markdown fences, no extra text):
{"approved": true, "reason": "brief explanation"}
or
{"approved": false, "reason": "brief explanation"}`;
}
function parseGateEvaluationResponse(response) {
const jsonMatch = response.match(/\{[\s\S]*?"approved"\s*:\s*(true|false)[\s\S]*?\}/);
if (jsonMatch) {
try {
const parsed = JSON.parse(jsonMatch[0]);
return parsed.approved === true;
} catch {
}
}
const lower = response.toLowerCase();
if (lower.includes('"approved": true') || lower.includes('"approved":true')) {
return true;
}
if (lower.includes('"approved": false') || lower.includes('"approved":false')) {
return false;
}
return false;
}
function emitStepEvent(options, workflowId, stepIndex, step, result) {
options.onStepComplete?.({
workflow_id: workflowId,
step_index: stepIndex,
step_type: step.type,
task_id: step.type === "delegate" ? step.task : void 0,
gate_kind: step.type === "gate" ? step.gate_kind : void 0,
outcome_status: result.outcome.status,
follow_ups_used: result.follow_ups_used,
retries_used: result.retries_used,
elapsed_ms: result.elapsed_ms
});
}
// src/lib/bound-resolver.ts
import { readFile as readFile2 } from "fs/promises";
import YAML2 from "yaml";
// src/lib/dsl-context.ts
import { readFile } from "fs/promises";
import YAML from "yaml";
// src/lib/schema-converter.ts
import { z as z2 } from "zod";
function applyStringValidations(schema, def) {
let s = schema;
if (typeof def.minLength === "number") s = s.min(def.minLength);
if (typeof def.maxLength === "number") s = s.max(def.maxLength);
if (typeof def.pattern === "string") s = s.regex(new RegExp(def.pattern));
return s;
}
function applyNumberValidations(schema, def, isInteger) {
let n = schema;
if (isInteger) n = n.int();
if (typeof def.minimum === "number") n = n.min(def.minimum);
if (typeof def.maximum === "number") n = n.max(def.maximum);
return n;
}
function applyArrayValidations(schema, def) {
let a = schema;
if (typeof def.minItems === "number") a = a.min(def.minItems);
if (typeof def.maxItems === "number") a = a.max(def.maxItems);
return a;
}
function jsonSchemaToZod(schema) {
if (!schema || typeof schema !== "object") return z2.unknown();
if (schema.enum) {
const vals = schema.enum;
if (vals.length === 0) return z2.unknown();
return z2.enum(vals);
}
if (schema.const !== void 0) {
const val = schema.const;
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
return z2.literal(val);
}
return z2.unknown();
}
const type = schema.type;
if (type === "string") return applyStringValidations(z2.string(), schema);
if (type === "integer") return applyNumberValidations(z2.number(), schema, true);
if (type === "number") return applyNumberValidations(z2.number(), schema, false);
if (type === "boolean") return z2.boolean();
if (type === "array") {
const items = schema.items;
const inner = items ? jsonSchemaToZod(items) : z2.unknown();
return applyArrayValidations(z2.array(inner), schema);
}
if (type === "object") {
const props = schema.properties;
if (!props) return z2.record(z2.string(), z2.unknown());
const required = new Set(schema.required ?? []);
const shape = {};
for (const [key, val] of Object.entries(props)) {
const fieldSchema = jsonSchemaToZod(val);
shape[key] = required.has(key) ? fieldSchema : fieldSchema.optional();
}
return z2.object(shape).passthrough();
}
if (schema.allOf) {
const parts = schema.allOf.map(jsonSchemaToZod);
if (parts.length === 0) return z2.unknown();
if (parts.length === 1) return parts[0];
let merged = parts[0];
for (let i = 1; i < parts.length; i++) {
const part = parts[i];
if (merged instanceof z2.ZodObject && part instanceof z2.ZodObject) {
merged = merged.merge(part);
}
}
return merged;
}
return z2.unknown();
}
// src/lib/dsl-context.ts
var DslValidationError = class extends Error {
missing;
constructor(missing) {
super(
"Required entities missing after DSL merge:\n" + missing.map((m) => ` - ${m}`).join("\n")
);
this.name = "DslValidationError";
this.missing = missing;
}
};
function buildAgentRegistry(agents) {
const registry = {};
for (const [id, agent] of Object.entries(agents)) {
const rawMode = agent.mode ?? "read-only";
const mode = rawMode === "read-write" ? "read-write" : "read-only";
registry[id] = {
id,
role_name: agent.role_name ?? id,
purpose: agent.purpose ?? "",
mode,
responsibilities: agent.responsibilities ?? [],
constraints: agent.constraints ?? [],
rules: agent.rules ?? [],
can_invoke_agents: agent.can_invoke_agents ?? void 0,
can_execute_tools: agent.can_execute_tools ?? void 0
};
}
return registry;
}
function buildTaskRegistry(tasks) {
const registry = {};
for (const [id, task] of Object.entries(tasks)) {
registry[id] = {
id,
description: task.description ?? "",
target_agent: task.target_agent ?? "",
result_handoff: task.result_handoff ?? "",
completion_criteria: task.completion_criteria ?? [],
model_class: task.model_class
};
}
return registry;
}
function buildWorkflowStep(step) {
if (step.type === "gate") {
return {
type: "gate",
gate_kind: step.gate_kind ?? "",
description: step.description ?? "",
depends_on: step.depends_on
};
}
return {
type: "delegate",
task: step.task ?? "",
from_agent: step.from_agent ?? "",
description: step.description ?? "",
optional: step.optional ?? false,
max_retries: step.max_retries ?? 0,
max_follow_ups: step.max_follow_ups,
depends_on: step.depends_on,
retry: step.retry
};
}
function buildWorkflowRegistry(workflows) {
const registry = {};
for (const [id, wf] of Object.entries(workflows)) {
const steps = (wf.steps ?? []).map(buildWorkflowStep);
registry[id] = { id, description: wf.description ?? "", steps };
}
return registry;
}
function buildHandoffSchemas(handoffTypes) {
const schemas = {};
for (const [id, ht] of Object.entries(handoffTypes)) {
const schema = ht.schema;
if (schema) {
schemas[id] = jsonSchemaToZod(schema);
}
}
return schemas;
}
function buildGuardrailRulesFromDsl(dsl) {
const raw = dsl._guardrailRules;
if (!raw) return void 0;
return {
commandRules: raw.commandRules ?? [],
fileRules: raw.fileRules ?? [],
contentRules: raw.contentRules ?? []
};
}
function buildRegistriesFromDsl(dsl) {
const agents = dsl.agents ?? {};
const tasks = dsl.tasks ?? {};
const workflows = dsl.workflow ?? {};
const handoffTypes = dsl.handoff_types ?? {};
return {
agentRegistry: buildAgentRegistry(agents),
taskRegistry: buildTaskRegistry(tasks),
workflowRegistry: buildWorkflowRegistry(workflows),
handoffSchemas: buildHandoffSchemas(handoffTypes)
};
}
function validateRequiredEntities(dsl, required) {
const missing = [];
const workflows = dsl.workflow ?? {};
const tasks = dsl.tasks ?? {};
const handoffTypes = dsl.handoff_types ?? {};
if (required.workflows) {
for (const wfId of required.workflows) {
if (!(wfId in workflows)) {
missing.push(`workflow "${wfId}" is required by the CLI but not found in the resolved DSL`);
}
}
}
if (required.tasks) {
for (const taskId of required.tasks) {
if (!(taskId in tasks)) {
missing.push(`task "${taskId}" is required by the CLI but not found in the resolved DSL`);
continue;
}
const task = tasks[taskId];
const invHandoff = task.invocation_handoff;
const resHandoff = task.result_handoff;
if (invHandoff && !(invHandoff in handoffTypes)) {
missing.push(`handoff_type "${invHandoff}" (invocation_handoff of task "${taskId}") not found`);
}
if (resHandoff && !(resHandoff in handoffTypes)) {
missing.push(`handoff_type "${resHandoff}" (result_handoff of task "${taskId}") not found`);
}
}
}
if (required.workflows) {
for (const wfId of required.workflows) {
if (!(wfId in workflows)) continue;
const wf = workflows[wfId];
const steps = wf.steps ?? [];
for (const step of steps) {
if (step.type !== "delegate") continue;
const taskId = step.task;
if (taskId && taskId in tasks) {
const task = tasks[taskId];
const resHandoff = task.result_handoff;
if (resHandoff && !(resHandoff in handoffTypes)) {
missing.push(`handoff_type "${resHandoff}" (result of task "${taskId}" in workflow "${wfId}") not found`);
}
}
}
}
}
if (missing.length > 0) {
throw new DslValidationError(missing);
}
}
async function loadDslContext(options) {
let dsl;
if (options.projectDslPath) {
const { resolve: resolveDsl, mergeDsl, expandDefaults } = await import("agent-contracts");
const raw = await readFile(options.projectDslPath, "utf8");
const projectData = YAML.parse(raw);
if (projectData.extends === "/$embedded/") {
const { extends: _extends, ...projectOverrides } = projectData;
const merged = mergeDsl(options.embeddedDsl, projectOverrides);
dsl = expandDefaults(merged);
} else {
const projectResult = await resolveDsl(options.projectDslPath);
const merged = mergeDsl(options.embeddedDsl, projectResult.data);
dsl = expandDefaults(merged);
}
} else {
dsl = options.embeddedDsl;
}
if (options.requiredEntities) {
validateRequiredEntities(dsl, options.requiredEntities);
}
const registries = buildRegistriesFromDsl(dsl);
const guardrailRules = buildGuardrailRulesFromDsl(dsl);
return { registries, dsl, guardrailRules };
}
// src/lib/bound-resolver.ts
function getEffectiveGuardrailIds(dsl, agentId) {
const ids = /* @__PURE__ */ new Set();
const agents = dsl.agents ?? {};
const agent = agents[agentId];
if (agent) {
for (const ref of agent.guardrails ?? []) {
ids.add(ref);
}
}
const guardrails = dsl.guardrails ?? {};
for (const [guardrailId, guardrail] of Object.entries(guardrails)) {
if (guardrail.scope?.agents?.includes(agentId)) {
ids.add(guardrailId);
}
}
return ids;
}
function guardrailCheckToCommandRule(c) {
return {
guardrail_id: c.guardrail_id,
pattern: c.pattern,
action: c.action,
message: c.message
};
}
function guardrailCheckToFileRule(c) {
return {
guardrail_id: c.guardrail_id,
pattern: c.pattern,
action: c.action,
message: c.message,
...c.exclude_glob ? { exclude_glob: c.exclude_glob } : {}
};
}
function guardrailCheckToContentRule(c) {
return {
guardrail_id: c.guardrail_id,
pattern: c.pattern,
action: c.action,
message: c.message,
...c.file_glob ? { file_glob: c.file_glob } : {},
...c.exclude_glob ? { exclude_glob: c.exclude_glob } : {}
};
}
function guardrailContextToRuleData(ctx, effectiveIds) {
const filter = (checks) => effectiveIds ? checks.filter((c) => effectiveIds.has(c.guardrail_id)) : checks;
return {
commandRules: filter(ctx.commandChecks).map(guardrailCheckToCommandRule),
fileRules: filter(ctx.fileChecks).map(guardrailCheckToFileRule),
contentRules: filter(ctx.contentChecks).map(guardrailCheckToContentRule)
};
}
function buildPerAgentGuardrails(dsl, guardrailCtx) {
const perAgent = /* @__PURE__ */ new Map();
const agents = dsl.agents ?? {};
for (const agentId of Object.keys(agents)) {
const effectiveIds = getEffectiveGuardrailIds(dsl, agentId);
const rules = guardrailContextToRuleData(guardrailCtx, effectiveIds);
if (rules.commandRules.length > 0 || rules.fileRules.length > 0 || rules.contentRules.length > 0) {
perAgent.set(agentId, rules);
}
}
return perAgent;
}
function hasGuardrailRules(rules) {
return rules.commandRules.length > 0 || rules.fileRules.length > 0 || rules.contentRules.length > 0;
}
var BoundResolver = class {
cache = /* @__PURE__ */ new Map();
/**
* Resolve binding + policy in one pass. Results are cached by component id.
*/
async resolve(opts) {
const cached = this.cache.get(opts.id);
if (cached) return cached;
const diagnostics = [];
let dsl;
if (opts.embeddedDsl) {
dsl = opts.embeddedDsl;
} else if (opts.dslPath) {
const { resolve: resolveDsl } = await import("agent-contracts");
const result = await resolveDsl(opts.dslPath);
dsl = result.data;
} else {
throw new Error("BoundResolver.resolve requires embeddedDsl or dslPath");
}
if (opts.artifactBinding) {
const { resolveBound } = await import("agent-contracts");
const boundResult = await resolveBound(dsl, {
artifactBinding: opts.artifactBinding,
paths: opts.paths
});
dsl = boundResult.data;
for (const d of boundResult.diagnostics) {
diagnostics.push({
path: d.rule,
message: d.message,
severity: d.severity
});
}
}
let guardrailCtx = {
commandChecks: [],
fileChecks: [],
contentChecks: [],
allChecks: [],
hasGuardrails: false
};
if (opts.bindingPaths && opts.bindingPaths.length > 0) {
for (const bindingPath of opts.bindingPaths) {
const raw = await readFile2(bindingPath, "utf8");
const binding = YAML2.parse(raw);
guardrailCtx = buildGuardrailContext(
dsl,
binding,
opts.activeGuardrailPolicy
);
}
}
const perAgent = guardrailCtx.hasGuardrails ? buildPerAgentGuardrails(dsl, guardrailCtx) : /* @__PURE__ */ new Map();
const component = {
id: opts.id,
dsl,
registries: buildRegistriesFromDsl(dsl),
guardrails: { perAgent },
diagnostics
};
this.cache.set(opts.id, component);
return component;
}
/** Clear cached components (e.g. for testing). */
clearCache() {
this.cache.clear();
}
};
function getGuardrailRulesForAgent(component, agentId, fallbackDsl) {
const perAgent = component.guardrails.perAgent.get(agentId);
if (perAgent && hasGuardrailRules(perAgent)) {
return perAgent;
}
if (fallbackDsl) {
const raw = fallbackDsl._guardrailRules;
if (raw) {
return {
commandRules: raw.commandRules ?? [],
fileRules: raw.fileRules ?? [],
contentRules: raw.contentRules ?? []
};
}
}
return void 0;
}
// src/lib/builder.ts
function createRuntime(config) {
return {
workflow(workflowId) {
return new WorkflowBuilder(config.adapter, workflowId, config.registries);
}
};
}
var WorkflowBuilder = class {
adapter;
invocation;
registries;
constructor(adapter, workflowId, registries) {
this.adapter = adapter;
this.invocation = { workflow: workflowId };
this.registries = registries;
}
/** Set a typed handoff envelope as input. */
handoff(input) {
this.invocation = { ...this.invocation, handoff: input };
return this;
}
/** Set a simple string request (convenience shorthand). */
request(userRequest) {
this.invocation = { ...this.invocation, user_request: userRequest };
return this;
}
maxFollowUps(n) {
this.invocation = {
...this.invocation,
runtime: { ...this.invocation.runtime, maxFollowUps: n }
};
return this;
}
maxRetries(n) {
this.invocation = {
...this.invocation,
runtime: { ...this.invocation.runtime, maxRetries: n }
};
return this;
}
onStepComplete(cb) {
this.invocation = {
...this.invocation,
hooks: { ...this.invocation.hooks, onStepComplete: cb }
};
return this;
}
onGate(cb) {
this.invocation = {
...this.invocation,
hooks: { ...this.invocation.hooks, onGate: cb }
};
return this;
}
onOption