/** * Reasoning / --no-tools runs cannot write deliverables via tools. * Mirror pi-native: when targets are missing, materialize assistant text. */ export function stripThinkBlocks(text) { return String(text || "") .replace(/[\s\S]*?<\/think>/gi, "") .replace(/```(?:json)?\s*/gi, "") .replace(/```/g, "") .trim(); } export function extractAssistantText(ev) { if (!ev || typeof ev !== "object") { return ""; } const t = ev.type || ""; if (t !== "message_end" && t !== "turn_end") { return ""; } const msg = ev.message; if (!msg || typeof msg !== "object" || msg.role !== "assistant") { return ""; } const parts = Array.isArray(msg.content) ? msg.content : []; const chunks = []; for (const part of parts) { if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") { chunks.push(part.text); } } return chunks.join(""); } /** * Write assistant text into missing/empty targets. Returns list of paths written. */ export function materializeReasoningTargets({ workspace, targets, assistantText, existsSync, readFileSync, writeFileSync, mkdirSync, resolve, dirname, }) { const cleaned = stripThinkBlocks(assistantText); if (!cleaned || !workspace) { return []; } const written = []; for (const rel of Array.isArray(targets) ? targets : []) { if (!rel || typeof rel !== "string") { continue; } const abs = resolve(workspace, rel); if (existsSync(abs)) { try { if (String(readFileSync(abs, "utf8")).trim()) { continue; } } catch { // fall through and overwrite empty/unreadable } } try { mkdirSync(dirname(abs), { recursive: true }); writeFileSync(abs, cleaned, "utf8"); written.push(rel); } catch { // leave missing; caller will classify } } return written; }