UNPKG

agent-contracts-runtime

Version:

Runtime bridge for executing agent-contracts workflows on Agent SDKs

1,447 lines (1,407 loc) 61.9 kB
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