import type { ActiveRun, KdToolResultContract, KdWorkingSet, KdWorkingSetFile } from "./types.ts"; const MAX_WORKING_FILES = 12; export function defaultWorkingSet(run: Pick, now = new Date().toISOString()): KdWorkingSet { return { focus: run.phase, activeGap: run.goal, currentAction: "读取当前阶段资料并处理最高优先级焦点。", recentFiles: [], forbiddenDrift: [ "禁止被最新消息覆盖 open question、resumeSnapshot 或 consistency errors。", "禁止把工具摘要当作 FormId、字段、接口映射或验收数据事实。", "禁止在 execute 前写产品源码。", ], updatedAt: now, }; } export function updateWorkingSetFromTool(run: ActiveRun, toolResult: KdToolResultContract, now = new Date().toISOString()): KdWorkingSet { const current = normalizeWorkingSet(run.workingSet, run, now); const nextFiles = mergeRecentFiles(current.recentFiles, filesFromToolResult(toolResult, now)); return { ...current, currentAction: toolResult.nextAction ?? current.currentAction, blockedBy: toolResult.status === "blocked" || toolResult.status === "failed" ? toolResult.summary : current.blockedBy, recentFiles: nextFiles, updatedAt: now, }; } export function updateWorkingSetFocus(run: ActiveRun, input: { focus: string; activeGap?: string; currentAction?: string; blockedBy?: string }, now = new Date().toISOString()): KdWorkingSet { const current = normalizeWorkingSet(run.workingSet, run, now); return { ...current, focus: input.focus, activeGap: input.activeGap ?? current.activeGap, currentAction: input.currentAction ?? current.currentAction, blockedBy: Object.prototype.hasOwnProperty.call(input, "blockedBy") ? input.blockedBy : current.blockedBy, updatedAt: now, }; } export function formatWorkingSet(workingSet: KdWorkingSet | undefined, run: ActiveRun): string { const current = normalizeWorkingSet(workingSet, run); const files = current.recentFiles.length > 0 ? current.recentFiles.map((file) => `- ${file.path}:${file.summary}`).join("\n") : "无。"; return [ `focus:${current.focus}`, current.activeGap ? `activeGap:${current.activeGap}` : undefined, current.currentAction ? `currentAction:${current.currentAction}` : undefined, current.blockedBy ? `blockedBy:${current.blockedBy}` : undefined, "recentFiles:", files, "forbiddenDrift:", ...current.forbiddenDrift.map((item) => `- ${item}`), ].filter(Boolean).join("\n"); } export function normalizeWorkingSet(value: KdWorkingSet | undefined, run: Pick, now = new Date().toISOString()): KdWorkingSet { if (!value || typeof value !== "object") return defaultWorkingSet(run, now); return { focus: typeof value.focus === "string" && value.focus.trim() ? value.focus.trim() : run.phase, activeGap: typeof value.activeGap === "string" && value.activeGap.trim() ? value.activeGap.trim() : run.goal, currentAction: typeof value.currentAction === "string" && value.currentAction.trim() ? value.currentAction.trim() : undefined, blockedBy: typeof value.blockedBy === "string" && value.blockedBy.trim() ? value.blockedBy.trim() : undefined, recentFiles: Array.isArray(value.recentFiles) ? value.recentFiles.flatMap(normalizeWorkingSetFile).slice(-MAX_WORKING_FILES) : [], forbiddenDrift: Array.isArray(value.forbiddenDrift) ? value.forbiddenDrift.filter((item): item is string => typeof item === "string" && Boolean(item.trim())).map((item) => item.trim()) : defaultWorkingSet(run, now).forbiddenDrift, updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : now, }; } function filesFromToolResult(toolResult: KdToolResultContract, now: string): KdWorkingSetFile[] { const paths = [...(toolResult.paths ?? []), ...(toolResult.modifiedFiles ?? []), ...(toolResult.evidencePaths ?? [])]; return [...new Set(paths)] .filter((path) => path.trim()) .map((path) => ({ path: path.trim(), summary: toolResult.summary, source: "tool" as const, updatedAt: now, })); } function mergeRecentFiles(existing: KdWorkingSetFile[], incoming: KdWorkingSetFile[]): KdWorkingSetFile[] { const byPath = new Map(); for (const file of [...existing, ...incoming]) { byPath.set(file.path.replace(/\\/g, "/").toLowerCase(), file); } return [...byPath.values()].slice(-MAX_WORKING_FILES); } function normalizeWorkingSetFile(value: unknown): KdWorkingSetFile[] { if (!value || typeof value !== "object" || Array.isArray(value)) return []; const record = value as Record; const path = typeof record.path === "string" ? record.path.trim() : ""; const summary = typeof record.summary === "string" ? record.summary.trim() : ""; if (!path || !summary) return []; return [ { path, summary, source: record.source === "user" || record.source === "artifact" ? record.source : "tool", updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : new Date().toISOString(), }, ]; }