//#region extensions/crypto/src/services/plan-executor.ts
var PlanExecutor = class {
	dispatcher;
	scheduler;
	confirmCallback;
	deadLetterCallback;
	activeContexts = /* @__PURE__ */ new Map();
	constructor(opts) {
		this.dispatcher = opts.dispatcher;
		this.scheduler = opts.scheduler;
		this.confirmCallback = opts.onConfirmRequired;
		this.deadLetterCallback = opts.onDeadLetter;
	}
	/**
	* Execute a plan. Returns the execution record.
	* For scheduled plans, this is called when the scheduler fires a trigger.
	*/
	async execute(plan, executionId) {
		const ctx = {
			planId: plan.id,
			executionId,
			userId: plan.userId,
			stepResults: /* @__PURE__ */ new Map(),
			steps: [],
			cancelled: false,
			startedAt: Date.now()
		};
		this.activeContexts.set(executionId, ctx);
		try {
			await this.executeNode(plan.root, ctx);
			const execution = {
				planId: plan.id,
				executionId,
				status: ctx.cancelled ? "cancelled" : "completed",
				startedAt: ctx.startedAt,
				completedAt: Date.now(),
				steps: ctx.steps
			};
			this.scheduler.markCompleted(plan.id, execution);
			return execution;
		} catch (err) {
			const isCancelled = ctx.cancelled || err instanceof ExecutionCancelledError;
			const execution = {
				planId: plan.id,
				executionId,
				status: isCancelled ? "cancelled" : "failed",
				startedAt: ctx.startedAt,
				completedAt: Date.now(),
				steps: ctx.steps
			};
			if (isCancelled) this.scheduler.markCompleted(plan.id, execution);
			else this.scheduler.markFailed(plan.id, execution);
			return execution;
		} finally {
			this.activeContexts.delete(executionId);
			this.deleteCheckpoint(executionId);
		}
	}
	/**
	* Cancel a running execution.
	*/
	cancel(executionId) {
		const ctx = this.activeContexts.get(executionId);
		if (!ctx) return false;
		ctx.cancelled = true;
		return true;
	}
	/**
	* Get the number of currently running executions.
	*/
	get activeCount() {
		return this.activeContexts.size;
	}
	writeCheckpoint(nodeId, ctx) {
		try {
			const cp = {
				executionId: ctx.executionId,
				planId: ctx.planId,
				userId: ctx.userId,
				currentNodeId: nodeId,
				stepResults: Array.from(ctx.stepResults.entries()),
				steps: ctx.steps,
				status: "running",
				startedAt: ctx.startedAt,
				updatedAt: Date.now()
			};
			this.scheduler.saveCheckpoint(cp);
		} catch {}
	}
	deleteCheckpoint(executionId) {
		try {
			this.scheduler.deleteCheckpoint(executionId);
		} catch {}
	}
	/**
	* Resume an execution from a persisted checkpoint.
	* Returns the execution record, or null if checkpoint not found.
	*/
	async resumeFromCheckpoint(plan, executionId) {
		const cp = this.scheduler.loadCheckpoint(executionId);
		if (!cp || cp.planId !== plan.id) return null;
		const ctx = {
			planId: cp.planId,
			executionId: cp.executionId,
			userId: cp.userId,
			stepResults: new Map(cp.stepResults),
			steps: cp.steps,
			cancelled: false,
			startedAt: cp.startedAt
		};
		this.activeContexts.set(executionId, ctx);
		const resumeNode = this.findNode(plan.root, cp.currentNodeId);
		try {
			if (resumeNode) await this.executeNode(resumeNode, ctx);
			const execution = {
				planId: plan.id,
				executionId,
				status: ctx.cancelled ? "cancelled" : "completed",
				startedAt: ctx.startedAt,
				completedAt: Date.now(),
				steps: ctx.steps
			};
			this.scheduler.markCompleted(plan.id, execution);
			return execution;
		} catch (err) {
			const isCancelled = ctx.cancelled || err instanceof ExecutionCancelledError;
			const execution = {
				planId: plan.id,
				executionId,
				status: isCancelled ? "cancelled" : "failed",
				startedAt: ctx.startedAt,
				completedAt: Date.now(),
				steps: ctx.steps
			};
			if (isCancelled) this.scheduler.markCompleted(plan.id, execution);
			else this.scheduler.markFailed(plan.id, execution);
			return execution;
		} finally {
			this.activeContexts.delete(executionId);
			this.deleteCheckpoint(executionId);
		}
	}
	/**
	* Get all pending checkpoints (for resume-on-startup).
	*/
	getPendingCheckpoints() {
		return this.scheduler.loadAllCheckpoints();
	}
	/** Find a node by ID in the plan tree. */
	findNode(node, targetId) {
		if (node.id === targetId) return node;
		switch (node.type) {
			case "sequence":
				for (const child of node.steps) {
					const found = this.findNode(child, targetId);
					if (found) return found;
				}
				return null;
			case "parallel":
				for (const child of node.steps) {
					const found = this.findNode(child, targetId);
					if (found) return found;
				}
				return null;
			case "if":
				if (node.then) {
					const found = this.findNode(node.then, targetId);
					if (found) return found;
				}
				if (node.else) {
					const found = this.findNode(node.else, targetId);
					if (found) return found;
				}
				return null;
			case "loop": {
				const found = this.findNode(node.body, targetId);
				if (found) return found;
				return null;
			}
			case "action":
				if (node.onError) {
					const found = this.findNode(node.onError, targetId);
					if (found) return found;
				}
				return null;
			default: return null;
		}
	}
	async executeNode(node, ctx) {
		if (ctx.cancelled) throw new ExecutionCancelledError();
		this.writeCheckpoint(node.id, ctx);
		switch (node.type) {
			case "action": return this.executeAction(node, ctx);
			case "sequence": return this.executeSequence(node, ctx);
			case "parallel": return this.executeParallel(node, ctx);
			case "if": return this.executeIf(node, ctx);
			case "wait": return this.executeWait(node, ctx);
			case "loop": return this.executeLoop(node, ctx);
		}
	}
	async executeAction(node, ctx) {
		const step = {
			nodeId: node.id,
			status: "running",
			startedAt: Date.now(),
			retryCount: 0
		};
		ctx.steps.push(step);
		const resolvedParams = await this.resolveParams(node.params, ctx);
		if (node.requireConfirmation && this.confirmCallback) {
			if (!await this.confirmCallback(node, resolvedParams, ctx.userId)) {
				step.status = "skipped";
				step.completedAt = Date.now();
				step.error = "User declined confirmation";
				return;
			}
		}
		if (!this.dispatcher.exists(node.tool)) {
			step.status = "failed";
			step.completedAt = Date.now();
			step.error = `Tool "${node.tool}" not found`;
			await this.handleFailure(node, step, ctx);
			return;
		}
		const policy = node.onFailure ?? { strategy: "abort" };
		const maxAttempts = policy.strategy === "retry" ? policy.maxAttempts : 1;
		const baseDelay = policy.strategy === "retry" ? policy.delayMs : 0;
		const multiplier = policy.strategy === "retry" ? policy.backoffMultiplier ?? 2 : 1;
		for (let attempt = 0; attempt < maxAttempts; attempt++) {
			if (ctx.cancelled) throw new ExecutionCancelledError();
			try {
				const result = node.timeoutMs ? await withTimeout(this.dispatcher.call(node.tool, resolvedParams, ctx.userId), node.timeoutMs) : await this.dispatcher.call(node.tool, resolvedParams, ctx.userId);
				step.status = "completed";
				step.completedAt = Date.now();
				step.result = result;
				step.retryCount = attempt;
				ctx.stepResults.set(node.id, result);
				return;
			} catch (err) {
				step.retryCount = attempt + 1;
				step.error = err.message ?? String(err);
				if (attempt < maxAttempts - 1) {
					await sleep(baseDelay * Math.pow(multiplier, attempt));
					continue;
				}
				step.status = "failed";
				step.completedAt = Date.now();
				if (node.onError) try {
					await this.executeNode(node.onError, ctx);
					return;
				} catch (fallbackErr) {
					step.error = `${step.error} | fallback failed: ${fallbackErr.message ?? String(fallbackErr)}`;
				}
				this.writeDeadLetter(node, step, resolvedParams, ctx);
				await this.handleFailure(node, step, ctx);
				return;
			}
		}
	}
	async executeSequence(node, ctx) {
		for (const child of node.steps) {
			if (ctx.cancelled) throw new ExecutionCancelledError();
			await this.executeNode(child, ctx);
		}
	}
	async executeParallel(node, ctx) {
		const results = await Promise.allSettled(node.steps.map((child) => this.executeNode(child, ctx)));
		if (!node.allowPartialFailure) {
			const failed = results.filter((r) => r.status === "rejected");
			if (failed.length > 0) throw failed[0].reason;
		}
	}
	async executeIf(node, ctx) {
		if (await this.evaluateCondition(node.condition, ctx)) await this.executeNode(node.then, ctx);
		else if (node.else) await this.executeNode(node.else, ctx);
	}
	async executeWait(node, ctx) {
		const step = {
			nodeId: node.id,
			status: "waiting",
			startedAt: Date.now()
		};
		ctx.steps.push(step);
		try {
			if (node.durationMs && !node.until && !node.untilTime) {
				await this.waitDuration(node.durationMs, ctx);
				step.status = "completed";
				step.completedAt = Date.now();
				return;
			}
			if (node.untilTime && !node.until) {
				const remaining = new Date(node.untilTime).getTime() - Date.now();
				if (remaining > 0) await this.waitDuration(remaining, ctx);
				step.status = "completed";
				step.completedAt = Date.now();
				return;
			}
			if (node.until) {
				const pollMs = node.pollIntervalMs ?? 6e4;
				const maxWait = node.maxWaitMs ?? 864e5;
				const deadline = Date.now() + maxWait;
				while (Date.now() < deadline) {
					if (ctx.cancelled) throw new ExecutionCancelledError();
					if (await this.evaluateCondition(node.until, ctx)) {
						step.status = "completed";
						step.completedAt = Date.now();
						return;
					}
					await this.waitDuration(Math.min(pollMs, deadline - Date.now()), ctx);
				}
				step.status = "failed";
				step.completedAt = Date.now();
				step.error = `Wait timed out after ${maxWait}ms`;
				await this.handleFailure({
					...node,
					type: "wait",
					id: node.id,
					label: node.label
				}, step, ctx);
			}
		} catch (err) {
			if (err instanceof ExecutionCancelledError) throw err;
			step.status = "failed";
			step.completedAt = Date.now();
			step.error = err.message ?? String(err);
		}
	}
	async executeLoop(node, ctx) {
		for (let i = 0; i < node.maxIterations; i++) {
			if (ctx.cancelled) throw new ExecutionCancelledError();
			if (node.exitWhen) {
				if (await this.evaluateCondition(node.exitWhen, ctx)) return;
			}
			await this.executeNode(node.body, ctx);
			if (node.delayMs && i < node.maxIterations - 1) await this.waitDuration(node.delayMs, ctx);
		}
	}
	async evaluateCondition(cond, ctx) {
		if (cond.type === "compare") {
			const left = await this.resolveConditionValue(cond.left, ctx);
			const right = await this.resolveConditionValue(cond.right, ctx);
			return this.compare(left, cond.op, right);
		} else if (cond.type === "logic") switch (cond.op) {
			case "and":
				for (const sub of cond.conditions) if (!await this.evaluateCondition(sub, ctx)) return false;
				return true;
			case "or":
				for (const sub of cond.conditions) if (await this.evaluateCondition(sub, ctx)) return true;
				return false;
			case "not": return !await this.evaluateCondition(cond.conditions[0], ctx);
		}
		return false;
	}
	async resolveConditionValue(ref, ctx) {
		switch (ref.type) {
			case "literal": return typeof ref.value === "number" ? ref.value : parseFloat(String(ref.value)) || 0;
			case "step_output": {
				const result = ctx.stepResults.get(ref.stepId);
				if (result === void 0) return 0;
				const val = getNestedValue(result, ref.path);
				return typeof val === "number" ? val : parseFloat(String(val)) || 0;
			}
			case "env": {
				const v = process.env[ref.key];
				return v ? parseFloat(v) || 0 : 0;
			}
			case "runtime": return this.scheduler.resolveValue(ref);
		}
	}
	compare(left, op, right) {
		switch (op) {
			case "gt": return left > right;
			case "gte": return left >= right;
			case "lt": return left < right;
			case "lte": return left <= right;
			case "eq": return Math.abs(left - right) < Number.EPSILON;
			case "neq": return Math.abs(left - right) >= Number.EPSILON;
			default: return false;
		}
	}
	async resolveParams(params, ctx) {
		const resolved = {};
		for (const [key, val] of Object.entries(params)) resolved[key] = await this.resolveValue(val, ctx);
		return resolved;
	}
	async resolveValue(ref, ctx) {
		if (typeof ref !== "object" || ref === null) return ref;
		switch (ref.type) {
			case "literal": return ref.value;
			case "step_output": {
				const result = ctx.stepResults.get(ref.stepId);
				if (result === void 0) throw new Error(`Step "${ref.stepId}" has no result yet (referenced by path "${ref.path}").`);
				return getNestedValue(result, ref.path);
			}
			case "env":
				if (!new Set([
					"NODE_ENV",
					"CLAWNCHER_NETWORK",
					"CLAWNCHER_API_URL",
					"CHAIN_ID",
					"DEFAULT_SLIPPAGE_BPS",
					"LOG_LEVEL"
				]).has(ref.key)) throw new Error(`Plan env ref "${ref.key}" is not in the allowed env var list.`);
				return process.env[ref.key] ?? void 0;
			case "runtime": return this.scheduler.resolveValue(ref);
		}
	}
	writeDeadLetter(node, step, resolvedParams, ctx) {
		if (!this.deadLetterCallback) return;
		try {
			this.deadLetterCallback({
				planId: ctx.planId,
				nodeId: node.id,
				executionId: ctx.executionId,
				userId: ctx.userId,
				error: step.error ?? "Unknown error",
				retryCount: step.retryCount ?? 0,
				tool: node.tool,
				params: resolvedParams,
				timestamp: Date.now()
			});
		} catch {}
	}
	async handleFailure(node, step, ctx) {
		const policy = node.onFailure ?? { strategy: "abort" };
		switch (policy.strategy) {
			case "abort": throw new StepFailedError(node.id, step.error ?? "Unknown error");
			case "skip":
				step.status = "skipped";
				return;
			case "retry": throw new StepFailedError(node.id, `All ${policy.maxAttempts} retry attempts exhausted: ${step.error}`);
		}
	}
	/** Wait for a duration, checking for cancellation periodically. */
	async waitDuration(ms, ctx) {
		const checkInterval = 1e3;
		let remaining = ms;
		while (remaining > 0) {
			if (ctx.cancelled) throw new ExecutionCancelledError();
			const wait = Math.min(remaining, checkInterval);
			await sleep(wait);
			remaining -= wait;
		}
	}
};
var ExecutionCancelledError = class extends Error {
	constructor() {
		super("Plan execution was cancelled");
		this.name = "ExecutionCancelledError";
	}
};
var StepFailedError = class extends Error {
	stepId;
	constructor(stepId, message) {
		super(`Step "${stepId}" failed: ${message}`);
		this.name = "StepFailedError";
		this.stepId = stepId;
	}
};
function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
function withTimeout(promise, ms) {
	return new Promise((resolve, reject) => {
		const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${ms}ms`)), ms);
		promise.then((val) => {
			clearTimeout(timer);
			resolve(val);
		}, (err) => {
			clearTimeout(timer);
			reject(err);
		});
	});
}
/**
* Access a nested value by dot-path. E.g., "result.amountOut" on { result: { amountOut: 100 } }
* returns 100.
*/
function getNestedValue(obj, path) {
	const parts = path.split(".");
	let current = obj;
	for (const part of parts) {
		if (current === null || current === void 0) return void 0;
		if (typeof current === "object") current = current[part];
		else return;
	}
	return current;
}
function formatExecutionSummary(exec, plan) {
	const lines = [
		`**Plan: ${plan.name}**`,
		`Status: ${{
			completed: "Done",
			failed: "FAILED",
			cancelled: "Cancelled",
			running: "Running"
		}[exec.status] ?? exec.status}`,
		`Duration: ${((exec.completedAt ?? Date.now()) - exec.startedAt) / 1e3}s`,
		""
	];
	for (const step of exec.steps) {
		const icon = {
			completed: "[OK]",
			failed: "[FAIL]",
			skipped: "[SKIP]",
			waiting: "[WAIT]",
			running: "[...]",
			pending: "[ ]"
		}[step.status] ?? "[ ]";
		const duration = step.startedAt && step.completedAt ? ` (${((step.completedAt - step.startedAt) / 1e3).toFixed(1)}s)` : "";
		const error = step.error ? ` — ${step.error}` : "";
		const retries = step.retryCount && step.retryCount > 0 ? ` (${step.retryCount} retries)` : "";
		lines.push(`${icon} ${step.nodeId}${duration}${retries}${error}`);
	}
	return lines.join("\n");
}
//#endregion
export { ExecutionCancelledError, PlanExecutor, StepFailedError, formatExecutionSummary };

//# sourceMappingURL=plan-executor.mjs.map