feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* recording-flush.ts
|
||||
*
|
||||
* Called at task end to:
|
||||
* 1. Flush any buffered recorder actions to recordings/{recordTo}.json.
|
||||
* 2. If recordTo ends with ".next", compile a candidate patch script at
|
||||
* scripts/{baseName}.next.js using the source script's description and
|
||||
* params as hints (self-healing patch staging).
|
||||
*
|
||||
* This helper is intentionally non-throwing — all errors are logged and
|
||||
* swallowed so a flush bug cannot crash the agent loop.
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { recorder } from '../engine/browser-recorder.js';
|
||||
import { parseScript, serializeScript } from './frontmatter.js';
|
||||
import { compileScriptBody } from './script-compiler.js';
|
||||
import { resolveUserSubdir } from './paths.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { RecordedAction } from '../engine/browser-recorder.js';
|
||||
|
||||
export function flushAndStageRecording(
|
||||
taskId: string,
|
||||
ownerId: string | undefined,
|
||||
userFolderRoot: string,
|
||||
): void {
|
||||
if (!ownerId) return;
|
||||
const recordTo = recorder.recordTo(taskId);
|
||||
if (!recordTo) return;
|
||||
|
||||
const flushedPath = recorder.flush(taskId, userFolderRoot, ownerId);
|
||||
if (!flushedPath) return;
|
||||
|
||||
if (!recordTo.endsWith('.next')) {
|
||||
logger.debug(`[recording] flushed regular recording: ${recordTo}.json`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Self-healing patch: compile a candidate .next.js from the flushed trace.
|
||||
const baseName = recordTo.slice(0, -'.next'.length);
|
||||
try {
|
||||
const sourceScriptPath = resolveUserSubdir(
|
||||
userFolderRoot,
|
||||
ownerId,
|
||||
'browser-macros',
|
||||
`${baseName}.js`,
|
||||
);
|
||||
if (!existsSync(sourceScriptPath)) {
|
||||
logger.warn(
|
||||
`[recording] cannot stage patch — source script ${baseName}.js missing`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceText = readFileSync(sourceScriptPath, 'utf-8');
|
||||
const sourceParsed = parseScript(sourceText);
|
||||
|
||||
const recordingPayload = JSON.parse(readFileSync(flushedPath, 'utf-8')) as {
|
||||
actions: RecordedAction[];
|
||||
};
|
||||
|
||||
// Compile just the body (we build frontmatter ourselves to control timestamps)
|
||||
const { body, paramSpecs } = compileScriptBody({
|
||||
recording: recordingPayload.actions,
|
||||
description: sourceParsed.frontmatter.description,
|
||||
sessionProfileId: sourceParsed.frontmatter.sessionProfileId,
|
||||
// We don't know which fill values map to which params after a recovery session.
|
||||
// Pass no paramHints so the patch contains literal values; the user will
|
||||
// manually re-parameterize during diff review.
|
||||
paramHints: [],
|
||||
recordingSource: `${recordTo}.json`,
|
||||
});
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const patched = serializeScript({
|
||||
frontmatter: {
|
||||
description: sourceParsed.frontmatter.description,
|
||||
params: paramSpecs,
|
||||
sessionProfileId: sourceParsed.frontmatter.sessionProfileId,
|
||||
recordingSource: `${recordTo}.json`,
|
||||
// Preserve original createdAt; stamp fresh updatedAt.
|
||||
createdAt: sourceParsed.frontmatter.createdAt ?? now,
|
||||
updatedAt: now,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
const targetPath = resolveUserSubdir(
|
||||
userFolderRoot,
|
||||
ownerId,
|
||||
'browser-macros',
|
||||
`${baseName}.next.js`,
|
||||
);
|
||||
const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
|
||||
mkdirSync(dirname(targetPath), { recursive: true });
|
||||
writeFileSync(tmpPath, patched, { encoding: 'utf-8', mode: 0o600 });
|
||||
renameSync(tmpPath, targetPath);
|
||||
logger.info(`[user-folder] staged self-healing patch: browser-macros/${baseName}.next.js`);
|
||||
} catch (e) {
|
||||
logger.warn(`[recording] failed to stage patch: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user