/**
 * ============================================================
 *  plugins/workflow-runner/src/agentCommon.ts —— workflow 内「有界 agent」的共享大脑
 * ------------------------------------------------------------
 *  工作流里有两处会临时起一个 agent（都跑 miniReAct）：
 *    - onError:'recover'（recovery.ts）—— 节点失败后救火
 *    - agent: 节点（stepExecutors/agent.ts）—— 作者显式声明的开放任务
 *  二者本质同构：受工作流目标/上下文指导、用工具随机应变、产出经验证后回灌 capture。
 *  为避免「同一个脑子写两份提示词、行为漂移」，这里收敛它们的**共享件**：
 *    1. 统一系统提示词基础内核（来自 promptAssembly.ts）—— 含工具/skill/plugin 完整列表；
 *       两处只在前面各加一句**角色开场白**（recover=救火 / agent=自主完成一步），
 *       后面追加共享纪律块。
 *    2. 统一验证闸门（interpretMiniReActResult）—— miniReAct 跑完后，判定「真交付」还是
 *       「放弃/不可信」，**永不把没验证过的产出当成功**。这是 Phase 2 验证闸门的提取。
 * ============================================================
 */
import { assembleBasePrompt } from '../../../src/prompts/promptAssembly.js';
/** agent 显式放弃的唯一标记；与 DISCIPLINE 收尾约束严格对应。recover 与 agent 节点共用。 */
export const GIVEUP_MARKER = '__AGENT_GIVEUP__';
/** agent 步骤的角色开场白（与 recovery 的 RECOVER_OPENING 对称） */
export const AGENT_OPENING = '你是一名资深工程师，被一个确定性工作流临时调来「自主完成一步开放任务」。';
/** 共享纪律块：所有 workflow 内 agent 的通用行为约束（角色开场白由各调用方拼在前面）。 */
const DISCIPLINE = `按这个纪律干活：
1. 先想清楚「这一步要产出什么」，必要时用只读工具（Read / Grep / Glob）做最小探查，别凭空猜、别凭空编。
2. 用工具**实际去做**（读文件、执行命令、写文件…），不要只描述计划。
3. **收尾前必须自我验证**：用工具确认产出确实存在且满足意图（写了文件就 Read 回来核对、跑了命令就检查退出码与输出）。没验证过不算完成。

安全护栏（你握有包括 Bash 在内的全部工具，务必克制）：
- 只做达成意图所必需的最小动作，优先可逆操作。
- 严禁破坏性行为：不 rm -rf、不删除/覆盖与本步无关的用户数据、不碰工作目录以外的东西。
- **绝不靠伪造、占位、或编造一段「看起来对」的文本来假装成功**——你的产出会被工作流绑给后续步骤，假结果比诚实失败更糟。

收尾（二选一，硬约束）：
- 真的达成了 → 最终消息**只输出这一步要的结果本身**，不要寒暄、不要复述要求、不要解释推理过程。若要求结构化输出，严格按 JSON Schema、**不要**用 markdown 代码块包裹。
- 确实做不成 → 最终消息**只输出一行**：\`${GIVEUP_MARKER}: <一句话说明为什么>\`，让工作流诚实地停下。绝不要为了「交差」而编一个假结果。`;
/**
 * 拼一个 workflow agent 的完整 system prompt：
 *
 *   角色开场白 + 基础内核（含 skill/plugin/工具完整列表） + 共享纪律
 *
 * 基础内核来自 assembleBasePrompt()，确保 workflow agent 与 TUI 普通对话
 * 看到完全相同的 skill 列表和插件信息。
 */
export async function buildWorkflowAgentSystem(cwd, opening, tools) {
    const baseKernel = await assembleBasePrompt({
        cwd,
        tools,
        includeProjectInstructions: false, // workflow 子步骤不需要项目指令
        includePlugins: true, // 插件列表对 agent 也有参考价值
    });
    return `${opening}\n\n${baseKernel}\n\n${DISCIPLINE}`;
}
/** 把 step.capture 翻成「下游会把你的结果存成哪些变量」，让 agent 产出对得上形状。 */
export function describeCaptureContract(step) {
    if (!step.capture)
        return '';
    const names = Object.values(step.capture);
    if (names.length === 0)
        return '';
    return `\n\n下游契约：工作流会把你的结果保存为变量【${names.join('、')}】供后续步骤引用，请确保你的产出能填好这些变量。`;
}
export function truncate(s, n) {
    return s.length > n ? `${s.slice(0, n)}…` : s;
}
/**
 * 解析非 schema 模式下 agent 最终文本里的放弃标记。
 *   - 命中 `__AGENT_GIVEUP__: <原因>` → 返回原因字符串（可能为空串）
 *   - 未命中 → null（视为给出了真实结果）
 */
export function parseGiveup(text) {
    const idx = text.indexOf(GIVEUP_MARKER);
    if (idx === -1)
        return null;
    return text
        .slice(idx + GIVEUP_MARKER.length)
        .replace(/^[:：\s]+/, '')
        .trim();
}
/**
 * 验证闸门：把 miniReAct 的 StepResult 判定为「真交付」或「放弃/不可信」。
 *   - schema 模式：miniReAct 已 strict 解析（解析失败会抛、落不到这里），能拿到结果即 ok；
 *     补 content 别名不覆盖具名字段。
 *   - 非 schema 模式：空产出 / 命中 GIVEUP 标记 → ok:false；否则 ok:true，raw 补 text/result/content
 *     三键别名，兼容 capture:{content:x}/{result:x}/{text:x}。
 * 注意：preview 这里给「干净版」（不加任何前缀），需要前缀的调用方（如 recover 的 "recovered: "）
 * 自行包装。
 */
export function interpretMiniReActResult(result, hasSchema) {
    if (hasSchema) {
        const raw = { ...result.raw };
        if (typeof raw.result === 'string' && raw.content === undefined) {
            raw.content = raw.result;
        }
        return { ok: true, value: { raw, preview: result.preview } };
    }
    const text = typeof result.raw.text === 'string' ? result.raw.text : '';
    const trimmed = text.trim();
    if (trimmed.length === 0) {
        return { ok: false, reason: '没有产出任何结果' };
    }
    const giveup = parseGiveup(trimmed);
    if (giveup !== null) {
        return { ok: false, reason: giveup || '显式放弃（未给原因）' };
    }
    const raw = {
        text: trimmed,
        result: trimmed,
        content: trimmed,
    };
    return {
        ok: true,
        value: {
            raw,
            preview: trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed,
        },
    };
}
