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:
@@ -1729,9 +1729,15 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
}
|
}
|
||||||
const sessionId = normalizeSessionId(body.sessionId)
|
const sessionId = normalizeSessionId(body.sessionId)
|
||||||
const { sessionId: _sessionId, ...recordingOptions } = body
|
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 manager = await getExecutorManager()
|
||||||
const executor = sessionId ? manager.getSession(sessionId) : null
|
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
|
||||||
if (sessionId && !executor) {
|
if (sessionId && !isCdpSessionId && !executor) {
|
||||||
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
||||||
}
|
}
|
||||||
const extensionId = executor?.getSessionMetadata().extensionId || null
|
const extensionId = executor?.getSessionMetadata().extensionId || null
|
||||||
@@ -1748,9 +1754,10 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
app.post('/recording/stop', async (c) => {
|
app.post('/recording/stop', async (c) => {
|
||||||
const body = (await c.req.json()) as { sessionId?: string | number }
|
const body = (await c.req.json()) as { sessionId?: string | number }
|
||||||
const sessionId = normalizeSessionId(body.sessionId)
|
const sessionId = normalizeSessionId(body.sessionId)
|
||||||
|
const isCdpSessionId = sessionId?.startsWith('pw-tab-')
|
||||||
const manager = await getExecutorManager()
|
const manager = await getExecutorManager()
|
||||||
const executor = sessionId ? manager.getSession(sessionId) : null
|
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
|
||||||
if (sessionId && !executor) {
|
if (sessionId && !isCdpSessionId && !executor) {
|
||||||
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
||||||
}
|
}
|
||||||
const extensionId = executor?.getSessionMetadata().extensionId || null
|
const extensionId = executor?.getSessionMetadata().extensionId || null
|
||||||
@@ -1767,8 +1774,9 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
app.get('/recording/status', async (c) => {
|
app.get('/recording/status', async (c) => {
|
||||||
const sessionId = normalizeSessionId(c.req.query('sessionId'))
|
const sessionId = normalizeSessionId(c.req.query('sessionId'))
|
||||||
const normalizedSessionId = sessionId || undefined
|
const normalizedSessionId = sessionId || undefined
|
||||||
|
const isCdpSessionId = normalizedSessionId?.startsWith('pw-tab-')
|
||||||
const manager = await getExecutorManager()
|
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 extensionId = executor?.getSessionMetadata().extensionId || null
|
||||||
const relay = getRecordingRelay(extensionId)
|
const relay = getRecordingRelay(extensionId)
|
||||||
if (!relay) {
|
if (!relay) {
|
||||||
@@ -1782,9 +1790,10 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
app.post('/recording/cancel', async (c) => {
|
app.post('/recording/cancel', async (c) => {
|
||||||
const body = (await c.req.json()) as { sessionId?: string | number }
|
const body = (await c.req.json()) as { sessionId?: string | number }
|
||||||
const sessionId = normalizeSessionId(body.sessionId)
|
const sessionId = normalizeSessionId(body.sessionId)
|
||||||
|
const isCdpSessionId = sessionId?.startsWith('pw-tab-')
|
||||||
const manager = await getExecutorManager()
|
const manager = await getExecutorManager()
|
||||||
const executor = sessionId ? manager.getSession(sessionId) : null
|
const executor = sessionId && !isCdpSessionId ? manager.getSession(sessionId) : null
|
||||||
if (sessionId && !executor) {
|
if (sessionId && !isCdpSessionId && !executor) {
|
||||||
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
return c.json({ success: false, error: `Session ${sessionId} not found` }, 404)
|
||||||
}
|
}
|
||||||
const extensionId = executor?.getSessionMetadata().extensionId || null
|
const extensionId = executor?.getSessionMetadata().extensionId || null
|
||||||
|
|||||||
+15
-14
@@ -1035,26 +1035,27 @@ export class PlaywrightExecutor {
|
|||||||
|
|
||||||
// Track execution timestamps relative to recording start (seconds).
|
// Track execution timestamps relative to recording start (seconds).
|
||||||
// Used to identify idle gaps that can be sped up in demo videos.
|
// 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 recordingStartSnapshot = this.recordingStartedAt
|
||||||
const execStartSec = recordingStartSnapshot !== null
|
const execStartSec = recordingStartSnapshot !== null
|
||||||
? (Date.now() - recordingStartSnapshot) / 1000
|
? (Date.now() - recordingStartSnapshot) / 1000
|
||||||
: -1
|
: -1
|
||||||
|
|
||||||
let result: unknown
|
const result = await (async () => {
|
||||||
try {
|
try {
|
||||||
result = await Promise.race([
|
return await Promise.race([
|
||||||
vm.runInContext(wrappedCode, vmContext, { timeout, displayErrors: true }),
|
vm.runInContext(wrappedCode, vmContext, { timeout, displayErrors: true }),
|
||||||
new Promise((_, reject) => setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout)),
|
new Promise((_, reject) => setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout)),
|
||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
// Record timestamp even on error — the execution still occupied real time
|
// Record timestamp even on error — the execution still occupied real time
|
||||||
// that should not be sped up in the demo video.
|
// that should not be sped up in the demo video.
|
||||||
if (recordingStartSnapshot !== null && execStartSec >= 0 && this.recordingStartedAt !== null) {
|
if (recordingStartSnapshot !== null && execStartSec >= 0 && this.recordingStartedAt !== null) {
|
||||||
const execEndSec = (Date.now() - recordingStartSnapshot) / 1000
|
const execEndSec = (Date.now() - recordingStartSnapshot) / 1000
|
||||||
this.executionTimestamps.push({ start: execStartSec, end: execEndSec })
|
this.executionTimestamps.push({ start: execStartSec, end: execEndSec })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})()
|
||||||
|
|
||||||
let responseText = formatConsoleLogs(consoleLogs)
|
let responseText = formatConsoleLogs(consoleLogs)
|
||||||
|
|
||||||
|
|||||||
@@ -420,13 +420,13 @@ export interface ExecutionTimestamp {
|
|||||||
export function computeIdleSections({
|
export function computeIdleSections({
|
||||||
executionTimestamps,
|
executionTimestamps,
|
||||||
totalDurationMs,
|
totalDurationMs,
|
||||||
speed = 4,
|
speed = 5,
|
||||||
bufferSeconds = INTERACTION_BUFFER_SECONDS,
|
bufferSeconds = INTERACTION_BUFFER_SECONDS,
|
||||||
}: {
|
}: {
|
||||||
executionTimestamps: ExecutionTimestamp[]
|
executionTimestamps: ExecutionTimestamp[]
|
||||||
/** Total recording duration in milliseconds (from stopRecording result) */
|
/** Total recording duration in milliseconds (from stopRecording result) */
|
||||||
totalDurationMs: number
|
totalDurationMs: number
|
||||||
/** Speed multiplier for idle sections (default 4) */
|
/** Speed multiplier for idle sections (default 5) */
|
||||||
speed?: number
|
speed?: number
|
||||||
/** Override the default buffer around each execution (seconds) */
|
/** Override the default buffer around each execution (seconds) */
|
||||||
bufferSeconds?: number
|
bufferSeconds?: number
|
||||||
@@ -497,7 +497,7 @@ export interface CreateDemoVideoOptions {
|
|||||||
durationMs: number
|
durationMs: number
|
||||||
/** Execution timestamps (from stopRecording result) */
|
/** Execution timestamps (from stopRecording result) */
|
||||||
executionTimestamps: ExecutionTimestamp[]
|
executionTimestamps: ExecutionTimestamp[]
|
||||||
/** Speed multiplier for idle sections (default 4) */
|
/** Speed multiplier for idle sections (default 5) */
|
||||||
speed?: number
|
speed?: number
|
||||||
/** Output file path (defaults to recordingPath with `-demo` suffix) */
|
/** Output file path (defaults to recordingPath with `-demo` suffix) */
|
||||||
outputFile?: string
|
outputFile?: string
|
||||||
@@ -522,7 +522,7 @@ export async function createDemoVideo(
|
|||||||
recordingPath,
|
recordingPath,
|
||||||
durationMs,
|
durationMs,
|
||||||
executionTimestamps,
|
executionTimestamps,
|
||||||
speed = 4,
|
speed = 5,
|
||||||
signal,
|
signal,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
|
|||||||
@@ -924,7 +924,7 @@ const demoPath = await createDemoVideo({
|
|||||||
recordingPath: recording.path,
|
recordingPath: recording.path,
|
||||||
durationMs: recording.duration,
|
durationMs: recording.duration,
|
||||||
executionTimestamps: recording.executionTimestamps,
|
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
|
// outputFile: './demo.mp4', // optional, defaults to recording-demo.mp4
|
||||||
})
|
})
|
||||||
console.log('Demo video:', demoPath)
|
console.log('Demo video:', demoPath)
|
||||||
|
|||||||
Reference in New Issue
Block a user