/**
 * ============================================================
 *  plugins/workflow-runner/src/recovery.ts —— 节点失败的自适应恢复
 * ------------------------------------------------------------
 *  「确定性工作流 × 随机应变 agent」的结合点（onError:'recover'）。
 *
 *  传统 DAG 的脆点：某节点抛错 → 整条 halt（或 onError:'continue' 盲跳，下游
 *  capture 拿 undefined 静默级联错）。当节点声明 onError:'recover' 时，runner
 *  不立刻 halt，而是调本模块起一个**有界恢复 agent**（复用 miniReAct），喂给它
 *  「工作流总目标 + 失败节点的意图 + 错误信息 + 当前已知变量 + 下游 capture 契约」，
 *  让它**随机应变去达成该节点本来要的结果**（换路径 / 换命令 / 先读后写…）。
 *
 *  —— 共享大脑 ——
 *  恢复 agent 与 agent: 节点（stepExecutors/agent.ts）本质同构，故系统提示词纪律与
 *  验证闸门都收敛在 agentCommon.ts：本模块只贴一句「救火」角色开场白（RECOVER_OPENING），
 *  跑完后交给 interpretMiniReActResult 判定「真救回 / 放弃」——绝不把没验证过的产出当成功
 *  绑给下游。返回 AgentOutcome：
 *    - { ok:true, value }  —— runner 用同一个 step.capture 绑定后继续 happy path
 *    - { ok:false, reason } —— runner 带原因 honest halt
 *
 *  设计取舍：复用 miniReAct 而非 runQuery —— 恢复要的正是「有界、白名单、不污染主
 *  history、直接返回 StepResult」；默认全量 ALL_TOOLS（与 skill/agent 节点同等授权、
 *  同一信任边界），step.allowed_tools 可收窄。本函数是 AsyncGenerator，yield*
 *  runMiniReAct 把恢复 agent 的内层思考透传给 UI；warning/plugin_progress/halt 仍由
 *  runner 在调用前后发（事件卫生集中在 runner）。
 * ============================================================
 */
import { ALL_TOOLS } from '../../../src/plugin-sdk.js';
import { buildWorkflowAgentSystem, describeCaptureContract, interpretMiniReActResult, truncate, } from './agentCommon.js';
import { interpolate, interpolateDeep } from './expressions.js';
import { runMiniReAct } from './miniReAct.js';
const RECOVER_OPENING = `你是一名资深的自动化工程师 / SRE，被一个确定性工作流临时调来「救火」。
工作流里一个原本写死的步骤刚刚执行失败了，现在交给你随机应变——先看错误信息诊断根因，再想办法真正达成这个步骤**本来要的结果**（关注它要的结果，别拘泥它原先怎么写）。`;
const RECOVERY_SYSTEM = buildWorkflowAgentSystem(RECOVER_OPENING);
/** 描述失败节点「本来要干什么」，喂给恢复 agent 作为意图说明。 */
function describeStepIntent(step, vars) {
    if (step.tool) {
        const args = interpolateDeep(step.args ?? {}, vars);
        return `调用工具 ${step.tool}，参数：${JSON.stringify(args)}`;
    }
    if (step.agent) {
        return `自主完成这个开放任务：${interpolate(step.agent, vars)}`;
    }
    if (step.llm) {
        return `用 LLM 生成内容，提示词：${interpolate(step.llm, vars)}`;
    }
    if (step.skill) {
        const input = step.input ? interpolate(step.input, vars) : '(无显式输入)';
        return `执行 skill「${interpolate(step.skill, vars)}」，输入：${input}`;
    }
    if (step.type === 'assert') {
        return `让这个断言成立：${step.condition}`;
    }
    return `完成步骤 ${step.id}`;
}
/**
 * 尝试自适应恢复一个失败的动作 step。
 *   - 救回 → { ok:true, value }（value.raw 已 normalize，可被原 step.capture 绑定）
 *   - 放弃 / 不可信 / 子循环抛错（含用户中断）→ { ok:false, reason }
 *     （中断的最终语义由调用方在拿到结果后另行检测 ctx.signal 决定）
 */
export async function* runRecovery(step, vars, ctx, errorMsg) {
    const intent = describeStepIntent(step, vars);
    const knownVars = truncate(JSON.stringify(vars.snapshot()), 1500);
    const goalLine = ctx.goal ? `工作流总目标：${ctx.goal}\n\n` : '';
    const captureLine = describeCaptureContract(step);
    const hintLine = step.recover_hint
        ? `\n\n作者给恢复的提示：${interpolate(step.recover_hint, vars)}`
        : '';
    const userMsg = `${goalLine}` +
        `失败的步骤（id=${step.id}）本来要做的事：\n${intent}\n\n` +
        `它失败时的错误：\n${truncate(errorMsg, 800)}\n\n` +
        `当前已知的变量（JSON）：\n${knownVars}` +
        `${captureLine}${hintLine}\n\n` +
        `请随机应变，把这一步要的结果做出来；做不成就按约定输出放弃标记。`;
    // 工具集：默认全量（与 skill/agent 节点同等 agency）；step.allowed_tools 收窄。
    const allowed = step.allowed_tools;
    const tools = allowed && allowed.length > 0
        ? ALL_TOOLS.filter((t) => allowed.includes(t.name))
        : ALL_TOOLS;
    const messages = [
        { role: 'system', content: RECOVERY_SYSTEM },
        { role: 'user', content: userMsg },
    ];
    try {
        const result = yield* runMiniReAct({
            messages,
            tools,
            output_schema: step.output_schema,
            provider: ctx.provider,
            signal: ctx.signal,
            maxTurns: step.maxTurns ?? 12,
        });
        const outcome = interpretMiniReActResult(result, step.output_schema !== undefined);
        if (!outcome.ok)
            return outcome;
        // 救回：preview 加 recovered: 前缀（runner 的 step_end output 用），raw 原样。
        return {
            ok: true,
            value: { raw: outcome.value.raw, preview: `recovered: ${outcome.value.preview}` },
        };
    }
    catch (e) {
        // miniReAct 抛错（用尽轮数 / 重复工具调用 / schema 解析失败 / 中断）→ 恢复失败。
        return { ok: false, reason: e.message ?? String(e) };
    }
}
