UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

174 lines 7.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.flushTbdSessions = flushTbdSessions; const promises_1 = require("fs/promises"); const CodeGenerator_1 = require("../../../codegen/CodeGenerator"); const Logger_1 = require("../../../utils/Logger"); /** * Pattern that matches `await page.tbd()` (with optional semicolons, * whitespace variations, and variable assignment). */ const TBD_CALL_PATTERN = /^(\s*)(?:(?:const|let|var)\s+\w+\s*=\s*)?await\s+page\.tbd\(\s*\)\s*;?\s*$/; /** * Processes the given tbd() sessions: for each affected source file, * replaces `await page.tbd()` lines with the generated Playwright code. * * Replacements are applied **bottom-up** (highest line number first) so * that earlier replacements don't shift the line numbers of later ones. */ async function flushTbdSessions(sessions) { if (sessions.length === 0) { return 0; } // Group sessions by source file. const sessionsByFile = new Map(); for (const session of sessions) { const file = session.callSite.file; const existing = sessionsByFile.get(file) ?? []; existing.push(session); sessionsByFile.set(file, existing); } let totalReplacements = 0; for (const [filePath, fileSessions] of sessionsByFile) { try { const replacements = await rewriteFile(filePath, fileSessions); totalReplacements += replacements; } catch (error) { Logger_1.appLogger.error(`tbd: failed to rewrite ${filePath}`, error); } } return totalReplacements; } async function rewriteFile(filePath, sessions) { const original = await (0, promises_1.readFile)(filePath, 'utf-8'); const lines = original.split('\n'); // Find ALL lines in the file that contain page.tbd() calls. const tbdLineIndices = []; for (let i = 0; i < lines.length; i++) { if (TBD_CALL_PATTERN.test(lines[i]) || lines[i].includes('page.tbd()')) { tbdLineIndices.push(i); } } if (tbdLineIndices.length === 0) { Logger_1.appLogger.warn(`tbd: no page.tbd() calls found in ${filePath} — skipping`); return 0; } // Match each session to the nearest tbd() line in the file. The stack // trace line number may be off by a few lines due to TypeScript // compilation, so we find the closest actual tbd() call. const assignments = matchSessionsToLines(sessions, tbdLineIndices); // Sort by line index descending (bottom-up) so replacements don't // shift earlier line numbers. const sorted = [...assignments].sort((a, b) => b.lineIndex - a.lineIndex); let replacementCount = 0; for (const { session, lineIndex } of sorted) { const line = lines[lineIndex]; // Generate the replacement code. const generatedCode = generateReplacementCode(session); if (!generatedCode) { Logger_1.appLogger.info(`tbd: no actions recorded for page.tbd() at ${filePath}:${lineIndex + 1} — removing the call`); lines.splice(lineIndex, 1); replacementCount++; continue; } // Detect the indentation of the original line. const indent = line.match(/^(\s*)/)?.[1] ?? ' '; // Indent each line of the generated code to match. const indentedCode = generatedCode .split('\n') .map((codeLine) => (codeLine.trim() ? indent + codeLine.trimStart() : '')) .join('\n'); // Replace the tbd() line with the generated code. lines.splice(lineIndex, 1, indentedCode); replacementCount++; } if (replacementCount === 0) { return 0; } // Reassemble and format. let result = lines.join('\n'); try { result = await (0, CodeGenerator_1.prettifyCode)(result); } catch (error) { // If prettier fails (e.g. generated code has syntax issues), write // the unformatted version so the user can still see what was generated. Logger_1.appLogger.warn('tbd: prettier formatting failed, writing raw output', error); } await (0, promises_1.writeFile)(filePath, result, 'utf-8'); Logger_1.appLogger.info(`tbd: replaced ${replacementCount} page.tbd() call(s) in ${filePath}`); return replacementCount; } /** * Matches sessions to actual tbd() lines in the file. Each session is * assigned to the nearest unmatched tbd() line (by line distance from * the reported call-site). * * When there's only one session and one tbd() line, it's trivially matched. * With multiple, we greedily assign each session to its closest available * line, processing in order of ascending distance. */ function matchSessionsToLines(sessions, tbdLineIndices) { const available = new Set(tbdLineIndices); const results = []; // Build (session, lineIndex, distance) tuples for all combinations. const candidates = []; for (const session of sessions) { const reportedIndex = session.callSite.line - 1; // 0-based for (const lineIndex of tbdLineIndices) { candidates.push({ session, lineIndex, distance: Math.abs(reportedIndex - lineIndex), }); } } // Sort by distance ascending so closest matches are assigned first. candidates.sort((a, b) => a.distance - b.distance); const assignedSessions = new Set(); for (const { session, lineIndex } of candidates) { if (assignedSessions.has(session) || !available.has(lineIndex)) { continue; } results.push({ session, lineIndex }); assignedSessions.add(session); available.delete(lineIndex); } // Warn about unmatched sessions. for (const session of sessions) { if (!assignedSessions.has(session)) { Logger_1.appLogger.warn(`tbd: could not match session (reported line ${session.callSite.line}) to any page.tbd() call in file`); } } return results; } /** * Converts a TbdSession's recorded actions into Playwright code. * * Manual interactions are converted using the existing codegen utility. * AI instructions become `await page.ai(...)` calls. */ function generateReplacementCode(session) { if (session.recordedActions.length === 0) { return null; } const codeLines = []; for (const action of session.recordedActions) { if (action.type === 'aiInstruction') { codeLines.push(`await page.ai(${JSON.stringify(action.instruction)});`); } else { try { const code = (0, CodeGenerator_1.convertProposedToolCallToPlaywrightCode)(action.toolCall); codeLines.push(code); } catch (error) { Logger_1.appLogger.warn(`tbd: failed to generate code for tool call ${action.toolCall.name}`, error); codeLines.push(`// TODO: Could not generate code for ${action.toolCall.name}: ${JSON.stringify(action.toolCall.parameters)}`); } } } return codeLines.join('\n'); } //# sourceMappingURL=fileRewriter.js.map