diff --git a/packages/core/src/resolve.ts b/packages/core/src/resolve.ts index e4faa0e3..c1ee67fc 100644 --- a/packages/core/src/resolve.ts +++ b/packages/core/src/resolve.ts @@ -172,6 +172,9 @@ export const COMMANDS: Record = { 'download-as': { desc: 'Set filename for next download', options: [], usage: 'download-as ', examples: ['download-as bills/rogers-2026-03.pdf'] }, + 'wait-download': { desc: 'Wait for the next browser download to complete (returns full path)', options: [], + usage: 'wait-download', + examples: ['wait-download'] }, 'start-recording': { desc: 'Start recording commands to .pw file', options: [], usage: 'start-recording [filename]', examples: ['start-recording', 'start-recording my-flow.pw'] }, @@ -197,7 +200,7 @@ export const CATEGORIES: Record = { 'Video': ['video-start', 'video-stop', 'video-chapter'], 'Tracing': ['tracing-start', 'tracing-stop'], 'Recording': ['start-recording', 'stop-recording', 'pause-recording', 'discard-recording'], - 'Other': ['dialog-accept', 'dialog-dismiss', 'route', 'route-list', 'unroute', 'resize', 'pdf', 'upload', 'download-as', 'config-print'], + 'Other': ['dialog-accept', 'dialog-dismiss', 'route', 'route-list', 'unroute', 'resize', 'pdf', 'upload', 'download-as', 'wait-download', 'config-print'], }; /** Commands that mutate the page — snapshot should be attached when includeSnapshot is on. */ diff --git a/packages/extension/src/background.ts b/packages/extension/src/background.ts index b9bccfd6..03615f4c 100644 --- a/packages/extension/src/background.ts +++ b/packages/extension/src/background.ts @@ -599,6 +599,9 @@ async function executeSingleCommand(command: string): Promise<{ text: string; is type BridgeResult = { text: string; isError: boolean; image?: string; blobUrl?: string; duration?: number; size?: number }; +// Pending resolver for wait-download command +let pendingDownloadResolve: ((result: BridgeResult) => void) | null = null; + function executeCommandPayload(msg: { command: string; scriptType?: 'command' | 'script'; @@ -716,6 +719,26 @@ async function handleBridgeCommand(msg: { return { text: `Next download will save as: ${path}`, isError: false }; } + // ── Wait download ─────────────────────────────────────────────────────────── + // Waits for the next browser download to complete and returns its full path. + if (cmd === 'wait-download') { + return new Promise((resolve) => { + if (pendingDownloadResolve) { + resolve({ text: 'A wait-download is already pending', isError: true }); + return; + } + const timer = setTimeout(() => { + pendingDownloadResolve = null; + resolve({ text: 'wait-download timed out after 30s', isError: true }); + }, 30_000); + pendingDownloadResolve = (result) => { + clearTimeout(timer); + pendingDownloadResolve = null; + resolve(result); + }; + }); + } + // Validate existing page connection is still alive (#849) if (currentPage) { try { @@ -994,6 +1017,21 @@ chrome.downloads.onDeterminingFilename.addListener((_item, suggest) => { } }); +// ─── Download completion tracking ─────────────────────────────────────────── +// Resolves pending wait-download promises when a download completes or fails. +chrome.downloads.onChanged.addListener((delta) => { + if (!pendingDownloadResolve) return; + if (delta.state?.current === 'complete') { + chrome.downloads.search({ id: delta.id }, (items) => { + if (items[0] && pendingDownloadResolve) { + pendingDownloadResolve({ text: items[0].filename, isError: false }); + } + }); + } else if (delta.state?.current === 'interrupted') { + pendingDownloadResolve({ text: `Download interrupted: ${delta.error?.current ?? 'unknown'}`, isError: true }); + } +}); + // Expose stable globals for swDebugEval — functions that never change go here, not inside attachToTab globalThis.downloadAs = (filename: string) => { globalThis.__downloadFilename = filename; }; globalThis.attachToTab = attachToTab; diff --git a/packages/stagecraft/AGENT.md b/packages/stagecraft/AGENT.md index 6dc0d598..656fafbf 100644 --- a/packages/stagecraft/AGENT.md +++ b/packages/stagecraft/AGENT.md @@ -161,6 +161,7 @@ Run `help` via MCP to get the full list. Key commands for recording flows: | `select ` | Select dropdown option | | `verify-text ` | Assert text is present | | `download-as ` | Set filename for next browser download | +| `wait-download` | Wait for download to complete; returns full saved path | | `start-recording [file]` | Begin recording to .pw file | | `stop-recording` | Save recording | | `pause-recording` | Pause/resume | diff --git a/packages/stagecraft/skills/rogers/SKILL.md b/packages/stagecraft/skills/rogers/SKILL.md index f10186f8..36549d85 100644 --- a/packages/stagecraft/skills/rogers/SKILL.md +++ b/packages/stagecraft/skills/rogers/SKILL.md @@ -4,19 +4,18 @@ description: Navigate to MyRogers and download bill PDFs category: tax/bills/telecom preconditions: Must be logged into rogers.com (use bridge mode) parameters: - - billing_periods: list of dates to check (e.g. "January 24, 2026", "February 24, 2026") -output: PDF files downloaded to browser's default download folder + - billing_period: billing period label to download (e.g. "January 24, 2026") + - filename: output filename for the download (e.g. "rogers-2026-01.pdf") +output: Full path to the downloaded PDF file (printed by wait-download) --- # Download Rogers Bill Navigates to MyRogers self-serve billing page, opens the Save PDF modal, -selects the requested billing periods, and triggers the download. +checks the requested billing period, downloads the PDF, and returns the saved path. -The agent should: -1. Run the .pw script to navigate to the billing page -2. Read the snapshot to see available billing periods -3. Check the requested periods -4. Click "Download bills" +Use the `.pw` skill for simple single-period downloads. Use the `.js` skill for +multi-period downloads or when you need `download.saveAs(path)` to save outside +the default Downloads folder. Note: The latest billing period is pre-checked by default. diff --git a/packages/stagecraft/skills/rogers/download-bill.pw b/packages/stagecraft/skills/rogers/download-bill.pw index 61407372..2ccc3c8d 100644 --- a/packages/stagecraft/skills/rogers/download-bill.pw +++ b/packages/stagecraft/skills/rogers/download-bill.pw @@ -3,11 +3,14 @@ # Requires: logged into rogers.com (bridge mode) # # Usage: -# replay skills/rogers/download-bill.pw --variable billing_period="January 24, 2026" +# replay skills/rogers/download-bill.pw --variable billing_period="January 24, 2026" --variable filename="rogers-2026-01.pdf" +# Ensure wide viewport — Rogers shows "Save PDF" at desktop width, different text at narrow width +resize 1280 800 goto https://www.rogers.com/consumer/self-serve/overview click "View your bill" --exact click "Save PDF" check "{{billing_period}}" download-as {{filename}} click "Download bills" +wait-download