agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
1,405 lines (1,364 loc) • 48.3 kB
JavaScript
import {
BoundResolver,
getGuardrailRulesForAgent
} from "./chunk-MWZXU25C.js";
import {
createGuardrailHooksFromRules,
pluginRegistry
} from "./chunk-MGRZBGQL.js";
import {
buildGuardrailRulesFromDsl,
loadDslContext
} from "./chunk-D2QTXQB2.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/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;
}
onOptionalStep(cb) {
this.invocation = {
...this.invocation,
hooks: { ...this.invocation.hooks, onOptionalStep: cb }
};
return this;
}
/** Set progress output sink for real-time execution visibility. */
progressOutput(sink) {
this.invocation = { ...this.invocation, progressOutput: sink };
return this;
}
async run() {
return runWorkflow(this.adapter, this.invocation, this.registries);
}
};
// src/lib/plugin-loader.ts
import { existsSync } from "fs";
import { readdir } from "fs/promises";
import { resolve, join } from "path";
var PLUGIN_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"];
function isPluginLike(obj) {
return typeof obj === "object" && obj !== null && typeof obj.id === "string";
}
async function scanDirectory(dir) {
if (!existsSync(dir)) return [];
const entries = await readdir(dir, { withFileTypes: true });
return entries.filter(
(e) => e.isFile() && PLUGIN_EXTENSIONS.some((ext) => e.name.endsWith(ext)) && !e.name.startsWith("_") && !e.name.endsWith(".d.ts") && !e.name.endsWith(".test.ts") && !e.name.endsWith(".spec.ts")
).map((e) => resolve(dir, e.name));
}
async function loadPluginModule(modulePath) {
const mod = await import(new URL(`file://${modulePath}`).href);
let count = 0;
for (const exportValue of Object.values(mod)) {
if (isPluginLike(exportValue)) {
try {
pluginRegistry.register(exportValue);
count++;
} catch {
}
}
}
return count;
}
async function loadPluginsFromPaths(paths, baseDir) {
const result = { loaded: 0, files: [], errors: [] };
for (const p of paths) {
const absPath = resolve(baseDir, p);
if (!existsSync(absPath)) {
result.errors.push({ file: p, error: `File not found: ${absPath}` });
continue;
}
try {
const count = await loadPluginModule(absPath);
result.loaded += count;
result.files.push(absPath);
} catch (err) {
result.errors.push({
file: p,
error: err.message
});
}
}
return result;
}
async function loadPluginsFromDirectory(dir) {
const result = { loaded: 0, files: [], errors: [] };
const candidates = await scanDirectory(dir);
for (const filePath of candidates) {
try {
const count = await loadPluginModule(filePath);
result.loaded += count;
result.files.push(filePath);
} catch (err) {
result.errors.push({
file: filePath,
error: err.message
});
}
}
return result;
}
async function loadPlugins(options) {
if (options.pluginPaths && options.pluginPaths.length > 0) {
return loadPluginsFromPaths(options.pluginPaths, options.projectDir);
}
const conventionDir = join(options.projectDir, "src", "plugins");
return loadPluginsFromDirectory(conventionDir);
}
// src/lib/progress-sink.ts
import { openSync, writeSync, closeSync, mkdirSync } from "fs";
import { dirname, join as join2, basename, extname } from "path";
function resolveFilePath(base, naming, label) {
if (naming === "single") return base;
const dir = dirname(base);
const ext = extname(base);
const stem = basename(base, ext);
const now = /* @__PURE__ */ new Date();
if (naming === "daily") {
const date = now.toISOString().slice(0, 10);
return join2(dir, `${stem}-${date}${ext}`);
}
const pad = (n) => String(n).padStart(2, "0");
const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}T${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const suffix = label ? `-${label}` : "";
return join2(dir, `${stem}-${ts}${suffix}${ext}`);
}
function createProgressSink(options) {
const useStderr = options.stderr ?? false;
let fd = null;
let resolvedPath;
if (options.file) {
const naming = options.naming ?? "single";
resolvedPath = resolveFilePath(options.file, naming, options.label);
mkdirSync(dirname(resolvedPath), { recursive: true });
fd = openSync(resolvedPath, "a");
}
if (!useStderr && fd === null) {
return { write() {
}, close() {
} };
}
return {
write(chunk) {
if (useStderr) process.stderr.write(chunk);
if (fd !== null) writeSync(fd, chunk);
},
close() {
if (fd !== null) {
closeSync(fd);
fd = null;
}
},
get filePath() {
return resolvedPath;
}
};
}
// src/lib/memory-store.ts
var InMemoryStore = class {
data = /* @__PURE__ */ new Map();
async save(ref, content) {
this.data.set(ref.id, content);
}
async load(ref) {
return this.data.get(ref.id) ?? null;
}
async delete(ref) {
this.data.delete(ref.id);
}
};
// src/lib/adapter-factory.ts
var ADAPTER_PACKAGES = {
claude: "@anthropic-ai/claude-agent-sdk",
gemini: "@google/adk",
openai: "@openai/agents"
};
async function createAdapter(name, options) {
const { model, cwd, tools, permissionMode, guardrailHooks } = options ?? {};
switch (name) {
case "mock": {
const { MockAdapter } = await import("./adapters/mock.js");
return new MockAdapter();
}
case "claude": {
try {
const { ClaudeAgentSdkAdapter } = await import("./adapters/claude-agent-sdk.js");
return new ClaudeAgentSdkAdapter({
guardrailHooks,
...model ? { model } : {},
...cwd ? { cwd } : {},
...tools ? { tools } : {},
...permissionMode ? { permissionMode } : {}
});
} catch (err) {
throwMissingPackageError("claude", err);
}
}
case "gemini": {
try {
const { AdkSdkAdapter } = await import("./adapters/adk-sdk.js");
return new AdkSdkAdapter({
guardrailHooks,
...model ? { model } : {}
});
} catch (err) {
throwMissingPackageError("gemini", err);
}
}
case "openai": {
try {
const { OpenAIAgentsSdkAdapter } = await import("./adapters/openai-agents-sdk.js");
return new OpenAIAgentsSdkAdapter({
guardrailHooks,
...model ? { model } : {}
});
} catch (err) {
throwMissingPackageError("openai", err);
}
}
default:
throw new Error(
`Unknown adapter: "${name}". Available: mock, claude, openai, gemini`
);
}
}
function throwMissingPackageError(adapter, err) {
const cause = err;
if (cause?.message?.includes("Cannot find package")) {
const pkg = ADAPTER_PACKAGES[adapter];
throw new Error(
`Adapter "${adapter}" requires package "${pkg}" which is not installed.
Install it with: npm install ${pkg}`
);
}
throw cause;
}
// src/lib/execute.ts
import { resolve as resolve2 } from "path";
async function resolveExecutionContext(options, requiredEntities) {
const useBoundResolver = options.resolvedComponent || options.bindingPaths && options.bindingPaths.length > 0 || options.artifactBinding;
if (useBoundResolver) {
const component = options.resolvedComponent ?? await new BoundResolver().resolve({
id: options.componentId ?? "default",
embeddedDsl: options.dsl,
bindingPaths: options.bindingPaths,
activeGuardrailPolicy: options.activeGuardrailPolicy,
artifactBinding: options.artifactBinding,
paths: options.paths
});
if (requiredEntities) {
const ctx2 = await loadDslContext({
embeddedDsl: component.dsl,
requiredEntities
});
return { registries: ctx2.registries, dsl: component.dsl, resolvedComponent: component };
}
return {
registries: component.registries,
dsl: component.dsl,
resolvedComponent: component
};
}
const ctx = await loadDslContext({
embeddedDsl: options.dsl,
projectDslPath: options.projectDslPath,
requiredEntities
});
return { registries: ctx.registries, dsl: ctx.dsl };
}
function injectGuardrailHooks(adapterOptions, execCtx, targetAgentId) {
if (adapterOptions.guardrailHooks) return;
if (execCtx.resolvedComponent && targetAgentId) {
const rules2 = getGuardrailRulesForAgent(
execCtx.resolvedComponent,
targetAgentId,
execCtx.dsl
);
if (rules2 && (rules2.commandRules.length > 0 || rules2.fileRules.length > 0 || rules2.contentRules.length > 0)) {
adapterOptions.guardrailHooks = createGuardrailHooksFromRules(rules2);
}
return;
}
const rules = buildGuardrailRulesFromDsl(execCtx.dsl);
if (rules && (rules.commandRules.length > 0 || rules.fileRules.length > 0 || rules.contentRules.length > 0)) {
adapterOptions.guardrailHooks = createGuardrailHooksFromRules(rules);
}
}
async function executeTask(taskId, options) {
const execCtx = await resolveExecutionContext(options, { tasks: [taskId] });
const targetAgentId = execCtx.registries.taskRegistry?.[taskId]?.target_agent;
const adapterOptions = { ...options.adapterOptions };
injectGuardrailHooks(adapterOptions, execCtx, targetAgentId);
const adapter = await createAdapter(options.adapter, {
model: options.model,
...adapterOptions
});
const progressSink = createProgressSink(
options.logFile ? { stderr: true, file: resolve2(options.logFile), naming: "single" } : { stderr: true }
);
try {
if (options.hooks?.beforeTask) {
await options.hooks.beforeTask({ adapter, registries: execCtx.registries, taskId });
}
const result = await runTask(adapter, taskId, { user_request: options.request }, {
maxFollowUps: options.maxFollowUps ?? 2,
maxRetries: options.maxRetries ?? 0,
progressOutput: progressSink,
...execCtx.registries
});
if (options.hooks?.afterTask) {
await options.hooks.afterTask(result);
}
return result;
} finally {
progressSink.close();
}
}
async function executeWorkflow(workflowId, options) {
const execCtx = await resolveExecutionContext(options, { workflows: [workflowId] });
const adapterOptions = { ...options.adapterOptions };
injectGuardrailHooks(adapterOptions, execCtx);
const adapter = await createAdapter(options.adapter, {
model: options.model,
...adapterOptions
});
const progressSink = createProgressSink(
options.logFile ? { stderr: true, file: resolve2(options.logFile), naming: "single" } : { stderr: true }
);
try {
if (options.hooks?.beforeWorkflow) {
await options.hooks.beforeWorkflow({ adapter, registries: execCtx.registries, workflowId });
}
const invocation = {
workflow: workflowId,
user_request: options.request,
handoff: options.handoff,
progressOutput: progressSink,
runtime: {
maxFollowUps: options.maxFollowUps ?? 2,
maxRetries: options.maxRetries ?? 0
},
hooks: {
onStepComplete: options.hooks?.onStepComplete,
onGate: options.hooks?.onGate ?? (options.autoApproveGates ? async () => true : void 0),
onOptionalStep: options.hooks?.onOptionalStep
},
context: options.context
};
const result = await runWorkflow(adapter, invocation, execCtx.registries);
if (options.hooks?.afterWorkflow) {
await options.hooks.afterWorkflow(result);
}
return result;
} finally {
progressSink.close();
}
}
export {
renderAgentSystemPrompt,
buildCandidateAgents,
zodSchemaToPromptDescription,
zodSchemaToYamlExample,
buildSplitTaskPrompt,
buildTaskPrompt,
extractStructuredResult,
buildRetryFollowUp,
runTask,
runWorkflow,
buildGateEvaluationPrompt,
parseGateEvaluationResponse,
createRuntime,
WorkflowBuilder,
loadPluginsFromPaths,
loadPluginsFromDirectory,
loadPlugins,
createProgressSink,
InMemoryStore,
createAdapter,
executeTask,
executeWorkflow
};
//# sourceMappingURL=chunk-5UGCLWDM.js.map