/** * CompactCapsule — 结构化数据,用于上下文压缩后的状态恢复。 * * 设计目标: * - 在 PI session compaction 后保留关键运行时状态 * - 确保 hard deny、open question、resume snapshot、gate 状态不丢失 * - 提供可插入 prompt 的文本渲染 * - 支持从 capsule 重建可操作状态 * * 参考:AGENT_GATE_LOOP_REFACTOR_PLAN.md Phase 4(post-compact 安全约束保留) */ import type { ActiveRun, KdPhase, KdQuestion, KdResumeSnapshot, KdSourceAnchorSnapshot, KdWriteTransaction, } from "../types.ts"; // ─── CompactCapsule 接口 ─────────────────────────────────────────────── export interface CompactCapsule { /** 活跃的 hard-deny findings 摘要 */ hardDenySummary: string[]; /** 未回答的阻断问题 */ openQuestions: CompactOpenQuestion[]; /** 恢复断点 */ resumeSnapshot: CompactResumeSnapshot | null; /** 当前门禁状态 */ currentGate: CompactGateState; /** 写入事务快照 */ writeTransactions: CompactWriteTransaction[]; /** 源码锚点快照 */ sourceAnchors: CompactSourceAnchor[]; /** Completion Promise ID(如有) */ completionPromiseId?: string; /** Stop Hook 状态(如有) */ stopHook?: CompactStopHook; /** 追踪 ID,关联到主 run */ traceId: string; /** capsule 创建时间 */ createdAt: string; } export interface CompactOpenQuestion { id: string; question: string; blocking: boolean; phase: KdPhase; } export interface CompactResumeSnapshot { phase: KdPhase; nextAction: string; pendingQuestionId?: string; } export interface CompactGateState { passed: boolean; reason?: string; findings: CompactGateFinding[]; } export interface CompactGateFinding { severity: "hard-deny" | "soft-deny" | "evidence-required" | "warning"; policy: string; message: string; } export interface CompactWriteTransaction { id: string; path: string; status: "planned" | "blocked" | "written" | "verified"; } export interface CompactSourceAnchor { path: string; fileHash: string; refCount: number; } export interface CompactStopHook { type: "done" | "blocked" | "budget-exhausted" | "stuck" | "unsafe" | "scope-drift"; reason: string; } // ─── RestoredState ────────────────────────────────────────────────────── export interface RestoredState { /** 恢复后的阶段 */ phase: KdPhase; /** 唯一下一动作 */ nextAction: string; /** 需要优先处理的阻断问题 */ pendingQuestions: CompactOpenQuestion[]; /** 活跃的 hard deny */ activeHardDenies: string[]; /** 需要验证的写入事务 */ pendingWriteTransactions: CompactWriteTransaction[]; /** 需要重新读取的源码锚点 */ staleSourceAnchors: CompactSourceAnchor[]; /** 恢复断点描述 */ resumeContext: string | null; /** 是否存在阻断状态 */ isBlocked: boolean; /** 阻断原因(如有) */ blockReason?: string; } // ─── createCompactCapsule ─────────────────────────────────────────────── export function createCompactCapsule(run: ActiveRun, traceId?: string): CompactCapsule { const now = new Date().toISOString(); return { hardDenySummary: extractHardDenySummary(run), openQuestions: extractOpenQuestions(run), resumeSnapshot: extractResumeSnapshot(run), currentGate: extractGateState(run), writeTransactions: extractWriteTransactions(run), sourceAnchors: extractSourceAnchors(run), completionPromiseId: extractCompletionPromiseId(run), stopHook: extractStopHook(run), traceId: traceId ?? run.id, createdAt: now, }; } // ─── validateCompactCapsule ───────────────────────────────────────────── export interface CompactCapsuleValidation { valid: boolean; missing: string[]; warnings: string[]; } export function validateCompactCapsule(capsule: CompactCapsule): CompactCapsuleValidation { const missing: string[] = []; const warnings: string[] = []; // 必须字段校验 if (!capsule.traceId || typeof capsule.traceId !== "string") { missing.push("traceId"); } if (!capsule.createdAt || typeof capsule.createdAt !== "string") { missing.push("createdAt"); } if (!Array.isArray(capsule.hardDenySummary)) { missing.push("hardDenySummary"); } if (!Array.isArray(capsule.openQuestions)) { missing.push("openQuestions"); } if (!capsule.currentGate || typeof capsule.currentGate !== "object") { missing.push("currentGate"); } if (!Array.isArray(capsule.writeTransactions)) { missing.push("writeTransactions"); } if (!Array.isArray(capsule.sourceAnchors)) { missing.push("sourceAnchors"); } // 语义校验:存在 hard deny 时必须有对应摘要 if (capsule.hardDenySummary.length > 0 && !capsule.currentGate) { missing.push("currentGate (hard deny 存在但 gate 状态缺失)"); } // 语义校验:有 blocking question 时 resumeSnapshot 应存在 const blockingQuestions = capsule.openQuestions.filter((q) => q.blocking); if (blockingQuestions.length > 0 && !capsule.resumeSnapshot) { warnings.push("存在 blocking question 但无 resumeSnapshot,恢复后可能丢失处理断点"); } // 语义校验:有 pending write 时应有 source anchor const pendingWrites = capsule.writeTransactions.filter((t) => t.status === "planned"); if (pendingWrites.length > 0 && capsule.sourceAnchors.length === 0) { warnings.push("存在 planned 写入事务但无 source anchor,恢复后写入校验可能失败"); } return { valid: missing.length === 0, missing, warnings, }; } // ─── restoreFromCapsule ───────────────────────────────────────────────── export function restoreFromCapsule(capsule: CompactCapsule): RestoredState { const validation = validateCompactCapsule(capsule); if (!validation.valid) { throw new Error(`CompactCapsule 校验失败,缺失字段:${validation.missing.join(", ")}`); } const pendingQuestions = capsule.openQuestions.filter((q) => q.blocking); const activeHardDenies = capsule.hardDenySummary.filter((d) => d.trim().length > 0); const pendingWrites = capsule.writeTransactions.filter( (t) => t.status === "planned" || t.status === "blocked", ); // 判断是否被阻断 const isBlocked = activeHardDenies.length > 0 || pendingQuestions.length > 0 || capsule.stopHook !== undefined; let blockReason: string | undefined; if (activeHardDenies.length > 0) { blockReason = `存在 ${activeHardDenies.length} 条 hard-deny`; } else if (pendingQuestions.length > 0) { blockReason = `存在 ${pendingQuestions.length} 个 blocking question 未回答`; } else if (capsule.stopHook) { blockReason = `Stop Hook 触发:${capsule.stopHook.type} — ${capsule.stopHook.reason}`; } // 构建恢复断点描述 const resumeContext = buildResumeContext(capsule); // 确定下一动作 const nextAction = determineNextAction(capsule); return { phase: capsule.resumeSnapshot?.phase ?? "execute", nextAction, pendingQuestions, activeHardDenies, pendingWriteTransactions: pendingWrites, staleSourceAnchors: capsule.sourceAnchors, resumeContext, isBlocked, blockReason, }; } // ─── renderCompactCapsulePrompt ───────────────────────────────────────── export function renderCompactCapsulePrompt(capsule: CompactCapsule): string { const sections: string[] = []; sections.push("## Compact Capsule(上下文压缩恢复)"); sections.push(""); sections.push(`Trace ID:${capsule.traceId}`); sections.push(`创建时间:${capsule.createdAt}`); // Hard Deny if (capsule.hardDenySummary.length > 0) { sections.push(""); sections.push("### Hard Deny(活跃阻断)"); capsule.hardDenySummary.forEach((d) => sections.push(`- ${d}`)); } // Open Questions if (capsule.openQuestions.length > 0) { sections.push(""); sections.push("### 未回答问题"); capsule.openQuestions.forEach((q) => { const blocking = q.blocking ? " [BLOCKING]" : ""; sections.push(`- ${q.id}${blocking}:${q.question}`); }); } // Resume Snapshot if (capsule.resumeSnapshot) { sections.push(""); sections.push("### 恢复断点"); sections.push(`阶段:${capsule.resumeSnapshot.phase}`); sections.push(`下一动作:${capsule.resumeSnapshot.nextAction}`); if (capsule.resumeSnapshot.pendingQuestionId) { sections.push(`待处理问题:${capsule.resumeSnapshot.pendingQuestionId}`); } } // Gate State sections.push(""); sections.push("### 门禁状态"); sections.push(`通过:${capsule.currentGate.passed ? "是" : "否"}`); if (capsule.currentGate.reason) { sections.push(`原因:${capsule.currentGate.reason}`); } if (capsule.currentGate.findings.length > 0) { sections.push("Findings:"); capsule.currentGate.findings.forEach((f) => sections.push(`- [${f.severity}] ${f.policy}:${f.message}`), ); } // Write Transactions if (capsule.writeTransactions.length > 0) { sections.push(""); sections.push("### 写入事务"); capsule.writeTransactions.forEach((t) => sections.push(`- ${t.id} ${t.path} [${t.status}]`), ); } // Source Anchors if (capsule.sourceAnchors.length > 0) { sections.push(""); sections.push("### 源码锚点"); capsule.sourceAnchors.forEach((a) => sections.push(`- ${a.path}:${a.refCount} anchors,fileHash=${a.fileHash.slice(0, 12)}`), ); } // Completion Promise if (capsule.completionPromiseId) { sections.push(""); sections.push(`### Completion Promise:${capsule.completionPromiseId}`); } // Stop Hook if (capsule.stopHook) { sections.push(""); sections.push("### Stop Hook"); sections.push(`类型:${capsule.stopHook.type}`); sections.push(`原因:${capsule.stopHook.reason}`); } // Non-negotiable rules reminder sections.push(""); sections.push("### 恢复后规则"); sections.push("- 存在 hard-deny 时不得绕过,必须修复状态或补证据"); sections.push("- 存在 blocking question 时唯一动作是回答或修订事实"); sections.push("- 写入事务必须重新校验 source anchor 后才能继续"); sections.push("- 不能凭记忆恢复 facts/evidence,必须从 run 状态读取"); return sections.join("\n"); } // ─── 内部提取函数 ──────────────────────────────────────────────────────── function extractHardDenySummary(run: ActiveRun): string[] { const findings: string[] = []; // 从 gate 状态提取 if (run.gate && !run.gate.passed && run.gate.reason) { findings.push(run.gate.reason); } // 从 resumeSnapshot 提取阻断原因 if (run.resumeSnapshot?.gateReason) { findings.push(run.resumeSnapshot.gateReason); } // 从 repair 状态提取 if (run.repair?.status === "blocked" && run.repair.lastFailureEvidence) { findings.push(`修复阻断:${run.repair.lastFailureEvidence}`); } // 去重 return Array.from(new Set(findings)); } function extractOpenQuestions(run: ActiveRun): CompactOpenQuestion[] { const questions = Array.isArray(run.questions) ? run.questions : []; return questions .filter((q) => q.status === "open") .map((q) => ({ id: q.id, question: q.question, blocking: q.blocking, phase: q.phase, })); } function extractResumeSnapshot(run: ActiveRun): CompactResumeSnapshot | null { if (!run.resumeSnapshot || run.resumeSnapshot.status !== "open") { return null; } return { phase: run.resumeSnapshot.phase, nextAction: run.resumeSnapshot.nextAction, pendingQuestionId: run.resumeSnapshot.pendingQuestionId, }; } function extractGateState(run: ActiveRun): CompactGateState { const findings: CompactGateFinding[] = []; // 从 gate 结果构建 findings if (run.gate && !run.gate.passed) { findings.push({ severity: "hard-deny", policy: "gate-result", message: run.gate.reason ?? "门禁未通过", }); } return { passed: run.gate?.passed ?? true, reason: run.gate?.reason, findings, }; } function extractWriteTransactions(run: ActiveRun): CompactWriteTransaction[] { const transactions = Array.isArray(run.writeTransactions) ? run.writeTransactions : []; return transactions.map((t) => ({ id: t.id, path: t.path, status: t.status, })); } function extractSourceAnchors(run: ActiveRun): CompactSourceAnchor[] { const anchors = Array.isArray(run.sourceAnchors) ? run.sourceAnchors : []; return anchors.map((a) => ({ path: a.path, fileHash: a.fileHash, refCount: a.refs.length, })); } function extractCompletionPromiseId(run: ActiveRun): string | undefined { // Completion Promise 暂未在 ActiveRun 中定义,预留接口 return undefined; } function extractStopHook(run: ActiveRun): CompactStopHook | undefined { // Stop Hook 暂未在 ActiveRun 中定义,从 repair 状态推断 if (run.repair?.status === "blocked") { return { type: "blocked", reason: run.repair.lastFailureEvidence ?? "修复阻断", }; } return undefined; } function buildResumeContext(capsule: CompactCapsule): string | null { const parts: string[] = []; if (capsule.resumeSnapshot) { parts.push(`阶段 ${capsule.resumeSnapshot.phase},下一动作:${capsule.resumeSnapshot.nextAction}`); } if (capsule.openQuestions.length > 0) { const blocking = capsule.openQuestions.filter((q) => q.blocking); if (blocking.length > 0) { parts.push(`${blocking.length} 个 blocking question 待回答`); } } if (capsule.hardDenySummary.length > 0) { parts.push(`${capsule.hardDenySummary.length} 条 hard-deny 待处理`); } return parts.length > 0 ? parts.join(";") : null; } function determineNextAction(capsule: CompactCapsule): string { // 优先级:Stop Hook > Hard Deny > Blocking Question > Resume Snapshot > Gate if (capsule.stopHook) { return `处理 Stop Hook:${capsule.stopHook.type} — ${capsule.stopHook.reason}`; } if (capsule.hardDenySummary.length > 0) { return `解决 hard-deny:${capsule.hardDenySummary[0]}`; } const blockingQuestions = capsule.openQuestions.filter((q) => q.blocking); if (blockingQuestions.length > 0) { return `回答 blocking question:${blockingQuestions[0].id} — ${blockingQuestions[0].question}`; } if (capsule.resumeSnapshot) { return capsule.resumeSnapshot.nextAction; } if (!capsule.currentGate.passed) { return `修复门禁:${capsule.currentGate.reason ?? "门禁未通过"}`; } return "继续当前阶段任务"; }