fix: recording session ID routing, IIFE for timestamps, default speed 5x

cdp-relay.ts:
- Fix pre-existing bug: recording HTTP routes (/recording/start, stop,
  status, cancel) were treating CDP session IDs (pw-tab-*) as executor
  session IDs, causing 'Session not found' errors. Now skip executor
  lookup when sessionId is a CDP session ID and pass it through directly
  to the extension for tab routing.

executor.ts:
- Use async IIFE instead of let+try for VM execution timestamp tracking.
  Cleaner pattern: const result = await (async () => { try/finally })()

ffmpeg.ts + skill.md:
- Change default idle speed from 4x to 5x
This commit is contained in:
Tommy D. Rossi
2026-02-25 13:12:41 +01:00
parent d84cdba528
commit 1cad56f1a2
4 changed files with 36 additions and 26 deletions
+16 -7
View File
@@ -1729,9 +1729,15 @@ export async function startPlayWriterCDPRelayServer({
}
const sessionId = normalizeSessionId(body.sessionId)
const { sessionId: _sessionId, ...recordingOptions } = body
// sessionId serves two purposes here:
// 1. CDP session ID (pw-tab-*) — routes to the correct tab in the extension
// 2. Executor session ID (numeric) — used to find which extension to route to
// Only look up the executor when sessionId is NOT a CDP session ID.
const isCdpSessionId = sessionId?.startsWith('pw-tab-')
const manager = await getExecutorManager()
const executor = sessionId ? manager.getSession(sessionId) : null
if (sessionId && !executor) {
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
if (sessionId && !isCdpSessionId && !executor) {
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
}
const extensionId = executor?.getSessionMetadata().extensionId || null
@@ -1748,9 +1754,10 @@ export async function startPlayWriterCDPRelayServer({
app.post('/recording/stop', async (c) => {
const body = (await c.req.json()) as { sessionId?: string | number }
const sessionId = normalizeSessionId(body.sessionId)
const isCdpSessionId = sessionId?.startsWith('pw-tab-')
const manager = await getExecutorManager()
const executor = sessionId ? manager.getSession(sessionId) : null
if (sessionId && !executor) {
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
if (sessionId && !isCdpSessionId && !executor) {
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
}
const extensionId = executor?.getSessionMetadata().extensionId || null
@@ -1767,8 +1774,9 @@ export async function startPlayWriterCDPRelayServer({
app.get('/recording/status', async (c) => {
const sessionId = normalizeSessionId(c.req.query('sessionId'))
const normalizedSessionId = sessionId || undefined
const isCdpSessionId = normalizedSessionId?.startsWith('pw-tab-')
const manager = await getExecutorManager()
const executor = normalizedSessionId ? manager.getSession(normalizedSessionId) : null
const executor = normalizedSessionId && !isCdpSessionId ? manager.getSession(normalizedSessionId) : null
const extensionId = executor?.getSessionMetadata().extensionId || null
const relay = getRecordingRelay(extensionId)
if (!relay) {
@@ -1782,9 +1790,10 @@ export async function startPlayWriterCDPRelayServer({
app.post('/recording/cancel', async (c) => {
const body = (await c.req.json()) as { sessionId?: string | number }
const sessionId = normalizeSessionId(body.sessionId)
const isCdpSessionId = sessionId?.startsWith('pw-tab-')
const manager = await getExecutorManager()
const executor = sessionId ? manager.getSession(sessionId) : null
if (sessionId && !executor) {
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
if (sessionId && !isCdpSessionId && !executor) {
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
}
const extensionId = executor?.getSessionMetadata().extensionId || null
+15 -14
View File
@@ -1035,26 +1035,27 @@ export class PlaywrightExecutor {
// Track execution timestamps relative to recording start (seconds).
// Used to identify idle gaps that can be sped up in demo videos.
// Captured before try so we can record timing even if execution throws.
// Captured before execution so we can record timing even if it throws.
const recordingStartSnapshot = this.recordingStartedAt
const execStartSec = recordingStartSnapshot !== null
? (Date.now() - recordingStartSnapshot) / 1000
: -1
let result: unknown
try {
result = await Promise.race([
vm.runInContext(wrappedCode, vmContext, { timeout, displayErrors: true }),
new Promise((_, reject) => setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout)),
])
} finally {
// Record timestamp even on error — the execution still occupied real time
// that should not be sped up in the demo video.
if (recordingStartSnapshot !== null && execStartSec >= 0 && this.recordingStartedAt !== null) {
const execEndSec = (Date.now() - recordingStartSnapshot) / 1000
this.executionTimestamps.push({ start: execStartSec, end: execEndSec })
const result = await (async () => {
try {
return await Promise.race([
vm.runInContext(wrappedCode, vmContext, { timeout, displayErrors: true }),
new Promise((_, reject) => setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout)),
])
} finally {
// Record timestamp even on error — the execution still occupied real time
// that should not be sped up in the demo video.
if (recordingStartSnapshot !== null && execStartSec >= 0 && this.recordingStartedAt !== null) {
const execEndSec = (Date.now() - recordingStartSnapshot) / 1000
this.executionTimestamps.push({ start: execStartSec, end: execEndSec })
}
}
}
})()
let responseText = formatConsoleLogs(consoleLogs)
+4 -4
View File
@@ -420,13 +420,13 @@ export interface ExecutionTimestamp {
export function computeIdleSections({
executionTimestamps,
totalDurationMs,
speed = 4,
speed = 5,
bufferSeconds = INTERACTION_BUFFER_SECONDS,
}: {
executionTimestamps: ExecutionTimestamp[]
/** Total recording duration in milliseconds (from stopRecording result) */
totalDurationMs: number
/** Speed multiplier for idle sections (default 4) */
/** Speed multiplier for idle sections (default 5) */
speed?: number
/** Override the default buffer around each execution (seconds) */
bufferSeconds?: number
@@ -497,7 +497,7 @@ export interface CreateDemoVideoOptions {
durationMs: number
/** Execution timestamps (from stopRecording result) */
executionTimestamps: ExecutionTimestamp[]
/** Speed multiplier for idle sections (default 4) */
/** Speed multiplier for idle sections (default 5) */
speed?: number
/** Output file path (defaults to recordingPath with `-demo` suffix) */
outputFile?: string
@@ -522,7 +522,7 @@ export async function createDemoVideo(
recordingPath,
durationMs,
executionTimestamps,
speed = 4,
speed = 5,
signal,
} = options
+1 -1
View File
@@ -924,7 +924,7 @@ const demoPath = await createDemoVideo({
recordingPath: recording.path,
durationMs: recording.duration,
executionTimestamps: recording.executionTimestamps,
speed: 4, // optional, default 4x for idle sections
speed: 5, // optional, default 5x for idle sections
// outputFile: './demo.mp4', // optional, defaults to recording-demo.mp4
})
console.log('Demo video:', demoPath)