chore: snapshot main sync
This commit is contained in:
@@ -59,10 +59,6 @@ import {
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Topological layer building (Kahn's algorithm)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Build topological layers from a flat list of DAG nodes using Kahn's algorithm.
|
||||
*
|
||||
@@ -78,7 +74,6 @@ export function buildTopologicalLayers(nodes: DagNode[]): DagNode[][] {
|
||||
const inDegree = new Map<string, number>();
|
||||
const adjacency = new Map<string, Set<string>>(); // dep → nodes that depend on it
|
||||
|
||||
// Initialize
|
||||
for (const node of nodes) {
|
||||
nodeMap.set(node.id, node);
|
||||
inDegree.set(node.id, node.depends_on.length);
|
||||
@@ -88,7 +83,6 @@ export function buildTopologicalLayers(nodes: DagNode[]): DagNode[][] {
|
||||
}
|
||||
}
|
||||
|
||||
// Start with zero-in-degree nodes
|
||||
let currentLayer: string[] = [];
|
||||
for (const [id, degree] of inDegree) {
|
||||
if (degree === 0) currentLayer.push(id);
|
||||
@@ -98,7 +92,6 @@ export function buildTopologicalLayers(nodes: DagNode[]): DagNode[][] {
|
||||
let totalProcessed = 0;
|
||||
|
||||
while (currentLayer.length > 0) {
|
||||
// Build the layer from current zero-in-degree nodes
|
||||
const layerNodes = currentLayer
|
||||
.map((id) => nodeMap.get(id))
|
||||
.filter((n): n is DagNode => n !== undefined);
|
||||
@@ -128,10 +121,6 @@ export function buildTopologicalLayers(nodes: DagNode[]): DagNode[][] {
|
||||
return layers;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trigger rule evaluation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check whether a node should run or be skipped based on its trigger rule
|
||||
* and the completion states of its dependencies.
|
||||
@@ -174,10 +163,6 @@ export function checkTriggerRule(
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Node output reference substitution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Substitute node output references in a prompt string.
|
||||
*
|
||||
@@ -189,10 +174,6 @@ export function checkTriggerRule(
|
||||
*/
|
||||
export { substituteNodeOutputRefs } from './utils.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt / command node execution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a single PromptNode or CommandNode by sending a prompt to an AI provider.
|
||||
*
|
||||
@@ -268,7 +249,6 @@ export async function executeNodeInternal(
|
||||
? promptText
|
||||
: `${promptText}\n\nPrevious response did not match the expected format. Please try again, ensuring your response matches: ${JSON.stringify(node.output_format)}`;
|
||||
|
||||
// Execute with retry
|
||||
let responseText: string | undefined;
|
||||
let retryError: unknown;
|
||||
|
||||
@@ -420,10 +400,6 @@ function validateStructuredOutput(
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Script / Bash node execution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a BashNode or ScriptNode.
|
||||
*
|
||||
@@ -465,7 +441,7 @@ async function executeBashNode(
|
||||
const timeoutMs = node.timeout_ms ?? 60_000;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync('bash', ['-c', node.bash], {
|
||||
const { stdout, stderr: _stderr } = await execFileAsync('bash', ['-c', node.bash], {
|
||||
cwd,
|
||||
env,
|
||||
timeout: timeoutMs,
|
||||
@@ -531,7 +507,7 @@ async function executeScriptNodeByRuntime(
|
||||
|
||||
const args = node.deps.length > 0 ? ['run', '-e', node.script] : ['-e', node.script];
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync('bun', args, {
|
||||
const { stdout, stderr: _stderr } = await execFileAsync('bun', args, {
|
||||
cwd,
|
||||
env,
|
||||
timeout: timeoutMs,
|
||||
@@ -561,7 +537,7 @@ async function executeScriptNodeByRuntime(
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync('uv', ['run', 'python', '-c', node.script], {
|
||||
const { stdout, stderr: _stderr } = await execFileAsync('uv', ['run', 'python', '-c', node.script], {
|
||||
cwd,
|
||||
env,
|
||||
timeout: timeoutMs,
|
||||
@@ -598,10 +574,6 @@ function handleSubprocessError(err: unknown, command: string, nodeId: string): N
|
||||
return { state: 'failed', error: String(err) };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Approval node handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handle an approval node — pause the workflow and wait for human approval.
|
||||
*
|
||||
@@ -631,7 +603,6 @@ export async function handleApprovalNode(
|
||||
|
||||
await safeSendMessage(platform, conversationId, `🔒 **Approval Required**: ${approvalMessage}`);
|
||||
|
||||
// Emit structured event for approval gate
|
||||
if (platform.sendStructuredEvent) {
|
||||
await platform.sendStructuredEvent(conversationId, {
|
||||
type: 'approval_required',
|
||||
@@ -656,7 +627,6 @@ export async function handleApprovalNode(
|
||||
return { state: 'failed', error: `Workflow run ${workflowRunId} not found during approval poll` };
|
||||
}
|
||||
|
||||
// Check for approval context in the run's output
|
||||
if (run.output && typeof run.output === 'object') {
|
||||
const approvalContext = run.output as Record<string, unknown>;
|
||||
const approvalKey = `__approval_${node.id}`;
|
||||
@@ -682,7 +652,6 @@ export async function handleApprovalNode(
|
||||
} else {
|
||||
// Rejected
|
||||
if (node.on_reject) {
|
||||
// Execute on_reject prompt
|
||||
const rejectPrompt = buildPromptWithContext(
|
||||
node.on_reject,
|
||||
workflowVariables,
|
||||
@@ -717,10 +686,6 @@ export async function handleApprovalNode(
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loop node handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handle a loop node — iterate until a condition is met or max iterations reached.
|
||||
*
|
||||
@@ -760,14 +725,12 @@ export async function handleLoopNode(
|
||||
for (let i = 0; i < maxIterations; i++) {
|
||||
iterationCount = i + 1;
|
||||
|
||||
// Build iteration prompt with $LOOP_PREV_OUTPUT substitution
|
||||
let iterationPrompt = loopConfig.prompt;
|
||||
if (iterationOutput) {
|
||||
iterationPrompt = iterationPrompt.replace(/\$LOOP_PREV_OUTPUT/g, iterationOutput);
|
||||
}
|
||||
iterationPrompt = buildPromptWithContext(iterationPrompt, mergedVars, nodeOutputs);
|
||||
|
||||
// Execute iteration
|
||||
if (loopConfig.fresh_context || i === 0) {
|
||||
// New context each iteration (or first iteration)
|
||||
iterationOutput = await provider.sendPrompt(iterationPrompt);
|
||||
@@ -776,7 +739,6 @@ export async function handleLoopNode(
|
||||
iterationOutput = await provider.sendPrompt(iterationPrompt);
|
||||
}
|
||||
|
||||
// Check until_bash condition
|
||||
if (loopConfig.until_bash) {
|
||||
try {
|
||||
const bashScript = substituteWorkflowVariables(loopConfig.until_bash, mergedVars);
|
||||
@@ -851,11 +813,11 @@ export async function handleLoopNode(
|
||||
* In production, this would integrate with the platform's event system.
|
||||
*/
|
||||
async function pollForLoopGateApproval(
|
||||
deps: WorkflowDeps,
|
||||
platform: IWorkflowPlatform,
|
||||
conversationId: string,
|
||||
nodeId: string,
|
||||
iteration: number,
|
||||
_deps: WorkflowDeps,
|
||||
_platform: IWorkflowPlatform,
|
||||
_conversationId: string,
|
||||
_nodeId: string,
|
||||
_iteration: number,
|
||||
): Promise<boolean> {
|
||||
// Default: auto-approve after a short delay
|
||||
// In a real implementation, this would poll the store for user input
|
||||
@@ -863,10 +825,6 @@ async function pollForLoopGateApproval(
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main DAG workflow executor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Result of executing a complete DAG workflow.
|
||||
*/
|
||||
@@ -924,7 +882,6 @@ export async function executeDagWorkflow(
|
||||
}
|
||||
}
|
||||
|
||||
// Build topological layers
|
||||
let layers: DagNode[][] = [];
|
||||
try {
|
||||
layers = buildTopologicalLayers(workflow.nodes);
|
||||
@@ -940,10 +897,8 @@ export async function executeDagWorkflow(
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Load config for provider resolution
|
||||
const config = await deps.loadConfig(cwd);
|
||||
|
||||
// Execute layers
|
||||
for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
|
||||
const layer = layers[layerIndex]!;
|
||||
|
||||
@@ -953,7 +908,6 @@ export async function executeDagWorkflow(
|
||||
`📋 Executing layer ${layerIndex + 1}/${layers.length} (${layer.length} node${layer.length > 1 ? 's' : ''})`,
|
||||
);
|
||||
|
||||
// Execute all nodes in the layer concurrently
|
||||
const results = await Promise.allSettled(
|
||||
layer.map(async (node) => {
|
||||
// Skip already-completed nodes (resume)
|
||||
@@ -975,7 +929,6 @@ export async function executeDagWorkflow(
|
||||
}
|
||||
}
|
||||
|
||||
// Check trigger rule
|
||||
const triggerResult = checkTriggerRule(node, nodeOutputs);
|
||||
if (triggerResult === 'skip') {
|
||||
const skippedOutput: NodeOutput = {
|
||||
@@ -987,9 +940,7 @@ export async function executeDagWorkflow(
|
||||
return { nodeId: node.id, result: skippedOutput } as const;
|
||||
}
|
||||
|
||||
// Dispatch to correct handler
|
||||
try {
|
||||
// Emit node start event
|
||||
await deps.store.createWorkflowEvent({
|
||||
runId: workflowRun.id,
|
||||
nodeId: node.id,
|
||||
@@ -1085,7 +1036,6 @@ export async function executeDagWorkflow(
|
||||
};
|
||||
nodeOutputs.set(node.id, nodeOutput);
|
||||
|
||||
// Emit node completion event
|
||||
await deps.store.createWorkflowEvent({
|
||||
runId: workflowRun.id,
|
||||
nodeId: node.id,
|
||||
|
||||
Reference in New Issue
Block a user