[comp] Production Deploy#3506
Conversation
…apture (#3417) * feat(api): add credential-backed auto re-login for browser automations Browser automations can now sign back in on their own when a vendor session expires, instead of always requiring a person to re-authenticate. - Store a profile's login (username, password, optional authenticator setup key) in a per-organization 1Password vault via a new credential-storage service and a POST profiles/:id/credentials endpoint. - Resolve those credentials just-in-time during a run through the previously-stubbed credential vault adapter, now backed by 1Password, and drive sign-in with Stagehand variable substitution so secret values are never sent to the model. - Retry once with a freshly generated one-time code to cover the rotation boundary; fall back to human re-authentication when a login needs a step the agent cannot complete (SMS/email/push/SSO). - Let the scheduler attempt a run for a needs-reauth profile that has stored credentials, and mark a profile verified again after a successful run. - Load the 1Password SDK lazily and mark it external for Trigger.dev builds; the feature stays inert unless OP_SERVICE_ACCOUNT_TOKEN is set. Adds unit tests for the credential resolver and the re-login flow. * feat(app): capture vendor login credentials in the connect flow Adds the first step of the browser-automation connect wizard: a credentials form (username, password, optional authenticator setup key) shown before the live sign-in. On submit it resolves the auth profile and stores the login via the credentials endpoint so scheduled and manual runs can sign in on their own. Credential storage failures are non-fatal and fall back to manual sign-in. Part of the Browser Automations redesign (connect-a-login flow). Adds unit tests for the form (fields, validation, submit, password toggle). * feat(app): redesign browser-automation first-time setup screen Replaces the bare "enter a URL" prompt with the three-step explainer (01 Connect a login / 02 Describe what to capture / 03 Evidence on schedule), a single "Connect a vendor login" action, and the 1Password reassurance line. The URL is now collected in the connect credentials form rather than here. Part of the Browser Automations redesign (first-time setup). * feat(app): use split-card layout for browser-automation setup screen Switches the first-time setup screen to the designer's 2c variant — a split card with the pitch and "Connect a Vendor Login" action on the left and a quiet "How it works" rail (01/02/03) on the right. Stacks to one column on mobile. * feat(api): detect vendor login methods to power smart connect Adds a login-analysis backend: POST /v1/browserbase/analyze-login opens the vendor sign-in page in a throwaway cloud browser, detects the supported login methods (password / SSO / passkey, identifier type, extra fields), and returns a recommendation (ready / works-with-check-ins / manual). Reads a public page only — no credentials — and always degrades to a manual fallback if it can't be read. Foundation for the smart-connect flow. Unit tests for the recommendation logic and the analyzer service. * feat(app): smart connect flow (analyze -> sign in -> capture) Replaces the connect flow with the "Browser-First Split" (1C) design: a stepper rail plus a browser stage that opens the vendor URL, runs AI login-method detection (/analyze-login), shows a recommendation (ready / works-with-check-ins / manual), opens the live sign-in, then captures the reusable credentials (username, password, authenticator setup key) after verification. Removes the superseded credentials-first form. Adds tests for the capture form. The SMS-only -> "enable an authenticator app" recommendation is deferred to the live sign-in step (2FA method isn't visible before sign-in). * feat(app): group automations by connection with per-connection status Rewrites the automations list to group automations under their vendor connection. Each connection shows its status (Connected / Needs reconnect / Needs your action / Not connected) with a one-click Reconnect for the ones that need a human. Adds a useBrowserProfiles hook to load connection statuses and wires reconnect through the existing live sign-in. * feat: capture arbitrary extra login fields (workspace, subdomain, etc.) Extends credential capture beyond username/password/TOTP to any site-specific fields a login needs. The capture form gains an "add a field" control; the API stores extras as custom 1Password fields, resolves them at run time (via items.get, additive to the existing resolve path so the standard path is unchanged), and the sign-in agent fills each by label using variable substitution (only labels reach the model, never values). * fix(api): degrade login analysis to manual when Browserbase is unavailable Move Browserbase session/context creation inside the try/catch so a missing BROWSERBASE_API_KEY (or any Browserbase outage) returns the manual-entry fallback instead of a 500 at the analyze step. Documents the required BROWSERBASE_API_KEY / BROWSERBASE_PROJECT_ID in .env.example. * chore(api): log full error/stack when login analysis falls back to manual * fix(app): require a real login URL in connect flow; clearer 'no form detected' copy * feat(browserbase): run vendor login analysis as a background task Move the connect-flow login analysis off the HTTP request path into a Trigger.dev task so the browser + AI work can't be cut short by a request or browser timeout. The analyzer also auto-navigates to the sign-in page, so a customer can paste a homepage or dashboard URL instead of the exact login page. - analyze-login now returns a run handle ({ runId, publicAccessToken }) - the connect flow subscribes with useRealtimeRun and picks up the result when the run completes, falling back to manual entry on failure * fix(app): accept any vendor URL in connect flow, not the exact login page Users shouldn't have to hunt down the precise /login URL. Accept a bare domain or homepage (e.g. notion.so), add the scheme automatically, and let the analyzer navigate to the sign-in page itself. Relabel the field to "Vendor website" and update the copy to match what we actually do. * fix(app): resume vendor connect flow after navigation; static https prefix The connect flow kept all state in component-local React state, so leaving the page (client-side navigation or in-app tab switch) unmounted it and lost the in-flight analysis — the background task kept running, but the UI forgot about it and forced a restart. Persist the analysis phase to sessionStorage (per task, 15-minute window) and re-subscribe to the run on return. Also show a static "https://" prefix on the URL field so users type just the domain; a pasted full URL has its scheme stripped so it isn't doubled. * fix(app): render detected login methods as proper labels (SSO, not Sso) CSS `capitalize` only uppercases the first letter, so the SSO acronym showed as "Sso". Map method keys to human labels instead. * feat(browserbase): sign in for the user from a chooser instead of a raw browser Detection now drives the connect flow instead of just describing it. After the check, the user picks how to sign in: - Email & password (recommended): they fill our form once and we perform the sign-in for them in the background, reusing the same credential auto-fill the scheduler uses. No typing into the raw browser, and no entering credentials twice. If an automated step can't complete (CAPTCHA, email/SMS code, SSO), we hand the live browser back to finish that one login. - SSO / passkey: the live browser, which those genuinely need. The automated sign-in runs as a background Trigger.dev task so the browser + AI work can't be cut short by a request or browser timeout. Credentials are never persisted client-side; only the analysis phase is resumable. * fix(app): move "how it works" to the left on the browser automations setup card * fix(app): drop the redundant "detected" panel once the method chooser is shown The chooser already lists each sign-in method, so repeating them in the rail's detected panel was redundant. Keep the panel only as the placeholder/skeleton during the check; hide it from the choose step onward. * fix(app): declutter sign-in details form with progressive disclosure Default to just username + password. Two-factor moves behind a "this login uses an authenticator app" checkbox, with copy that makes clear it wants the one-time setup key (not the rotating 6-digit code). Extra site fields open automatically when detected (workspace, subdomain, …) and are otherwise a collapsed manual fallback. * fix(app): give the live sign-in browser more room; calmer message when auto sign-in isn't provisioned Enlarge the embedded browser (taller iframe, trimmed padding on the sign-in step) so it uses the available space. When the credential vault isn't configured on an environment, fall back to manual sign-in with an informational message instead of surfacing a raw error. * docs(api): clarify OP_SERVICE_ACCOUNT_TOKEN — what it enables and where to get it * feat(browserbase): classify why an automated sign-in couldn't complete After filling stored credentials, read the resulting page and classify it — logged in, wrong username/password, two-factor required, or a human challenge — instead of a single logged-in/not check. The connect flow uses this to route the user precisely: wrong password sends them back to the form with a clear message; a 2FA prompt or CAPTCHA hands them the live browser (and, for 2FA, suggests adding an authenticator setup key for unattended runs). * feat(browserbase): watch the automated sign-in and take over in place Run the automated sign-in on a live browser the user can watch, instead of an invisible background session. The session is created up front and shown as a live view; the background task drives it (fill credentials, handle 2FA) while the user watches. If it can't finish — 2FA prompt, a challenge, or an unclear state — the same browser stays open on exactly that page so the user finishes in place and confirms. Wrong password still routes back to the form to fix the stored value. Also navigate to the actual sign-in form first (shared with login analysis), so a homepage/dashboard URL doesn't leave the fill on the wrong page. * fix(browserbase): fill each sign-in field in its own action Stagehand's act() performs one action per call, so a single "enter username and password and submit" instruction only filled the username — the password was never typed and the form never submitted, so the sign-in stalled (and, sharing this code, so did the scheduler's auto re-login). Split it into atomic steps: each extra field, username, an optional Next/Continue for two-step logins, password, submit, then the one-time code. Secrets still go through variable substitution and never reach the model as text. * fix(app): keep the browser open on any sign-in failure instead of a toast When the automated sign-in didn't succeed, the flow bounced the user back to the form with a toast — hiding what actually happened (a real error, a 2FA prompt, or a site rate-limit/verify step after repeated attempts). Now every non-success keeps the same live browser open (take-over) with an in-view caption explaining the state, plus a "Re-enter details" action to correct stored credentials. Success still closes the session and connects. * fix(app): don't act on a stale run result when starting a new sign-in useRealtimeRun keeps emitting the previous run's terminal result for a render after switching runs. On a retry that made the effect close the freshly created session and re-route on the old outcome — so the new sign-in's task then failed with "session is missing a connect URL". Ignore any run state whose id isn't the current run's, release a prior session before starting a new one, and stop Trigger from blindly retrying the browser sign-in/analysis tasks. * fix(app): guide 2FA take-over to the authenticator code (passkeys can't work here) Passkeys need a hardware authenticator on the user's own device, so they hang forever in the remote browser. On a 2FA hand-over, tell the user to use their authenticator app's code (via "More options") instead. Also extract the sign-in session lifecycle into a focused hook to keep the flow component under budget. * fix(app): keep the 2FA take-over hint vendor-neutral Drop the site-specific button name from the take-over caption so it reads correctly for any vendor, not just one whose 2FA page happens to say "More options". * fix(app): warn up front when a vendor only offers non-unattended sign-in If a site's only detected methods are SSO or passkey, tell the user before they pick that it can't run on a schedule unattended (we keep the session alive and email for a manual refresh). Clarify the passkey option needs their device each time. Vendor-neutral. * fix(app): drop the "what we detect" placeholder panel from the connect rail Detected methods are now shown as the chooser options, so the rail's placeholder/skeleton panel is redundant. Remove it (and the now-unused detecting/showDetectPanel props). * feat(app): show who's in control of the live sign-in browser Add a banner + colored frame around the live view: a blue "AI is signing you in — sit tight" while the automation drives, and an amber "Your turn — <what to do>" when the user needs to act. Removes the ambiguity of not knowing whether to wait or to do something. * feat(app): narrate the automated sign-in in real time The sign-in task now publishes live, plain-language progress ("Opening the sign-in page…", "Entering your password…", "Checking two-factor…") via run metadata, and the connect flow shows it under the "AI is signing you in" banner. The user can follow exactly what the automation is doing, moment to moment. * feat(app): redesign the live sign-in as an activity panel (design 1b) Replace the single status banner with the designer's two-pane card: the live cloud browser on the left, and a "Sign-in activity" panel on the right — a who's-in-control pill, a step-by-step timeline of what the AI is doing (new StepList), and the action the user needs to take (enter a 2FA code, or finish a sign-in the AI can't complete). The sign-in task now emits a step timeline via run metadata instead of a single status string; the flow renders these steps full-width and keeps them visible through the take-over. * feat(browserbase): manage connections — edit and remove (design 1f) Add connection management: PATCH /profiles/:id (edit name / sign-in URL; moving to a different host marks it needs-reconnect) and DELETE /profiles/:id (removes the profile and best-effort deletes its stored login from 1Password). Surface it in the connection header via a ⋯ menu — an inline edit popover (name + URL) and a guarded remove dialog that states what stops running. Reconnect already existed. Backend covered by tests. * fix(app): don't show a fresh evidence task as already connected Connection status is org-level (any verified profile), so a brand-new task was inheriting "connected" from a connection made on another task and skipping the setup/how-it-works step. Gate the empty states on this task instead: a task with no automations of its own shows the onboarding, and only advances to the "add your first automation" state after the user connects here. * fix(app): taller live browser; connected copy points to the next step Give the live sign-in browser more of the available space (iframe 460→640px, up to 80vh). And reword the connected screen so it doesn't imply runs are already happening — it now says the session is saved and prompts the user to add their first instruction (what to capture). * feat(app): "Browser evidence" section — 1n header + 3-step empty state Start the section redesign (1n): rename to "Browser evidence" with a status badge and a plain "what this does" subtitle, and replace the onboarding with the 3-step explainer (Connect once → Add instructions → Evidence lands here). Rename the create action to "Add instruction". Connection card + run history come next. * feat(app): restructure browser evidence active view into connection + instructions Split the grouped list into a labelled "Connection" card (avatar, display name, hostname, status pill, reconnect, manage menu) followed by an "Instructions" list, matching the section IA design. Establishes the per-connection presentation the instruction composer will consume. * feat(app): add run-history strip and run-detail overlay to browser evidence Below each connection's instructions, show a horizontal strip of recent run screenshots with pass/fail badges and timestamps. Clicking a tile opens a detail overlay with the full screenshot, the check verdict, any failure reason, timing, and re-run / open / download actions. * feat(api): live test-run for browser instructions before saving Add an ad-hoc test endpoint that runs a not-yet-saved instruction against a connection's live session and streams its progress, so the composer can show the AI working before the instruction is committed to the schedule. Reuses the same evidence runner scheduled runs use and persists nothing — no automation, no run record. The execution engine now emits each stage through an optional callback so the run's activity can be streamed live. * feat(app): split composer — write, watch the AI test it live, then save Replace the create/edit dialog with a two-pane instruction composer: write the instruction on the left, test it against the connection and watch the AI work live on the right, then save once it passes. Drops the separate name and schedule fields (name derives from the instruction; schedule lives on the task), and defaults the starting URL from the connection with an advanced override. * feat(app): edit instruction cadence inline; remove old config dialog Since the composer no longer carries a schedule field, each instruction row now has an inline cadence picker (daily…yearly) that saves on change — so an instruction's schedule stays editable after the modal moved out. Remove the now-unused create/edit dialog the composer replaced. * test(api): use a valid failure code in the instruction-test spec * fix(api): capture the browser at 1920x1080 for a sharper live view Our panels are well under 1920px wide, so a larger native viewport lets the embedded live view (and evidence screenshots) downscale instead of upscale — upscaling was what looked soft, especially on HiDPI displays. Browserbase exposes no device-pixel-ratio setting, so viewport size is the only lever. * feat(app): gentle success beat when a connection signs in Instead of snapping from the live browser straight to the connected screen, hold a brief "Signed in" confirmation over the still-visible browser, then fade into the connected screen — a softer hand-off. * feat(app): make the pass/fail check first-class with example chips Surface the pass/fail check by default (drop the opt-in toggle and OPTIONAL label) since evidence instructions usually want a verdict; leave it blank to capture a screenshot only. Add clickable example chips under both the instruction and check fields — a short label inserts a full example — shown while the field is empty and hidden once the user starts typing. * fix(app): bind new instructions to the just-connected vendor; align activity header The composer resolved the connection from a stale default host, so a freshly connected vendor could be tested against the wrong site's saved profile. Pass the connected URL up from the connect flow so instructions bind to the vendor the user actually connected. Also align the "Sign-in activity" header with the browser chrome bar (same 34px row). * fix(api): stop re-login from misreading a successful sign-in as failed After signing in, the run navigated back to the entered URL before checking the result — but some apps always show a login page at that URL, so a successful sign-in read as "did not complete" and forced a needless re-auth. Verify on the page the site lands on after login instead, and detect the signed-in state by the absence of a sign-in prompt rather than an avatar/profile marker (many apps have none), which was causing false negatives. * feat(app): enlarge the test-run live view to fill its panel Give the live view a 16:9 frame matching the capture resolution so it fills the panel width instead of leaving wide side gaps, and stays sharp (downscaled). * fix(api): don't call a login page a "session expired" The step fired whenever the first page looked like a sign-in prompt — including sites that always show login at the root, where nothing had expired. Reword it to state plainly that we're not signed in on this page and are signing in. * feat(app): clean up the connection chip and frame the pass/fail check Show the connection as its hostname with an avatar and a status-driven dot (green verified / amber needs-reconnect / grey unverified) instead of the raw profile name with an always-green dot. Frame the check as "PASS when <your condition> / FAIL otherwise", so the pass/fail logic reads at a glance without a redundant second condition. * fix(api): give test runs more time and nudge the agent to be efficient Sign-in alone can eat ~40s, so a 4-minute cap cut multi-step lookups off mid-run. Raise the budget and tell the agent to find its own way and read what's already on screen instead of over-navigating — the common cause of hitting the limit. * feat(app): align the first field with the live view and clarify copy Line the instruction field up with the top of the live browser panel. Explain that the AI finds its own way (no exact steps needed) and what the advanced start-page override is for, and surface a clear, actionable message when a test run times out. * feat(api): make the navigation model swappable and let it self-correct Keep navigation screenshot-based but make the computer-use model configurable via env (Claude or OpenAI, no per-site tuning) and default to the newest Claude our Stagehand supports. Give the agent more steps and a generous time budget so it can recover from a wrong turn on a complex site, and tell it to verify the page matches the request and fix a wrong item before finishing. The page-reading model is a separate env knob too. * feat(api): default navigation to OpenAI's computer-use model Use openai/computer-use-preview for screenshot-based navigation by default — it's purpose-built for GUI agents — with a safe fallback to Claude when no OpenAI key is set. Page reading / verdicts stay on a general model. * feat(api): fall back to Claude at runtime if the navigation model fails If the primary computer-use model is unavailable mid-run (preview access, rate limits, upstream errors), retry navigation once on a Claude computer-use model instead of failing the run. Use claude-sonnet-4-6 for the fallback — sonnet-5 isn't a computer-use model Stagehand supports. * feat(api): default navigation to GPT-5.6 Terra Switch the default computer-use model to openai/gpt-5.6-terra — the balanced tier of OpenAI's newest family, strong on browser automation at about half Sol's cost. Still env-swappable (e.g. gpt-5.6-sol for max accuracy) with the Claude fallback intact. * fix(api): capture the named item and judge the check against that target Two evidence-quality fixes: tell the agent to open the specific item an instruction names so the screenshot shows just that (not a whole list), and pass the instruction into the evaluator so a pass/fail check is judged about the intended target — not any matching value that happens to appear elsewhere on the page (which let an unrelated product's price satisfy the check). * fix(app): align the instruction field with the live view top The left field label and the right "Test run" header had different heights (the header carries a status pill), so the textarea and the browser view didn't line up. Fix both headers to the same height so they share one baseline. * feat(app): drop the manual "start from a specific page" option The AI is meant to find its own way, so the composer no longer asks for a start page — instructions always run from the connection. Removes the advanced override and its field. * feat: target the authenticated home so runs reuse the session When automated sign-in succeeds, capture the page the app lands on and use it as the connection's URL. Runs then open the authenticated app directly instead of a login page, so the persisted session is reused and credentials aren't re-entered every time. Genuine expiry still redirects to login and re-authenticates. * fix(api): capture the full page so evidence has no empty gap Screenshots were viewport-height, so a short page left a tall band of empty viewport below the content — a wide gap above the audit overlay. Capture the full page instead: the overlay sits right under the real content and the evidence is complete. * feat(app): one flow after connect, and support multiple vendors After a successful sign-in, go straight into writing the first instruction instead of bouncing back to a "create" button. And make multiple connections first-class: each connection has its own "Add instruction", and a "Connect another vendor" button adds more vendors — any number of connections, each with any number of instructions. * fix(app): stop offering passkey as a sign-in method Passkeys are device-bound and can't complete in Comp's cloud browser, so offering one was a dead end. Remove passkey from the method chooser. If a site only supports passkeys, show a plain "can't be connected here" message instead of a sign-in that can never finish. * feat: drive SSO up to the identity provider automatically Picking SSO no longer just dumps you in a blank browser. The AI opens the vendor, clicks through to your identity provider, and streams the steps — then hands the live browser over for you to finish the IdP login (which we can't hold or automate). If the saved session is still valid it signs in with no interaction at all. Reuses the live sign-in run via a mode flag. * feat(app): smart one-click reconnect for expired connections Reconnect no longer dumps you into a fully manual browser. A password connection re-signs-in automatically with the stored credentials (no interaction); an SSO connection has the AI drive to the identity provider so you only finish there. Reuses the sign-in run and just refreshes the list when done. * feat(api): render human-facing sign-in at a smaller viewport for legibility Sign-in, take-over, SSO and reconnect sessions now use a 1280x800 viewport so the page renders larger (and stays crisp) in the embedded live view — easier to read and fill. Unattended evidence runs keep the 1920x1080 viewport for sharp full-page screenshots. Viewport is now a parameter, defaulting to capture. * feat(app): animated activity border showing who's driving the live view Add a glowing border around the live browser: a primary-color glow while the AI is acting, amber when it's the user's turn. Makes it obvious at a glance whether to wait or take over, on the connect sign-in, the instruction test run, and execution/auth views. Decorative and click-through, so take-over still works. * fix(app): make the activity border visible and stop it lagging The ring used a mid-tone that vanished on dark pages, and animating box-shadow repainted every frame over the live iframe (the lag). Use a clear green (amber for your-turn), draw the ring as a static box-shadow that's always visible, and pulse opacity instead — GPU-cheap, no repaint. Respects reduced-motion. * fix(api): reduce vendor-page motion so the live view stops looking laggy Inject reduced-motion styles into every session page so a vendor's own looping animations (hero backgrounds, marquees, spinners) don't constantly repaint the streamed live view — that churn is what read as lag. Also steadies evidence screenshots. Best-effort and applied at session start. * fix(app): make the activity border render reliably via inline styles The border's box-shadow lived in globals.css, but the rule wasn't making it into the compiled stylesheet (only the class name did), so the ring never showed. Move the ring to an inline box-shadow with an explicit color (bright green for AI, amber for your turn) and use Tailwind's animate-pulse — inline styles can't be stripped or go stale. Drop the now-unused global CSS. * feat(app): show the activity border only while the AI is driving Render the green live-view ring only during AI control (sign-in, test run, execution). On the user's turn there's no ring, so the two states read differently at a glance. * feat(app): implement the "breathing glow" AI control ring Adopt direction 1 from the ai-control-ring design: a constant green ring with a soft inner glow that breathes (opacity-only, so the live iframe never repaints). Adapted for our overflow-hidden live-view container — glow inward instead of the design's outer halo, and keep the static ring inline so it's always visible. * fix(app): soften the AI control ring Drop the hard solid green line for a feathered, semi-transparent glow that breathes at the edges, with a faint always-on haze so it stays gentle but present. Still opacity-only (no iframe repaint). * feat(app): add the "AI is controlling" pill to the control ring Add the design's status pill (green, pulsing dot) inside the live view. Placed at the bottom-center rather than the design's top-outside spot, which our overflow-hidden container would clip and which clashes with the browser chrome bar. Pulse honors reduced-motion; still click-through. * feat(app): show a "Your turn" pill when the user is driving Symmetry for the live view: AI control shows the green glow + "AI is controlling", and the user's turn now shows an amber "Your turn" pill (no glow — the ring stays reserved for the AI, so the two states read differently). Applies to the connect take-over and the manual auth view. * fix(app): drop the ring border and fix the sign-in method chooser Remove the green border line from the AI control ring, leaving only the soft breathing glow bleeding in from the edges. Make the sign-in method options feel clickable: pointer cursor, a hover/pressed background, and a nudged arrow. On SSO, switch to the live view the moment it's clicked (it shows "Opening…" until the session is ready) instead of waiting for the background run to start, so the click has instant feedback. * fix(app): fade the control ring in instead of popping it in The overlay appeared instantly while the live view was still loading, so it looked abrupt. Ease it in over ~1.4s (honors reduced-motion) so it proceeds in with the page. * fix(app): confirm before discarding an in-progress live sign-in Closing the live sign-in card used to tear the session down on a single click, so a stray click mid-login wiped the user's progress. Now, while a live session is active (signing-in / takeover / manual sign-in), the close button asks for confirmation first; outside a live session it still closes immediately. * fix(app): show the control ring on browser load and hug the view Gate the ring on the iframe's load event so it fades in with the page instead of appearing over a blank frame (the fixed timer fired during loading, so it looked instant). Wrap the ring around the browser view only, not our chrome bar. * fix(app): drop the control-ring glow, keep the status pill Remove the green glow entirely — the live view now shows only the status pill ("AI is controlling" / "Your turn"). Also stop the composer's action buttons from being dragged to the bottom when the test panel grows: keep them under the form instead of pinning them to the column's foot. * feat(app): add the Browser Connections settings page Org-level management for the vendor logins that power browser automations — a dense table (design 2a) with a health summary, per-connection reconnect, change-login, rename, and remove, wired to the existing org-scoped profile API. Enables the previously-stubbed settings route and surfaces it in the settings nav (gated by the web-automations flag + integration:read). Replaces the old single-connection scaffolding. * feat: count automations per browser connection listProfiles now tallies how many BrowserAutomations in the org run on each connection (matched by targetUrl hostname, no FK) and returns it as automationCount. Surfaced as the "Automations" column on the Connections page and in the remove-connection warning ("N automations stop until reconnected"). * feat(db): add browser automation steps schema Add BrowserAutomationStep (ordered, bound to a connection) and BrowserAutomationStepRun (per-step run evidence) — the schema for multi-vendor sequential automations where each step reuses its connection's saved session. Additive migration: creates the tables and backfills one step per existing automation (matched to a connection by hostname). The inline automation fields stay for now; the engine, API, and UI move onto steps next, and the columns are dropped once nothing reads them. * feat(api): store browser automations as ordered steps Create/update now accept a steps[] array (each step = connection + targetUrl + instruction + optional check) and persist BrowserAutomationStep rows. The legacy single fields are mirrored from the first step so the current execution path keeps working until it reads steps directly; a bare inline instruction is stored as a one-step automation. Reads include the ordered steps. * feat(api): run browser automations step by step Execution now runs an automation's ordered steps in sequence, each on its own connection's saved session (no re-login), recording per-step evidence as BrowserAutomationStepRun and rolling the results into one run verdict (fails if any step fails; the check fails if any step's check fails). A single-step or legacy automation behaves exactly as before. Split the step orchestration into a step-runner service and a pure results module to keep each file focused and under the size limit. * feat(app): carry automation steps through the app types and hooks Add BrowserAutomationStep / BrowserAutomationStepInput and a steps field on the BrowserAutomation type, and let the create/update hook forward steps[] to the API. Foundation for the multi-step composer UI (no behavior change yet — the single-step composer keeps sending the inline instruction). * feat(app): multi-step (multi-vendor) automation composer Replace the single-instruction composer with a stacked step-cards editor (design: Stacked step-cards). Each step picks its own connection and has its own instruction + optional pass/fail check; steps can be added, removed, and reordered, with "session reused — no sign-in" connectors between them. A step on a needs-reconnect connection blocks saving until it's fixed. Test runs the active step; Save sends the ordered steps[] to the API. Drops the old single-step form. * fix(app): guard the composer against a missing connections prop Default connections to [] and fall back to the primary connection so the composer never crashes on connectionOf(...).find and the picker is never empty, even if a caller (or a stale HMR bundle) renders it without the list. * fix(app): tidy the step card — hostname label, single-step chrome, spacing - Connection picker showed the raw profile id: resolve the label to the hostname explicitly, and always include the step's own connection in the options so it matches. - Hide the reorder ↑/↓ (and remove) controls on a single-step automation — they only make sense with more than one step. - Keep "Runs on" on one line and let the picker flex, so the header no longer wraps in the narrow column. * feat(db): add BrowserAutomationDraft schema Server-side store for in-progress (unsaved) automations so a draft survives refreshes and follows the user across devices. A draft holds the composer state (steps as JSON) per task; it becomes a BrowserAutomation on save and is deleted then. Additive migration — new table only. * feat(api): browser automation draft endpoints CRUD for in-progress automation drafts (list per task, create, autosave via PATCH, delete), org-scoped through the task and gated by the same task:* perms as automations. Steps are stored as plain JSON so a half-written draft persists; the frontend deletes the draft once the automation is saved for real. * feat(app): autosave + resume browser automation drafts The composer autosaves an in-progress automation as you edit (debounced, create mode) to a server-side draft, so a refresh — or another device — resumes exactly where you left off. Drafts appear pinned in a dashed strip above the section with Continue / Delete. Saving finalizes the automation and discards its draft; canceling keeps it for later. Adds the drafts hook + DraftsStrip (+ tests). * feat(app): automation-centric evidence list with vendor chains (design 4a) Rework the list from connection-grouped to automation-centric: each row is one automation — which can span several vendors — showing its ordered vendor chain (GH → AWS → OK), and flags inline when one of its connections needs reconnect. Header shows the count + New evidence + Connect another; long lists get Load more. Connection health/management now lives on the Connections page, so the per-connection grouping and manage menu are dropped here. * fix(app): make the connect flow resume from anywhere on refresh Persist the URL from the moment it's entered (resume at the URL step) and the analysis once we have it (resume at the method chooser), and never store a live analysis-run token or sign-in session — both die on reload. A live sign-in now falls back to the method chooser (analysis kept) or the URL step instead of dropping to the empty "Connect a vendor" screen. So a refresh or navigation never loses your URL/progress; only the live sign-in itself must be re-entered (the remote browser session can't survive a reload). * fix(app): tighten the step-card type scale to fit the compact composer The DS Label/Textarea defaults (~14px) read oversized in the dense step card next to the 10px "Runs on" / 12.5px rows. Drop to a 13px label and shrink the textareas (and their placeholders) to 13px so everything sits on one scale. (DS Label/Textarea omit className, so this uses a plain label + an arbitrary-variant wrapper on the textarea.) * fix(app): shrink the step-card connection picker to match Bring the "Runs on" connection Select down to the step card's 13px scale (and a tighter 32px height) via a wrapper arbitrary-variant, since the DS Select omits className. * feat: capture a focused close-up beside the full-page evidence (option D) Evidence runs now also capture the agent's final viewport as a focused close-up, so a reviewer gets the proof readable (close-up) plus full context (full page) — robust, no flaky coordinate-cropping. The pass/fail reason also names the value and where it appears. Shown in the composer's test result; persisting it for scheduled-run history (schema + run detail) is the next step. * fix(app): draft vendor identity, no duplicate evidence shot, capped previews - Drafts now show the vendor monogram(s) + host (resolved from the step's connection) so a draft is identifiable, not a bare line. Monogram, not a fetched favicon — a favicon CDN would leak which vendors the org uses. - Only capture the close-up when the page is actually taller than one screen; when the page fits, the close-up equals the full page, so we skip the dup. - Cap the test-result screenshots at 360px (scroll within the box) so a tall full-page image no longer pushes the step timeline off-screen. * fix(app): compact the automation row Collapse the stacked last-run / big schedule picker / summary into one muted meta line, and move the cadence picker into the actions row (compact). The row is now two tidy lines instead of a tall five-line stack. * feat(app): show real vendor logos across browser automations Replace the two-letter monogram placeholders with each vendor's actual favicon in the Connections table, the drafts strip, and the automation list rows. Logos are served through a first-party /api/vendor-logo proxy so the browser never calls a third-party icon CDN directly (that would leak which vendors an org uses and trip the app CSP); the proxy caches hard and validates the host is a bare hostname. Falls back to a colored monogram while loading, on error, or when a site has no favicon. * fix(app): shrink the automation row's schedule and action controls The Daily schedule dropdown (legacy 36px trigger) and the 32px icon buttons made the right actions cluster read chunkier than the compact left content. Bring the whole cluster to a uniform 24px: narrower/shorter Daily picker and icon-sm buttons, matching the Run button. * feat(app): compact schedule menu in the automation row (designer option C) Replace the wide inline "Daily" schedule dropdown with a compact Calendar-icon menu placed next to Edit, per the designer's option C. The frequency (Daily/Weekly/Monthly/Quarterly/Yearly) is picked from a small radio dropdown wired to the existing onChangeSchedule; the schedule still reads in the meta line ("Runs Daily · next …"). This narrows the actions cluster by ~70px, fixing the crowding, while keeping the per-automation schedule editable — it's a distinct field from the task's own frequency and has no other UI to change it. Adds AutomationItem tests covering the schedule control. * feat(app): fold schedule into a Run split button; neutral action icons Merge the standalone schedule (calendar) button into the Run button as a split control — Run on the left, the cadence menu on the calendar half — so the actions row carries one fewer button. Also neutralize the pause icon (was primary-green) so the whole cluster reads as uniform muted icons; run state is still conveyed by the status dot, Paused pill, and dimmed row. * feat(app): match designer's C mockup for the automation row - Run becomes a solid-green split button: Run on the left, the schedule (calendar) menu on the right, split by a hairline — one entity. - Pause/Edit/Delete become outlined icon buttons with uniform muted glyphs (matches the mockup's bordered targets). - Mobile: the actions bar wraps onto its own full-width line below the content, icons left and the Run entity pushed right; desktop stays on one row. Status dot top-aligns to the name on mobile. * fix(app): neutral schedule half; clearer "first run" label - The calendar (schedule) half of the Run split is no longer green: Run stays solid primary, the calendar becomes a neutral outlined segment with a muted icon, joined as one control. - ScheduleSummary labels a never-run automation "first run" instead of "next". A never-run automation is due on the next tick for any cadence (server isDueToday treats null lastRunAt as always due), so the date is today for daily OR weekly; the date only reflects the cadence after the first run. This removes the "Runs Weekly · next: today" confusion. * feat(app): implement designer option A — unified Run split + meta grammar Run control (option A): a single unified split button — Run and a ▾ caret, both solid primary, split by a hairline; the caret opens the cadence list. Running state no longer shifts the layout: the play glyph swaps for a same-size (12px) spinner and the "Run" label stays, so the button keeps its exact width instead of widening to "Running…". Meta line: new AutomationMetaLine with a per-state grammar (one fact per clause, no restating): • never run → "First run today · then daily" (kills the old "Never run · … · first run" redundancy) • ran ok → "Last ran … · daily · next Jul 24" • failed → red "Last run failed …" · weekly · next Jul 28 • paused → "Paused · monthly schedule on hold" Next-run dates are formatted in UTC (formatMonthDayUtc) to avoid off-by-one in negative-UTC timezones. Shared schedule math extracted to schedule-utils and reused by ScheduleSummary. Tests cover all four meta states and the no-shift running label. * fix(app): remove the white seam in the Run split button The 25%-white hairline between Run and the ▾ caret read as a white gap. Drop it so the two halves are one seamless green pill; hover still darkens each half independently, and the caret keeps signalling the cadence menu. * feat(app): darker-green caret half to distinguish the schedule zone Give the ▾ cadence half of the Run split a slightly darker shade of the same primary (brightness-90, darker on hover) so it reads as a distinct dropdown zone — the standard split-button treatment — instead of a white seam. The two halves share the green; only the shade differs. * fix(app): match Run split icon sizes to the rest of the row The play icon, running spinner, and cadence caret were 12px while every other row icon (pause/edit/delete/expand) is 14px, so the Run split read as slightly undersized. Bump them to 14px for a consistent icon scale. * fix(browser-automations): show a screenshot per step for multi-step runs A multi-step (multi-vendor) automation captures one screenshot per step (stored in BrowserAutomationStepRun), but the run response only ever returned the single rolled-up run.screenshotUrl and the detail view only rendered that — so a GitHub→AWS automation showed just one screenshot. - API: include stepRuns (with their step's targetUrl) on run fetches and presign each step's screenshot via a shared presignRun helper. - App: BrowserAutomationRun gains stepRuns; RunDetailOverlay renders one labelled screenshot + verdict per step for multi-step runs, and a step with no screenshot now surfaces why (its error/reason) — which is how a silently-blocked later step becomes visible. Single-step keeps its layout. * feat(browser-automations): task-wide schedule in the section header Browser evidence runs daily across the board and there's no real need to schedule each automation separately, so move the cadence to one control in the "Browser evidence" header (calendar icon → Daily/Weekly/…). It sets the schedule for every automation on the task at once, and new automations inherit the task's current cadence. - API: PATCH automations/task/:taskId/schedule (SetTaskScheduleDto, task:update) bulk-updates every automation's scheduleFrequency; create inherits the task's existing cadence when none is given. Tests added. - App: useBrowserAutomations.setTaskSchedule; the header dropdown shows and sets the shared cadence. This removes the per-row schedule caret, so the Run split button is gone — Run is a plain primary button again, and the only remaining row arrow is the run-history expand chevron (no more duplicate down-arrows). Running state keeps its no-shift spinner. * feat(browser-automations): show the AI's live step list during Run Run now streams the AI's steps the same way the setup/Test screen does, instead of running quietly. The step list appears next to the live browser so you can see what the AI is doing ("signed in", "opened repo", "captured…"). - Run executes as a background task (execute-automation-live) so it can stream a step timeline via realtime metadata (runSteps) — mirroring the test-vendor-instruction flow. New POST automations/:id/execute-live returns the run handle; the live view subscribes with useRealtimeRun. - Extract the shared step-timeline builder (createEvidenceTimeline) reused by the Test service and the run execute path; executeAutomationOnSession gains an onSteps callback. - BrowserLiveView renders the StepList beside the browser during a run. Note: the realtime streaming only runs with the app + `trigger dev` up, so it can't be verified in CI — the logic mirrors the proven Test flow. * fix(app): cap the run screenshot preview height in the expanded run Full-page screenshots render very tall at full width. Cap the preview to max-h-80 with scroll; "Open full size" still opens the real image. * fix(browser-automations): interactive Run executes every vendor, not just the first Clicking Run only ran the first step (executeAutomationOnSession runs the single inline instruction), so a GitHub→AWS automation stopped after GitHub. Route the interactive Run through the full multi-step engine instead. - runSteps gains firstSessionId + onSteps: step 0 runs on the pre-opened live session (watchable), later vendors each get their own session; a combined step timeline streams to the Run view (with a "Step N · host" boundary per vendor). runStep picks executeEvidenceOnSession (live) vs runEvidence (fresh). - New executeAutomationLive runs the whole sequence on the existing run; the execute-automation-live task now calls it. Scheduled/unattended runs are unchanged (no firstSessionId/onSteps). Each vendor now produces its own step-run + screenshot (shown per step in the detail). Note: the live iframe shows the first vendor's session; later vendors run in their own sessions (watchable-live multi-vendor is a separate piece). * feat(browser-automations): step-ledger run detail + persist close-up screenshot Implements the designer's C3 layout for the run detail: each step is a compact ledger row (number, vendor, host, reason, verdict, "N screenshots"); expanding a row shows the close-up "proof" beside the scrollable full-page "context". Single-step runs auto-expand; multi-vendor runs list a row per vendor; failed steps show the reason in red with no screenshots. Shared RunStepLedger is used by both the inline run (RunItem) and the run-detail dialog (RunDetailOverlay), replacing their single-screenshot layouts. Also persist the close-up: the runner already captured and uploaded a focused viewport, but it was dropped on save. Add focusScreenshotUrl to BrowserAutomationRun + BrowserAutomationStepRun (migration), store it in finishRun/finishStepRun, and presign it alongside the full page — so the ledger shows both screenshots per step. * fix(app): align the two run-detail screenshot panes to one height The close-up and full-page panes rendered at different heights and looked uneven. Give both a fixed height: the close-up is contained/centered, the full page scrolls inside its box, so they line up side by side. * feat(browser-automations): follow each vendor's live view during a multi-step run The Run iframe only showed the first vendor while the steps panel advanced to the next — because each vendor is a separate browser session and only the first was wired to the live view. Each session already has a watchable live-view URL, so stream the current step's URL and swap the iframe to it as the run moves between vendors. - runEvidence exposes its session via an onSession callback; the step runner threads onLiveView through to it (fresh-session steps only — step 0 already runs on the visible live session). - execute-automation-live publishes the current live view to `liveViewUrl` realtime metadata; useBrowserExecution swaps the iframe when it changes. * feat(browser-automations): run-history ledger, truncated names, on/off switch Implements the designer's Run History and Row Fixes options 1A + 2A + 3A. 1A — run history is now a compact ledger: each run is a text row (date · vendor chain · verdict · N steps · M screenshots · duration) that expands into the per-step ledger. Removed the redundant thumbnail strip (and the run-detail dialog it opened) — screenshots live in exactly one place. RunItem rewritten as the run row; RunHistory is a flat bordered list. 2A — the automation name truncates to one line with the full text on hover (title), so long names/instructions no longer break the row. 3A — the enable/pause control is a real switch (teal = runs on, gray = paused) instead of a static power icon, so the paused state is visible. Deletes now-unused RunHistoryStrip + RunDetailOverlay (+ tests). * feat(app): tuck Edit + Delete into a ⋯ menu on the automation row The row was a wall of icons (switch, edit, delete, Run, chevron). Move the low-frequency Edit and Delete into an overflow (⋯) menu, leaving the row clean: pause switch · Run · ⋯ · expand chevron. Delete still confirms inline (replaces the actions with "Delete this automation? Delete / Cancel"). * feat(app): calmer automation row and deeper run history Row (1B + 2B): move the on/off switch to the left beside the name it governs and drop the redundant status dot; list one compact line per step (number + vendor logo + instruction, truncated with a tooltip) so what runs, in order, reads without expanding; fold the schedule meta inline with the name. Run + a ⋯ overflow menu (Edit steps / Delete) sit on the right, and the whole row is the expand target — the separate chevron is gone. Run history (3B): flatten the latest run — its step ledger with every screenshot shown inline, nothing to open — and collapse older runs into an "Earlier" chip strip; selecting a chip shows that run in place. Also add a flat mode to the step ledger (always-expanded, non-clickable rows) and an inline variant to the meta line, and drop the now-unused RunItem row. * fix(app): cover the live view between vendors so runs never look crashed A multi-vendor run opens a separate browser session per vendor and closes each one when its step finishes. When a session closes, Browserbase's live-view iframe briefly shows its own "WebSocket disconnected" notice — which, arriving right as the run wraps up, reads to the user like a crash, even though the evidence was already captured and saved server-side. Stream a live-view phase over the run's realtime metadata: the runner signals right before it tears a session down ('switching' when another vendor follows, 'finishing' on the last), and clears back to 'running' when the next vendor's live view opens. The Run view uses this to cover the iframe with a calm "Switching to the next vendor…/Saving evidence…" overlay during the teardown window, so the disconnect flash never reaches the user. Single-vendor and scheduled runs are unaffected. * feat(app): auto-expand a manual run's row when it finishes After clicking Run, the automation's row stayed collapsed on completion, so the user had to click it again to see the screenshots and verdict. Remember which automation the user ran, and once its fresh run has been refetched, expand that row automatically so the results show at once. The signal is a new object per completion, so re-running the same automation re-expands it. Scheduled runs don't go through this path and are unaffected. * fix(app): align the composer close button with the title The header close button sat at the top of the two-line title/subtitle block (items-start) with a tiny 12px icon, so it read as small and slightly off. Put it on the same row as the title (items-center), bump the icon to 16px to match the title text, and give it a slightly larger body with a subtle hover surface. * feat(app): richer "Browser evidence" header with a status strip The header carried only controls and no signal. Implement the designer's option A: title + subtitle stay left, a quiet status strip sits centered in the free space — aggregate run health (All passing / N failing / Not run yet), an overlapping stack of the distinct vendor logos, and the task-wide next run — and the actions stay right. On mobile the strip drops to its own line and the schedule + connect fold into a ⋯ menu; read-only keeps the strip and drops the actions. Extracted into BrowserEvidenceHeader to keep the list under the file-size limit. * fix(app): pin the Manage connection sheet's close to the top-right The DS SheetContent's built-in close button positions itself with a className on a DS Button, but DS Button hardcodes its own className and drops any passed in — so the close rendered unpositioned and fell to the bottom-left of the flex-col panel. Hide the built-in close (showCloseButton={false}) and render our own SheetClose pinned top-right. * feat(app): redesign the Manage connection panel (grouped sections) Implement the designer's option A: a favicon-anchored identity header with the vendor logo, name, host, and a status pill (plus a blocked reason when relevant); a bordered facts block; SESSION and DETAILS groups; and a fenced danger zone pinned to the bottom (SheetFooter). Reconnect goes solid teal only when the session needs attention, and degrades to a "view access" note for read-only users. SSO connections hide "Change login" (no stored password) and the credentials row. * refactor(app): reuse the shared connect flow on the Connections page The org-level Connections page had its own bespoke connect/reconnect: a raw interactive-browser iframe that required signing in entirely by hand, which was unreliable (clicks often didn't register). Replace it with the same ConnectVendorLoginFlow the evidence task uses — method detection, automated credential entry, and a working live takeover only when needed — for both connecting a new vendor and reconnecting an existing one (seed derived from the connection's method). Delete the bespoke BrowserConnectionLiveView and its session/navigate/verify machinery. The task flow is untouched; the flow is only cosmetically task-scoped (a localStorage resume key), so the page passes a stable "org-connections" key. Management (rename / change login / remove) is unchanged. * feat(app): detected-field sign-in form + passkey-aware method chooser Part 1 (designer 1A): the capture form is now built from what we detect on the vendor's login page — the actual fields, in order, with the vendor's labels — instead of a fixed email+password form. A detected field that IS the identifier (e.g. AWS "IAM username") is promoted to the identifier so it's no longer duplicated by a generic "Username or email", and email isn't shown when the site has no email login. Detection failures fall back to the generic form + "add a field". A pure deriveCaptureFields() drives it; the form maps back to the unchanged {username, password, totpSeed, extraFields} runtime contract on submit, so the working sign-in is untouched. Part 2 (designer 1E): the method chooser becomes radio cards (password recommended, SSO neutral) + Continue, with a clear footnote when the site also offers passkey (device-bound — can't work in the cloud browser), plus dedicated passkey-only and nothing-detected states. Softened the SSO copy (dropped the "can't run unattended / we'll email you" warning — reconnect is universal and already surfaced when it happens). Multi-step logins (email → Continue → password) already work at runtime and are unchanged. * fix(app): honest method-card label + "Comp AI" branding in connect copy Name the credentials method after what the vendor actually asks for — "Username & password" when the identifier is a username (e.g. AWS IAM), "Email & password" otherwise — instead of always saying "Email" when the site has no email login. Also correct the product name in the browser-automations / connections copy: standalone "Comp" → "Comp AI" (method chooser, live sign-in, empty states, connections page). * fix(api): use Stagehand mode:'cua' instead of deprecated cua:true Silences the "[DEPRECATED] The 'cua: true' option is deprecated. Use 'mode: cua' instead" warning from Stagehand's agent. Cosmetic — behavior is unchanged. * fix(api): don't mark a connection verified while still on the sign-in page The sign-in flow short-circuits to "already signed in → verified" when a classifier reads the page as logged-in. On vendors that bounce through sign-in hosts (e.g. AWS redirecting via signin.aws.amazon.com), the classifier misread a mid-redirect / sign-in page as logged-in, so the connection was marked verified instantly — without ever entering credentials or reaching the real form (the reported false success). Guard both the pre-fill "already signed in?" check and the post-fill verification with looksLikeSignInUrl(): a genuinely signed-in user is redirected OFF the sign-in host, so a "logged in" verdict while still on a signin/login/auth/sso URL is treated as a false positive (proceed to sign in / report failure instead of verifying). Also strengthen the classifier prompt to require positive evidence of being signed in and to never answer logged_in for a blank, loading, or redirecting page. * refactor(api): let the classifier judge login by URL + content, not a regex Replace the hardcoded sign-in URL patterns (looksLikeSignInUrl) with a URL-aware classifier: pass the browser's current URL into the login classifier so the model itself decides whether we're on the real app or still on a sign-in / identity-provider page. It generalizes to any vendor (it already knows hosts like signin.aws.amazon.com or login.microsoftonline.com) without us enumerating patterns, and still fixes the AWS false "signed in" — the classifier no longer calls a mid-redirect / sign-in page "logged in". The URL is a hint layered on the page content; it degrades to content-only when unavailable. * fix(api): wait for sign-in redirects to settle before reading/filling navigateToSignIn opened the "Sign in" link then waited a flat 1.5s — too short for vendors that bounce through a redirect chain (e.g. AWS from the marketing homepage → signin.aws.amazon.com), so classification and credential fill ran against the stale homepage (no fields), producing the misleading "Entering username/password" steps on a page that has none. Now wait until the URL stops changing (bounded ~5.6s) before proceeding, so we act on the real sign-in form. Also nudge the instruction to include "Sign in to Console"-style links. (For AWS, pasting the direct sign-in URL still avoids the homepage hunt entirely.) * feat(app): show the vendor's real identifier-field label in sign-in steps The live sign-in steps showed a generic "Entering username" even when the vendor's field is something else (e.g. AWS "IAM username") — extra fields already stream their detected labels, but the identifier didn't, which read as hardcoded. Thread the detected identifier label from the capture form through the sign-in call to the step log, so the streamed step names the real field. Kept out-of-band from credential storage/resolution (no change to how secrets are stored) and the fill stays semantic, so nothing about the working sign-in changes; scheduled runs fall back to the generic label. Password label is universal, left as-is. * fix(app): don't trap the connect flow on a stale resumed step Cancelling the connect flow left its resume-state in localStorage, so re-opening "Connect a vendor" reloaded the last in-progress step (e.g. "choose sign-in method" for the previous vendor) with no way back — the user couldn't start a new vendor or escape. handleCancel now clears the resume-state (fixes both the Connections page and the evidence task, since the flow is shared). The Connections page additionally clears on open, so "Connect a vendor" there always starts fresh at URL entry (a settings page has no "resume where I left off"). * fix(api): bring the sign-in page to front so the live view follows it When a vendor opens its sign-in in a new tab (e.g. AWS from its marketing homepage), the AI signed in on the real form — reaching the 2FA prompt — but the session live view kept showing the front tab (the homepage), so the user couldn't see the sign-in page or complete the 2FA take-over. Evidence runs already handle this via resolveEvidencePage + bringEvidencePageToFront; the connect sign-in never did. Reuse resolveEvidencePage after navigating to the sign-in form to bring the right page to front (best-effort). For AWS, pasting the direct sign-in URL also reduces the redirect/tab hopping. * revert(api): drop the sign-in bring-to-front (picked the wrong tab) resolveEvidencePage prefers the initial page when the target hostname doesn't match (AWS's entered URL redirects to a different host), so it brought the marketing homepage to front instead of the sign-in tab — making the live-view mismatch worse. Revert to reassess with a correct multi-tab approach. * feat(app): live view follows the AI's active tab during sign-in When a vendor opens its sign-in in a new tab (e.g. AWS from its homepage), the AI worked on that tab but the live view stayed pinned to the original (the marketing homepage), so the user couldn't watch — or complete a 2FA take-over on — the real form. Follow the active tab (designer option B): after navigating to the sign-in (and after the SSO click), resolve the live-view URL of the tab the AI is actually on via Browserbase `sessions.debug()` per-tab URLs, and stream it over the run's metadata as `signinLiveViewUrl`; the connect flow re-points its iframe to it. Tab selection (match by URL, then hostname, then newest) is a pure `selectLiveViewUrl` with unit tests. Best-effort — the live view keeps its current tab if lookup fails. * fix(api): screenshot the tab the agent ended on, not the stale start page An automation's screenshot and its pass/fail check disagreed: the check read the real pa…
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
There was a problem hiding this comment.
39 issues found across 157 files
Confidence score: 2/5
- In
InstructionComposer.tsxanduseBrowserExecution.ts, cancel/switch races can leave in-flight Browserbase runs alive, then attach stale results to the wrong step or keep executing after the user canceled; this is a direct regression risk for task correctness and evidence integrity — tie responses to a request generation and propagate cancelation throughexecute-liveend-to-end. apps/app/src/app/api/vendor-logo/route.tscurrently permits unauthenticated outbound fetches, which can be abused as a public proxy and bypass expected RBAC boundaries — apply the shared request/auth guard on this route before exposing it.- In
browser-auth-profile.service.tsandbrowser-instruction-test.service.ts, profile deletion/status handling can make tests pass while saved automations later run under a different or invalid account, creating silent account drift and misleading success signals — preserve unresolved bindings and enforce the same profile-status gate in test execution. browser-credential-storage.service.tsandbrowser-evidence-execution.tsintroduce persistence/replay hazards: credential re-saves can orphan old 1Password items, and broad instruction retries after partial side effects can duplicate submissions — reuse or delete superseded vault items and restrict retries to known-safe failure classes.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserExecution.ts">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserExecution.ts:133">
P1: Canceling the live view does not cancel the background automation: it only closes the current Browserbase session, while `execute-live` can continue into later vendor steps and persist evidence after the user canceled. A cancellation path for the Trigger run and/or the persisted automation run would keep canceled actions from continuing.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/settings/browser-connection/components/BrowserConnectionClient.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/settings/browser-connection/components/BrowserConnectionClient.tsx:41">
P2: Users with `integration:create` but without `integration:update` can open this connect flow, but it cannot complete after credentials are entered because the required save/sign-in/verification calls are update-protected. Derive `canConnect` from both permissions (or otherwise gate the flow to the permissions it requires) so custom RBAC roles are not offered an action they cannot finish.
(Based on your team's feedback about RBAC permission checks.) [FEEDBACK_USED].</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx:308">
P2: Moving an inactive step changes which step is active. Because `handleMove` always sets `activeIndex` to the moved step's destination, swapping an inactive step with the active step leaves the formerly inactive step selected and makes the test pane target the wrong instruction. The active index can remain unchanged for unrelated moves, and should swap indices only when the active step is one of the two moved steps.</violation>
<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx:336">
P1: Cancelling or switching steps while the test-start request is in flight can leak a Browserbase session and attach the stale run to the wrong step. The returned handle needs to be tied to a request generation/mounted state; stale handles should be closed immediately instead of being committed to component state.</violation>
<violation number="3" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx:348">
P2: Editing an automation drops its saved path-level target URL. The composer discards `BrowserAutomationStep.targetUrl` during initialization and rebuilds every target from the connection origin on save, so an edit can make the automation start at the wrong page. Preserve each step's target URL (and the first step's top-level URL) through the editable state.</violation>
</file>
<file name="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts">
<violation number="1" location="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts:98">
P2: A 1Password outage or authentication failure is silently converted into an absent username/password, so automated sign-in falls back as if the profile had no credentials and the actual vault failure is hidden. Handling only a known missing-field error as optional and rethrowing other failures would preserve outage visibility and retry/error behavior.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:86">
P2: Editing while the initial draft POST is in flight drops the newer payload, leaving the server draft stale after the request completes. Queue the latest payload and update the created draft once its ID is available instead of returning without retrying.</violation>
<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:208">
P2: Creating new evidence on an existing task can select the wrong vendor: any GitHub profile is preferred because `authUrl` defaults to GitHub, even when the task's automations use another host. The just-connected branch should only be considered when `justConnected` is true, then fall back to the task's existing connection.</violation>
</file>
<file name="apps/api/src/browserbase/browser-login-navigation.ts">
<violation number="1" location="apps/api/src/browserbase/browser-login-navigation.ts:8">
P2: With multiple tabs open, redirect settling can watch the newest tab instead of the tab Stagehand is actively operating, so extraction or credential filling may begin while the acted-on page is still navigating. Reading `context.activePage()` keeps the URL hint aligned with Stagehand's action target.</violation>
</file>
<file name="apps/api/src/browserbase/browserbase.service.ts">
<violation number="1" location="apps/api/src/browserbase/browserbase.service.ts:300">
P2: A transient Browserbase lookup failure is reported as a successful close while the live session can remain running until its timeout. The close path could tolerate only a confirmed missing-session response and rethrow availability/transport errors so callers can retry and cleanup failures remain visible.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserProfiles.ts">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserProfiles.ts:20">
P1: On first load, an API or network error leaves `profiles` as `[]` — indistinguishable from "no connections yet." The in-line comment recognizes this masking problem but doesn't solve it for the initial-load case. Add an `error` / `isError` state so consumers can render a distinct error/retry UI instead of showing an empty list. See `useTaskAutomations` and `useTask` for the established pattern.</violation>
</file>
<file name="apps/api/src/browserbase/browser-credential-signin.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-credential-signin.service.ts:228">
P2: When a vendor opens the sign-in or landing page in a new tab, a successful run can return the original tab's URL as `homeUrl`, causing the connect flow to save a URL that still leads to login on future runs. Selecting the newest active page here would keep the returned target consistent with the page used for classification and live-view updates.</violation>
</file>
<file name="apps/api/src/browserbase/browser-auth-profile.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-auth-profile.service.ts:75">
P2: Automation dependency counts are incorrect for multiple identities and multi-step automations: host-only counting assigns the same total to every same-host connection and misses profiles used by later steps. Counting the actual step/profile bindings would keep the settings table and deletion warnings accurate.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-auth-profile.service.ts:283">
P2: Changing a connection to another hostname leaves it unable to reconnect: the new URL is stored while the profile's hostname remains the old one, and the sign-in guard later rejects that URL. Reject cross-host edits or create a new profile instead of persisting a URL that cannot pass the profile guard.</violation>
<violation number="3" location="apps/api/src/browserbase/browser-auth-profile.service.ts:308">
P1: Deleting a connection can silently switch an automation to another account: the step binding is nulled by the profile delete, then execution falls back to a verified profile with the same hostname. Preserving an unresolved binding or preventing deletion while steps reference the profile would make the automation stop until explicitly reconnected.</violation>
</file>
<file name="apps/api/src/browserbase/browser-mfa-instructions.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-mfa-instructions.service.ts:154">
P2: Cache misses can trigger duplicate and attacker-chosen numbers of Firecrawl/Claude requests because in-flight generations are not deduplicated or rate-limited. Tracking pending promises per hostname and applying endpoint-level cost/rate controls would prevent a permitted caller from turning this endpoint into a provider-cost or availability drain.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-mfa-instructions.service.ts:289">
P2: Malformed `host` values are silently accepted and fed into both web search and model generation, allowing request text to poison the generated and cached guidance. Invalid hostnames/URLs should be rejected at the API boundary instead of falling back to the raw query string.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useInstructionTest.ts">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useInstructionTest.ts:41">
P2: Navigating away while the test-start request is in flight can leak the newly created Browserbase session: this hook returns the handle after the caller's cleanup has already run, so no subsequent `closeTestSession` call is made. An abort/unmount-aware cleanup path would ensure a late response closes `sessionId` instead of leaving the `keepAlive` session open.</violation>
</file>
<file name="apps/api/src/browserbase/browser-automation-step-results.ts">
<violation number="1" location="apps/api/src/browserbase/browser-automation-step-results.ts:110">
P2: Multi-step runs drop every step's focused screenshot from the rolled-up result. `finishRun` persists `result.focusScreenshotKey`, so the parent run has no focused proof even though each step produced one; carry the selected step's focus fields through the roll-up.</violation>
</file>
<file name="apps/api/src/browserbase/browser-automation-crud.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-automation-crud.service.ts:202">
P2: Legacy edits to `targetUrl`, `instruction`, or `evaluationCriteria` return a stale first step. The parent update loads `steps` before the mirror `updateMany`, so callers must refetch to see the change; updating the step first or re-reading the automation before returning would keep the response consistent.</violation>
</file>
<file name="apps/api/src/browserbase/browser-credential-storage.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-credential-storage.service.ts:75">
P1: Re-saving credentials leaves the previous 1Password item orphaned while retaining its username, password, and TOTP seed in the organization vault. Updating/reusing the existing item, or deleting the superseded item after a successful reference update with failure-safe cleanup, would prevent stale secret retention.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-credential-storage.service.ts:306">
P2: A crafted extra-field label can inject instructions into the Stagehand action used during automated sign-in while `%value%` carries a stored secret, potentially redirecting where that secret is entered or what the agent clicks. Treat labels as constrained data or avoid interpolating untrusted labels into the model prompt before using this credential path.</violation>
</file>
<file name="apps/api/src/browserbase/browser-credential-login.ts">
<violation number="1" location="apps/api/src/browserbase/browser-credential-login.ts:114">
P2: Profiles with a TOTP are submitted twice after any failed auth check, including invalid credentials or a human challenge, not just an expired code. Gating this retry on a 2FA-specific outcome would avoid unnecessary submissions and reduce account-lockout or challenge side effects.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-credential-login.ts:215">
P2: Evidence runs can try to fill a homepage instead of the vendor's login form and then report `needs_reauth`: this helper calls `performCredentialLogin` immediately, unlike the connect flow, which first calls `navigateToSignIn`. Starting each attempt by navigating to the sign-in form would cover target URLs that show only a Sign in link.</violation>
<violation number="3" location="apps/api/src/browserbase/browser-credential-login.ts:325">
P2: The rotation-boundary retry replays the entire username/password flow on the existing 2FA page instead of submitting only the refreshed code. Providers that expose only a code field can reject or restart these actions, turning a recoverable OTP retry into an exception or `unknown` result; the retry should target the current code prompt or explicitly restart navigation.</violation>
</file>
<file name="apps/api/src/browserbase/browser-evidence-execution.ts">
<violation number="1" location="apps/api/src/browserbase/browser-evidence-execution.ts:57">
P2: Setting `BROWSERBASE_CUA_MODEL` to a non-OpenAI, non-Anthropic provider causes CUA authentication to use the Anthropic key and fail before navigation. Provider selection should resolve the matching credential (or leave the key unset for Stagehand's provider-specific environment resolution) instead of treating every non-OpenAI model as Anthropic.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-evidence-execution.ts:268">
P1: A navigation failure after a side effect now replays the entire instruction from the partially modified browser state, which can duplicate submissions or other mutations. Fallback would be safer only for errors known to occur before actions or for explicitly retry-safe failures.</violation>
</file>
<file name="apps/api/src/browserbase/browserbase-session.service.ts">
<violation number="1" location="apps/api/src/browserbase/browserbase-session.service.ts:32">
P2: Setting `BROWSERBASE_STAGEHAND_MODEL` to a non-Anthropic model makes Stagehand `extract()`/`act()` calls authenticate with `ANTHROPIC_API_KEY`, so the configured model fails unless the keys happen to match. Provider-key resolution should follow the model prefix, or the override should be restricted to `anthropic/*`.</violation>
</file>
<file name="apps/app/src/app/api/vendor-logo/route.ts">
<violation number="1" location="apps/app/src/app/api/vendor-logo/route.ts:12">
P1: This customer-facing API route allows unauthenticated callers to use the app as an outbound DuckDuckGo proxy, bypassing the repository's RBAC contract and enabling unrestricted request abuse. Applying the shared `requireApiPermission` check (for example, `vendor:read`) before parsing or fetching would enforce the active organization permission without hardcoded role checks.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useTotpStatus.ts">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useTotpStatus.ts:29">
P2: A vault read failure is presented as "Automatic 2FA off" rather than unknown, because this fallback treats the API's failure sentinel `{ configured: false }` as a confirmed disabled state. Preserving an explicit unknown/error result from the status endpoint would prevent users from being offered a setup flow that can replace an existing key.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/AutomationItem.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/AutomationItem.tsx:82">
P2: Keyboard users cannot reliably activate nested controls: Enter/Space events bubble to the row, which expands history and prevents the child button's default action. Restrict row keyboard handling to events whose target is the row itself (or stop key propagation from each nested control).</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/connect-capture-fields.ts">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/connect-capture-fields.ts:22">
P2: Required fields such as `Email domain` or `Login domain` can be classified as the identifier because the regex matches any `e-mail`/`login` substring, causing the actual username field to be rendered as an extra or omitted and `username` to receive the wrong value. A constrained whole-label match or detector-provided identifier metadata would avoid promoting arbitrary site-specific fields.</violation>
<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/connect-capture-fields.ts:63">
P1: Sites whose detected order is `[identifier, extra]` can fail automated sign-in because the extra is filled before the identifier, even though this form displays it after the identifier. Removing the promoted identifier from the ordered list and sending the remaining fields through `extraFields` loses its position; preserving order requires an ordered runtime contract or a corresponding backend change.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx:21">
P2: Passwords containing leading or trailing spaces are silently stripped before they reach the sign-in flow, so valid credentials can be rejected and the stored secret is incorrect. The password value should be validated for non-whitespace content without using a transforming `.trim()` schema.</violation>
<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx:102">
P1: Vendor-controlled field labels are allowed to become instructions in the Stagehand prompt, which can make automated sign-in perform unintended page actions. Field targeting should use a structured/allow-listed representation, or labels should be sanitized and kept out of the model instruction text.</violation>
</file>
<file name="apps/api/src/browserbase/browser-instruction-test.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-instruction-test.service.ts:72">
P1: A test can report success for a blocked or unverified connection even though the corresponding saved automation will not run. Applying the same profile-status gate used by `BrowserAutomationStepRunnerService` before invoking the evidence runner would keep the preview result consistent with unattended execution.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectMethodChooser.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectMethodChooser.tsx:71">
P2: When `analysis` changes while the chooser remains mounted, Continue can submit a method that is no longer detected because `selected` is initialized only once. Keep the selection synchronized with the current `options`, or fall back to the first available option before rendering and submitting.
(Based on your team's feedback about resyncing local state when props change.) [cfebfe92-296b-49c2-8b2a-f09bf3f8941d]</violation>
</file>
<file name="apps/api/src/browserbase/browser-automation-execution.service.ts">
<violation number="1" location="apps/api/src/browserbase/browser-automation-execution.service.ts:192">
P1: A duplicate or late live task can continue driving browser sessions after the parent run is no longer active, because this multi-step path never revalidates the run before each step. Passing an active-run assertion through `runSteps` (as the existing session path does) would prevent post-terminal actions and duplicate evidence.</violation>
<violation number="2" location="apps/api/src/browserbase/browser-automation-execution.service.ts:207">
P2: Some bookkeeping or profile-health failures leave the parent run stuck in `running` because all `ConflictException` and `NotFoundException` errors are rethrown before `finishRun`. Replay detection should be isolated to an explicit active-run check, while failures from step bookkeeping and profile updates should be converted to a failed result and finalized.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| setPhase('idle'); | ||
| return; | ||
| } | ||
| setSessionId(handle.sessionId); |
There was a problem hiding this comment.
P1: Cancelling or switching steps while the test-start request is in flight can leak a Browserbase session and attach the stale run to the wrong step. The returned handle needs to be tied to a request generation/mounted state; stale handles should be closed immediately instead of being committed to component state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx, line 336:
<comment>Cancelling or switching steps while the test-start request is in flight can leak a Browserbase session and attach the stale run to the wrong step. The returned handle needs to be tied to a request generation/mounted state; stale handles should be closed immediately instead of being committed to component state.</comment>
<file context>
@@ -0,0 +1,470 @@
+ setPhase('idle');
+ return;
+ }
+ setSessionId(handle.sessionId);
+ setLiveViewUrl(handle.liveViewUrl);
+ setTestRun({ runId: handle.runId, accessToken: handle.publicAccessToken });
</file context>
| // apiClient resolves (doesn't throw) on an HTTP error — keep the existing | ||
| // list on failure instead of blanking it (which would mask the error as | ||
| // "no connections"). | ||
| if (res.error) return; |
There was a problem hiding this comment.
P1: On first load, an API or network error leaves profiles as [] — indistinguishable from "no connections yet." The in-line comment recognizes this masking problem but doesn't solve it for the initial-load case. Add an error / isError state so consumers can render a distinct error/retry UI instead of showing an empty list. See useTaskAutomations and useTask for the established pattern.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserProfiles.ts, line 20:
<comment>On first load, an API or network error leaves `profiles` as `[]` — indistinguishable from "no connections yet." The in-line comment recognizes this masking problem but doesn't solve it for the initial-load case. Add an `error` / `isError` state so consumers can render a distinct error/retry UI instead of showing an empty list. See `useTaskAutomations` and `useTask` for the established pattern.</comment>
<file context>
@@ -0,0 +1,32 @@
+ // apiClient resolves (doesn't throw) on an HTTP error — keep the existing
+ // list on failure instead of blanking it (which would mask the error as
+ // "no connections").
+ if (res.error) return;
+ setProfiles(Array.isArray(res.data) ? res.data : []);
+ } catch {
</file context>
| }); | ||
|
|
||
| const { ItemCategory } = await loadOnePasswordModule(); | ||
| const item = await client.items.create({ |
There was a problem hiding this comment.
P1: Re-saving credentials leaves the previous 1Password item orphaned while retaining its username, password, and TOTP seed in the organization vault. Updating/reusing the existing item, or deleting the superseded item after a successful reference update with failure-safe cleanup, would prevent stale secret retention.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-storage.service.ts, line 75:
<comment>Re-saving credentials leaves the previous 1Password item orphaned while retaining its username, password, and TOTP seed in the organization vault. Updating/reusing the existing item, or deleting the superseded item after a successful reference update with failure-safe cleanup, would prevent stale secret retention.</comment>
<file context>
@@ -0,0 +1,314 @@
+ });
+
+ const { ItemCategory } = await loadOnePasswordModule();
+ const item = await client.items.create({
+ category: ItemCategory.Login,
+ vaultId,
</file context>
| const finalize = (ok: boolean, message?: string) => { | ||
| if (ok) toast.success('Automation completed successfully'); | ||
| else toast.error(message || 'Automation failed'); | ||
| if (sessionId) void closeSession(sessionId); |
There was a problem hiding this comment.
P1: Canceling the live view does not cancel the background automation: it only closes the current Browserbase session, while execute-live can continue into later vendor steps and persist evidence after the user canceled. A cancellation path for the Trigger run and/or the persisted automation run would keep canceled actions from continuing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserExecution.ts, line 133:
<comment>Canceling the live view does not cancel the background automation: it only closes the current Browserbase session, while `execute-live` can continue into later vendor steps and persist evidence after the user canceled. A cancellation path for the Trigger run and/or the persisted automation run would keep canceled actions from continuing.</comment>
<file context>
@@ -32,81 +86,94 @@ export function useBrowserExecution({ onNeedsReauth, onComplete }: UseBrowserExe
+ const finalize = (ok: boolean, message?: string) => {
+ if (ok) toast.success('Automation completed successfully');
+ else toast.error(message || 'Automation failed');
+ if (sessionId) void closeSession(sessionId);
+ // Capture which automation ran before reset clears it, then auto-expand it
+ // once its fresh run is refetched — so a manual Run shows its results
</file context>
| * directly — that would leak which vendors an org uses and trip the app CSP. | ||
| * Cached hard (logos rarely change); the caller falls back to a monogram on 404. | ||
| */ | ||
| export async function GET(req: NextRequest): Promise<NextResponse> { |
There was a problem hiding this comment.
P1: This customer-facing API route allows unauthenticated callers to use the app as an outbound DuckDuckGo proxy, bypassing the repository's RBAC contract and enabling unrestricted request abuse. Applying the shared requireApiPermission check (for example, vendor:read) before parsing or fetching would enforce the active organization permission without hardcoded role checks.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/api/vendor-logo/route.ts, line 12:
<comment>This customer-facing API route allows unauthenticated callers to use the app as an outbound DuckDuckGo proxy, bypassing the repository's RBAC contract and enabling unrestricted request abuse. Applying the shared `requireApiPermission` check (for example, `vendor:read`) before parsing or fetching would enforce the active organization permission without hardcoded role checks.</comment>
<file context>
@@ -0,0 +1,36 @@
+ * directly — that would leak which vendors an org uses and trip the app CSP.
+ * Cached hard (logos rarely change); the caller falls back to a monogram on 404.
+ */
+export async function GET(req: NextRequest): Promise<NextResponse> {
+ const host = req.nextUrl.searchParams.get('host')?.toLowerCase().trim() ?? '';
+ if (!host || host.length > 253 || !HOSTNAME.test(host)) {
</file context>
| Boolean(field.value?.trim()), | ||
| ) | ||
| .map((field) => ({ label: field.title, value: field.value })); | ||
| } catch { |
There was a problem hiding this comment.
P2: A 1Password outage or authentication failure is silently converted into an absent username/password, so automated sign-in falls back as if the profile had no credentials and the actual vault failure is hidden. Handling only a known missing-field error as optional and rethrowing other failures would preserve outage visibility and retry/error behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/onepassword-credential-vault.adapter.ts, line 98:
<comment>A 1Password outage or authentication failure is silently converted into an absent username/password, so automated sign-in falls back as if the profile had no credentials and the actual vault failure is hidden. Handling only a known missing-field error as optional and rethrowing other failures would preserve outage visibility and retry/error behavior.</comment>
<file context>
@@ -0,0 +1,117 @@
+ Boolean(field.value?.trim()),
+ )
+ .map((field) => ({ label: field.title, value: field.value }));
+ } catch {
+ return [];
+ }
</file context>
| error instanceof Error ? error.message : String(error) | ||
| }`, | ||
| ); | ||
| return; |
There was a problem hiding this comment.
P2: A transient Browserbase lookup failure is reported as a successful close while the live session can remain running until its timeout. The close path could tolerate only a confirmed missing-session response and rethrow availability/transport errors so callers can retry and cleanup failures remain visible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browserbase.service.ts, line 300:
<comment>A transient Browserbase lookup failure is reported as a successful close while the live session can remain running until its timeout. The close path could tolerate only a confirmed missing-session response and rethrow availability/transport errors so callers can retry and cleanup failures remain visible.</comment>
<file context>
@@ -91,6 +269,67 @@ export class BrowserbaseService {
+ error instanceof Error ? error.message : String(error)
+ }`,
+ );
+ return;
+ }
+ if (!contextId) return;
</file context>
| ); | ||
|
|
||
| return { | ||
| configured: data?.configured ?? false, |
There was a problem hiding this comment.
P2: A vault read failure is presented as "Automatic 2FA off" rather than unknown, because this fallback treats the API's failure sentinel { configured: false } as a confirmed disabled state. Preserving an explicit unknown/error result from the status endpoint would prevent users from being offered a setup flow that can replace an existing key.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useTotpStatus.ts, line 29:
<comment>A vault read failure is presented as "Automatic 2FA off" rather than unknown, because this fallback treats the API's failure sentinel `{ configured: false }` as a confirmed disabled state. Preserving an explicit unknown/error result from the status endpoint would prevent users from being offered a setup flow that can replace an existing key.</comment>
<file context>
@@ -0,0 +1,36 @@
+ );
+
+ return {
+ configured: data?.configured ?? false,
+ isLoading: enabled && isLoading,
+ // Surface the failure so the UI can show an "unknown" state instead of
</file context>
| await record('logged_in'); | ||
| finish('done'); | ||
| // Re-read the active page: signing in usually navigates to an app/home page. | ||
| const landed = await this.sessions.ensureActivePage(activeStagehand); |
There was a problem hiding this comment.
P2: When a vendor opens the sign-in or landing page in a new tab, a successful run can return the original tab's URL as homeUrl, causing the connect flow to save a URL that still leads to login on future runs. Selecting the newest active page here would keep the returned target consistent with the page used for classification and live-view updates.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-signin.service.ts, line 228:
<comment>When a vendor opens the sign-in or landing page in a new tab, a successful run can return the original tab's URL as `homeUrl`, causing the connect flow to save a URL that still leads to login on future runs. Selecting the newest active page here would keep the returned target consistent with the page used for classification and live-view updates.</comment>
<file context>
@@ -0,0 +1,256 @@
+ await record('logged_in');
+ finish('done');
+ // Re-read the active page: signing in usually navigates to an app/home page.
+ const landed = await this.sessions.ensureActivePage(activeStagehand);
+ return { isLoggedIn: true, homeUrl: landed.url() };
+ }
</file context>
|
|
||
| return profiles.map((profile) => ({ | ||
| ...profile, | ||
| automationCount: countByHost.get(profile.hostname) ?? 0, |
There was a problem hiding this comment.
P2: Automation dependency counts are incorrect for multiple identities and multi-step automations: host-only counting assigns the same total to every same-host connection and misses profiles used by later steps. Counting the actual step/profile bindings would keep the settings table and deletion warnings accurate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-auth-profile.service.ts, line 75:
<comment>Automation dependency counts are incorrect for multiple identities and multi-step automations: host-only counting assigns the same total to every same-host connection and misses profiles used by later steps. Counting the actual step/profile bindings would keep the settings table and deletion warnings accurate.</comment>
<file context>
@@ -41,10 +47,33 @@ export class BrowserAuthProfileService {
+
+ return profiles.map((profile) => ({
+ ...profile,
+ automationCount: countByHost.get(profile.hostname) ?? 0,
+ }));
}
</file context>
…ks (#3507) * fix(api): scheduled browser runs must not overwrite a not_relevant task A scheduled browser automation flipped any non-done/non-failed task to done or failed — including tasks a human deliberately marked not_relevant (with a justification). That silently destroyed the compliance decision. Guard both the done and failed transitions with isTaskStatusProtectedFromAutomation, matching the codebase norm (cloud-security skips not_relevant as user intent). +tests. * fix(api): make the not_relevant guard an atomic conditional update The status check and the update were separate queries, so a human marking a task not_relevant between them could still be overwritten. Move the guard into the WHERE clause of an atomic updateMany (excluding done/failed + not_relevant), and derive statusChangedToFailed from whether a row actually changed. * test(api): worker-level coverage for the atomic not_relevant guard Assert the worker's done and failed transitions both go through updateMany with a status filter that excludes not_relevant (and done/failed), and that a zero-count result is treated as no transition — so a regression in either atomic filter is caught instead of silently letting not_relevant be overwritten.
This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.
Summary by cubic
Production release for browser automations: adds credential-backed auto sign-in via 1Password, a smart connect flow with live sign-in, multi-step automations with per-step evidence and live streaming, and an org-level Connections page. Also safeguards scheduled runs from changing tasks marked not_relevant.
New Features
Migration
@1password/sdk(loaded lazily).Written for commit 99393e5. Summary will update on new commits.