UNPKG

agent-contracts-runtime

Version:

Runtime bridge for executing agent-contracts workflows on Agent SDKs

1,405 lines (1,364 loc) 48.3 kB
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