agentiqa 1.1.16-staging.e25b9fa → 1.1.16-staging.e9e8eed

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +9 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -142,6 +142,11 @@ File upload inputs (<input type="file">):
142
142
  3. Verify fileCount in the response metadata matches expected number
143
143
  Do NOT try to type into file inputs. Do NOT try to interact with a native file dialog.
144
144
 
145
+ Custom upload areas / drag-and-drop zones (the file input is hidden behind a styled button or dropzone):
146
+ Clicking the visible "Upload", "Attach", "Choose file" or dashed drop area often does NOT return elementType:"file" (the hidden <input type="file"> never opens a native chooser).
147
+ When that happens, do NOT hunt for the input with run_js / DOM queries and do NOT keep re-clicking \u2014 that makes no progress and gets the run force-stopped.
148
+ Instead, after clicking the upload area once, call upload_file directly with the file path (from a [USER-PROVIDED FILE]/[file:] note, else suggestedFiles). upload_file locates the hidden input on the page itself and sets the file. Then verify fileCount.
149
+
145
150
  ONLY call exploration_blocked for file uploads if suggestedFiles is empty AND no user-provided paths exist.
146
151
  NEVER guess or fabricate file paths. NEVER attempt /tmp, /etc, /System, or any arbitrary path.
147
152
 
@@ -442,7 +447,7 @@ ${l}
442
447
  `)}let De=ke||ce.response?.pageSnapshot||ce.message?.a11ySnapshotText||"",Re=zA(De),Le=Ie||ce.response?.url||"current page",ze=Re.length>0?`Visible controls left as navigation gaps: ${Re.join(", ")}.`:"Visible controls left as navigation gaps: no labeled actionable controls were available in the captured snapshot.",se=`Partial sitemap recovered before repeated JavaScript extraction was suppressed.
443
448
  Captured page: ${Le}.
444
449
  ${ze}
445
- The repeated run_js extraction target was suppressed after a loop warning, so this result preserves the captured page instead of retrying until a hard loop block.`;this.baseDeps.sink.emit({kind:"agent_lifecycle",ts:Date.now(),sessionId:t.id,event:"run_js_loop_salvage",iteration:E,details:`capturedPage=${Le}; visibleControls=${Re.length}`});let ye={sessionId:t.id,id:de("msg"),role:"model",text:se,timestamp:Date.now(),actionArgs:{status:"partial",capturedPage:Le,visibleControlGaps:Re,recoveryReason:P.message,runJsResultStatus:"suppressed_repeated_target",runJsResult:{value:JSON.stringify({kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re}),truncated:!1}}};await this.baseDeps.chatRepo.addMessage(ye),this.emit("message:added",{sessionId:t.id,message:ye}),k.push({name:O.name,response:{url:Le,status:"error",errorClass:"RepeatedRunJsSuppressed",error:P.message,runJsResultStatus:"suppressed_repeated_target",runJsResult:{value:JSON.stringify({kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re}),truncated:!1},pageSnapshot:ke||void 0,recovery:{kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re,instruction:"Do not retry the same run_js extraction. Finish with the captured page as partial discovery coverage and list the visible controls as unverified navigation gaps."}},...!s&&ge?{parts:[{inlineData:{mimeType:"image/png",data:ge}}]}:{}}),oe=!0,h=!0,N=se;break}let pe,_e="";if(o)pe=(await this.baseDeps.mobileMcpService.takeScreenshot(this.sessionId)).base64;else{if(!this.baseDeps.computerUseService)throw new Error("[BaseRuntime] Loop detection screenshot not available \u2014 no computerUseService");let ge=await this.baseDeps.computerUseService.invoke({sessionId:t.id,action:"screenshot",args:{},config:t.config});pe=ge.screenshot,_e=ge.url??""}k.push({name:O.name,response:{url:_e,status:"error",metadata:{error:P.message}},...!s&&pe?{parts:[{inlineData:{mimeType:"image/png",data:pe}}]}:{}});continue}}if(k.push({name:O.name,response:ce.response,...ce.parts?.length?{parts:ce.parts}:{}}),ce.message&&(await this.baseDeps.chatRepo.addMessage(ce.message,ce.screenshotBase64?{screenshotBase64:ce.screenshotBase64}:void 0),this.emit("message:added",{sessionId:t.id,message:ce.message,...ce.screenshotBase64?{screenshotBase64:ce.screenshotBase64}:{},durationMs:He,tokenCount:this.tokenCount})),ce.supervisorEntry&&this.progressBus.recordSupervisorEntry(ce.supervisorEntry),this.progressBus.flush(E,{action:"proceed"}),!ce.isMetaTool&&ce.response&&typeof ce.response=="object"){let P=ce.response,pe=P.events;if(!!pe||P.status==="error"||!!P.error||!!P.valueMismatch){let ge={timestamp:Date.now(),toolName:O.name,intent:ce.message?.actionArgs?.intent,url:P.url,status:P.status,error:P.error,errorClass:P.errorClass,...P.valueMismatch?{valueMismatch:!0}:{},...P.clickTarget?{clickTarget:P.clickTarget}:{},...pe?{events:pe}:{}};this.recentActionsForEvidence.push(ge),this.recentActionsForEvidence.length>r.EVIDENCE_BUFFER_SIZE&&this.recentActionsForEvidence.splice(0,this.recentActionsForEvidence.length-r.EVIDENCE_BUFFER_SIZE)}}if(ce.loopDetectorUpdate&&this.updateLoopDetector(v,ce,ne.has(X)),ce.screenshotBase64&&(S=ce.screenshotBase64),ce.done){oe=!0,h=!0,(ce.message?.actionName==="exploration_blocked"||ce.response?.status==="awaiting_user_guidance")&&(f=!0,g="agent",p=ce.message?.actionArgs?.obstacle||(typeof ce.response=="object"&&ce.response?ce.response.obstacle:void 0)||"Agent reported it was blocked");break}}let G=this.resolvedSupervisorVerdict;if(!oe&&G){this.resolvedSupervisorVerdict=null;let O=await this.applySupervisorVerdict(G,t,E,k);O.done&&(oe=!0,h=!0,f=!0,p=O.blockedReason||"Supervisor stopped the run",g="supervisor")}let Y=this.supervisorActionLog.filter(O=>!tg.has(O.action)).length;if(!oe&&this.supervisorEnabled&&this.baseDeps.supervisorService&&!this.pendingSupervisorVerdict&&E>=KA&&E%YA===0&&k.length>0&&Y>=JA){this.log("info","Supervisor","Firing async evaluation",{iteration:E,actionLogSize:this.supervisorActionLog.length,substantiveActionCount:Y});let O=[...this.supervisorActionLog],V=this.getSupervisorTaskDescription(a??"");this.pendingSupervisorVerdict=this.baseDeps.supervisorService.evaluate(O,V,S,l).then(B=>{let ce=B.action==="redirect"||B.action==="wrap_up"?B.message:B.action==="block"?B.reason:void 0;return this.log("info","Supervisor","Verdict received",{action:B.action,message:ce,rawText:B.rawText}),this.resolvedSupervisorVerdict=B,this.pendingSupervisorVerdict=null,B}).catch(B=>(this.log("warn","Supervisor","Evaluation failed, defaulting to continue",{error:B?.message}),this.pendingSupervisorVerdict=null,{action:"continue"}))}for(this.baseDeps.sink.flush();k.length<J.length;){let O=k.length;k.push({name:J[O].name,response:{status:"skipped",reason:"execution stopped"}})}let M=[],R=[];for(let O=0;O<k.length;O++){let{parts:V,...B}=k[O];M.push({functionResponse:{...B,id:J[O]?.toolCallId??B.id}}),V?.length&&R.push(...V)}if(d.push({role:"user",parts:M}),R.length>0&&d.push({role:"user",parts:R}),N&&d.push({role:"model",parts:[{text:N}]}),this.stripOldScreenshots(d),await this.persistConversationTrace(t,d),e.preserveAllPageSnapshots||this.stripOldPageSnapshots(d,s),this.onIterationEnd(d),await this.maybeSummarizeContext(t,{source:"BaseRuntime",iteration:E,tokenCount:this.tokenCount}),I=k,oe)break}return!h&&this._isRunning&&w>=n&&await this.onLoopExhausted(t,n),this._activeLoopDetector=null,{reported:h,blocked:f,blockedReason:p,blockKind:g,lastIteration:w}}patchDanglingToolCalls(e){let t=!1;for(let n=0;n<e.length;n++){let s=e[n];if(s.role!=="model")continue;let o=(s.parts??[]).filter(c=>c?.functionCall);if(o.length===0)continue;let i=e[n+1];if(i?.role==="user"&&i.parts?.some(c=>c?.functionResponse))continue;let l=o.map(c=>({functionResponse:{name:c.functionCall.name,id:c.functionCall.id,response:{status:"interrupted",reason:"interrupted \u2014 synthetic response injected during stop"}}}));e.splice(n+1,0,{role:"user",parts:l}),this.log("info","BaseRuntime",`Patched ${o.length} dangling tool call(s) at trace index ${n}`),t=!0}return t}};async function nn(r,e,t){let n=t.secretsService.getProjectCredentialsWithSecrets,s=t.computerUseService?.seedCredentials;if(!n||!s)return;let o=await n.call(t.secretsService,e).catch(()=>[]);o.length>0&&s.call(t.computerUseService,r,o)}import{z as fe}from"zod";import{z as ae}from"zod";var rg=ae.object({}),ng={description:"Open the web browser session.",inputSchema:rg},sg=ae.object({}),og={description:"Capture a screenshot of the current viewport.",inputSchema:sg},ig=ae.object({}),ag={description:"Capture a full-page screenshot (entire scrollable content). Use this for page exploration/verification to see all content at once.",inputSchema:ig},lg=ae.object({}),cg={description:"Request a fresh full page snapshot (accessibility tree). Use when element refs from a previous action no longer work or when you need to see all elements on the page. Returns the complete element tree with updated refs.",inputSchema:lg},ug=ae.object({width:ae.number().describe("Viewport width in pixels"),height:ae.number().describe("Viewport height in pixels")}),dg={description:"Switch browser viewport to a different layout/device size. Presets: mobile (390x844), tablet (834x1112), small_laptop (1366x768), big_laptop (1440x900).",inputSchema:ug},pg=ae.object({url:ae.string()}),hg={description:"Navigate to a URL.",inputSchema:pg},mg=ae.object({label:ae.string().describe('Visible or accessibility name of the control to click (e.g. "Sign in"). When provided, label is preferred over ref/x/y. It resolves against the live page, so it clicks what you see even after a page re-renders and ref numbers change.').optional(),ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),modifiers:ae.array(ae.enum(["Control","Shift","Alt","Meta"])).describe("Modifier keys to hold during click. Use Control for Ctrl+click (multi-select on Windows/Linux), Meta for Cmd+click (Mac), Shift for range selection.").optional()}),fg={description:'Click a control by its visible name (label), by element ref from the page snapshot, or by normalized coordinates (0-1000 scale). Prefer label for buttons and links that have a clear visible name \u2014 especially on login pages where a "Forgot your password?" link sits right next to the "Sign in" button: naming the control you SEE clicks that exact control on the live page and never the adjacent one. If the target is a <select>, the response returns elementType="select" with availableOptions \u2014 use set_focused_input_value to pick an option. For multi-select, use modifiers: ["Control"] (Windows/Linux) or ["Meta"] (Mac). If the target is a file input, the response returns elementType="file" with accept and multiple \u2014 use upload_file to set files. If the click triggers a file download, the response returns elementType="download" with downloadFilename and downloadUrl \u2014 the download is not saved, just detected.',inputSchema:mg},gg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional()}),yg={description:"Right-click (context menu click) at normalized coordinates (0-1000 scale) or by element ref from page snapshot.",inputSchema:gg},vg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional()}),bg={description:"Hover at normalized coordinates (0-1000 scale) or by element ref from page snapshot.",inputSchema:vg},_g=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),label:ae.string().describe('Visible or accessibility label of the target text field. When provided, label is preferred over ref/x/y. Use this for adjacent form fields with labels such as "Contract #" and "Booking #".').optional(),x:ae.number().optional(),y:ae.number().optional(),text:ae.string(),pressEnter:ae.boolean().optional(),clearBeforeTyping:ae.boolean().optional()}),wg={description:"Type text into a text input field by visible label, element ref, or coordinates. Prefer label for adjacent form fields with visible labels (for example Contract # vs Booking #). Use ONLY for text inputs (input, textarea, contenteditable). Do NOT use for <select> dropdowns - use click_at to open the dropdown, then click_at again on the option. Coordinates are normalized (0-1000). The response includes typedIntoField with the accessible/visible name of the field that received input \u2014 verify it matches your intended target.",inputSchema:_g},Sg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),credentialName:ae.string().describe("Exact name of a credential from PROJECT MEMORY"),pressEnter:ae.boolean().optional(),clearBeforeTyping:ae.boolean().optional()}),Tg={description:"Type the hidden SECRET/PASSWORD of a stored project credential into a form field by element ref or coordinates. The credential name shown in PROJECT MEMORY is visible to you \u2014 type it as plain text with type_text_at for username/email fields. This tool ONLY types the hidden secret value. ONLY use credential names explicitly listed in PROJECT MEMORY. Do NOT guess or assume credential names exist.",inputSchema:Sg},xg=ae.object({direction:ae.enum(["up","down","left","right"])}),Ig={description:"Scroll the document.",inputSchema:xg},kg=ae.object({}),Eg={description:"Scroll to the bottom of the page.",inputSchema:kg},Ag=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),direction:ae.enum(["up","down","left","right"]),magnitude:ae.number().optional()}),Rg={description:"Scroll at coordinates or element ref with direction and magnitude (normalized).",inputSchema:Ag},Cg=ae.object({seconds:ae.number().describe("Seconds to wait (1-30, default 2)").optional()}),Mg={description:"Wait for a specified number of seconds before taking a screenshot. Use after clicks that trigger loading states (spinners, progress bars). Choose duration based on expected load time. For content-specific waits, prefer wait_for_element.",inputSchema:Cg},Og=ae.object({textContent:ae.string().describe('Text the element should contain (substring match). Be specific \u2014 "Order confirmed" not just "Order".'),timeoutSeconds:ae.number().describe("Max seconds to wait (default 5, max 30)").optional()}),Pg={description:"Wait for specific text to become visible on the page. Use when you know what content should appear (loading spinner resolves to results, success message appears, tab content loads). Matches as a case-sensitive substring \u2014 be specific to avoid matching loading indicators. Returns a screenshot once the text is found. If not found within the timeout, returns current page state with a timeout error.",inputSchema:Og},Ng=ae.object({}),Dg={description:"Go back.",inputSchema:Ng},Lg=ae.object({}),$g={description:"Go forward.",inputSchema:Lg},Ug=ae.object({keys:ae.array(ae.string())}),jg={description:'Press a key combination. Provide keys as an array of strings (e.g., ["Command","L"]).',inputSchema:Ug},Fg=ae.object({value:ae.string().describe('Value to set. For select/dropdown elements: use the visible option text (e.g., "Damage deposit"). For date/time inputs: use ISO format (date: "2026-02-15", time: "14:30", datetime-local: "2026-02-15T14:30"). For text inputs: plain text.')}),qg={description:"Set value on the currently focused input or select. Call click_at first to focus the element, then this tool. Works for all input types including date/time and select dropdowns. Returns elementType, valueBefore, valueAfter in the response. For selects: also returns availableOptions. For date: YYYY-MM-DD. For time: HH:MM (24h). For datetime-local: YYYY-MM-DDTHH:MM.",inputSchema:Fg},Bg=ae.object({ref:ae.string().describe('Source element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),destinationRef:ae.string().describe("Destination element reference from page snapshot. When provided, destinationX/destinationY are ignored.").optional(),x:ae.number().optional(),y:ae.number().optional(),destinationX:ae.number().optional(),destinationY:ae.number().optional()}),Hg={description:"Drag and drop using element refs from page snapshot (ref, destinationRef) or normalized coords (x, y, destinationX, destinationY, 0-1000 scale).",inputSchema:Bg},Vg=ae.object({filePaths:ae.array(ae.string()).describe('Absolute paths to files to upload (e.g., ["/Users/alex/Desktop/photo.png"]).')}),Wg={description:'Upload file(s) to a file input. PREREQUISITE: click_at on the file input first \u2014 the response will show elementType="file" with accept types and multiple flag. Then call this tool with absolute file paths. The files must exist on the local filesystem.',inputSchema:Vg},zg=ae.object({tab:ae.enum(["tab1","tab2"]).describe("Which tab to switch to")}),Gg={description:"Switch between browser tabs. Tab 1 is the original page, tab 2 is opened by links or popups.",inputSchema:zg},Yg=ae.object({}),Kg={description:"Close the current tab and switch to the other. Cannot close tab 1.",inputSchema:Yg},Jg=ae.object({url:ae.string().describe("The URL to send the request to"),method:ae.enum(["GET","POST","PUT","PATCH","DELETE"]).describe("HTTP method. Defaults to GET.").optional(),headers:ae.record(ae.string(),ae.string()).describe('Optional request headers as key-value pairs (e.g., {"Content-Type": "application/json"})').optional(),body:ae.string().describe("Optional request body (for POST/PUT/PATCH). Send JSON as a string.").optional()}),Qg={description:"Make an HTTP request. Shares the browser session's cookies and auth context (including httpOnly cookies) but is NOT subject to CORS \u2014 can reach any URL. Use this to verify API state after UI actions, set up test data, or test API endpoints directly. Response body is truncated to 50KB.",inputSchema:Jg},Xg=ae.object({code:ae.string().describe("JavaScript expression or statement to evaluate in the page context. Use cases: clear browser-side persistent storage (cookies plus the standard Web Storage APIs) to recover from a sticky rejection state; read hidden DOM state via document.querySelector when it is not exposed by the accessibility snapshot; force-set form values that do not accept normal click/type. Capped at 4000 characters; serialized return value capped at 5000 characters.")}),Zg={description:"Execute arbitrary JavaScript in the page context via Playwright `page.evaluate`. Use ONLY as a last resort when normal UI actions cannot make progress \u2014 e.g. clearing browser-side persistent storage (cookies plus the standard Web Storage APIs) to recover from a sticky rejection state, reading hidden DOM state that is not exposed via the accessibility snapshot, or force-setting form values that do not accept normal click/type. Prefer typed UI actions whenever possible \u2014 defaulting to JS bypasses the UX you are testing. Each call is logged. Returns the JSON-serialized return value (or `undefined`), capped at 5KB.",inputSchema:Xg},_R={open_web_browser:ng,screenshot:og,full_page_screenshot:ag,switch_layout:dg,navigate:hg,click_at:fg,right_click_at:yg,hover_at:bg,type_text_at:wg,type_project_credential_at:Tg,scroll_document:Ig,scroll_to_bottom:Eg,scroll_at:Rg,wait:Mg,wait_for_element:Pg,go_back:Dg,go_forward:$g,key_combination:jg,set_focused_input_value:qg,drag_and_drop:Hg,upload_file:Wg,switch_tab:Gg,close_tab:Kg,http_request:Qg,run_js:Zg};function nt(r,e){return{description:r,inputSchema:ae.object({intent:ae.string().describe('Brief explanation of what you are doing and why (e.g., "Clicking Login button to access account", "Scrolling to find pricing section")'),screen:ae.string().describe('Name of the screen you are currently looking at (e.g., "Login Page", "Dashboard", "Settings > Billing"). Use consistent names across actions on the same screen.'),visible_navigation:ae.array(ae.object({label:ae.string().describe("Text label of the navigation element"),element:ae.string().describe('Element type: "nav-link", "button", "tab", "menu-item", "sidebar-link", etc.')})).optional().describe("On the FIRST action of each new screen, list the main navigation elements visible (links, buttons, tabs that lead to other screens). Omit on subsequent actions on the same screen."),...e.shape})}}var $c=_R,kn={open_web_browser:nt(ng.description,rg),screenshot:nt(og.description,sg),full_page_screenshot:nt(ag.description,ig),switch_layout:nt(dg.description,ug),navigate:nt(hg.description,pg),click_at:nt(fg.description,mg),right_click_at:nt(yg.description,gg),hover_at:nt(bg.description,vg),type_text_at:nt(wg.description,_g),type_project_credential_at:nt(Tg.description,Sg),scroll_document:nt(Ig.description,xg),scroll_to_bottom:nt(Eg.description,kg),scroll_at:nt(Rg.description,Ag),wait:nt(Mg.description,Cg),wait_for_element:nt(Pg.description,Og),go_back:nt(Dg.description,Ng),go_forward:nt($g.description,Lg),key_combination:nt(jg.description,Ug),set_focused_input_value:nt(qg.description,Fg),drag_and_drop:nt(Hg.description,Bg),upload_file:nt(Wg.description,Vg),switch_tab:nt(Gg.description,zg),close_tab:nt(Kg.description,Yg),http_request:nt(Qg.description,Jg),run_js:nt(Zg.description,Xg)},wR=new Set(["screenshot","full_page_screenshot"]);function ey(r){let e={...r};for(let t of wR)delete e[t];return e}var ty={...ey($c),snapshot:cg},bs={...ey(kn),snapshot:nt(cg.description,lg)};import{z as Pe}from"zod";var ry=Pe.object({}),SR={description:"Capture a screenshot of the current device screen.",inputSchema:ry},ny=Pe.object({x:Pe.number().describe("X coordinate (0-1000 scale, left to right)"),y:Pe.number().describe("Y coordinate (0-1000 scale, top to bottom)")}),TR={description:"Tap at normalized coordinates (0-1000 scale). Look at the screenshot to determine where to tap.",inputSchema:ny},sy=Pe.object({x:Pe.number().describe("X coordinate (0-1000)"),y:Pe.number().describe("Y coordinate (0-1000)"),duration_ms:Pe.number().describe("Hold duration in milliseconds (default: 1000)").optional()}),xR={description:"Long press at normalized coordinates (0-1000 scale).",inputSchema:sy},oy=Pe.object({direction:Pe.enum(["up","down","left","right"]),distance:Pe.number().describe("Swipe distance (0-1000 scale, default: 500)").optional(),from_x:Pe.number().describe("Start X (0-1000, default: 500 = center)").optional(),from_y:Pe.number().describe("Start Y (0-1000, default: 500 = center)").optional()}),IR={description:"Swipe in a direction from center of screen or from specific coordinates.",inputSchema:oy},iy=Pe.object({text:Pe.string().describe('Text to type. Replaces any existing content in the focused field. For unique-per-run values: use {{unique}} for name/text fields (letters only, e.g. "John{{unique}}") or {{timestamp}} for emails/IDs (digits, e.g. "test-{{timestamp}}@example.com"). Tokens are replaced at execution time.'),submit:Pe.boolean().describe("Press Enter/Done after typing, which also dismisses the keyboard (default: false). Use submit:true on the last field of a form to dismiss the keyboard before tapping buttons.").optional()}),kR={description:"Type text into the currently focused input field.",inputSchema:iy},ay=Pe.object({button:Pe.enum(["BACK","HOME","ENTER","VOLUME_UP","VOLUME_DOWN"])}),ER={description:"Press a device button.",inputSchema:ay},ly=Pe.object({button:Pe.enum(["HOME","ENTER","VOLUME_UP","VOLUME_DOWN"])}),AR={description:"Press a device button. Note: iOS has no BACK button \u2014 use swipe-from-left-edge to go back.",inputSchema:ly},cy=Pe.object({url:Pe.string().describe("URL to open")}),RR={description:"Open a URL in the device browser.",inputSchema:cy},uy=Pe.object({packageName:Pe.string().describe("Package name of the app")}),CR={description:"Launch or re-launch the app under test.",inputSchema:uy},dy=Pe.object({credentialName:Pe.string().describe("Exact name of a credential from PROJECT MEMORY"),submit:Pe.boolean().describe("Press Enter/Done after typing (default: false)").optional()}),MR={description:"Type the hidden SECRET/PASSWORD of a stored project credential into the currently focused input field. The credential name shown in PROJECT MEMORY is visible to you \u2014 type it as plain text with mobile_type_text for username/email fields. This tool ONLY types the hidden secret value. ONLY use credential names explicitly listed in PROJECT MEMORY. Do NOT guess or assume credential names exist.",inputSchema:dy},py=Pe.object({}),OR={description:"Uninstall the app under test from the device. Use this when APK install fails due to version downgrade or signature mismatch.",inputSchema:py},Qi=Pe.object({}),PR={description:"Install the app under test from the project's configured APK file. Run mobile_uninstall_app first if reinstalling.",inputSchema:Qi},NR={description:"Install the app under test from the project's configured app file (.app bundle or .apk).",inputSchema:Qi},hy=Pe.object({}),DR={description:"Clear all data and cache for the app under test (equivalent to a fresh install state without reinstalling).",inputSchema:hy},my=Pe.object({}),LR={description:"List all third-party apps installed on the device.",inputSchema:my},fy=Pe.object({}),$R={description:"Force stop the app under test.",inputSchema:fy},gy=Pe.object({}),UR={description:"Force stop and relaunch the app under test.",inputSchema:gy};function Tt(r,e){return{description:r,inputSchema:Pe.object({intent:Pe.string().describe('Brief explanation of what you are doing and why (e.g., "Tapping Login button to access account", "Swiping down to refresh feed")'),screen:Pe.string().describe('Name of the screen you are currently looking at (e.g., "Login Page", "Dashboard", "Settings > Billing"). Use consistent names across actions on the same screen.'),visible_navigation:Pe.array(Pe.object({label:Pe.string().describe("Text label of the navigation element"),element:Pe.string().describe('Element type: "nav-link", "button", "tab", "menu-item", "sidebar-link", etc.')})).optional().describe("On the FIRST action of each new screen, list the main navigation elements visible (links, buttons, tabs that lead to other screens). Omit on subsequent actions on the same screen."),...e.shape})}}var Ji={mobile_screenshot:Tt(SR.description,ry),mobile_tap:Tt(TR.description,ny),mobile_long_press:Tt(xR.description,sy),mobile_swipe:Tt(IR.description,oy),mobile_type_text:Tt(kR.description,iy),mobile_press_button:Tt(ER.description,ay),mobile_open_url:Tt(RR.description,cy),mobile_launch_app:Tt(CR.description,uy),mobile_type_credential:Tt(MR.description,dy),mobile_uninstall_app:Tt(OR.description,py),mobile_install_app:Tt(PR.description,Qi),mobile_clear_app_data:Tt(DR.description,hy),mobile_list_installed_apps:Tt(LR.description,my),mobile_stop_app:Tt($R.description,fy),mobile_restart_app:Tt(UR.description,gy)},jR=new Set(["mobile_clear_app_data"]);function _s(r){if(r==="android")return Ji;let e={};for(let[t,n]of Object.entries(Ji))jR.has(t)||(t==="mobile_press_button"?e[t]=Tt(AR.description,ly):t==="mobile_install_app"?e[t]=Tt(NR.description,Qi):e[t]=n);return e}function Ro(){return" When type='verify', the criteria array MUST contain at least one concrete outcome check. Never leave criteria empty or omit it on verify steps; the runner has nothing to assert against and may skip the check."}function Xi(){return"Concrete check describing the expected OUTCOME. Focus on data created or changed during the test. When action steps use {{unique}} or {{timestamp}}, criteria must refer to the same token. Do not quote transient UI text from memory; describe the expected outcome and let the runner read the live screen."}function Zi(){return"REQUIRED for verify steps. Include one or more concrete checks the runner can perform against the live screen. Omit for setup/action steps."}function ea({isMobile:r=!1}={}){let e=r?"For setup/action: write executable user intent for app launch, navigation, field entry, selection, or command steps.":"For setup/action: write executable user intent for navigation, field entry, selection, or command steps.",t=r?"NEVER include coordinates, tool names, platform automation details, or keystroke arrays.":"NEVER include coordinates, tool names, browser automation details, or keystroke arrays.";return"Describe WHAT to do, not HOW. "+e+' A routine credential/login/unlock mini-flow may be one setup step when it ends in a stable state and is not the behavior under test. Stable field-fill groups may be one action step when all fields are visible on the same stable form surface, values are deterministic, and no field depends on intermediate UI changes. Keep risky transitions separate: navigation, tab changes, dynamic pickers, virtualized lists, search filters, validation-triggering values, submit, save, apply, confirm, upload, wait, memory save, and verify behavior. For navigation between pages within the app, write the step as the in-app path a user would take \u2014 click the sidebar, menu, or tab link by its visible label (for example, "Navigate to the Sourcing Agent page via the sidebar" or "Click Lio Assistant in the menu") \u2014 using the visible link or page name rather than a deep URL. Only the first step that reaches the app (opening the site or logging in) may use a full URL; every later page change uses the in-app label. When a submit/save/apply control is disabled until prerequisites are met, write the executable step as restoring the prerequisite or verifying the disabled state; do not write a click on the disabled control expecting validation feedback. Do not mix a stable field-fill group with any risky transition. Do not add observed state, expected result, evidence, option inventory, or verification wording to setup/action text; put checks in verify steps/criteria. For verify: write an outcome-focused intent. Include exact facts saved with log_observation purpose=include_in_plan only when they are a future input, stable locator, expected outcome, or fixed price/amount. '+t+" For relative dates, use only the relative term and never include a resolved calendar date. For unique-per-run values, use {{unique}} for alphabetic name/text fields, {{timestamp}} for numeric or identifier-like values, and {{unique-email}} for the email field in signup/registration steps. Never hardcode example values for unique fields. Steps must read like user instructions."+Ro()}function yy(){return`\u2550\u2550\u2550 TEST PLAN FORMAT \u2550\u2550\u2550
450
+ The repeated run_js extraction target was suppressed after a loop warning, so this result preserves the captured page instead of retrying until a hard loop block.`;this.baseDeps.sink.emit({kind:"agent_lifecycle",ts:Date.now(),sessionId:t.id,event:"run_js_loop_salvage",iteration:E,details:`capturedPage=${Le}; visibleControls=${Re.length}`});let ye={sessionId:t.id,id:de("msg"),role:"model",text:se,timestamp:Date.now(),actionArgs:{status:"partial",capturedPage:Le,visibleControlGaps:Re,recoveryReason:P.message,runJsResultStatus:"suppressed_repeated_target",runJsResult:{value:JSON.stringify({kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re}),truncated:!1}}};await this.baseDeps.chatRepo.addMessage(ye),this.emit("message:added",{sessionId:t.id,message:ye}),k.push({name:O.name,response:{url:Le,status:"error",errorClass:"RepeatedRunJsSuppressed",error:P.message,runJsResultStatus:"suppressed_repeated_target",runJsResult:{value:JSON.stringify({kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re}),truncated:!1},pageSnapshot:ke||void 0,recovery:{kind:"partial_sitemap_salvage",capturedPage:Le,visibleControlGaps:Re,instruction:"Do not retry the same run_js extraction. Finish with the captured page as partial discovery coverage and list the visible controls as unverified navigation gaps."}},...!s&&ge?{parts:[{inlineData:{mimeType:"image/png",data:ge}}]}:{}}),oe=!0,h=!0,N=se;break}let pe,_e="";if(o)pe=(await this.baseDeps.mobileMcpService.takeScreenshot(this.sessionId)).base64;else{if(!this.baseDeps.computerUseService)throw new Error("[BaseRuntime] Loop detection screenshot not available \u2014 no computerUseService");let ge=await this.baseDeps.computerUseService.invoke({sessionId:t.id,action:"screenshot",args:{},config:t.config});pe=ge.screenshot,_e=ge.url??""}k.push({name:O.name,response:{url:_e,status:"error",metadata:{error:P.message}},...!s&&pe?{parts:[{inlineData:{mimeType:"image/png",data:pe}}]}:{}});continue}}if(k.push({name:O.name,response:ce.response,...ce.parts?.length?{parts:ce.parts}:{}}),ce.message&&(await this.baseDeps.chatRepo.addMessage(ce.message,ce.screenshotBase64?{screenshotBase64:ce.screenshotBase64}:void 0),this.emit("message:added",{sessionId:t.id,message:ce.message,...ce.screenshotBase64?{screenshotBase64:ce.screenshotBase64}:{},durationMs:He,tokenCount:this.tokenCount})),ce.supervisorEntry&&this.progressBus.recordSupervisorEntry(ce.supervisorEntry),this.progressBus.flush(E,{action:"proceed"}),!ce.isMetaTool&&ce.response&&typeof ce.response=="object"){let P=ce.response,pe=P.events;if(!!pe||P.status==="error"||!!P.error||!!P.valueMismatch){let ge={timestamp:Date.now(),toolName:O.name,intent:ce.message?.actionArgs?.intent,url:P.url,status:P.status,error:P.error,errorClass:P.errorClass,...P.valueMismatch?{valueMismatch:!0}:{},...P.clickTarget?{clickTarget:P.clickTarget}:{},...pe?{events:pe}:{}};this.recentActionsForEvidence.push(ge),this.recentActionsForEvidence.length>r.EVIDENCE_BUFFER_SIZE&&this.recentActionsForEvidence.splice(0,this.recentActionsForEvidence.length-r.EVIDENCE_BUFFER_SIZE)}}if(ce.loopDetectorUpdate&&this.updateLoopDetector(v,ce,ne.has(X)),ce.screenshotBase64&&(S=ce.screenshotBase64),ce.done){oe=!0,h=!0,(ce.message?.actionName==="exploration_blocked"||ce.response?.status==="awaiting_user_guidance")&&(f=!0,g="agent",p=ce.message?.actionArgs?.obstacle||(typeof ce.response=="object"&&ce.response?ce.response.obstacle:void 0)||"Agent reported it was blocked");break}}let G=this.resolvedSupervisorVerdict;if(!oe&&G){this.resolvedSupervisorVerdict=null;let O=await this.applySupervisorVerdict(G,t,E,k);O.done&&(oe=!0,h=!0,f=!0,p=O.blockedReason||"Supervisor stopped the run",g="supervisor")}let Y=this.supervisorActionLog.filter(O=>!tg.has(O.action)).length;if(!oe&&this.supervisorEnabled&&this.baseDeps.supervisorService&&!this.pendingSupervisorVerdict&&E>=KA&&E%YA===0&&k.length>0&&Y>=JA){this.log("info","Supervisor","Firing async evaluation",{iteration:E,actionLogSize:this.supervisorActionLog.length,substantiveActionCount:Y});let O=[...this.supervisorActionLog],V=this.getSupervisorTaskDescription(a??"");this.pendingSupervisorVerdict=this.baseDeps.supervisorService.evaluate(O,V,S,l).then(B=>{let ce=B.action==="redirect"||B.action==="wrap_up"?B.message:B.action==="block"?B.reason:void 0;return this.log("info","Supervisor","Verdict received",{action:B.action,message:ce,rawText:B.rawText}),this.resolvedSupervisorVerdict=B,this.pendingSupervisorVerdict=null,B}).catch(B=>(this.log("warn","Supervisor","Evaluation failed, defaulting to continue",{error:B?.message}),this.pendingSupervisorVerdict=null,{action:"continue"}))}for(this.baseDeps.sink.flush();k.length<J.length;){let O=k.length;k.push({name:J[O].name,response:{status:"skipped",reason:"execution stopped"}})}let M=[],R=[];for(let O=0;O<k.length;O++){let{parts:V,...B}=k[O];M.push({functionResponse:{...B,id:J[O]?.toolCallId??B.id}}),V?.length&&R.push(...V)}if(d.push({role:"user",parts:M}),R.length>0&&d.push({role:"user",parts:R}),N&&d.push({role:"model",parts:[{text:N}]}),this.stripOldScreenshots(d),await this.persistConversationTrace(t,d),e.preserveAllPageSnapshots||this.stripOldPageSnapshots(d,s),this.onIterationEnd(d),await this.maybeSummarizeContext(t,{source:"BaseRuntime",iteration:E,tokenCount:this.tokenCount}),I=k,oe)break}return!h&&this._isRunning&&w>=n&&await this.onLoopExhausted(t,n),this._activeLoopDetector=null,{reported:h,blocked:f,blockedReason:p,blockKind:g,lastIteration:w}}patchDanglingToolCalls(e){let t=!1;for(let n=0;n<e.length;n++){let s=e[n];if(s.role!=="model")continue;let o=(s.parts??[]).filter(c=>c?.functionCall);if(o.length===0)continue;let i=e[n+1];if(i?.role==="user"&&i.parts?.some(c=>c?.functionResponse))continue;let l=o.map(c=>({functionResponse:{name:c.functionCall.name,id:c.functionCall.id,response:{status:"interrupted",reason:"interrupted \u2014 synthetic response injected during stop"}}}));e.splice(n+1,0,{role:"user",parts:l}),this.log("info","BaseRuntime",`Patched ${o.length} dangling tool call(s) at trace index ${n}`),t=!0}return t}};async function nn(r,e,t){let n=t.secretsService.getProjectCredentialsWithSecrets,s=t.computerUseService?.seedCredentials;if(!n||!s)return;let o=await n.call(t.secretsService,e).catch(()=>[]);o.length>0&&s.call(t.computerUseService,r,o)}import{z as fe}from"zod";import{z as ae}from"zod";var rg=ae.object({}),ng={description:"Open the web browser session.",inputSchema:rg},sg=ae.object({}),og={description:"Capture a screenshot of the current viewport.",inputSchema:sg},ig=ae.object({}),ag={description:"Capture a full-page screenshot (entire scrollable content). Use this for page exploration/verification to see all content at once.",inputSchema:ig},lg=ae.object({}),cg={description:"Request a fresh full page snapshot (accessibility tree). Use when element refs from a previous action no longer work or when you need to see all elements on the page. Returns the complete element tree with updated refs.",inputSchema:lg},ug=ae.object({width:ae.number().describe("Viewport width in pixels"),height:ae.number().describe("Viewport height in pixels")}),dg={description:"Switch browser viewport to a different layout/device size. Presets: mobile (390x844), tablet (834x1112), small_laptop (1366x768), big_laptop (1440x900).",inputSchema:ug},pg=ae.object({url:ae.string()}),hg={description:"Navigate to a URL.",inputSchema:pg},mg=ae.object({label:ae.string().describe('Visible or accessibility name of the control to click (e.g. "Sign in"). When provided, label is preferred over ref/x/y. It resolves against the live page, so it clicks what you see even after a page re-renders and ref numbers change.').optional(),ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),modifiers:ae.array(ae.enum(["Control","Shift","Alt","Meta"])).describe("Modifier keys to hold during click. Use Control for Ctrl+click (multi-select on Windows/Linux), Meta for Cmd+click (Mac), Shift for range selection.").optional()}),fg={description:'Click a control by its visible name (label), by element ref from the page snapshot, or by normalized coordinates (0-1000 scale). Prefer label for buttons and links that have a clear visible name \u2014 especially on login pages where a "Forgot your password?" link sits right next to the "Sign in" button: naming the control you SEE clicks that exact control on the live page and never the adjacent one. If the target is a <select>, the response returns elementType="select" with availableOptions \u2014 use set_focused_input_value to pick an option. For multi-select, use modifiers: ["Control"] (Windows/Linux) or ["Meta"] (Mac). If the target is a file input, the response returns elementType="file" with accept and multiple \u2014 use upload_file to set files. If the click triggers a file download, the response returns elementType="download" with downloadFilename and downloadUrl \u2014 the download is not saved, just detected.',inputSchema:mg},gg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional()}),yg={description:"Right-click (context menu click) at normalized coordinates (0-1000 scale) or by element ref from page snapshot.",inputSchema:gg},vg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional()}),bg={description:"Hover at normalized coordinates (0-1000 scale) or by element ref from page snapshot.",inputSchema:vg},_g=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),label:ae.string().describe('Visible or accessibility label of the target text field. When provided, label is preferred over ref/x/y. Use this for adjacent form fields with labels such as "Contract #" and "Booking #".').optional(),x:ae.number().optional(),y:ae.number().optional(),text:ae.string(),pressEnter:ae.boolean().optional(),clearBeforeTyping:ae.boolean().optional()}),wg={description:"Type text into a text input field by visible label, element ref, or coordinates. Prefer label for adjacent form fields with visible labels (for example Contract # vs Booking #). Use ONLY for text inputs (input, textarea, contenteditable). Do NOT use for <select> dropdowns - use click_at to open the dropdown, then click_at again on the option. Coordinates are normalized (0-1000). The response includes typedIntoField with the accessible/visible name of the field that received input \u2014 verify it matches your intended target.",inputSchema:_g},Sg=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),credentialName:ae.string().describe("Exact name of a credential from PROJECT MEMORY"),pressEnter:ae.boolean().optional(),clearBeforeTyping:ae.boolean().optional()}),Tg={description:"Type the hidden SECRET/PASSWORD of a stored project credential into a form field by element ref or coordinates. The credential name shown in PROJECT MEMORY is visible to you \u2014 type it as plain text with type_text_at for username/email fields. This tool ONLY types the hidden secret value. ONLY use credential names explicitly listed in PROJECT MEMORY. Do NOT guess or assume credential names exist.",inputSchema:Sg},xg=ae.object({direction:ae.enum(["up","down","left","right"])}),Ig={description:"Scroll the document.",inputSchema:xg},kg=ae.object({}),Eg={description:"Scroll to the bottom of the page.",inputSchema:kg},Ag=ae.object({ref:ae.string().describe('Element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),x:ae.number().optional(),y:ae.number().optional(),direction:ae.enum(["up","down","left","right"]),magnitude:ae.number().optional()}),Rg={description:"Scroll at coordinates or element ref with direction and magnitude (normalized).",inputSchema:Ag},Cg=ae.object({seconds:ae.number().describe("Seconds to wait (1-30, default 2)").optional()}),Mg={description:"Wait for a specified number of seconds before taking a screenshot. Use after clicks that trigger loading states (spinners, progress bars). Choose duration based on expected load time. For content-specific waits, prefer wait_for_element.",inputSchema:Cg},Og=ae.object({textContent:ae.string().describe('Text the element should contain (substring match). Be specific \u2014 "Order confirmed" not just "Order".'),timeoutSeconds:ae.number().describe("Max seconds to wait (default 5, max 30)").optional()}),Pg={description:"Wait for specific text to become visible on the page. Use when you know what content should appear (loading spinner resolves to results, success message appears, tab content loads). Matches as a case-sensitive substring \u2014 be specific to avoid matching loading indicators. Returns a screenshot once the text is found. If not found within the timeout, returns current page state with a timeout error.",inputSchema:Og},Ng=ae.object({}),Dg={description:"Go back.",inputSchema:Ng},Lg=ae.object({}),$g={description:"Go forward.",inputSchema:Lg},Ug=ae.object({keys:ae.array(ae.string())}),jg={description:'Press a key combination. Provide keys as an array of strings (e.g., ["Command","L"]).',inputSchema:Ug},Fg=ae.object({value:ae.string().describe('Value to set. For select/dropdown elements: use the visible option text (e.g., "Damage deposit"). For date/time inputs: use ISO format (date: "2026-02-15", time: "14:30", datetime-local: "2026-02-15T14:30"). For text inputs: plain text.')}),qg={description:"Set value on the currently focused input or select. Call click_at first to focus the element, then this tool. Works for all input types including date/time and select dropdowns. Returns elementType, valueBefore, valueAfter in the response. For selects: also returns availableOptions. For date: YYYY-MM-DD. For time: HH:MM (24h). For datetime-local: YYYY-MM-DDTHH:MM.",inputSchema:Fg},Bg=ae.object({ref:ae.string().describe('Source element reference from page snapshot (e.g. "e5"). When provided, x/y are ignored.').optional(),destinationRef:ae.string().describe("Destination element reference from page snapshot. When provided, destinationX/destinationY are ignored.").optional(),x:ae.number().optional(),y:ae.number().optional(),destinationX:ae.number().optional(),destinationY:ae.number().optional()}),Hg={description:"Drag and drop using element refs from page snapshot (ref, destinationRef) or normalized coords (x, y, destinationX, destinationY, 0-1000 scale).",inputSchema:Bg},Vg=ae.object({filePaths:ae.array(ae.string()).describe('Absolute paths to files to upload (e.g., ["/Users/alex/Desktop/photo.png"]).')}),Wg={description:'Upload file(s) to a file input. Normally click_at the file input first (the response shows elementType="file" with accept/suggestedFiles), then call this with absolute file paths. For a CUSTOM upload area / dropzone that does NOT return elementType="file" (hidden input), just click the upload area once and call this tool directly \u2014 it locates the hidden <input type="file"> on the page itself. Do NOT hunt for the input with run_js. The files must exist on the local filesystem.',inputSchema:Vg},zg=ae.object({tab:ae.enum(["tab1","tab2"]).describe("Which tab to switch to")}),Gg={description:"Switch between browser tabs. Tab 1 is the original page, tab 2 is opened by links or popups.",inputSchema:zg},Yg=ae.object({}),Kg={description:"Close the current tab and switch to the other. Cannot close tab 1.",inputSchema:Yg},Jg=ae.object({url:ae.string().describe("The URL to send the request to"),method:ae.enum(["GET","POST","PUT","PATCH","DELETE"]).describe("HTTP method. Defaults to GET.").optional(),headers:ae.record(ae.string(),ae.string()).describe('Optional request headers as key-value pairs (e.g., {"Content-Type": "application/json"})').optional(),body:ae.string().describe("Optional request body (for POST/PUT/PATCH). Send JSON as a string.").optional()}),Qg={description:"Make an HTTP request. Shares the browser session's cookies and auth context (including httpOnly cookies) but is NOT subject to CORS \u2014 can reach any URL. Use this to verify API state after UI actions, set up test data, or test API endpoints directly. Response body is truncated to 50KB.",inputSchema:Jg},Xg=ae.object({code:ae.string().describe("JavaScript expression or statement to evaluate in the page context. Use cases: clear browser-side persistent storage (cookies plus the standard Web Storage APIs) to recover from a sticky rejection state; read hidden DOM state via document.querySelector when it is not exposed by the accessibility snapshot; force-set form values that do not accept normal click/type. Capped at 4000 characters; serialized return value capped at 5000 characters.")}),Zg={description:"Execute arbitrary JavaScript in the page context via Playwright `page.evaluate`. Use ONLY as a last resort when normal UI actions cannot make progress \u2014 e.g. clearing browser-side persistent storage (cookies plus the standard Web Storage APIs) to recover from a sticky rejection state, reading hidden DOM state that is not exposed via the accessibility snapshot, or force-setting form values that do not accept normal click/type. Prefer typed UI actions whenever possible \u2014 defaulting to JS bypasses the UX you are testing. Each call is logged. Returns the JSON-serialized return value (or `undefined`), capped at 5KB.",inputSchema:Xg},_R={open_web_browser:ng,screenshot:og,full_page_screenshot:ag,switch_layout:dg,navigate:hg,click_at:fg,right_click_at:yg,hover_at:bg,type_text_at:wg,type_project_credential_at:Tg,scroll_document:Ig,scroll_to_bottom:Eg,scroll_at:Rg,wait:Mg,wait_for_element:Pg,go_back:Dg,go_forward:$g,key_combination:jg,set_focused_input_value:qg,drag_and_drop:Hg,upload_file:Wg,switch_tab:Gg,close_tab:Kg,http_request:Qg,run_js:Zg};function nt(r,e){return{description:r,inputSchema:ae.object({intent:ae.string().describe('Brief explanation of what you are doing and why (e.g., "Clicking Login button to access account", "Scrolling to find pricing section")'),screen:ae.string().describe('Name of the screen you are currently looking at (e.g., "Login Page", "Dashboard", "Settings > Billing"). Use consistent names across actions on the same screen.'),visible_navigation:ae.array(ae.object({label:ae.string().describe("Text label of the navigation element"),element:ae.string().describe('Element type: "nav-link", "button", "tab", "menu-item", "sidebar-link", etc.')})).optional().describe("On the FIRST action of each new screen, list the main navigation elements visible (links, buttons, tabs that lead to other screens). Omit on subsequent actions on the same screen."),...e.shape})}}var $c=_R,kn={open_web_browser:nt(ng.description,rg),screenshot:nt(og.description,sg),full_page_screenshot:nt(ag.description,ig),switch_layout:nt(dg.description,ug),navigate:nt(hg.description,pg),click_at:nt(fg.description,mg),right_click_at:nt(yg.description,gg),hover_at:nt(bg.description,vg),type_text_at:nt(wg.description,_g),type_project_credential_at:nt(Tg.description,Sg),scroll_document:nt(Ig.description,xg),scroll_to_bottom:nt(Eg.description,kg),scroll_at:nt(Rg.description,Ag),wait:nt(Mg.description,Cg),wait_for_element:nt(Pg.description,Og),go_back:nt(Dg.description,Ng),go_forward:nt($g.description,Lg),key_combination:nt(jg.description,Ug),set_focused_input_value:nt(qg.description,Fg),drag_and_drop:nt(Hg.description,Bg),upload_file:nt(Wg.description,Vg),switch_tab:nt(Gg.description,zg),close_tab:nt(Kg.description,Yg),http_request:nt(Qg.description,Jg),run_js:nt(Zg.description,Xg)},wR=new Set(["screenshot","full_page_screenshot"]);function ey(r){let e={...r};for(let t of wR)delete e[t];return e}var ty={...ey($c),snapshot:cg},bs={...ey(kn),snapshot:nt(cg.description,lg)};import{z as Pe}from"zod";var ry=Pe.object({}),SR={description:"Capture a screenshot of the current device screen.",inputSchema:ry},ny=Pe.object({x:Pe.number().describe("X coordinate (0-1000 scale, left to right)"),y:Pe.number().describe("Y coordinate (0-1000 scale, top to bottom)")}),TR={description:"Tap at normalized coordinates (0-1000 scale). Look at the screenshot to determine where to tap.",inputSchema:ny},sy=Pe.object({x:Pe.number().describe("X coordinate (0-1000)"),y:Pe.number().describe("Y coordinate (0-1000)"),duration_ms:Pe.number().describe("Hold duration in milliseconds (default: 1000)").optional()}),xR={description:"Long press at normalized coordinates (0-1000 scale).",inputSchema:sy},oy=Pe.object({direction:Pe.enum(["up","down","left","right"]),distance:Pe.number().describe("Swipe distance (0-1000 scale, default: 500)").optional(),from_x:Pe.number().describe("Start X (0-1000, default: 500 = center)").optional(),from_y:Pe.number().describe("Start Y (0-1000, default: 500 = center)").optional()}),IR={description:"Swipe in a direction from center of screen or from specific coordinates.",inputSchema:oy},iy=Pe.object({text:Pe.string().describe('Text to type. Replaces any existing content in the focused field. For unique-per-run values: use {{unique}} for name/text fields (letters only, e.g. "John{{unique}}") or {{timestamp}} for emails/IDs (digits, e.g. "test-{{timestamp}}@example.com"). Tokens are replaced at execution time.'),submit:Pe.boolean().describe("Press Enter/Done after typing, which also dismisses the keyboard (default: false). Use submit:true on the last field of a form to dismiss the keyboard before tapping buttons.").optional()}),kR={description:"Type text into the currently focused input field.",inputSchema:iy},ay=Pe.object({button:Pe.enum(["BACK","HOME","ENTER","VOLUME_UP","VOLUME_DOWN"])}),ER={description:"Press a device button.",inputSchema:ay},ly=Pe.object({button:Pe.enum(["HOME","ENTER","VOLUME_UP","VOLUME_DOWN"])}),AR={description:"Press a device button. Note: iOS has no BACK button \u2014 use swipe-from-left-edge to go back.",inputSchema:ly},cy=Pe.object({url:Pe.string().describe("URL to open")}),RR={description:"Open a URL in the device browser.",inputSchema:cy},uy=Pe.object({packageName:Pe.string().describe("Package name of the app")}),CR={description:"Launch or re-launch the app under test.",inputSchema:uy},dy=Pe.object({credentialName:Pe.string().describe("Exact name of a credential from PROJECT MEMORY"),submit:Pe.boolean().describe("Press Enter/Done after typing (default: false)").optional()}),MR={description:"Type the hidden SECRET/PASSWORD of a stored project credential into the currently focused input field. The credential name shown in PROJECT MEMORY is visible to you \u2014 type it as plain text with mobile_type_text for username/email fields. This tool ONLY types the hidden secret value. ONLY use credential names explicitly listed in PROJECT MEMORY. Do NOT guess or assume credential names exist.",inputSchema:dy},py=Pe.object({}),OR={description:"Uninstall the app under test from the device. Use this when APK install fails due to version downgrade or signature mismatch.",inputSchema:py},Qi=Pe.object({}),PR={description:"Install the app under test from the project's configured APK file. Run mobile_uninstall_app first if reinstalling.",inputSchema:Qi},NR={description:"Install the app under test from the project's configured app file (.app bundle or .apk).",inputSchema:Qi},hy=Pe.object({}),DR={description:"Clear all data and cache for the app under test (equivalent to a fresh install state without reinstalling).",inputSchema:hy},my=Pe.object({}),LR={description:"List all third-party apps installed on the device.",inputSchema:my},fy=Pe.object({}),$R={description:"Force stop the app under test.",inputSchema:fy},gy=Pe.object({}),UR={description:"Force stop and relaunch the app under test.",inputSchema:gy};function Tt(r,e){return{description:r,inputSchema:Pe.object({intent:Pe.string().describe('Brief explanation of what you are doing and why (e.g., "Tapping Login button to access account", "Swiping down to refresh feed")'),screen:Pe.string().describe('Name of the screen you are currently looking at (e.g., "Login Page", "Dashboard", "Settings > Billing"). Use consistent names across actions on the same screen.'),visible_navigation:Pe.array(Pe.object({label:Pe.string().describe("Text label of the navigation element"),element:Pe.string().describe('Element type: "nav-link", "button", "tab", "menu-item", "sidebar-link", etc.')})).optional().describe("On the FIRST action of each new screen, list the main navigation elements visible (links, buttons, tabs that lead to other screens). Omit on subsequent actions on the same screen."),...e.shape})}}var Ji={mobile_screenshot:Tt(SR.description,ry),mobile_tap:Tt(TR.description,ny),mobile_long_press:Tt(xR.description,sy),mobile_swipe:Tt(IR.description,oy),mobile_type_text:Tt(kR.description,iy),mobile_press_button:Tt(ER.description,ay),mobile_open_url:Tt(RR.description,cy),mobile_launch_app:Tt(CR.description,uy),mobile_type_credential:Tt(MR.description,dy),mobile_uninstall_app:Tt(OR.description,py),mobile_install_app:Tt(PR.description,Qi),mobile_clear_app_data:Tt(DR.description,hy),mobile_list_installed_apps:Tt(LR.description,my),mobile_stop_app:Tt($R.description,fy),mobile_restart_app:Tt(UR.description,gy)},jR=new Set(["mobile_clear_app_data"]);function _s(r){if(r==="android")return Ji;let e={};for(let[t,n]of Object.entries(Ji))jR.has(t)||(t==="mobile_press_button"?e[t]=Tt(AR.description,ly):t==="mobile_install_app"?e[t]=Tt(NR.description,Qi):e[t]=n);return e}function Ro(){return" When type='verify', the criteria array MUST contain at least one concrete outcome check. Never leave criteria empty or omit it on verify steps; the runner has nothing to assert against and may skip the check."}function Xi(){return"Concrete check describing the expected OUTCOME. Focus on data created or changed during the test. When action steps use {{unique}} or {{timestamp}}, criteria must refer to the same token. Do not quote transient UI text from memory; describe the expected outcome and let the runner read the live screen."}function Zi(){return"REQUIRED for verify steps. Include one or more concrete checks the runner can perform against the live screen. Omit for setup/action steps."}function ea({isMobile:r=!1}={}){let e=r?"For setup/action: write executable user intent for app launch, navigation, field entry, selection, or command steps.":"For setup/action: write executable user intent for navigation, field entry, selection, or command steps.",t=r?"NEVER include coordinates, tool names, platform automation details, or keystroke arrays.":"NEVER include coordinates, tool names, browser automation details, or keystroke arrays.";return"Describe WHAT to do, not HOW. "+e+' A routine credential/login/unlock mini-flow may be one setup step when it ends in a stable state and is not the behavior under test. Stable field-fill groups may be one action step when all fields are visible on the same stable form surface, values are deterministic, and no field depends on intermediate UI changes. Keep risky transitions separate: navigation, tab changes, dynamic pickers, virtualized lists, search filters, validation-triggering values, submit, save, apply, confirm, upload, wait, memory save, and verify behavior. For navigation between pages within the app, write the step as the in-app path a user would take \u2014 click the sidebar, menu, or tab link by its visible label (for example, "Navigate to the Sourcing Agent page via the sidebar" or "Click Lio Assistant in the menu") \u2014 using the visible link or page name rather than a deep URL. Only the first step that reaches the app (opening the site or logging in) may use a full URL; every later page change uses the in-app label. When a submit/save/apply control is disabled until prerequisites are met, write the executable step as restoring the prerequisite or verifying the disabled state; do not write a click on the disabled control expecting validation feedback. Do not mix a stable field-fill group with any risky transition. Do not add observed state, expected result, evidence, option inventory, or verification wording to setup/action text; put checks in verify steps/criteria. For verify: write an outcome-focused intent. Include exact facts saved with log_observation purpose=include_in_plan only when they are a future input, stable locator, expected outcome, or fixed price/amount. '+t+" For relative dates, use only the relative term and never include a resolved calendar date. For unique-per-run values, use {{unique}} for alphabetic name/text fields, {{timestamp}} for numeric or identifier-like values, and {{unique-email}} for the email field in signup/registration steps. Never hardcode example values for unique fields. Steps must read like user instructions."+Ro()}function yy(){return`\u2550\u2550\u2550 TEST PLAN FORMAT \u2550\u2550\u2550
446
451
  Title: 3-5 words max. Use abbreviations. Do not include words that merely restate the action of testing.
447
452
  When the user asks for a reusable test plan or flow, assistant_v2_report must include draftTestCase with executable steps unless the flow was blocked before you could observe it.
448
453
  Setup/action steps: executable intents, not transcript summaries.
@@ -498,7 +503,7 @@ ${c}
498
503
  [END FILE]`})}else if(o.category==="image")if(o.sizeBytes<=5242880)try{let a=await this.deps.attachmentStorageService.readBinary(i),l=Buffer.from(a.data).toString("base64");t.push({text:`[ATTACHED IMAGE: ${o.originalName} | path:${i} | ${o.sizeBytes}B | ${o.mimeType}]`}),t.push({inlineData:{mimeType:o.mimeType,data:l},_attachment:{name:o.originalName,path:i,sizeBytes:o.sizeBytes}})}catch(a){t.push({text:`[ATTACHED IMAGE: ${o.originalName} | path:${i} | ERROR: ${a.message}. Use view_image('${i}') to view.]`})}else t.push({text:`[ATTACHED IMAGE: ${o.originalName} | path:${i} | ${o.sizeBytes}B \u2014 too large for inline. Use view_image('${i}') to view.]`});else t.push({text:`[USER-PROVIDED FILE: ${o.originalName} | path:${i} | ${o.sizeBytes}B | ${o.mimeType}]
499
504
  The user attached this file for this task. When a step requires uploading or attaching a file, call upload_file with this exact path (${i}). User-provided files take priority over bundled sample files \u2014 do NOT substitute a sample file when this attachment matches the task.`})}return t}stripOldFileAttachments(e){let t=0,n=0;for(let s=e.length-1;s>=0;s--){let o=e[s];if(!(!o||!Array.isArray(o.parts)))for(let i=o.parts.length-1;i>=0;i--){let a=o.parts[i];if(typeof a?.text=="string"&&a.text.startsWith("[ATTACHED FILE:")&&(t++,t>PC)){let l=a.text.match(/\[ATTACHED FILE: (.+?) \| path:(.+?) \| (\d+B) \| (.+?)\]/);if(l){let[,c,u,d]=l;o.parts[i]={text:`[FILE EVICTED: ${c} (${d}) \u2014 use read_file('${u}') to reload, or upload_file(['${u}']) to use in browser]`}}}if(a?.inlineData&&a?._attachment&&(n++,n>NC)){let{name:l,path:c,sizeBytes:u}=a._attachment;o.parts[i]={text:`[IMAGE EVICTED: ${l} (${u}B) \u2014 use view_image('${c}') to reload, or upload_file(['${c}']) to use in browser]`}}typeof a?.text=="string"&&a.text.startsWith("[ATTACHED IMAGE:")&&!o.parts[i+1]?.inlineData&&n++}}}redactPII(e){return String(e??"").replace(/\[REDACTED\]/g,"").replace(/\s{2,}/g," ").trim()}extractDiscoveredAreasFromTrace(){let e=new Set,t=[],n=s=>{try{let o=new URL(s);return o.origin+o.pathname.replace(/\/+$/,"")}catch{return null}};for(let s of this.conversationTrace)if(s.role==="user")for(let o of s.parts??[]){let i=o?.functionResponse;if(!i?.response?.url)continue;let a=i.response.url;if(!a.startsWith("http"))continue;let l=n(a);if(!l||e.has(l))continue;e.add(l);let c=new URL(a).pathname.split("/").filter(f=>f&&!/^[a-z]{2}$/.test(f)),u=c.length>0?c.map(f=>f.replace(/[-_]/g," ").replace(/\b\w/g,p=>p.toUpperCase())).join(" "):"Home",d=i.response.pageSnapshot,h=d?d.split(`
500
505
  `).filter(Boolean).slice(0,2).join(" ").slice(0,200):`Page at ${new URL(a).pathname}`;t.push({name:u,url:a,description:h,interactive:[],requires_auth:!1})}return t}buildInterruptedDiscoveryAreas(){let e=new Set,t=[],n=s=>{let o=this.discoveryAreaKey(s.url||s.name);!o||e.has(o)||(e.add(o),t.push(s))};for(let s of this.loggedObservationCheckpoints){let o=this.discoveryAreaFromObservationCheckpoint(s);o&&n(o)}for(let s of this.extractDiscoveredAreasFromTrace())n(s);return t}discoveryAreaFromObservationCheckpoint(e){let t=e.page||this.discoveryAreaNameFromUrl(e.url)||"Observed page",n=e.observations.map(o=>o.fact).filter(Boolean),s=n.length>0?n.slice(0,2).join(" "):`Observed ${t} before the run was stopped.`;return!e.url&&!e.page&&n.length===0?null:{name:t,url:e.url??"",description:s.slice(0,200),interactive:[],requires_auth:!1}}discoveryAreaNameFromUrl(e){if(!e)return null;try{let t=new URL(e).pathname.split("/").filter(n=>n&&!/^[a-z]{2}$/.test(n));return t.length===0?"Home":t.map(n=>n.replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())).join(" ")}catch{return null}}discoveryAreaKey(e){let t=String(e??"").trim();if(!t)return null;try{let n=new URL(t);return`${n.origin}${n.pathname.replace(/\/+$/,"")}`.toLowerCase()}catch{return t.toLowerCase()}}buildMissingEmailVerificationRejection(e,t){let n=String(e.args?.status??"ok").trim();return n!=="ok"&&n!=="done"||!bt(t.session?.config)?.canCheckInbox||!this._emailVerificationExpected||this._checkEmailCallCount>0?null:{response:{status:"error",error:"Account creation appears to require email verification but check_email was never called. Read the inbox via check_email and complete verification (click the verification link or enter the verification code) before reporting complete."},isMetaTool:!0}}async handleReport(e,t){let{session:n,isMobile:s}=t,o=n,i=String(e.args?.status??"ok").trim(),a=this.redactPII(String(e.args?.summary??"")).trim(),l=String(e.args?.question??"").trim(),c=n.config?.testingEmailPolicy;(i==="ok"||i==="done")&&c?.enabled&&c.canCheckInbox===!0&&this._emailVerificationExpected&&this._checkEmailCallCount>0&&this._lastCheckEmailCount===0&&(this.log("warn","ExplorerRuntime","email_verification_gate: premature success \u2192 needs_user",{checkEmailCalls:this._checkEmailCallCount,lastCheckCount:this._lastCheckEmailCount}),i="needs_user",l||(l=`${this.describeEmailWaitSoFar()} What would you like to do?`));let u=l?this.redactPII(l).slice(0,800):"",d=this.sanitizeDraftTestCase(e.args?.draftTestCase??null,n.config),h=this.redactPII(String(e.args?.reflection??"")).trim(),f=e.args?.coverage??null;if(i==="needs_user"){let x=Array.isArray(e.args?.options)?e.args.options.map(L=>String(L).trim()).filter(Boolean):void 0,{question:S,options:I,waitMarker:E}=this.finalizeAskUser((u||a||"I need your input to continue.").trim(),x,o,e.args?.waiting_for),A={sessionId:o.id,id:de("msg"),role:"model",text:S,timestamp:Date.now(),actionName:"ask_user",actionArgs:{question:S,...I?.length?{options:I}:{}}};return await this.deps.chatRepo.addMessage(A),this.emit("message:added",{sessionId:o.id,message:A}),this.lastResult={status:"blocked",summary:`ask_user: ${E?`${E} `:""}${S}`,issues:this.reportedIssues,...d?{draftTestCase:d}:{}},{response:{status:"awaiting_user_reply"},done:!0,isMetaTool:!0}}let p=sa(this.currentAttachments);if(d?.steps&&(this.uploadAssetBatches.length>0||p.length>0)){let x=0,S=0;for(let I of d.steps)jc(I)&&(p.length>0?(I.fileAssets=p,S++):x<this.uploadAssetBatches.length&&(I.fileAssets=this.uploadAssetBatches[x],x++,S++));S>0&&this.log("info","ExplorerRuntime","Injected fileAssets into upload steps",{injectedSteps:S,durableUserFiles:p.length,recordedBatches:this.uploadAssetBatches.length})}if(d?.steps){let x=wy(d.steps);if(x.length>0)return{response:{status:"invalid_test_plan",invalidSteps:x,instruction:Sy(x)},isMetaTool:!0}}if(d?.steps){let x=await this.findMissingPlanObservations(o,d);if(x.length>0)return{response:{status:"observation_coverage_missing",missing:x,instruction:"Rewrite draftTestCase so each missing observation is represented by its exact quoted/value literals or full fact text. Do not call log_observation to fix this response unless you observed a new fact; call assistant_v2_report again."},isMetaTool:!0}}let g=[a,u?`Question: ${u}`:""].filter(Boolean).join(`
501
- `),m=de("msg"),b=!1,w;if(s&&this.deps.mobileMcpService)try{let x=await this.deps.mobileMcpService.takeScreenshot(o.id);x.base64&&this.deps.imageStorageService&&o.projectId&&(await this.deps.imageStorageService.save({projectId:o.projectId,sessionId:o.id,messageId:m,type:"message",base64:x.base64}),b=!0,w=x.base64)}catch(x){this.log("warn","ExplorerRuntime","Failed to capture report screenshot",{error:x?.message})}let _={sessionId:o.id,id:m,role:"model",text:g||(i==="needs_user"?"I need one clarification.":"Done."),timestamp:Date.now(),actionName:"assistant_v2_report",actionArgs:{status:i,draftTestCase:d,reflection:h},hasScreenshot:b||void 0};await this.deps.chatRepo.addMessage(_),this.emit("message:added",{sessionId:o.id,message:_,...w?{screenshotBase64:w}:{}});let T=Array.isArray(e.args?.discoveredAreas)&&e.args.discoveredAreas.length>0?e.args.discoveredAreas:null,v=T??(this.deps.isDiscoveryRun?this.extractDiscoveredAreasFromTrace():void 0);return!T&&this.deps.isDiscoveryRun&&v?.length&&this.log("info","ExplorerRuntime","Fallback: extracted discoveredAreas from trace",{count:v.length,urls:v.map(x=>x.url)}),this.lastResult={status:"completed",summary:a||"",discoveredAreas:v,coverage:f||void 0,draftTestCase:d||void 0,issues:this.reportedIssues},{response:{status:"ok"},done:!0,isMetaTool:!0}}async findMissingPlanObservations(e,t){let n=await this.collectActivePlanObservationFacts(e);if(n.length===0)return[];let s=JSON.stringify(t?.steps??[]);return n.filter(o=>!this.isPlanObservationCovered(fi(o,e.config),s))}sanitizeDraftTestCase(e,t){if(!e||typeof e!="object"||!Array.isArray(e.steps))return e;let n=e.steps.map(s=>{if(!s||typeof s!="object")return s;let o={...s};return(s.type==="setup"||s.type==="action")&&typeof s.text=="string"&&(o.text=this.sanitizeExecutableStepText(s.text)),typeof o.text=="string"&&(o.text=fi(o.text,t)),Array.isArray(o.criteria)&&(o.criteria=o.criteria.map(i=>i&&typeof i=="object"&&typeof i.check=="string"?{...i,check:fi(i.check,t)}:i)),o});return{...e,steps:this.compactStableFieldFillSteps(n)}}compactStableFieldFillSteps(e){let t=[],n=[],s=()=>{n.length>=2?t.push({...n[0],text:`Fill the stable form fields: ${n.map(o=>this.stableFieldFillFragment(o.text)).join("; ")}.`}):n.length===1&&t.push(n[0]),n=[]};for(let o of e){if(this.isStableFieldFillStep(o)){n.push(o);continue}s(),t.push(o)}return s(),t}isStableFieldFillStep(e){if(!e||e.type!=="action"||typeof e.text!="string"||e.criteria||e.fileAssets)return!1;let t=this.normalizeObservationCoverageText(e.text);return!/\b(?:enter|fill|set|type)\b/.test(t)||/\b(?:navigate|click|submit|save|apply|confirm|upload|wait|verify|select|open|filter|search)\b/.test(t)||/\b(?:date|time|calendar|picker|amount|price|currency|deposit|total|subtotal|quantity|count)\b/.test(t)?!1:/\{\{(?:unique|timestamp)\}\}/.test(e.text)||/\b(?:field|comment|name|email|username|identifier|id|number|text)\b/.test(t)||/#/.test(e.text)}stableFieldFillFragment(e){return e.replace(/[.!?]+$/g,"").replace(/^(?:enter|fill|set|type)\s+/i,"").trim()}sanitizeExecutableStepText(e){return e.replace(/\s*\(([^)]*)\)\.?/g,(n,s)=>this.isObservedStateParenthetical(s)?"":n).replace(/\s+/g," ").replace(/\s+([,.;:!?])/g,"$1").trim()||e.trim()}isObservedStateParenthetical(e){let t=this.normalizeObservationCoverageText(e);return/\b(?:is|are|was|were)\s+(?:set|shown|displayed|visible|available|entered|selected|marked|priced|saved|created|updated|present|filled|applied|enabled|disabled|checked|unchecked)\b/.test(t)||/\b(?:filter|field|password|comment|value|status|request|button|message|toast|modal)\b[^.]*\b(?:is|are|was|were)\b/.test(t)||/\b(?:approval|amount|department|request|status|submitted\s+after)\s*:\s*[^,)]/.test(t)||/\b(?:actual\s+)?(?:value|number|identifier|id|record|item|entry)\s+used\s*:\s*[^,)]/.test(t)||/\bused\s+(?:value|number|identifier|id|record|item|entry)\s*:\s*[^,)]/.test(t)}isPlanObservationCovered(e,t){let n=this.normalizeObservationCoverageText(e),s=this.normalizeObservationCoverageText(t);if(n&&s.includes(n)||this.isStateValueObservationCovered(n,s))return!0;let o=this.extractObservationCoverageLiterals(e);return o.length===0?!1:o.every(i=>s.includes(this.normalizeObservationCoverageText(i)))}isStateValueObservationCovered(e,t){let n=e.replace(/[.!?]+$/g,"").trim(),s=[/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+set\s+to\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+selected\s+(?:as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+filled\s+(?:with|as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+entered\s+(?:as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+entered$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+(.+)$/];for(let o of s){let i=n.match(o);if(!i)continue;let a=[...this.extractSignificantObservationTerms(i[1]??""),...this.extractSignificantObservationTerms(i[2]??"")];if(!(a.length<2)&&a.every(l=>t.includes(l)))return!0}return!1}extractSignificantObservationTerms(e){let t=new Set(["a","an","and","are","as","at","field","filter","for","in","input","is","of","on","select","selected","set","the","to","value","with"]),n=new Set;return e.replace(/["'`]/g," ").split(/[^a-z0-9$/.@:-]+/i).map(s=>s.trim().toLowerCase()).filter(s=>s.length>=2&&!t.has(s)).filter(s=>n.has(s)?!1:(n.add(s),!0))}extractObservationCoverageLiterals(e){let t=[],n=new Set,s=a=>{let l=this.normalizeObservationCoverageText(a??"");l.length<2||n.has(l)||(n.add(l),t.push(a.trim()))},o=/"([^"]+)"|'([^']+)'|`([^`]+)`/g;for(let a of e.matchAll(o))s(a[1]??a[2]??a[3]);let i=/\$[\d][\d,]*(?:\.\d+)?(?:\s*\/\s*[A-Za-z]+)?|https?:\/\/[^\s"'`<>]+|[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}|\b\d+(?:,\d{3})*(?:\.\d+)?%/g;for(let a of e.matchAll(i))s(a[0]);return t}normalizeObservationCoverageText(e){return e.normalize("NFKC").replace(/[“”]/g,'"').replace(/[‘’]/g,"'").replace(/\s*\/\s*/g,"/").replace(/\s+/g," ").trim().toLowerCase()}async collectActivePlanObservationFacts(e){let t=[],n=new Set;try{let s=await this.deps.chatRepo.listMessages(e.id);for(let o of s)if(o.actionName==="log_observation")for(let i of this.extractLoggedObservations(o)){for(let a of i.replaces??[]){n.delete(a);let l=t.indexOf(a);l>=0&&t.splice(l,1)}i.purpose==="include_in_plan"&&(n.has(i.fact)||(n.add(i.fact),t.push(i.fact)))}}catch(s){this.log("warn","ExplorerRuntime","Failed to read observations for report coverage",{error:s?.message})}return t}extractLoggedObservations(e){return(Array.isArray(e.actionArgs?.observations)?e.actionArgs.observations:[]).flatMap(n=>{if(!n||typeof n!="object")return[];let s=n,o=typeof s.fact=="string"?s.fact.trim():"";if(!o)return[];let i=s.purpose==="include_in_plan"||s.purpose==="context_only"?s.purpose:"context_only",a=typeof s.subject=="string"?s.subject.trim():"",l=Array.isArray(s.replaces)?s.replaces.filter(c=>typeof c=="string").map(c=>c.trim()).filter(Boolean):void 0;return[{fact:o,purpose:i,...a?{subject:a}:{},...l&&l.length>0?{replaces:l}:{}}]})}async handleReportIssue(e,t){let n=this.recentActionsSnapshot(),s=this.getIssueReportRejectionReason(e.args??{},n);if(s){let d=s==="disabled_control_prerequisite"?Oo(n,Dr(e.args??{})):null;return{response:{status:"issue_rejected",reason:s,instruction:s==="automation_only"?"Do not report automation/tooling limits as application bugs. Continue testing, or finish with assistant_v2_report if the user-visible behavior works.":s==="disabled_control_prerequisite"?ca(d??void 0):s==="negative_state_unconfirmed"?la():s==="negative_state_recovered"?En():"Before reporting missing feedback or no visible response, wait for the expected result or collect hard failure evidence such as a failed request, page error, or console error."},isMetaTool:!0}}let{session:o,isMobile:i}=t,a=o,l,c="";if(i)l=(await this.deps.mobileMcpService.takeScreenshot(this.sessionId)).base64;else{let d=await this.deps.computerUseService.invoke({sessionId:a.id,action:"screenshot",args:{},config:a.config});if(l=d.screenshot,c=d.url??"",Lr(Dr(e.args??{}))&&Ss(e.args??{},d.aiSnapshot))return{response:{status:"issue_rejected",reason:"negative_state_recovered",instruction:En()},isMetaTool:!0}}return{response:{status:"reported",issueId:(await this.recordIssue({title:e.args.title,description:e.args.description,severity:e.args.severity,category:e.args.category,confidence:e.args.confidence,reproSteps:e.args.reproSteps??[]},{session:a,evidenceActions:n,screenshotBase64:l,screenshotUrl:c,chatActionArgs:{...e.args}})).id},isMetaTool:!0}}async recordIssue(e,t){let{session:n,evidenceActions:s}=t,o=de("issue"),i=!1,a;if(t.screenshotBase64)try{let f=await this.deps.imageStorageService?.save({projectId:n.projectId,issueId:o,type:"issue",base64:t.screenshotBase64});i=!0,f&&typeof f=="object"&&f.url&&(a=f.url)}catch(f){this.log("error","ExplorerRuntime","Failed to save issue screenshot",{error:f?.message})}let l=Date.now(),c=s.length>0?{capturedAt:l,actions:s}:void 0,u={id:o,projectId:n.projectId,status:this.deps.isChildAgent?"draft":"pending",title:e.title,description:e.description,severity:e.severity,category:e.category,confidence:e.confidence,reproSteps:e.reproSteps??[],hasScreenshot:i,screenshotUrl:a,url:t.screenshotUrl??"",detectedAt:l,detectedInSessionId:n.id,evidence:c,createdAt:l,updatedAt:l};await this.deps.issuesRepo.upsert(u);let d=u;this.reportedIssues.push({id:d.id,title:d.title,severity:d.severity,description:d.description,repro_steps:d.reproSteps,hasScreenshot:d.hasScreenshot,screenshotUrl:a,evidence:c});let h={id:de("msg"),sessionId:n.id,role:"model",text:"",timestamp:Date.now(),actionName:"report_issue",actionArgs:{issueId:d.id,...t.chatActionArgs??{title:d.title,description:d.description,severity:d.severity,category:d.category,confidence:d.confidence,reproSteps:d.reproSteps},evidence:c,screenshotUrl:a}};return await this.deps.chatRepo.addMessage(h),this.emit("message:added",{sessionId:n.id,message:h}),d}getIssueReportRejectionReason(e,t=[]){let n=Array.isArray(e.reproSteps)?e.reproSteps.filter(l=>typeof l=="string"):[],s=this.normalizeObservationCoverageText([typeof e.title=="string"?e.title:"",typeof e.description=="string"?e.description:"",...n].join(" "));if(!s)return null;let o=/\b(?:automated tools?|automated verification|automation|tooling)\b/.test(s),i=/\b(?:difficult|hard|unable|cannot|can't|failed|attempt)\b.{0,80}\b(?:capture|verify|observe)\b/.test(s)||/\b(?:capture|verify|observe)\b.{0,80}\b(?:difficult|hard|unable|cannot|can't|failed)\b/.test(s),a=/\b(?:notification|toast|success message|transient|short-lived)\b/.test(s);return o&&(i||a)||/\btoo transient for verification\b/.test(s)||/\bdifficult to capture or verify\b/.test(s)?"automation_only":Oo(t,s)?"disabled_control_prerequisite":this.describesMissingFeedback(s)&&this.isPrematureMissingFeedbackReport(t)?"insufficient_evidence":Lr(s)&&!ws(s)&&qc(t)?"negative_state_unconfirmed":null}async maybeRejectBlockedNegativeState(e,t){let n=Dr(e.args??{}),s=ua({claimText:n,evidenceActions:this.recentActionsSnapshot(),terminalAction:"block"});if(s)return s;if(!n||!Lr(n)||ws(n)||t.isMobile)return null;let o=await this.deps.computerUseService.invoke({sessionId:t.session.id,action:"screenshot",args:{},config:t.session.config});return Ss(e.args??{},o.aiSnapshot)?{response:{status:"block_rejected",reason:"negative_state_recovered",instruction:En()},isMetaTool:!0}:null}describesMissingFeedback(e){return/\bunresponsive\b/.test(e)||/\bnothing happens\b/.test(e)||/\bno\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|effect|state change|visible change|message)\b/.test(e)||/\bwithout\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|state change|message)\b/.test(e)||/\b(?:missing|absent)\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|message)\b/.test(e)||/\b(?:feedback|confirmation|notification|toast|success message|response|message)\b.{0,40}\b(?:missing|absent)\b/.test(e)||/\b(?:does not|doesn't|did not|didn't|fails? to)\b.{0,40}\b(?:show|display|trigger|produce|provide|respond|change)\b/.test(e)}isPrematureMissingFeedbackReport(e){if(e.length===0||Bc(e))return!1;let t=-1;for(let n=e.length-1;n>=0;n-=1)if(/^(click_at|right_click_at|mobile_tap|mobile_long_press)$/.test(e[n].toolName)){t=n;break}return t<0?!1:!e.slice(t+1).some(n=>n.toolName==="wait"||n.toolName==="wait_for_element")}async handleRecallHistory(e,t){let n=String(e.args?.query??"").trim();return{response:{results:await this.searchHistory(n)},isMetaTool:!0}}async handleRefreshContext(e,t){let{session:n,isMobile:s}=t,o=n,i=await this.deps.secretsService.listProjectCredentials(o.projectId);await nn(o.id,o.projectId,this.deps),this._lastSeededSessionId=o.id;let a=await this.deps.memoryRepo.list(o.projectId),l=s?"mobile_type_credential":"type_project_credential_at";return this.log("info","ExplorerRuntime","refresh_context",{credentials:i.length,memoryItems:a.length}),{response:{credentials:i.length>0?i.map(c=>`"${c.name}" (use ${l})`):["(none)"],memory:a.length>0?a.map(c=>c.text):["(empty)"]},isMetaTool:!0}}async handleLogObservation(e,t){let n=e.args?.decision==="none"?"none":"capture",s=typeof e.args?.page=="string"?e.args.page.trim().slice(0,200):"",o=typeof e.args?.url=="string"?e.args.url.trim().slice(0,200):"",i=n==="capture"&&Array.isArray(e.args?.observations)?e.args.observations.flatMap(d=>{if(!d||typeof d!="object")return[];let h=d,f=this.truncateObservationString(h.fact,240);if(!f)return[];let p=h.purpose==="include_in_plan"||h.purpose==="context_only"?h.purpose:null;if(!p)return[];let g=this.truncateObservationString(h.subject,120),m=Array.isArray(h.replaces)?h.replaces.map(b=>this.truncateObservationString(b,240)).filter(Boolean).slice(0,8):[];return[{fact:f,purpose:p,...g?{subject:g}:{},...m.length>0?{replaces:m}:{}}]}).slice(0,8):[],a=s||o,l=i.length>0?i.map(d=>d.fact).join("; "):"no durable observations",c=`${a?`${a}: `:""}${l}`;(s||o||i.length>0)&&this.loggedObservationCheckpoints.push({...s?{page:s}:{},...o?{url:o}:{},observations:i});let u={sessionId:t.session.id,id:de("msg"),role:"system",text:c,timestamp:Date.now(),actionName:"log_observation",actionArgs:{decision:n,...s?{page:s}:{},...o?{url:o}:{},observations:i}};return await this.deps.chatRepo.addMessage(u),this.emit("message:added",{sessionId:t.session.id,message:u}),this.markObservationCheckpoint(),{response:{status:"logged",decision:n,factCount:i.length},isMetaTool:!0}}truncateObservationString(e,t){return typeof e=="string"?e.trim().slice(0,t):""}async handleReadFile(e,t){let n=String(e.args?.path??"").trim();if(!this.deps.fileReadService)return{response:{error:"read_file is not available in this environment"},isMetaTool:!0};if(!n)return{response:{error:"path parameter is required"},isMetaTool:!0};try{let s={};return typeof e.args?.offset=="number"&&(s.offset=e.args.offset),typeof e.args?.limit=="number"&&(s.limit=e.args.limit),{response:await this.deps.fileReadService.readFile(n,s),isMetaTool:!0}}catch(s){return{response:{error:s.message||String(s),path:n},isMetaTool:!0}}}async handleViewImage(e,t){let{snapshotOnly:n}=t,s=String(e.args?.path??"").trim();if(!this.deps.fileReadService)return{response:{error:"view_image is not available in this environment"},isMetaTool:!0};if(!s)return{response:{error:"path parameter is required"},isMetaTool:!0};try{let o=await this.deps.fileReadService.readImage(s);return{response:{path:o.path,sizeBytes:o.sizeBytes,mimeType:o.mimeType},parts:n?void 0:[{inlineData:{mimeType:o.mimeType,data:o.base64}}],isMetaTool:!0}}catch(o){return{response:{error:o.message||String(o),path:s},isMetaTool:!0}}}static WAIT_TOOLS=new Set(["wait","wait_for_element","wait_for_text"]);static PASSIVE_WAIT_TOOLS=new Set(["log_observation","screenshot","full_page_screenshot","check_email","view_image","read_file","recall_history","refresh_context"]);_waitStreakStartedAt=null;_waitStreakIntent="";_waitStreakTriggerTool=null;_waitStreakEscalationCycles=0;_waitStreakHangIssueFiled=!1;_waitStreakProgressBaseline=void 0;_loopBlockIssueRecordedStep=-1;async maybeEscalateWaitStreak(e,t){let n=e.name;if(r.WAIT_TOOLS.has(n)){let s=e.args,o=String(s?.intent??"").trim();o&&(this._waitStreakIntent=o);let i=Wc();if(this._waitStreakStartedAt===null)return this._waitStreakStartedAt=Date.now(),this._waitStreakProgressBaseline=this.currentLoopProgressState()?.actionsSinceProgress,null;let a=Date.now()-this._waitStreakStartedAt,l=Number(n==="wait"?s?.seconds:s?.timeoutSeconds),c=Number.isFinite(l)&&l>0?l*1e3:15e3;return a>=i||a+c>=i?this.escalateWaitStreak(t,a):null}if(r.PASSIVE_WAIT_TOOLS.has(n))return null;if(n==="ask_user"&&this._waitStreakStartedAt!==null){let s=Date.now()-this._waitStreakStartedAt;if(s>=Wc()&&this.classifyCurrentWaitStreak(s)==="page_hang")return this.escalatePageHangIssue(t,s)}return this._waitStreakTriggerTool=n,this._waitStreakStartedAt=null,this._waitStreakEscalationCycles=0,this._waitStreakHangIssueFiled=!1,this._waitStreakProgressBaseline=void 0,null}classifyCurrentWaitStreak(e){let t=this.currentLoopProgressState(),n=this._waitStreakProgressBaseline,s=t!=null&&n!=null?t.actionsSinceProgress<n:!1,o=Vy({screenProgressed:s,triggerTool:this._waitStreakTriggerTool,pageSettled:this.lastNavigationSettled(),elapsedMs:e,escalationCycles:this._waitStreakEscalationCycles});return this.log("info","ExplorerRuntime","wait_streak_classified",{kind:o,screenProgressed:s,triggerTool:this._waitStreakTriggerTool??"(none)",pageSettled:this.lastNavigationSettled(),elapsedMs:e,escalationCycles:this._waitStreakEscalationCycles}),o}async escalateWaitStreak(e,t){return this.classifyCurrentWaitStreak(t)==="page_hang"?this.escalatePageHangIssue(e,t):(this._waitStreakEscalationCycles++,this.escalateWaitPause(e))}async escalatePageHangIssue(e,t){let{session:n,isMobile:s}=e,o=tr(t),i=this.lastNavigationSettled();this._waitStreakStartedAt=null,this._waitStreakEscalationCycles=0,this._waitStreakProgressBaseline=void 0;let a,l="";if(!s&&this.deps.computerUseService)try{let h=await this.deps.computerUseService.invoke({sessionId:n.id,action:"screenshot",args:{},config:n.config});a=h.screenshot,l=h.url??""}catch(h){this.log("warn","ExplorerRuntime","page_hang_issue: screenshot capture failed",{error:h?.message})}let c=i===!1?"the page never reached a ready DOM (domcontentloaded did not settle)":i===!0?"the DOM reported ready but the page produced no new content afterward":"the page produced no new content",u=l?` at ${l}`:"",d=this.recentLoopDrivenByNonCommittedWrites();return!this._waitStreakHangIssueFiled&&!d&&(this._waitStreakHangIssueFiled=!0,await this.recordIssue({title:`Page did not finish loading after ~${o}`,description:`The page${u} did not finish loading after about ${o}: ${c}. Throughout the wait the screen produced no new content, so the page appears stuck rather than performing a legitimate background operation. A real user would likely see a blank or perpetually loading screen here.`,severity:"high",category:"logical",confidence:.6,reproSteps:[]},{session:n,evidenceActions:this.recentActionsSnapshot(),screenshotBase64:a,screenshotUrl:l})),this.log("info","ExplorerRuntime","wait_escalation: filed page-hang issue",{elapsedMs:t,pageSettled:i,url:l||"(unknown)",suppressedAsNonCommitWriteLoop:d}),{response:{status:"issue_recorded",instruction:"The current page appears stuck / never finished loading \u2014 this has been recorded as an issue. Do NOT keep waiting on it. Move on: navigate to a different page or workflow, or finish the run if there is nothing else to test."},isMetaTool:!0}}async recordLoopBlockIssue(e,t,n){if(this._loopBlockIssueRecordedStep===n)return;if(this._loopBlockIssueRecordedStep=n,this.recentLoopDrivenByNonCommittedWrites()){this.log("info","ExplorerRuntime","loop_block: suppressed false customer issue \u2014 streak driven by non-committed writes (AG-6107)",{stepIndex:n,detectorSignal:t});return}let s,o="";if(this.deps.computerUseService)try{let a=await this.deps.computerUseService.invoke({sessionId:e.id,action:"screenshot",args:{},config:e.config});s=a.screenshot,o=a.url??""}catch(a){this.log("warn","ExplorerRuntime","loop_block_issue: screenshot capture failed",{error:a?.message})}let i=o?` at ${o}`:"";await this.recordIssue({title:"Page appears stuck \u2014 repeated actions made no progress",description:`The page${i} kept cycling between the same states without converging, so testing could not proceed past this point. A real user would likely perceive the screen as frozen or unresponsive. Detector signal: ${t}`,severity:"high",category:"logical",confidence:.5,reproSteps:[]},{session:e,evidenceActions:this.recentActionsSnapshot(),screenshotBase64:s,screenshotUrl:o}),this.log("info","ExplorerRuntime","loop_block: filed possible issue",{stepIndex:n,url:o||"(unknown)"})}async escalateWaitPause(e){let{session:t}=e,n=this._waitStreakStartedAt?Date.now()-this._waitStreakStartedAt:0,s=Wy(this._waitStreakIntent);this._waitStreakStartedAt=null;let o=`Still waiting for ${s} after ${tr(n)}. What would you like to do?`,{question:i,options:a,waitMarker:l}=this.finalizeAskUser(o,void 0,t,s),c={sessionId:t.id,id:de("msg"),role:"model",text:i,timestamp:Date.now(),actionName:"ask_user",actionArgs:{question:i,...a?.length?{options:a}:{}}};return await this.deps.chatRepo.addMessage(c),this.emit("message:added",{sessionId:t.id,message:c}),this.lastResult={status:"blocked",summary:`ask_user: ${l?`${l} `:""}${i}`,issues:this.reportedIssues},this.log("info","ExplorerRuntime","wait_escalation: auto-paused on async wait",{waitingFor:s,elapsedMs:n}),{response:{status:"awaiting_user_reply"},done:!0,isMetaTool:!0}}finalizeAskUser(e,t,n,s){let o=n.config?.testingEmailPolicy,i=e;o?.canonicalEmail&&(i=i.split(o.canonicalEmail).join("the testing inbox"));let a=t&&t.length>0?t:void 0,l=!!o?.enabled&&o.canCheckInbox===!0&&this._emailVerificationExpected&&this._lastCheckEmailCount===0,c=typeof s=="string"?s.trim():"",u;if(l){if(u=Hc,a||(a=[...Po]),this._firstCheckEmailAt&&!/\d+\s*(?:ms|s\b|sec|second|min|minute|hour)/i.test(i)){let d=this.describeEmailWaitSoFar();i=/what would you like to do\??\s*$/i.test(i.trim())?i.trim().replace(/what would you like to do\??\s*$/i,`${d} What would you like to do?`):`${i.trim()} ${d}`.trim()}}else c&&(u=Py(c),a||(a=[...Po]));return{question:i,options:a,waitMarker:u}}describeEmailWaitSoFar(){let e=this._checkEmailCallCount,t=this._firstCheckEmailAt?Date.now()-this._firstCheckEmailAt:0,n=`${e} inbox check${e===1?"":"s"}`;return t<1e3?`The verification email has not arrived yet (${n}).`:`The verification email has not arrived after about ${tr(t)} (${n}).`}async handleAskUser(e,t){let{session:n}=t,s=typeof e.args?.context=="string"?String(e.args.context).trim():void 0,o=Array.isArray(e.args?.options)?e.args.options.map(u=>String(u).trim()).filter(Boolean):void 0,{question:i,options:a,waitMarker:l}=this.finalizeAskUser(String(e.args?.question??"").trim(),o,n,e.args?.waiting_for),c={sessionId:n.id,id:de("msg"),role:"model",text:i,timestamp:Date.now(),actionName:"ask_user",actionArgs:{question:i,context:s,...a?.length?{options:a}:{}}};return await this.deps.chatRepo.addMessage(c),this.emit("message:added",{sessionId:n.id,message:c}),this.lastResult={status:"blocked",summary:`ask_user: ${l?`${l} `:""}${i}`,issues:this.reportedIssues},{response:{status:"awaiting_user_reply"},done:!0,isMetaTool:!0}}async handleBlocked(e,t){let{session:n}=t,s=n,o=String(e.args?.attempted??"").trim(),i=String(e.args?.obstacle??"").trim(),a=String(e.args?.question??"").trim(),l={sessionId:s.id,id:de("msg"),role:"model",text:a,timestamp:Date.now(),actionName:"exploration_blocked",actionArgs:{attempted:o,obstacle:i,question:a}};return await this.deps.chatRepo.addMessage(l),this.emit("message:added",{sessionId:s.id,message:l}),this.lastResult={status:"blocked",summary:`Blocked: ${i}`,issues:this.reportedIssues},{response:{status:"awaiting_user_guidance"},done:!0,isMetaTool:!0}}async sendMessage(e,t,n){if(this.deps.authService.isAuthRequired()&&!await this.deps.authService.ensureAuthenticated()){this.emit("auth:required",{sessionId:this.sessionId,action:"send_message"});return}if(this._isRunning){let l="Session is already running";throw this.emit("session:error",{sessionId:this.sessionId,error:l}),new Error(l)}if(!await(this.deps.llmAccessService?.hasApiKey()??Promise.resolve(!0))){let l="Gemini API key not set";throw this.emit("session:error",{sessionId:this.sessionId,error:l}),new Error(l)}this.beginRun(),this.currentProjectId=e.projectId,this.currentSessionKind=e.kind??null;try{let l=await this.deps.projectsRepo?.get(e.projectId);this.currentProjectName=l?.name??null}catch{this.currentProjectName=null}let o=!1,i,a=null;try{let l=await this.deps.chatRepo.getSession(this.sessionId)??e,c={...l,activeRunId:typeof l.activeRunId>"u"?null:l.activeRunId},d=(c.config?.platform||"web")==="mobile",h=d?c.config?.mobileConfig?.platform||"android":void 0,f=zy("AGENTIQA_EXPERIMENT_FAST_START_PROMPT"),p=zy("AGENTIQA_EXPERIMENT_MINIMAL_INITIAL_CONTEXT"),g=h==="ios",m=d&&vn(c.config?.mobileConfig),b=!d&&(c.config?.snapshotOnly??!1),w={sessionId:c.id,id:de("msg"),role:"user",text:t,timestamp:Date.now(),...n?.length&&{attachments:n.map(U=>({id:U.id,originalName:U.originalName,mimeType:U.mimeType,sizeBytes:U.sizeBytes}))}};i=w.id,await this.deps.chatRepo.addMessage(w),this.emit("message:added",{sessionId:c.id,message:w});let _=await this.deps.memoryRepo.list(c.projectId),T=await this.deps.secretsService.listProjectCredentials(c.projectId);await nn(c.id,c.projectId,this.deps),this._lastSeededSessionId=c.id;let v=await this.deps.issuesRepo.list(c.projectId,{status:["confirmed","dismissed"]});this.log("info","ExplorerRuntime","Context loaded",{projectId:c.projectId,memory:_.length,credentials:T.length,issues:v.length}),this.recordStartupMilestone("context_loaded",{projectId:c.projectId,memoryCount:_.length,credentialCount:T.length,issueCount:v.length}),this.baseDeps.sink.emit({kind:"session_start",ts:Date.now(),sessionId:c.id,sessionMeta:{...mn(c,this.baseDeps.sessionMetaExtras),memoryItems:_.map(U=>U.text),credentialNames:T.map(U=>U.name)}});let x=await this.ensureConversationTraceLoaded(c);if(await this.maybeSummarizeContext(c,{source:"ExplorerRuntime",iteration:0,tokenCount:c.lastTokenCount??this.tokenCount}),x.length===0){let U=`
506
+ `),m=de("msg"),b=!1,w;if(s&&this.deps.mobileMcpService)try{let x=await this.deps.mobileMcpService.takeScreenshot(o.id);x.base64&&this.deps.imageStorageService&&o.projectId&&(await this.deps.imageStorageService.save({projectId:o.projectId,sessionId:o.id,messageId:m,type:"message",base64:x.base64}),b=!0,w=x.base64)}catch(x){this.log("warn","ExplorerRuntime","Failed to capture report screenshot",{error:x?.message})}let _={sessionId:o.id,id:m,role:"model",text:g||(i==="needs_user"?"I need one clarification.":"Done."),timestamp:Date.now(),actionName:"assistant_v2_report",actionArgs:{status:i,draftTestCase:d,reflection:h},hasScreenshot:b||void 0};await this.deps.chatRepo.addMessage(_),this.emit("message:added",{sessionId:o.id,message:_,...w?{screenshotBase64:w}:{}});let T=Array.isArray(e.args?.discoveredAreas)&&e.args.discoveredAreas.length>0?e.args.discoveredAreas:null,v=T??(this.deps.isDiscoveryRun?this.extractDiscoveredAreasFromTrace():void 0);return!T&&this.deps.isDiscoveryRun&&v?.length&&this.log("info","ExplorerRuntime","Fallback: extracted discoveredAreas from trace",{count:v.length,urls:v.map(x=>x.url)}),this.lastResult={status:"completed",summary:a||"",discoveredAreas:v,coverage:f||void 0,draftTestCase:d||void 0,issues:this.reportedIssues},{response:{status:"ok"},done:!0,isMetaTool:!0}}async findMissingPlanObservations(e,t){let n=await this.collectActivePlanObservationFacts(e);if(n.length===0)return[];let s=JSON.stringify(t?.steps??[]);return n.filter(o=>!this.isPlanObservationCovered(fi(o,e.config),s))}sanitizeDraftTestCase(e,t){if(!e||typeof e!="object"||!Array.isArray(e.steps))return e;let n=e.steps.map(s=>{if(!s||typeof s!="object")return s;let o={...s};return(s.type==="setup"||s.type==="action")&&typeof s.text=="string"&&(o.text=this.sanitizeExecutableStepText(s.text)),typeof o.text=="string"&&(o.text=fi(o.text,t)),Array.isArray(o.criteria)&&(o.criteria=o.criteria.map(i=>i&&typeof i=="object"&&typeof i.check=="string"?{...i,check:fi(i.check,t)}:i)),o});return{...e,steps:this.compactStableFieldFillSteps(n)}}compactStableFieldFillSteps(e){let t=[],n=[],s=()=>{n.length>=2?t.push({...n[0],text:`Fill the stable form fields: ${n.map(o=>this.stableFieldFillFragment(o.text)).join("; ")}.`}):n.length===1&&t.push(n[0]),n=[]};for(let o of e){if(this.isStableFieldFillStep(o)){n.push(o);continue}s(),t.push(o)}return s(),t}isStableFieldFillStep(e){if(!e||e.type!=="action"||typeof e.text!="string"||e.criteria||e.fileAssets)return!1;let t=this.normalizeObservationCoverageText(e.text);return!/\b(?:enter|fill|set|type)\b/.test(t)||/\b(?:navigate|click|submit|save|apply|confirm|upload|wait|verify|select|open|filter|search)\b/.test(t)||/\b(?:date|time|calendar|picker|amount|price|currency|deposit|total|subtotal|quantity|count)\b/.test(t)?!1:/\{\{(?:unique|timestamp)\}\}/.test(e.text)||/\b(?:field|comment|name|email|username|identifier|id|number|text)\b/.test(t)||/#/.test(e.text)}stableFieldFillFragment(e){return e.replace(/[.!?]+$/g,"").replace(/^(?:enter|fill|set|type)\s+/i,"").trim()}sanitizeExecutableStepText(e){return e.replace(/\/tmp\/agentiqa-attachments\/\d+-([^\s'"]+)/g,"$1").replace(/\s*\(([^)]*)\)\.?/g,(n,s)=>this.isObservedStateParenthetical(s)?"":n).replace(/\s+/g," ").replace(/\s+([,.;:!?])/g,"$1").trim()||e.trim()}isObservedStateParenthetical(e){let t=this.normalizeObservationCoverageText(e);return/\b(?:is|are|was|were)\s+(?:set|shown|displayed|visible|available|entered|selected|marked|priced|saved|created|updated|present|filled|applied|enabled|disabled|checked|unchecked)\b/.test(t)||/\b(?:filter|field|password|comment|value|status|request|button|message|toast|modal)\b[^.]*\b(?:is|are|was|were)\b/.test(t)||/\b(?:approval|amount|department|request|status|submitted\s+after)\s*:\s*[^,)]/.test(t)||/\b(?:actual\s+)?(?:value|number|identifier|id|record|item|entry)\s+used\s*:\s*[^,)]/.test(t)||/\bused\s+(?:value|number|identifier|id|record|item|entry)\s*:\s*[^,)]/.test(t)}isPlanObservationCovered(e,t){let n=this.normalizeObservationCoverageText(e),s=this.normalizeObservationCoverageText(t);if(n&&s.includes(n)||this.isStateValueObservationCovered(n,s))return!0;let o=this.extractObservationCoverageLiterals(e);return o.length===0?!1:o.every(i=>s.includes(this.normalizeObservationCoverageText(i)))}isStateValueObservationCovered(e,t){let n=e.replace(/[.!?]+$/g,"").trim(),s=[/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+set\s+to\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+selected\s+(?:as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+filled\s+(?:with|as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+entered\s+(?:as|to)\s+(.+)$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+entered$/,/^(?:the\s+)?(.+?)\s+(?:is|are|was|were)\s+(.+)$/];for(let o of s){let i=n.match(o);if(!i)continue;let a=[...this.extractSignificantObservationTerms(i[1]??""),...this.extractSignificantObservationTerms(i[2]??"")];if(!(a.length<2)&&a.every(l=>t.includes(l)))return!0}return!1}extractSignificantObservationTerms(e){let t=new Set(["a","an","and","are","as","at","field","filter","for","in","input","is","of","on","select","selected","set","the","to","value","with"]),n=new Set;return e.replace(/["'`]/g," ").split(/[^a-z0-9$/.@:-]+/i).map(s=>s.trim().toLowerCase()).filter(s=>s.length>=2&&!t.has(s)).filter(s=>n.has(s)?!1:(n.add(s),!0))}extractObservationCoverageLiterals(e){let t=[],n=new Set,s=a=>{let l=this.normalizeObservationCoverageText(a??"");l.length<2||n.has(l)||(n.add(l),t.push(a.trim()))},o=/"([^"]+)"|'([^']+)'|`([^`]+)`/g;for(let a of e.matchAll(o))s(a[1]??a[2]??a[3]);let i=/\$[\d][\d,]*(?:\.\d+)?(?:\s*\/\s*[A-Za-z]+)?|https?:\/\/[^\s"'`<>]+|[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}|\b\d+(?:,\d{3})*(?:\.\d+)?%/g;for(let a of e.matchAll(i))s(a[0]);return t}normalizeObservationCoverageText(e){return e.normalize("NFKC").replace(/[“”]/g,'"').replace(/[‘’]/g,"'").replace(/\s*\/\s*/g,"/").replace(/\s+/g," ").trim().toLowerCase()}async collectActivePlanObservationFacts(e){let t=[],n=new Set;try{let s=await this.deps.chatRepo.listMessages(e.id);for(let o of s)if(o.actionName==="log_observation")for(let i of this.extractLoggedObservations(o)){for(let a of i.replaces??[]){n.delete(a);let l=t.indexOf(a);l>=0&&t.splice(l,1)}i.purpose==="include_in_plan"&&(n.has(i.fact)||(n.add(i.fact),t.push(i.fact)))}}catch(s){this.log("warn","ExplorerRuntime","Failed to read observations for report coverage",{error:s?.message})}return t}extractLoggedObservations(e){return(Array.isArray(e.actionArgs?.observations)?e.actionArgs.observations:[]).flatMap(n=>{if(!n||typeof n!="object")return[];let s=n,o=typeof s.fact=="string"?s.fact.trim():"";if(!o)return[];let i=s.purpose==="include_in_plan"||s.purpose==="context_only"?s.purpose:"context_only",a=typeof s.subject=="string"?s.subject.trim():"",l=Array.isArray(s.replaces)?s.replaces.filter(c=>typeof c=="string").map(c=>c.trim()).filter(Boolean):void 0;return[{fact:o,purpose:i,...a?{subject:a}:{},...l&&l.length>0?{replaces:l}:{}}]})}async handleReportIssue(e,t){let n=this.recentActionsSnapshot(),s=this.getIssueReportRejectionReason(e.args??{},n);if(s){let d=s==="disabled_control_prerequisite"?Oo(n,Dr(e.args??{})):null;return{response:{status:"issue_rejected",reason:s,instruction:s==="automation_only"?"Do not report automation/tooling limits as application bugs. Continue testing, or finish with assistant_v2_report if the user-visible behavior works.":s==="disabled_control_prerequisite"?ca(d??void 0):s==="negative_state_unconfirmed"?la():s==="negative_state_recovered"?En():"Before reporting missing feedback or no visible response, wait for the expected result or collect hard failure evidence such as a failed request, page error, or console error."},isMetaTool:!0}}let{session:o,isMobile:i}=t,a=o,l,c="";if(i)l=(await this.deps.mobileMcpService.takeScreenshot(this.sessionId)).base64;else{let d=await this.deps.computerUseService.invoke({sessionId:a.id,action:"screenshot",args:{},config:a.config});if(l=d.screenshot,c=d.url??"",Lr(Dr(e.args??{}))&&Ss(e.args??{},d.aiSnapshot))return{response:{status:"issue_rejected",reason:"negative_state_recovered",instruction:En()},isMetaTool:!0}}return{response:{status:"reported",issueId:(await this.recordIssue({title:e.args.title,description:e.args.description,severity:e.args.severity,category:e.args.category,confidence:e.args.confidence,reproSteps:e.args.reproSteps??[]},{session:a,evidenceActions:n,screenshotBase64:l,screenshotUrl:c,chatActionArgs:{...e.args}})).id},isMetaTool:!0}}async recordIssue(e,t){let{session:n,evidenceActions:s}=t,o=de("issue"),i=!1,a;if(t.screenshotBase64)try{let f=await this.deps.imageStorageService?.save({projectId:n.projectId,issueId:o,type:"issue",base64:t.screenshotBase64});i=!0,f&&typeof f=="object"&&f.url&&(a=f.url)}catch(f){this.log("error","ExplorerRuntime","Failed to save issue screenshot",{error:f?.message})}let l=Date.now(),c=s.length>0?{capturedAt:l,actions:s}:void 0,u={id:o,projectId:n.projectId,status:this.deps.isChildAgent?"draft":"pending",title:e.title,description:e.description,severity:e.severity,category:e.category,confidence:e.confidence,reproSteps:e.reproSteps??[],hasScreenshot:i,screenshotUrl:a,url:t.screenshotUrl??"",detectedAt:l,detectedInSessionId:n.id,evidence:c,createdAt:l,updatedAt:l};await this.deps.issuesRepo.upsert(u);let d=u;this.reportedIssues.push({id:d.id,title:d.title,severity:d.severity,description:d.description,repro_steps:d.reproSteps,hasScreenshot:d.hasScreenshot,screenshotUrl:a,evidence:c});let h={id:de("msg"),sessionId:n.id,role:"model",text:"",timestamp:Date.now(),actionName:"report_issue",actionArgs:{issueId:d.id,...t.chatActionArgs??{title:d.title,description:d.description,severity:d.severity,category:d.category,confidence:d.confidence,reproSteps:d.reproSteps},evidence:c,screenshotUrl:a}};return await this.deps.chatRepo.addMessage(h),this.emit("message:added",{sessionId:n.id,message:h}),d}getIssueReportRejectionReason(e,t=[]){let n=Array.isArray(e.reproSteps)?e.reproSteps.filter(l=>typeof l=="string"):[],s=this.normalizeObservationCoverageText([typeof e.title=="string"?e.title:"",typeof e.description=="string"?e.description:"",...n].join(" "));if(!s)return null;let o=/\b(?:automated tools?|automated verification|automation|tooling)\b/.test(s),i=/\b(?:difficult|hard|unable|cannot|can't|failed|attempt)\b.{0,80}\b(?:capture|verify|observe)\b/.test(s)||/\b(?:capture|verify|observe)\b.{0,80}\b(?:difficult|hard|unable|cannot|can't|failed)\b/.test(s),a=/\b(?:notification|toast|success message|transient|short-lived)\b/.test(s);return o&&(i||a)||/\btoo transient for verification\b/.test(s)||/\bdifficult to capture or verify\b/.test(s)?"automation_only":Oo(t,s)?"disabled_control_prerequisite":this.describesMissingFeedback(s)&&this.isPrematureMissingFeedbackReport(t)?"insufficient_evidence":Lr(s)&&!ws(s)&&qc(t)?"negative_state_unconfirmed":null}async maybeRejectBlockedNegativeState(e,t){let n=Dr(e.args??{}),s=ua({claimText:n,evidenceActions:this.recentActionsSnapshot(),terminalAction:"block"});if(s)return s;if(!n||!Lr(n)||ws(n)||t.isMobile)return null;let o=await this.deps.computerUseService.invoke({sessionId:t.session.id,action:"screenshot",args:{},config:t.session.config});return Ss(e.args??{},o.aiSnapshot)?{response:{status:"block_rejected",reason:"negative_state_recovered",instruction:En()},isMetaTool:!0}:null}describesMissingFeedback(e){return/\bunresponsive\b/.test(e)||/\bnothing happens\b/.test(e)||/\bno\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|effect|state change|visible change|message)\b/.test(e)||/\bwithout\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|state change|message)\b/.test(e)||/\b(?:missing|absent)\b.{0,40}\b(?:feedback|confirmation|notification|toast|success message|response|message)\b/.test(e)||/\b(?:feedback|confirmation|notification|toast|success message|response|message)\b.{0,40}\b(?:missing|absent)\b/.test(e)||/\b(?:does not|doesn't|did not|didn't|fails? to)\b.{0,40}\b(?:show|display|trigger|produce|provide|respond|change)\b/.test(e)}isPrematureMissingFeedbackReport(e){if(e.length===0||Bc(e))return!1;let t=-1;for(let n=e.length-1;n>=0;n-=1)if(/^(click_at|right_click_at|mobile_tap|mobile_long_press)$/.test(e[n].toolName)){t=n;break}return t<0?!1:!e.slice(t+1).some(n=>n.toolName==="wait"||n.toolName==="wait_for_element")}async handleRecallHistory(e,t){let n=String(e.args?.query??"").trim();return{response:{results:await this.searchHistory(n)},isMetaTool:!0}}async handleRefreshContext(e,t){let{session:n,isMobile:s}=t,o=n,i=await this.deps.secretsService.listProjectCredentials(o.projectId);await nn(o.id,o.projectId,this.deps),this._lastSeededSessionId=o.id;let a=await this.deps.memoryRepo.list(o.projectId),l=s?"mobile_type_credential":"type_project_credential_at";return this.log("info","ExplorerRuntime","refresh_context",{credentials:i.length,memoryItems:a.length}),{response:{credentials:i.length>0?i.map(c=>`"${c.name}" (use ${l})`):["(none)"],memory:a.length>0?a.map(c=>c.text):["(empty)"]},isMetaTool:!0}}async handleLogObservation(e,t){let n=e.args?.decision==="none"?"none":"capture",s=typeof e.args?.page=="string"?e.args.page.trim().slice(0,200):"",o=typeof e.args?.url=="string"?e.args.url.trim().slice(0,200):"",i=n==="capture"&&Array.isArray(e.args?.observations)?e.args.observations.flatMap(d=>{if(!d||typeof d!="object")return[];let h=d,f=this.truncateObservationString(h.fact,240);if(!f)return[];let p=h.purpose==="include_in_plan"||h.purpose==="context_only"?h.purpose:null;if(!p)return[];let g=this.truncateObservationString(h.subject,120),m=Array.isArray(h.replaces)?h.replaces.map(b=>this.truncateObservationString(b,240)).filter(Boolean).slice(0,8):[];return[{fact:f,purpose:p,...g?{subject:g}:{},...m.length>0?{replaces:m}:{}}]}).slice(0,8):[],a=s||o,l=i.length>0?i.map(d=>d.fact).join("; "):"no durable observations",c=`${a?`${a}: `:""}${l}`;(s||o||i.length>0)&&this.loggedObservationCheckpoints.push({...s?{page:s}:{},...o?{url:o}:{},observations:i});let u={sessionId:t.session.id,id:de("msg"),role:"system",text:c,timestamp:Date.now(),actionName:"log_observation",actionArgs:{decision:n,...s?{page:s}:{},...o?{url:o}:{},observations:i}};return await this.deps.chatRepo.addMessage(u),this.emit("message:added",{sessionId:t.session.id,message:u}),this.markObservationCheckpoint(),{response:{status:"logged",decision:n,factCount:i.length},isMetaTool:!0}}truncateObservationString(e,t){return typeof e=="string"?e.trim().slice(0,t):""}async handleReadFile(e,t){let n=String(e.args?.path??"").trim();if(!this.deps.fileReadService)return{response:{error:"read_file is not available in this environment"},isMetaTool:!0};if(!n)return{response:{error:"path parameter is required"},isMetaTool:!0};try{let s={};return typeof e.args?.offset=="number"&&(s.offset=e.args.offset),typeof e.args?.limit=="number"&&(s.limit=e.args.limit),{response:await this.deps.fileReadService.readFile(n,s),isMetaTool:!0}}catch(s){return{response:{error:s.message||String(s),path:n},isMetaTool:!0}}}async handleViewImage(e,t){let{snapshotOnly:n}=t,s=String(e.args?.path??"").trim();if(!this.deps.fileReadService)return{response:{error:"view_image is not available in this environment"},isMetaTool:!0};if(!s)return{response:{error:"path parameter is required"},isMetaTool:!0};try{let o=await this.deps.fileReadService.readImage(s);return{response:{path:o.path,sizeBytes:o.sizeBytes,mimeType:o.mimeType},parts:n?void 0:[{inlineData:{mimeType:o.mimeType,data:o.base64}}],isMetaTool:!0}}catch(o){return{response:{error:o.message||String(o),path:s},isMetaTool:!0}}}static WAIT_TOOLS=new Set(["wait","wait_for_element","wait_for_text"]);static PASSIVE_WAIT_TOOLS=new Set(["log_observation","screenshot","full_page_screenshot","check_email","view_image","read_file","recall_history","refresh_context"]);_waitStreakStartedAt=null;_waitStreakIntent="";_waitStreakTriggerTool=null;_waitStreakEscalationCycles=0;_waitStreakHangIssueFiled=!1;_waitStreakProgressBaseline=void 0;_loopBlockIssueRecordedStep=-1;async maybeEscalateWaitStreak(e,t){let n=e.name;if(r.WAIT_TOOLS.has(n)){let s=e.args,o=String(s?.intent??"").trim();o&&(this._waitStreakIntent=o);let i=Wc();if(this._waitStreakStartedAt===null)return this._waitStreakStartedAt=Date.now(),this._waitStreakProgressBaseline=this.currentLoopProgressState()?.actionsSinceProgress,null;let a=Date.now()-this._waitStreakStartedAt,l=Number(n==="wait"?s?.seconds:s?.timeoutSeconds),c=Number.isFinite(l)&&l>0?l*1e3:15e3;return a>=i||a+c>=i?this.escalateWaitStreak(t,a):null}if(r.PASSIVE_WAIT_TOOLS.has(n))return null;if(n==="ask_user"&&this._waitStreakStartedAt!==null){let s=Date.now()-this._waitStreakStartedAt;if(s>=Wc()&&this.classifyCurrentWaitStreak(s)==="page_hang")return this.escalatePageHangIssue(t,s)}return this._waitStreakTriggerTool=n,this._waitStreakStartedAt=null,this._waitStreakEscalationCycles=0,this._waitStreakHangIssueFiled=!1,this._waitStreakProgressBaseline=void 0,null}classifyCurrentWaitStreak(e){let t=this.currentLoopProgressState(),n=this._waitStreakProgressBaseline,s=t!=null&&n!=null?t.actionsSinceProgress<n:!1,o=Vy({screenProgressed:s,triggerTool:this._waitStreakTriggerTool,pageSettled:this.lastNavigationSettled(),elapsedMs:e,escalationCycles:this._waitStreakEscalationCycles});return this.log("info","ExplorerRuntime","wait_streak_classified",{kind:o,screenProgressed:s,triggerTool:this._waitStreakTriggerTool??"(none)",pageSettled:this.lastNavigationSettled(),elapsedMs:e,escalationCycles:this._waitStreakEscalationCycles}),o}async escalateWaitStreak(e,t){return this.classifyCurrentWaitStreak(t)==="page_hang"?this.escalatePageHangIssue(e,t):(this._waitStreakEscalationCycles++,this.escalateWaitPause(e))}async escalatePageHangIssue(e,t){let{session:n,isMobile:s}=e,o=tr(t),i=this.lastNavigationSettled();this._waitStreakStartedAt=null,this._waitStreakEscalationCycles=0,this._waitStreakProgressBaseline=void 0;let a,l="";if(!s&&this.deps.computerUseService)try{let h=await this.deps.computerUseService.invoke({sessionId:n.id,action:"screenshot",args:{},config:n.config});a=h.screenshot,l=h.url??""}catch(h){this.log("warn","ExplorerRuntime","page_hang_issue: screenshot capture failed",{error:h?.message})}let c=i===!1?"the page never reached a ready DOM (domcontentloaded did not settle)":i===!0?"the DOM reported ready but the page produced no new content afterward":"the page produced no new content",u=l?` at ${l}`:"",d=this.recentLoopDrivenByNonCommittedWrites();return!this._waitStreakHangIssueFiled&&!d&&(this._waitStreakHangIssueFiled=!0,await this.recordIssue({title:`Page did not finish loading after ~${o}`,description:`The page${u} did not finish loading after about ${o}: ${c}. Throughout the wait the screen produced no new content, so the page appears stuck rather than performing a legitimate background operation. A real user would likely see a blank or perpetually loading screen here.`,severity:"high",category:"logical",confidence:.6,reproSteps:[]},{session:n,evidenceActions:this.recentActionsSnapshot(),screenshotBase64:a,screenshotUrl:l})),this.log("info","ExplorerRuntime","wait_escalation: filed page-hang issue",{elapsedMs:t,pageSettled:i,url:l||"(unknown)",suppressedAsNonCommitWriteLoop:d}),{response:{status:"issue_recorded",instruction:"The current page appears stuck / never finished loading \u2014 this has been recorded as an issue. Do NOT keep waiting on it. Move on: navigate to a different page or workflow, or finish the run if there is nothing else to test."},isMetaTool:!0}}async recordLoopBlockIssue(e,t,n){if(this._loopBlockIssueRecordedStep===n)return;if(this._loopBlockIssueRecordedStep=n,this.recentLoopDrivenByNonCommittedWrites()){this.log("info","ExplorerRuntime","loop_block: suppressed false customer issue \u2014 streak driven by non-committed writes (AG-6107)",{stepIndex:n,detectorSignal:t});return}let s,o="";if(this.deps.computerUseService)try{let a=await this.deps.computerUseService.invoke({sessionId:e.id,action:"screenshot",args:{},config:e.config});s=a.screenshot,o=a.url??""}catch(a){this.log("warn","ExplorerRuntime","loop_block_issue: screenshot capture failed",{error:a?.message})}let i=o?` at ${o}`:"";await this.recordIssue({title:"Page appears stuck \u2014 repeated actions made no progress",description:`The page${i} kept cycling between the same states without converging, so testing could not proceed past this point. A real user would likely perceive the screen as frozen or unresponsive. Detector signal: ${t}`,severity:"high",category:"logical",confidence:.5,reproSteps:[]},{session:e,evidenceActions:this.recentActionsSnapshot(),screenshotBase64:s,screenshotUrl:o}),this.log("info","ExplorerRuntime","loop_block: filed possible issue",{stepIndex:n,url:o||"(unknown)"})}async escalateWaitPause(e){let{session:t}=e,n=this._waitStreakStartedAt?Date.now()-this._waitStreakStartedAt:0,s=Wy(this._waitStreakIntent);this._waitStreakStartedAt=null;let o=`Still waiting for ${s} after ${tr(n)}. What would you like to do?`,{question:i,options:a,waitMarker:l}=this.finalizeAskUser(o,void 0,t,s),c={sessionId:t.id,id:de("msg"),role:"model",text:i,timestamp:Date.now(),actionName:"ask_user",actionArgs:{question:i,...a?.length?{options:a}:{}}};return await this.deps.chatRepo.addMessage(c),this.emit("message:added",{sessionId:t.id,message:c}),this.lastResult={status:"blocked",summary:`ask_user: ${l?`${l} `:""}${i}`,issues:this.reportedIssues},this.log("info","ExplorerRuntime","wait_escalation: auto-paused on async wait",{waitingFor:s,elapsedMs:n}),{response:{status:"awaiting_user_reply"},done:!0,isMetaTool:!0}}finalizeAskUser(e,t,n,s){let o=n.config?.testingEmailPolicy,i=e;o?.canonicalEmail&&(i=i.split(o.canonicalEmail).join("the testing inbox"));let a=t&&t.length>0?t:void 0,l=!!o?.enabled&&o.canCheckInbox===!0&&this._emailVerificationExpected&&this._lastCheckEmailCount===0,c=typeof s=="string"?s.trim():"",u;if(l){if(u=Hc,a||(a=[...Po]),this._firstCheckEmailAt&&!/\d+\s*(?:ms|s\b|sec|second|min|minute|hour)/i.test(i)){let d=this.describeEmailWaitSoFar();i=/what would you like to do\??\s*$/i.test(i.trim())?i.trim().replace(/what would you like to do\??\s*$/i,`${d} What would you like to do?`):`${i.trim()} ${d}`.trim()}}else c&&(u=Py(c),a||(a=[...Po]));return{question:i,options:a,waitMarker:u}}describeEmailWaitSoFar(){let e=this._checkEmailCallCount,t=this._firstCheckEmailAt?Date.now()-this._firstCheckEmailAt:0,n=`${e} inbox check${e===1?"":"s"}`;return t<1e3?`The verification email has not arrived yet (${n}).`:`The verification email has not arrived after about ${tr(t)} (${n}).`}async handleAskUser(e,t){let{session:n}=t,s=typeof e.args?.context=="string"?String(e.args.context).trim():void 0,o=Array.isArray(e.args?.options)?e.args.options.map(u=>String(u).trim()).filter(Boolean):void 0,{question:i,options:a,waitMarker:l}=this.finalizeAskUser(String(e.args?.question??"").trim(),o,n,e.args?.waiting_for),c={sessionId:n.id,id:de("msg"),role:"model",text:i,timestamp:Date.now(),actionName:"ask_user",actionArgs:{question:i,context:s,...a?.length?{options:a}:{}}};return await this.deps.chatRepo.addMessage(c),this.emit("message:added",{sessionId:n.id,message:c}),this.lastResult={status:"blocked",summary:`ask_user: ${l?`${l} `:""}${i}`,issues:this.reportedIssues},{response:{status:"awaiting_user_reply"},done:!0,isMetaTool:!0}}async handleBlocked(e,t){let{session:n}=t,s=n,o=String(e.args?.attempted??"").trim(),i=String(e.args?.obstacle??"").trim(),a=String(e.args?.question??"").trim(),l={sessionId:s.id,id:de("msg"),role:"model",text:a,timestamp:Date.now(),actionName:"exploration_blocked",actionArgs:{attempted:o,obstacle:i,question:a}};return await this.deps.chatRepo.addMessage(l),this.emit("message:added",{sessionId:s.id,message:l}),this.lastResult={status:"blocked",summary:`Blocked: ${i}`,issues:this.reportedIssues},{response:{status:"awaiting_user_guidance"},done:!0,isMetaTool:!0}}async sendMessage(e,t,n){if(this.deps.authService.isAuthRequired()&&!await this.deps.authService.ensureAuthenticated()){this.emit("auth:required",{sessionId:this.sessionId,action:"send_message"});return}if(this._isRunning){let l="Session is already running";throw this.emit("session:error",{sessionId:this.sessionId,error:l}),new Error(l)}if(!await(this.deps.llmAccessService?.hasApiKey()??Promise.resolve(!0))){let l="Gemini API key not set";throw this.emit("session:error",{sessionId:this.sessionId,error:l}),new Error(l)}this.beginRun(),this.currentProjectId=e.projectId,this.currentSessionKind=e.kind??null;try{let l=await this.deps.projectsRepo?.get(e.projectId);this.currentProjectName=l?.name??null}catch{this.currentProjectName=null}let o=!1,i,a=null;try{let l=await this.deps.chatRepo.getSession(this.sessionId)??e,c={...l,activeRunId:typeof l.activeRunId>"u"?null:l.activeRunId},d=(c.config?.platform||"web")==="mobile",h=d?c.config?.mobileConfig?.platform||"android":void 0,f=zy("AGENTIQA_EXPERIMENT_FAST_START_PROMPT"),p=zy("AGENTIQA_EXPERIMENT_MINIMAL_INITIAL_CONTEXT"),g=h==="ios",m=d&&vn(c.config?.mobileConfig),b=!d&&(c.config?.snapshotOnly??!1),w={sessionId:c.id,id:de("msg"),role:"user",text:t,timestamp:Date.now(),...n?.length&&{attachments:n.map(U=>({id:U.id,originalName:U.originalName,mimeType:U.mimeType,sizeBytes:U.sizeBytes}))}};i=w.id,await this.deps.chatRepo.addMessage(w),this.emit("message:added",{sessionId:c.id,message:w});let _=await this.deps.memoryRepo.list(c.projectId),T=await this.deps.secretsService.listProjectCredentials(c.projectId);await nn(c.id,c.projectId,this.deps),this._lastSeededSessionId=c.id;let v=await this.deps.issuesRepo.list(c.projectId,{status:["confirmed","dismissed"]});this.log("info","ExplorerRuntime","Context loaded",{projectId:c.projectId,memory:_.length,credentials:T.length,issues:v.length}),this.recordStartupMilestone("context_loaded",{projectId:c.projectId,memoryCount:_.length,credentialCount:T.length,issueCount:v.length}),this.baseDeps.sink.emit({kind:"session_start",ts:Date.now(),sessionId:c.id,sessionMeta:{...mn(c,this.baseDeps.sessionMetaExtras),memoryItems:_.map(U=>U.text),credentialNames:T.map(U=>U.name)}});let x=await this.ensureConversationTraceLoaded(c);if(await this.maybeSummarizeContext(c,{source:"ExplorerRuntime",iteration:0,tokenCount:c.lastTokenCount??this.tokenCount}),x.length===0){let U=`
502
507
 
503
508
  PROJECT MEMORY:
504
509
  `;if(f)U=DC(T,d);else{if(_.length===0&&T.length===0)U+=`(empty - no memories or credentials stored)
@@ -682,7 +687,7 @@ USER MESSAGE: "${r.slice(0,500)}"
682
687
 
683
688
  Rules:
684
689
  - "edit": change wording, values, or structure of existing steps, or remove a step
685
- - "explore": add new test coverage, run the test, investigate app behavior, or anything needing a browser`;try{return(await yt({model:t,messages:[{role:"user",content:s}],temperature:0,maxOutputTokens:20,output:kc.object({schema:ov.object({intent:ov.enum(["edit","explore"])})})})).output?.intent==="edit"?"edit":"explore"}catch{return"explore"}}function hM(r){return!!(r.stopReason!==void 0||r.errName==="AbortError"||r.errMsg.includes("cancelled")||r.errMsg.includes("aborted"))}function mM(r){return r.stopReason!==void 0?!0:r.isStartupFailure?!1:hM(r)}var Yc=15e3,xs=class extends Error{stage;constructor(e,t){super(e),this.stage=t,this.name="RunnerStartupFailureError"}};function fM(r){return r instanceof xs}function lv(r,e){return`Saved test-plan startup failed before runner work began: ${r} did not finish within ${Math.round(e/1e3)}s. No browser or LLM actions were attempted.`}async function Ht(r,e,t){let n=Date.now()-e,s=Yc-n;if(s<=0)throw new xs(lv(t,Yc),t);let o;try{return await Promise.race([r,new Promise((i,a)=>{o=setTimeout(()=>{a(new xs(lv(t,Yc),t))},s)})])}finally{o&&clearTimeout(o)}}async function gM(r,e){let t;try{return await Promise.race([r,new Promise(n=>{t=setTimeout(()=>n(null),e)})])}finally{t&&clearTimeout(t)}}var yM=/\b(?:run\s+)?memory\s+key\s+["“'‘]([^"”'’]+)["”'’]/giu;function vM(r,e){return r.status!=="running"?!1:(r.status="blocked",r.summary=r.summary||"Run ended without a verdict. The agent did not call run_complete before the loop exited \u2014 likely hit the iteration ceiling, infra crash, or LLM access was blocked. Check session events for details.",r.endedAt=r.endedAt??e,r.updatedAt=e,!0)}function bM(r){return r==="error"?"error":"completed"}function _M(r){let e=[];for(let t of r.matchAll(yM)){let n=t[1]?.trim();if(!n)continue;let s=t.index??0,o=Math.max(0,s-90),i=Math.min(r.length,s+t[0].length+90),a=r.slice(o,i).toLowerCase(),l=/\b(save|store|record|capture|write|set)\b[\s\S]{0,90}\b(to|in|into|as|under)\s+(?:the\s+)?(?:run\s+)?memory\s+key\b/iu.test(a);(/\b(open|navigate|go to|visit|load|use|using|from|read|retrieve|fill|enter|paste|select|click|verify|check|ensure|confirm|return to)\b[\s\S]{0,90}\b(?:run\s+)?memory\s+key\b/iu.test(a)||/\b(?:run\s+)?memory\s+key\b[\s\S]{0,90}\b(open|navigate|go to|visit|load|use|using|read|retrieve|fill|enter|paste|select|click|verify|check|ensure|confirm|return to)\b/iu.test(a))&&!l&&!e.includes(n)&&e.push(n)}return e}function wM(r,e){let t=[],n=new Set;return r.steps.forEach((s,o)=>{let i=[s.text,...s.criteria?.map(a=>a.check)??[]];for(let a of i)for(let l of _M(a))e.has(l)||n.has(l)||(n.add(l),t.push({key:l,stepIndex:o+1,stepText:s.text}))}),t}function Kc(r){return r.map(e=>`"${e}"`).join(", ")}async function SM(r,e){let t=await import("node:os"),n=await import("node:fs"),s=await import("node:path"),o=s.join(t.tmpdir(),"agentiqa-attachments");n.existsSync(o)||n.mkdirSync(o,{recursive:!0});let i=s.join(o,`${Date.now()}-${e}`),a=await fetch(r);if(!a.ok)throw new Error(`Failed to download ${r}: ${a.status}`);let l=new Uint8Array(await a.arrayBuffer());return n.writeFileSync(i,l),i}async function cv(r,e="run",t=[],n=[],s=[],o=!1,i=!1,a=!1,l,c,u={},d=bt(r.config),h=[],f=[]){let p=Math.floor(Date.now()/1e3),m=(await Promise.all(r.steps.map(async(j,$)=>{let H=`${$+1}. [${j.type.toUpperCase()}] ${kr(j.text,p)}`;h.includes($)?H+=" (already satisfied \u2014 session is authenticated; do NOT perform)":f.includes($)&&(H+=" (a previous session may already be authenticated \u2014 if you are already logged in, do NOT repeat this step)");let q=j.criteria;if(j.type==="verify"&&(!q||q.length===0)){let F=j.text.replace(/^Verify\s+/i,"").trim();q=[{check:F,strict:!0}],console.warn(`[RunnerRuntime] verify step ${$+1} has no criteria; synthesized from text: "${F}"`)}if(j.type==="verify"&&q&&q.length>0){let F=q.map(re=>` ${re.strict?"\u2022":"\u25CB"} ${kr(re.check,p)}${re.strict?"":" (warning only)"}`).join(`
690
+ - "explore": add new test coverage, run the test, investigate app behavior, or anything needing a browser`;try{return(await yt({model:t,messages:[{role:"user",content:s}],temperature:0,maxOutputTokens:20,output:kc.object({schema:ov.object({intent:ov.enum(["edit","explore"])})})})).output?.intent==="edit"?"edit":"explore"}catch{return"explore"}}function hM(r){return!!(r.stopReason!==void 0||r.errName==="AbortError"||r.errMsg.includes("cancelled")||r.errMsg.includes("aborted"))}function mM(r){return r.stopReason!==void 0?!0:r.isStartupFailure?!1:hM(r)}var Yc=45e3,xs=class extends Error{stage;constructor(e,t){super(e),this.stage=t,this.name="RunnerStartupFailureError"}};function fM(r){return r instanceof xs}function lv(r,e){return`Saved test-plan startup failed before runner work began: ${r} did not finish within ${Math.round(e/1e3)}s. No browser or LLM actions were attempted.`}async function Ht(r,e,t){let n=Date.now()-e,s=Yc-n;if(s<=0)throw new xs(lv(t,Yc),t);let o;try{return await Promise.race([r,new Promise((i,a)=>{o=setTimeout(()=>{a(new xs(lv(t,Yc),t))},s)})])}finally{o&&clearTimeout(o)}}async function gM(r,e){let t;try{return await Promise.race([r,new Promise(n=>{t=setTimeout(()=>n(null),e)})])}finally{t&&clearTimeout(t)}}var yM=/\b(?:run\s+)?memory\s+key\s+["“'‘]([^"”'’]+)["”'’]/giu;function vM(r,e){return r.status!=="running"?!1:(r.status="blocked",r.summary=r.summary||"Run ended without a verdict. The agent did not call run_complete before the loop exited \u2014 likely hit the iteration ceiling, infra crash, or LLM access was blocked. Check session events for details.",r.endedAt=r.endedAt??e,r.updatedAt=e,!0)}function bM(r){return r==="error"?"error":"completed"}function _M(r){let e=[];for(let t of r.matchAll(yM)){let n=t[1]?.trim();if(!n)continue;let s=t.index??0,o=Math.max(0,s-90),i=Math.min(r.length,s+t[0].length+90),a=r.slice(o,i).toLowerCase(),l=/\b(save|store|record|capture|write|set)\b[\s\S]{0,90}\b(to|in|into|as|under)\s+(?:the\s+)?(?:run\s+)?memory\s+key\b/iu.test(a);(/\b(open|navigate|go to|visit|load|use|using|from|read|retrieve|fill|enter|paste|select|click|verify|check|ensure|confirm|return to)\b[\s\S]{0,90}\b(?:run\s+)?memory\s+key\b/iu.test(a)||/\b(?:run\s+)?memory\s+key\b[\s\S]{0,90}\b(open|navigate|go to|visit|load|use|using|read|retrieve|fill|enter|paste|select|click|verify|check|ensure|confirm|return to)\b/iu.test(a))&&!l&&!e.includes(n)&&e.push(n)}return e}function wM(r,e){let t=[],n=new Set;return r.steps.forEach((s,o)=>{let i=[s.text,...s.criteria?.map(a=>a.check)??[]];for(let a of i)for(let l of _M(a))e.has(l)||n.has(l)||(n.add(l),t.push({key:l,stepIndex:o+1,stepText:s.text}))}),t}function Kc(r){return r.map(e=>`"${e}"`).join(", ")}async function SM(r,e){let t=await import("node:os"),n=await import("node:fs"),s=await import("node:path"),o=s.join(t.tmpdir(),"agentiqa-attachments");n.existsSync(o)||n.mkdirSync(o,{recursive:!0});let i=s.join(o,`${Date.now()}-${e}`),a=await fetch(r);if(!a.ok)throw new Error(`Failed to download ${r}: ${a.status}`);let l=new Uint8Array(await a.arrayBuffer());return n.writeFileSync(i,l),i}async function cv(r,e="run",t=[],n=[],s=[],o=!1,i=!1,a=!1,l,c,u={},d=bt(r.config),h=[],f=[]){let p=Math.floor(Date.now()/1e3),m=(await Promise.all(r.steps.map(async(j,$)=>{let H=`${$+1}. [${j.type.toUpperCase()}] ${kr(j.text,p)}`;h.includes($)?H+=" (already satisfied \u2014 session is authenticated; do NOT perform)":f.includes($)&&(H+=" (a previous session may already be authenticated \u2014 if you are already logged in, do NOT repeat this step)");let q=j.criteria;if(j.type==="verify"&&(!q||q.length===0)){let F=j.text.replace(/^Verify\s+/i,"").trim();q=[{check:F,strict:!0}],console.warn(`[RunnerRuntime] verify step ${$+1} has no criteria; synthesized from text: "${F}"`)}if(j.type==="verify"&&q&&q.length>0){let F=q.map(re=>` ${re.strict?"\u2022":"\u25CB"} ${kr(re.check,p)}${re.strict?"":" (warning only)"}`).join(`
686
691
  `);H+=`
687
692
  ${F}`}if(j.fileAssets&&j.fileAssets.length>0){let F=await Promise.all(j.fileAssets.map(async re=>{let le;return re.storedPath.startsWith("/")&&(le=re.storedPath),le||(le=await l?.testAssetStorageService?.getAbsolutePath(re.storedPath).catch(()=>{})),le||(le=await l?.attachmentStorageService?.getAbsolutePath(re.storedPath).catch(()=>{})),!le&&re.r2Url&&(le=await SM(re.r2Url,re.originalName)),` [file: ${re.originalName}] ${le??re.storedPath}`}));H+=`
688
693
  `+F.join(`
@@ -1661,7 +1666,7 @@ Reply with ONLY the title, no quotes, no punctuation at the end.`,w=Es(di,t);n&&
1661
1666
  z-index: 999999;
1662
1667
  transform: translate(-50%, -50%);
1663
1668
  transition: left 0.1s, top 0.1s;
1664
- `,document.body.appendChild(i)),i.style.left=`${s}px`,i.style.top=`${o}px`},{x:t,y:n})}catch{}}getSuggestedSampleFiles(e,t){return[]}async ensureBrowser(){if(!this.browser){console.log("[BasePlaywright] Launching browser");let e=performance.now();this.browser=await this.launchBrowser();let t=Math.round(performance.now()-e);console.log(`[BasePlaywright] Browser launched in ${t}ms`),this.browser.on("disconnected",()=>{console.log("[BasePlaywright] Browser disconnected"),this.browser=null,this.sessions.clear(),this.onBrowserDisconnected?.()})}return this.browser}async ensureSession(e,t){if(this.sessions.has(e))return this.sessions.get(e);e.includes(":child-")||await this.cleanupOtherSessions(e);let n=await this.createSession(e,t);return this.sessions.set(e,n),n}async invoke(e){let t=await this.ensureSession(e.sessionId,e.config);t.lastInvokeAt=Date.now(),t.browserNavigationScope=Array.isArray(e.config?.browserNavigationScope)?e.config.browserNavigationScope.map(String):void 0,t.allowExternalNavigation=!!e.config?.allowExternalNavigation,t.testingEmailPolicy=bt(e.config)??void 0,t.isTestPlanReplay=!!e.config?.isTestPlanReplay;let n=e.config?.loginCredentialEmails;t.loginCredentialEmails=Array.isArray(n)?n.map(String):void 0,t.appUnderTestOrigin=R_(e.config);let s=e.args??{},o=performance.now(),i,a,l=this.safeCurrentUrl(t);try{let c=await this.dispatch(t,e.action,s);c=this.withSessionMetadata(t,c);let u=Math.round(performance.now()-o);if(console.log(`[BasePlaywright] ${e.action} completed in ${u}ms`),t.tab2||t.isExtensionSession){let d=t.tab1&&!t.tab1.isClosed(),h=t.tab2&&!t.tab2.isClosed(),f=[];d&&f.push(t.tab1.url()),h&&f.push(t.tab2.url()),c={...c,metadata:{activeTab:t.activeTab,tabCount:(d?1:0)+(h?1:0),tabUrls:f,...t.pendingExtensionPopup?{pendingExtensionPopup:!0}:{},...c.metadata}},t.pendingExtensionPopup=!1}i={screenshot:c.screenshot.toString("base64"),url:c.url,aiSnapshot:c.aiSnapshot,domChanges:c.domChanges,metadata:c.metadata},a=t.page}catch(c){let u=String(c?.message||"");if(c instanceof Kt)throw c;if(u.includes("Execution context was destroyed")||u.includes("most likely because of a navigation")||u.includes("navigation")){console.log(`[BasePlaywright] Navigation detected during ${e.action}, recovering`),t.needsFullSnapshot=!0;try{await t.page.waitForLoadState("load",{timeout:5e3})}catch{}let d=await this.captureState(t),h=this.withSessionMetadata(t,d);i={screenshot:h.screenshot.toString("base64"),url:h.url,aiSnapshot:h.aiSnapshot,metadata:h.metadata},a=t.page}else if(u.includes("Browser session closed")||u.includes("Target closed")||u.includes("has been closed")||u.includes("Page crashed")){console.log(`[BasePlaywright] Session closed for ${e.sessionId}, recreating`),this.sessions.delete(e.sessionId);try{let d=await this.ensureSession(e.sessionId,e.config),h=this.withSessionMetadata(d,await this.dispatch(d,e.action,s));i={screenshot:h.screenshot.toString("base64"),url:h.url,aiSnapshot:h.aiSnapshot,metadata:h.metadata},a=d.page}catch(d){console.error("[BasePlaywright] Retry after session recreation failed:",d);let h=d instanceof Error?d.message:String(d);throw new Error(`Browser session recovery failed: ${h}`)}}else throw c}return e.action!=="navigate"&&a&&(await this.assertNoCloudflareBlockAfterCrossOriginNav(a,l,i.url),await this.assertNoCaptchaGate(t,a,e.action,s,i.url),await this.assertNoCredentialRejection(t,a,e.action,s,i.url),await this.assertNoThirdPartyIdpLogin(t,e.action,s,i.url)),i.metadata={...i.metadata??{},events:this.buildAndResetDigest(a)},i}buildAndResetDigest(e){let t=e?Zo.get(e):void 0;if(!t)return{consoleErrors:[],pageErrors:[],failedRequests:[],pendingRequests:[],recentWrites:[]};let n=Date.now(),s={consoleErrors:t.rollingConsoleErrors.slice(),pageErrors:t.rollingPageErrors.slice(),failedRequests:t.rollingFailedRequests.slice(),pendingRequests:Array.from(t.pendingWrites).map(o=>({url:o.url,method:o.method,ageMs:n-o.startTs})),recentWrites:t.rollingRecentWrites.slice()};return t.rollingConsoleErrors=[],t.rollingPageErrors=[],t.rollingFailedRequests=[],t.rollingRecentWrites=[],s}async readFocusedFieldValue(e){try{return await e.evaluate(()=>{let t=document.activeElement;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)return t.value;if(t instanceof HTMLElement&&t.isContentEditable)return t.textContent??""})}catch{return}}async getFocusedFieldName(e){try{return await e.evaluate(()=>{let t=document.activeElement;if(!t||t===document.body)return;let n=u=>{let d=[];for(let h of Array.from(u.childNodes))if(h.nodeType===Node.TEXT_NODE){let f=h.textContent?.trim();f&&d.push(f)}else if(h instanceof HTMLElement&&!s(h)){let f=h.innerText?.trim();f&&d.push(f)}return d.join(" ").replace(/\s+/g," ").trim()},s=u=>u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement||u.isContentEditable,o=t.getAttribute("aria-label");if(o)return o;let i=t.getAttribute("aria-labelledby");if(i){let u=i.split(/\s+/).map(d=>document.getElementById(d)?.textContent?.trim()).filter(Boolean).join(" ");if(u)return u}if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){let u=t.labels?.[0]?.textContent?.trim();if(u)return u}let a=t.id;if(a){let u=document.querySelector(`label[for="${a}"]`);if(u)return u.textContent?.trim()}if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){if(t.placeholder)return t.placeholder;if(t.name)return t.name}let l=t.previousElementSibling;for(;l;){let u=l instanceof HTMLElement?l.innerText?.trim():void 0;if(u)return u;l=l.previousElementSibling}let c=t.parentElement;for(let u=0;c&&u<3;u+=1,c=c.parentElement){let d=n(c);if(d)return d}})}catch{return}}async awaitPageReady(e){return e.waitForLoadState("domcontentloaded",{timeout:5e3}).then(()=>!0).catch(()=>!1)}async screenshotWithSlowSiteRetry(e,t={}){let{page:n}=e,s=(e.screenshotCount??0)===0,o=15e3,i=25e3,l={type:"png",timeout:s?o:5e3};t.fullPage&&(l.fullPage=!0);try{let c=await n.screenshot(l);return e.screenshotCount=(e.screenshotCount??0)+1,c}catch(c){if(!s||!Fc(c))throw c;let u=n.url();this.diagLog?.(e.sessionId,"screenshot_first_timeout_retry",{url:u,firstTimeoutMs:o,retryTimeoutMs:i});let d={type:"png",timeout:i};t.fullPage&&(d.fullPage=!0);try{let h=await n.screenshot(d);return e.screenshotCount=(e.screenshotCount??0)+1,this.diagLog?.(e.sessionId,"screenshot_first_retry_success",{url:u}),h}catch(h){throw Fc(h)?new on({url:u,totalTimeoutMs:o+i,attemptCount:2}):h}}}async captureState(e){let{page:t}=e;e.expectDomQuiescence&&!process.env.AQ_NO_DOM_OBSERVER&&!process.env.AQ_NO_DOM_DELAY&&await nr.waitForQuiescence(t,{minQuietMs:100,maxMs:800});let n=await this.screenshotWithSlowSiteRetry(e),s=t.url(),o;try{let a=await t._snapshotForAI({track:e.sessionId+":"+e.activeTab}),l=typeof a=="string"?a:a?.full,c=typeof a=="object"?a?.incremental:void 0;!e.needsFullSnapshot&&c?o=c:(o=l,e.needsFullSnapshot=!1)}catch{o=void 0,e.needsFullSnapshot=!0}let i=process.env.AQ_NO_DOM_OBSERVER?void 0:await nr.flush(t)??void 0;return{screenshot:n,url:s,aiSnapshot:o,domChanges:i}}withSessionMetadata(e,t){return e.httpsCertFallback?{...t,metadata:{...t.metadata??{},httpsCertFallback:e.httpsCertFallback}}:t}shouldResetDomObserverBeforeAction(e){return!new Set(["open_web_browser","screenshot","snapshot","wait","wait_5_seconds","wait_for_element","full_page_screenshot","run_js"]).has(e)}shouldWaitForDomQuiescenceAfterAction(e){return this.shouldResetDomObserverBeforeAction(e)}async dispatch(e,t,n){let s=await this.dispatchPlatformAction(e,t,n);if(s)return s;let{viewportWidth:o,viewportHeight:i}=e,a=d=>Math.floor(d/1e3*o),l=d=>Math.floor(d/1e3*i),c=!process.env.AQ_NO_DOM_OBSERVER&&this.shouldResetDomObserverBeforeAction(t),u=e.expectDomQuiescence;c&&await nr.reset(e.page),e.expectDomQuiescence=this.shouldWaitForDomQuiescenceAfterAction(t);try{switch(t){case"open_web_browser":case"screenshot":return await this.captureState(e);case"snapshot":return e.needsFullSnapshot=!0,await this.captureState(e);case"click_at":{let d=Array.isArray(n.modifiers)?n.modifiers.map(String):[];if(n.label)return await this.clickByLabel(e,String(n.label),d);if(n.ref)return await this.clickByRef(e,String(n.ref),d);let h=this.requireNormalizedPoint(t,n);return await this.clickAt(e,a(h.x),l(h.y),d)}case"right_click_at":{if(n.ref)return await this.rightClickByRef(e,String(n.ref));let d=this.requireNormalizedPoint(t,n);return await this.rightClickAt(e,a(d.x),l(d.y))}case"hover_at":{if(n.ref)return await this.hoverByRef(e,String(n.ref));let d=this.requireNormalizedPoint(t,n);return await this.hoverAt(e,a(d.x),l(d.y))}case"type_text_at":{let d=n.clearBeforeTyping??n.clear_before_typing??!0;if(n.label)return await this.typeByLabel(e,String(n.label),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d);if(n.ref)return await this.typeByRef(e,String(n.ref),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d);let h=this.requireNormalizedPoint(t,n);return await this.typeTextAt(e,a(h.x),l(h.y),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d)}case"scroll_document":return await this.scrollDocument(e,String(n.direction));case"scroll_to_bottom":return await this.scrollToBottom(e);case"scroll_at":{let d=String(n.direction),h=n.magnitude!=null?Number(n.magnitude):800;if(d==="up"||d==="down"?h=l(h):(d==="left"||d==="right")&&(h=a(h)),n.ref){let p=await this.resolveRefCenter(e,String(n.ref));return p?await this.scrollAt(e,p.x,p.y,d,h):await this.refNotFoundError(e,String(n.ref))}let f=this.requireNormalizedPoint(t,n);return await this.scrollAt(e,a(f.x),l(f.y),d,h)}case"wait":return await this.waitSeconds(e,Number(n.seconds||2));case"wait_for_element":return await this.waitForElement(e,String(n.textContent??""),Number(n.timeoutSeconds||5));case"wait_5_seconds":return await this.waitSeconds(e,5);case"full_page_screenshot":return await this.fullPageScreenshot(e);case"switch_layout":{let d=Number(n.width),h=Number(n.height);return e.viewportWidth=d,e.viewportHeight=h,await this.switchLayout(e,d,h)}case"go_back":return await this.goBack(e);case"go_forward":return await this.goForward(e);case"navigate":{let d=String(n.url??n.href??"");if(e.isExtensionSession){if(d.startsWith("chrome-extension://")){if(e.tab1&&!e.tab1.isClosed())await e.tab1.goto(d),await this.awaitPageReady(e.tab1);else{let h=await e.context.newPage();Za(h),await h.goto(d),await this.awaitPageReady(h)}return e.tab1&&!e.tab1.isClosed()&&(e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0,await e.page.bringToFront()),await this.captureState(e)}e.tab2&&!e.tab2.isClosed()&&(e.page=e.tab2,e.activeTab="tab2")}else e.activeTab==="tab2"&&e.tab1&&!e.tab1.isClosed()&&(e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0);return await this.navigate(e,d)}case"key_combination":return await this.keyCombination(e,Array.isArray(n.keys)?n.keys.map(String):[]);case"set_focused_input_value":return await this.setFocusedInputValue(e,String(n.value??""));case"drag_and_drop":{let d,h;if(n.ref){let g=await this.resolveRefCenter(e,String(n.ref));if(!g)return await this.refNotFoundError(e,String(n.ref));d=g.x,h=g.y}else{let g=this.requireNormalizedPoint(t,n);d=a(g.x),h=l(g.y)}let f,p;if(n.destinationRef){let g=await this.resolveRefCenter(e,String(n.destinationRef));if(!g)return await this.refNotFoundError(e,String(n.destinationRef));f=g.x,p=g.y}else{let g=this.requireNormalizedPoint(t,n,n.destinationX!==void 0?"destinationX":"destination_x",n.destinationY!==void 0?"destinationY":"destination_y");f=a(g.x),p=l(g.y)}return await this.dragAndDrop(e,d,h,f,p)}case"upload_file":{let d=Array.isArray(n.filePaths)?n.filePaths.map(String):[String(n.filePaths??"")];return await this.uploadFile(e,d)}case"http_request":return await this.httpRequest(e,String(n.url??""),String(n.method??"GET"),n.headers,n.body!=null?String(n.body):void 0);case"run_js":return await this.runJs(e,String(n.code??""));case"switch_tab":{let d=String(n.tab??"tab1"),h=d==="main"?"tab1":d==="extension"?"tab2":d;return await this.switchTab(e,h)}case"close_tab":return await this.closeTab(e);default:return console.warn(`[BasePlaywright] Unsupported action: ${t}`),await this.captureState(e)}}finally{e.expectDomQuiescence=u}}async clickAt(e,t,n,s=[]){let{page:o}=e;try{await o.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}await this.onBeforeAction(e,t,n);let i={isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:null,clickTarget:null};try{let h=await o.evaluateHandle(g=>document.elementFromPoint(g.x,g.y),{x:t,y:n}),f=h.asElement(),p=f?await f.evaluate(C_).catch(()=>null):null;await h.dispose().catch(()=>{}),i=await o.evaluate(g=>{let m=document.elementFromPoint(g.x,g.y);if(!m)return{isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:null,clickTarget:null};let b={tag:m.tagName.toLowerCase(),text:(m.textContent||"").trim().slice(0,80),role:m.getAttribute("role")||""},w=m.closest("a[href], area[href]"),_=null,T=m.closest("select");if(T)_=T;else if(m instanceof HTMLLabelElement&&m.htmlFor){let v=document.getElementById(m.htmlFor);v instanceof HTMLSelectElement&&(_=v)}else{let v=m instanceof HTMLLabelElement?m:m.closest("label");if(v){let x=v.querySelector("select");x&&(_=x)}}if(_){_.focus();let v=_.options[_.selectedIndex]?.textContent?.trim()||"",x=Array.from(_.options).map(I=>I.textContent?.trim()||I.value);return{isSelect:!0,isMultiple:_.multiple,selectedText:v,options:x,clickedElement:null,clickTarget:null}}return{isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:b,clickTarget:null,linkHref:w?.href||""}},{x:t,y:n}),i.clickTarget=p}catch(h){let f=String(h?.message||"");if(!(f.includes("Execution context was destroyed")||f.includes("navigation")))throw h}if(i.isSelect&&!i.isMultiple)return await this.awaitPageReady(o),{...await this.captureState(e),metadata:{elementType:"select",valueBefore:i.selectedText,valueAfter:i.selectedText,availableOptions:i.options,...i.clickTarget?{clickTarget:i.clickTarget}:{}}};if(i.linkHref){let h=await this.blockScopedNavigationIfNeeded(e,i.linkHref);if(h)return h}let a=o.waitForEvent("filechooser",{timeout:150}).catch(()=>null),l=o.waitForEvent("download",{timeout:500}).catch(()=>null);await nr.reset(o);for(let h of s)await o.keyboard.down(h);await o.mouse.click(t,n);for(let h of s)await o.keyboard.up(h);let c=await a;if(c){let f=await c.element().evaluate(m=>{let b=m;return document.querySelectorAll("[data-agentiqa-file-target]").forEach(w=>w.removeAttribute("data-agentiqa-file-target")),b.setAttribute("data-agentiqa-file-target","true"),b instanceof HTMLInputElement?{accept:b.accept||"*",multiple:b.multiple}:{accept:"*",multiple:!1}}),p=this.getSuggestedSampleFiles(f.accept,f.multiple);return console.log(`[BasePlaywright] FILE CHOOSER INTERCEPTED: accept="${f.accept}", multiple=${f.multiple}`),{...await this.captureState(e),metadata:{elementType:"file",accept:f.accept,multiple:f.multiple,suggestedFiles:p}}}let u=await l;if(u){let h=u.suggestedFilename(),f=u.url();console.log(`[BasePlaywright] DOWNLOAD INTERCEPTED: "${h}" from ${f}`),await u.cancel();try{await o.goBack({waitUntil:"domcontentloaded",timeout:5e3})}catch{}return{...await this.captureState(e),metadata:{elementType:"download",downloadFilename:h,downloadUrl:f,clickedElement:i.clickedElement??void 0,clickTarget:i.clickTarget??void 0}}}await this.awaitPageReady(o);let d=await this.captureState(e);return i.clickedElement||i.clickTarget?{...d,metadata:{...i.clickedElement?{clickedElement:i.clickedElement}:{},...i.clickTarget?{clickTarget:i.clickTarget}:{}}}:d}async clickByRef(e,t,n=[]){let{page:s}=e,o=3e3;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let i=s.locator(`aria-ref=${t}`),a=await i.boundingBox({timeout:o});a&&await this.onBeforeAction(e,a.x+a.width/2,a.y+a.height/2);let l=await i.evaluate(b=>({tag:b.tagName.toLowerCase(),text:(b.textContent||"").trim().slice(0,80),role:b.getAttribute("role")||"",linkHref:b.closest("a[href], area[href]")?.href})).catch(()=>null),c=await i.evaluate(C_).catch(()=>null),u=await i.evaluate(b=>{let w=b instanceof HTMLSelectElement?b:b.closest("select");if(!w)return null;w.focus();let _=w.options[w.selectedIndex]?.textContent?.trim()||"",T=Array.from(w.options).map(v=>v.textContent?.trim()||v.value);return{selectedText:_,options:T,isMultiple:w.multiple}}).catch(()=>null);if(u&&!u.isMultiple)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:u.selectedText,valueAfter:u.selectedText,availableOptions:u.options,...c?{clickTarget:c}:{}}};if(l?.linkHref){let b=await this.blockScopedNavigationIfNeeded(e,l.linkHref);if(b)return b}let d=s.waitForEvent("filechooser",{timeout:500}).catch(()=>null),h=s.waitForEvent("download",{timeout:500}).catch(()=>null),f=n.map(b=>b).filter(Boolean);await nr.reset(s),await i.click({force:!0,timeout:o,modifiers:f.length?f:void 0});let p=await d;if(p){let w=await p.element().evaluate(v=>{let x=v;return document.querySelectorAll("[data-agentiqa-file-target]").forEach(S=>S.removeAttribute("data-agentiqa-file-target")),x.setAttribute("data-agentiqa-file-target","true"),x instanceof HTMLInputElement?{accept:x.accept||"*",multiple:x.multiple}:{accept:"*",multiple:!1}}),_=this.getSuggestedSampleFiles(w.accept,w.multiple);return console.log(`[BasePlaywright] FILE CHOOSER INTERCEPTED via ref=${t}: accept="${w.accept}"`),{...await this.captureState(e),metadata:{elementType:"file",accept:w.accept,multiple:w.multiple,suggestedFiles:_}}}let g=await h;if(g){let b=g.suggestedFilename(),w=g.url();console.log(`[BasePlaywright] DOWNLOAD INTERCEPTED via ref=${t}: "${b}" from ${w}`),await g.cancel();try{await s.goBack({waitUntil:"domcontentloaded",timeout:5e3})}catch{}return{...await this.captureState(e),metadata:{elementType:"download",downloadFilename:b,downloadUrl:w,clickedElement:l??void 0,clickTarget:c??void 0}}}await this.awaitPageReady(s);let m=await this.captureState(e);return l||c?{...m,metadata:{...l?{clickedElement:l}:{},...c?{clickTarget:c}:{}}}:m}catch(i){console.warn(`[BasePlaywright] clickByRef ref=${t} failed: ${i.message}`);let a=await this.captureState(e),c=(i.message??"").includes("intercepts pointer events")?`Ref "${t}" is covered by another element (overlay/popup). Dismiss the overlay first, or try a different ref.`:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`;return{...a,metadata:{error:c}}}}async clickByLabel(e,t,n=[]){let{page:s}=e,o=3e3;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}let i=await s.evaluate(a=>{let l=x=>x.replace(/\s+/g," ").trim().replace(/[?:.!]+$/,"").toLowerCase(),c=x=>l(x).replace(/[\s:_-]+/g,""),u=l(a),d=c(a),h=(x,S)=>!!S&&x.startsWith(S)&&(x.length===S.length||/[\s:>/-]/.test(x.charAt(S.length))),f=x=>{let S=l(x);return S?S===u?0:c(x)===d?1:h(S,u)||h(u,S)?2:null:null},p=x=>{let S=window.getComputedStyle(x);if(S.display==="none"||S.visibility==="hidden")return!1;let I=x.getBoundingClientRect();return I.width>0&&I.height>0},g=x=>{let S=x.getAttribute("aria-label");if(S&&S.trim())return S.trim();let I=x.getAttribute("aria-labelledby");if(I){let L=I.split(/\s+/).map(W=>document.getElementById(W)?.textContent?.trim()).filter(Boolean).join(" ");if(L)return L}if(x instanceof HTMLInputElement&&(x.type==="submit"||x.type==="button"||x.type==="reset")&&x.value&&x.value.trim())return x.value.trim();let E=(x.innerText||x.textContent||"").trim();if(E)return E;let A=x.getAttribute("title");return A&&A.trim()?A.trim():""},m=Array.from(document.querySelectorAll('button, a[href], [role="button"], [role="link"], [role="menuitem"], [role="tab"], input[type="submit"], input[type="button"], input[type="reset"], summary')).filter(x=>p(x)),b=[];for(let x of m){let S=g(x),I=f(S);I!=null&&b.push({element:x,name:S,rank:I})}if(b.length===0)return{status:"none"};b.sort((x,S)=>x.rank-S.rank);let w=b[0].rank,_=b.filter(x=>x.rank===w);if(Array.from(new Set(_.map(x=>x.element))).length>1)return{status:"ambiguous",matches:_.slice(0,5).map(x=>x.name)};let v=`agentiqa_click_target_${Date.now()}_${Math.random().toString(36).slice(2)}`;return _[0].element.setAttribute("data-agentiqa-click-target",v),{status:"ok",marker:v,matchedName:_[0].name}},t);if(i.status!=="ok"){let a=await this.captureState(e),l=i.status==="ambiguous"?`More than one control is named "${t}" (${(i.matches||[]).join(", ")}). Click by ref from the latest snapshot, or use a more specific name.`:`No clickable control named "${t}" was found on the page. Check the latest snapshot and click by ref, or use coordinates.`;return{...a,metadata:{...a.metadata,note:l}}}try{let a=s.locator(`[data-agentiqa-click-target="${i.marker}"]`).first(),l=await a.boundingBox({timeout:o});l&&await this.onBeforeAction(e,l.x+l.width/2,l.y+l.height/2);let c=await a.evaluate(h=>({tag:h.tagName.toLowerCase(),text:(h.textContent||"").trim().slice(0,80),role:h.getAttribute("role")||"",linkHref:h.closest("a[href], area[href]")?.href})).catch(()=>null);if(c?.linkHref){let h=await this.blockScopedNavigationIfNeeded(e,c.linkHref);if(h)return h}let u=n.map(h=>h).filter(Boolean);await nr.reset(s),await a.click({force:!0,timeout:o,modifiers:u.length?u:void 0}),await this.awaitPageReady(s);let d=await this.captureState(e);return c?{...d,metadata:{...d.metadata,clickedElement:c}}:d}catch(a){let l=await this.captureState(e);return{...l,metadata:{...l.metadata,note:`Could not click "${t}": ${a?.message??"unknown error"}. Try clicking by ref from the latest snapshot.`}}}}async rightClickAt(e,t,n){let{page:s}=e;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}return await this.onBeforeAction(e,t,n),await s.mouse.click(t,n,{button:"right"}),await this.awaitPageReady(s),await this.captureState(e)}async rightClickByRef(e,t){let{page:n}=e,s=3e3;try{await n.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let o=n.locator(`aria-ref=${t}`),i=await o.boundingBox({timeout:s});return i&&await this.onBeforeAction(e,i.x+i.width/2,i.y+i.height/2),await o.click({button:"right",force:!0,timeout:s}),await this.awaitPageReady(n),await this.captureState(e)}catch(o){console.warn(`[BasePlaywright] rightClickByRef ref=${t} failed: ${o.message}`);let i=await this.captureState(e),l=(o.message??"").includes("intercepts pointer events")?`Ref "${t}" is covered by another element (overlay/popup). Dismiss the overlay first, or try a different ref.`:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`;return{...i,metadata:{error:l}}}}async hoverAt(e,t,n){let{page:s}=e;return await this.onBeforeAction(e,t,n),await s.mouse.move(t,n),await new Promise(o=>setTimeout(o,300)),await this.captureState(e)}async hoverByRef(e,t){let{page:n}=e,s=3e3;try{await n.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let o=n.locator(`aria-ref=${t}`),i=await o.boundingBox({timeout:s});return i&&await this.onBeforeAction(e,i.x+i.width/2,i.y+i.height/2),await o.hover({force:!0,timeout:s}),await new Promise(a=>setTimeout(a,300)),await this.captureState(e)}catch(o){return console.warn(`[BasePlaywright] hoverByRef ref=${t} failed: ${o.message}`),{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}}async typeTextAt(e,t,n,s,o,i,a=!1){let{page:l}=e;await this.onBeforeAction(e,t,n),await l.mouse.click(t,n);let c;try{c=await l.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement?{type:"input",inputType:v.type}:v instanceof HTMLTextAreaElement?{type:"textarea",inputType:"textarea"}:v instanceof HTMLSelectElement?{type:"select",inputType:"select"}:v.isContentEditable?{type:"contenteditable",inputType:"contenteditable"}:{type:"other",inputType:"none"}})}catch{console.warn("[BasePlaywright] page.evaluate blocked in typeTextAt, falling back to keyboard typing"),c={type:"input",inputType:"text"}}let u=["date","time","datetime-local","month","week"],d=c.type==="input"&&u.includes(c.inputType),f=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(c.inputType),p=f&&!d?await this.readFocusedFieldValue(l):void 0,g=i===!0||i==="true";if(s=this.normalizeTestingEmailForFocusedField(e,s,{inputType:c.inputType}),d){let v=!1;try{v=await l.evaluate(x=>{let S=document.activeElement;if(S instanceof HTMLInputElement){let I=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;return I?I.call(S,x):S.value=x,S.dispatchEvent(new Event("input",{bubbles:!0})),S.dispatchEvent(new Event("change",{bubbles:!0})),!0}return!1},s)}catch{}v||(g&&f&&(await l.keyboard.press("ControlOrMeta+a"),await l.keyboard.press("Backspace")),await l.keyboard.type(s,{delay:10}))}else g&&f&&(await l.keyboard.press("ControlOrMeta+a"),await l.keyboard.press("Backspace")),await l.keyboard.type(s,{delay:10});o&&(await l.keyboard.press("Enter"),await this.awaitPageReady(l));try{await l.evaluate(()=>new Promise(v=>requestAnimationFrame(()=>setTimeout(v,50))))}catch{}let m=await this.getFocusedFieldName(l),b=f&&!d?await this.readFocusedFieldValue(l):void 0,w=Du({requested:s,valueBefore:p,valueAfter:b,pressEnter:o,sensitive:a}),_=await this.captureState(e),T={..._.metadata};return m&&(T.typedIntoField=m),!a&&p!==void 0&&(T.valueBefore=p),!a&&b!==void 0&&(T.valueAfter=b),w.valueMismatch&&(T.valueMismatch=!0,T.error=w.error),{..._,metadata:T}}async typeByRef(e,t,n,s,o,i=!1){let{page:a}=e,l=3e3;try{await a.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let c=a.locator(`aria-ref=${t}`),u=await c.boundingBox({timeout:l});u&&await this.onBeforeAction(e,u.x+u.width/2,u.y+u.height/2),await c.click({force:!0,timeout:l});let d;try{d=await a.evaluate(()=>{let S=document.activeElement;return S instanceof HTMLInputElement?{inputType:S.type}:S instanceof HTMLTextAreaElement?{inputType:"textarea"}:S.isContentEditable?{inputType:"contenteditable"}:{inputType:"none"}})}catch{console.warn(`[BasePlaywright] page.evaluate blocked for typeByRef ref=${t}, falling back to keyboard typing`),d={inputType:"text"}}let f=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(d.inputType),g=["date","time","datetime-local","month","week"].includes(d.inputType);if(!f&&!g)return{...await this.captureState(e),metadata:{error:`Ref "${t}" resolved to a non-text element (${d.inputType||"unknown"}). Use a different ref that targets the text input directly.`}};let m=o===!0||o==="true";n=this.normalizeTestingEmailForFocusedField(e,n,{inputType:d.inputType});let b=g?void 0:await this.readFocusedFieldValue(a);if(g)try{await a.evaluate(S=>{let I=document.activeElement;if(I instanceof HTMLInputElement){let E=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;E?E.call(I,S):I.value=S,I.dispatchEvent(new Event("input",{bubbles:!0})),I.dispatchEvent(new Event("change",{bubbles:!0}))}},n)}catch{await a.keyboard.type(n,{delay:15})}else m&&f?(await a.keyboard.press("ControlOrMeta+a"),n?await a.keyboard.type(n,{delay:15}):await a.keyboard.press("Backspace")):n&&await a.keyboard.type(n,{delay:15});s&&await a.keyboard.press("Enter"),await this.awaitPageReady(a);try{await a.evaluate(()=>new Promise(S=>requestAnimationFrame(()=>setTimeout(S,50))))}catch{}let w=await this.getFocusedFieldName(a),_=g?void 0:await this.readFocusedFieldValue(a),T=g?{valueMismatch:!1}:Du({requested:n,valueBefore:b,valueAfter:_,pressEnter:s,sensitive:i}),v=await this.captureState(e),x={...v.metadata};return w&&(x.typedIntoField=w),!i&&b!==void 0&&(x.valueBefore=b),!i&&_!==void 0&&(x.valueAfter=_),T.valueMismatch&&(x.valueMismatch=!0,x.error=T.error),{...v,metadata:x}}catch(c){return console.warn(`[BasePlaywright] typeByRef ref=${t} failed: ${c.message}`),{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}}async typeByLabel(e,t,n,s,o){let{page:i}=e,a=3e3,l=await i.evaluate(v=>{let x=k=>k.replace(/\s+/g," ").trim().replace(/:$/,"").toLowerCase(),S=k=>x(k).replace(/[\s:_-]+/g,""),I=x(v),E=S(v),A=k=>{let N=x(k);return N?N===I?0:S(k)===E?1:N.includes(I)||I.includes(N)?2:null:null},L=k=>{let N=window.getComputedStyle(k);if(N.display==="none"||N.visibility==="hidden")return!1;let oe=k.getBoundingClientRect();return oe.width>0&&oe.height>0},W=k=>k instanceof HTMLTextAreaElement?!k.disabled:k instanceof HTMLInputElement?k.disabled||k.type==="hidden"?!1:["text","password","email","search","url","tel","number","date","time","datetime-local","month","week"].includes(k.type):k.isContentEditable,U=k=>k instanceof HTMLInputElement?`input[type=${k.type}]`:k instanceof HTMLTextAreaElement?"textarea":k.isContentEditable?"contenteditable":k.tagName.toLowerCase(),j=k=>k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement?k.value:k.isContentEditable?k.textContent??"":"",$=k=>{let N=[];for(let oe of Array.from(k.childNodes))if(oe.nodeType===Node.TEXT_NODE){let ne=oe.textContent?.trim();ne&&N.push(ne)}else if(oe instanceof HTMLElement&&!W(oe)){let ne=oe.innerText?.trim();ne&&N.push(ne)}return N.join(" ").replace(/\s+/g," ").trim()},H=k=>{let N=[],oe=(M,R,O)=>{let V=M?.replace(/\s+/g," ").trim();V&&N.push({label:V,source:R,priority:O})};oe(k.getAttribute("aria-label"),"aria-label",0);let ne=k.getAttribute("aria-labelledby");if(ne&&oe(ne.split(/\s+/).map(M=>document.getElementById(M)?.textContent?.trim()).filter(Boolean).join(" "),"aria-labelledby",0),k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement)for(let M of Array.from(k.labels??[]))oe(M.textContent,"label",0);let X=k.id;X&&oe(document.querySelector(`label[for="${X}"]`)?.textContent,"label[for]",0),(k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement)&&(oe(k.placeholder,"placeholder",1),oe(k.name,"name",2),oe(k.id,"id",2));let G=k.previousElementSibling;for(;G;)oe(G instanceof HTMLElement?G.innerText:G.textContent,"previous-sibling",3),G=G.previousElementSibling;let Y=k.parentElement;for(let M=0;Y&&M<3;M+=1,Y=Y.parentElement)oe($(Y),"container-text",4+M);return N},q=[],F=Array.from(document.querySelectorAll('input:not([type="hidden"]), textarea, [contenteditable="true"]')).filter(k=>W(k)&&L(k));for(let k of F)for(let N of H(k)){let oe=A(N.label);oe!=null&&q.push({element:k,label:N.label,source:N.source,priority:N.priority,matchRank:N.priority*10+oe,elementType:U(k),valueBefore:j(k)})}if(q.length===0)return{status:"none",error:`No text input found for label "${v}". Check the latest page snapshot for the field label or use ref/x/y.`};q.sort((k,N)=>k.matchRank-N.matchRank);let re=q[0].matchRank,le=q.filter(k=>k.matchRank===re);if(Array.from(new Set(le.map(k=>k.element))).length>1)return{status:"ambiguous",error:`Multiple text inputs matched label "${v}". Use a more specific label or a direct ref.`,matches:le.map(k=>({label:k.label,source:k.source,elementType:k.elementType}))};let J=`agentiqa_label_target_${Date.now()}_${Math.random().toString(36).slice(2)}`;return le[0].element.setAttribute("data-agentiqa-label-target",J),{status:"ok",marker:J,matchedLabel:le[0].label,labelSource:le[0].source,elementType:le[0].elementType,valueBefore:le[0].valueBefore}},t);if(l.status!=="ok"){let v=await this.captureState(e);return{...v,metadata:{...v.metadata,error:l.error,...l.status==="ambiguous"&&{matches:l.matches}}}}let c=i.locator(`[data-agentiqa-label-target="${l.marker}"]`).first(),u=await c.boundingBox({timeout:a});u&&await this.onBeforeAction(e,u.x+u.width/2,u.y+u.height/2),await c.click({force:!0,timeout:a});let d=await i.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement?{inputType:v.type}:v instanceof HTMLTextAreaElement?{inputType:"textarea"}:v.isContentEditable?{inputType:"contenteditable"}:{inputType:"none"}}),f=["date","time","datetime-local","month","week"].includes(d.inputType),g=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(d.inputType);if(!g&&!f){let v=await this.captureState(e);return{...v,metadata:{...v.metadata,error:`Label "${t}" resolved to a non-text element (${d.inputType||"unknown"}). Use a different label or direct ref.`}}}let m=o===!0||o==="true";if(n=this.normalizeTestingEmailForFocusedField(e,n,{inputType:d.inputType,label:t,matchedLabel:l.matchedLabel}),f)try{await i.evaluate(v=>{let x=document.activeElement;if(x instanceof HTMLInputElement){let S=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;S?S.call(x,v):x.value=v,x.dispatchEvent(new Event("input",{bubbles:!0})),x.dispatchEvent(new Event("change",{bubbles:!0}))}},n)}catch{await i.keyboard.type(n,{delay:15})}else m&&g?(await i.keyboard.press("ControlOrMeta+a"),n?await i.keyboard.type(n,{delay:15}):await i.keyboard.press("Backspace")):n&&await i.keyboard.type(n,{delay:15});s&&await i.keyboard.press("Enter"),await this.awaitPageReady(i);try{await i.evaluate(()=>new Promise(v=>requestAnimationFrame(()=>setTimeout(v,50))))}catch{}let b=await this.getFocusedFieldName(i),w=await i.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement||v instanceof HTMLTextAreaElement?v.value:v instanceof HTMLElement&&v.isContentEditable?v.textContent??"":""}),_=f?{valueMismatch:!1}:Du({requested:n,valueBefore:l.valueBefore,valueAfter:w,pressEnter:s}),T=await this.captureState(e);return{...T,metadata:{...T.metadata,typedIntoField:b??l.matchedLabel,matchedLabel:l.matchedLabel,labelSource:l.labelSource,elementType:l.elementType,valueBefore:l.valueBefore,valueAfter:w,..._.valueMismatch?{valueMismatch:!0,error:_.error}:{}}}}async scrollDocument(e,t){let{page:n,viewportHeight:s}=e,o=Math.floor(s*.8),i=t==="up"||t==="down",a=t==="up"||t==="left"?-o:o;return await n.evaluate(({vertical:l,signed:c})=>{let u=document.scrollingElement||document.documentElement,d=null,h=0;for(let m of Array.from(document.querySelectorAll("*"))){let b=l?m.scrollHeight-m.clientHeight:m.scrollWidth-m.clientWidth;if(b<=h)continue;let w=l?getComputedStyle(m).overflowY:getComputedStyle(m).overflowX;w!=="auto"&&w!=="scroll"||(h=b,d=m)}let f=l?u.scrollHeight-u.clientHeight:u.scrollWidth-u.clientWidth,p=l?0:c,g=l?c:0;!d||f>=h?window.scrollBy(p,g):d.scrollBy(p,g)},{vertical:i,signed:a}),await new Promise(l=>setTimeout(l,200)),await this.captureState(e)}async scrollToBottom(e){let{page:t}=e;return await t.evaluate(()=>{let n=document.scrollingElement||document.documentElement,s=null,o=0;for(let a of Array.from(document.querySelectorAll("*"))){let l=a.scrollHeight-a.clientHeight;if(l<=o)continue;let c=getComputedStyle(a).overflowY;c!=="auto"&&c!=="scroll"||(o=l,s=a)}let i=n.scrollHeight-n.clientHeight;!s||i>=o?window.scrollTo(0,n.scrollHeight):s.scrollTo(0,s.scrollHeight)}),await new Promise(n=>setTimeout(n,200)),await this.captureState(e)}async scrollAt(e,t,n,s,o){let{page:i}=e;await i.mouse.move(t,n);let a=0,l=0;switch(s){case"up":l=-o;break;case"down":l=o;break;case"left":a=-o;break;case"right":a=o;break}return await i.mouse.wheel(a,l),await new Promise(c=>setTimeout(c,200)),await this.captureState(e)}async waitSeconds(e,t){let n=Math.min(Math.max(t,1),30);return await new Promise(s=>setTimeout(s,n*1e3)),await this.captureState(e)}async waitForElement(e,t,n){let{page:s}=e,o=(()=>{let l=typeof process<"u"&&process?.env?Number(process.env.REPLAY_WAIT_POLL_MS):NaN;return Number.isFinite(l)&&l>0?Math.round(l/1e3):180})(),i=e.isTestPlanReplay===!0?o:30,a=Math.min(Math.max(n,1),i);try{return await s.getByText(t,{exact:!1}).first().waitFor({state:"visible",timeout:a*1e3}),await new Promise(c=>setTimeout(c,300)),await this.captureState(e)}catch{return{...await this.captureState(e),metadata:{error:`Text "${t}" not found within ${a}s. Do NOT retry \u2014 the page likely loaded with different text. Inspect the screenshot and proceed with the next action, or report_issue if blocked.`}}}}async fullPageScreenshot(e){let{page:t}=e,n=await this.screenshotWithSlowSiteRetry(e,{fullPage:!0}),s=t.url();return{screenshot:n,url:s}}async switchLayout(e,t,n){let{page:s}=e;return await s.setViewportSize({width:t,height:n}),await this.captureState(e)}async goBack(e){let{page:t}=e;return e.needsFullSnapshot=!0,await t.goBack(),await this.awaitPageReady(t),await this.captureState(e)}async goForward(e){let{page:t}=e;return e.needsFullSnapshot=!0,await t.goForward(),await this.awaitPageReady(t),await this.captureState(e)}async navigate(e,t){let{page:n}=e,s=$r(t),o=await this.blockScopedNavigationIfNeeded(e,s);if(o)return o;let i=await Nu(s,{allowPrivateNetwork:this.preflightAllowsPrivateNetwork()});if(!i.ok)throw new er(i);e.needsFullSnapshot=!0;let a=await n.goto(s,{waitUntil:"domcontentloaded"}),l=await this.awaitPageReady(n);await this.assertNoCloudflareBlock(n,a,s);let c=[],u=a?.request().redirectedFrom()??null;for(;u;)c.unshift(u.url()),u=u.redirectedFrom();let d=n.url();await this.assertNoThirdPartyIdpLogin(e,"navigate",{},d);let h=Rc(s,d,c,(p,g,m,b)=>console.warn(`[BasePlaywright] [${g}] ${m}`,b??"")),f=await this.captureState(e);return{...f,metadata:{...f.metadata??{},navigateMeta:h,pageSettled:l}}}async blockScopedNavigationIfNeeded(e,t){let n=Uv({targetUrl:t,currentUrl:e.page.url(),scope:e.browserNavigationScope,allowExternalNavigation:e.allowExternalNavigation,httpsCertFallbackHost:e.httpsCertFallback?.host});if(n.allowed)return null;let s=await this.captureState(e),o=e.browserNavigationScope?.join(", ")||"(none)";return{...s,metadata:{...s.metadata??{},error:`Navigation blocked: ${t} is outside the allowed scope (${o}). Record/report the external link without opening it unless the user explicitly asked to validate external links.`,scopedNavigation:{status:"blocked",targetUrl:t,reason:n.reason}}}}async keyCombination(e,t){let{page:n}=e,s=t.map(i=>tL[i.toLowerCase()]??i),o=s.some(i=>Lu.has(i));if(s.length===1)await n.keyboard.press(s[0]);else if(o){let i=s.filter(l=>Lu.has(l)),a=s.filter(l=>!Lu.has(l));for(let l of i)await n.keyboard.down(l);for(let l of a)await n.keyboard.press(l);for(let l of i.reverse())await n.keyboard.up(l)}else for(let i of s)await n.keyboard.press(i);return await this.awaitPageReady(n),await this.captureState(e)}async setFocusedInputValue(e,t){let{page:n}=e,s={isSelect:!1,inputType:"none"};try{s=await n.evaluate(()=>{let a=document.activeElement;return a instanceof HTMLSelectElement?{isSelect:!0,inputType:"select"}:a instanceof HTMLInputElement?{isSelect:!1,inputType:a.type}:a instanceof HTMLTextAreaElement?{isSelect:!1,inputType:"textarea"}:a?.isContentEditable?{isSelect:!1,inputType:"contenteditable"}:{isSelect:!1,inputType:"none"}})}catch{return console.warn("[BasePlaywright] page.evaluate blocked in setFocusedInputValue, falling back to keyboard typing"),t=this.normalizeTestingEmailForFocusedField(e,t,{}),await n.keyboard.press("ControlOrMeta+a"),await n.keyboard.type(t,{delay:10}),{...await this.captureState(e),metadata:{elementType:"unknown (evaluate blocked)",valueBefore:"",valueAfter:t}}}if(s.isSelect)return await this.setSelectValue(e,t);t=this.normalizeTestingEmailForFocusedField(e,t,{inputType:s.inputType});let o=await n.evaluate(a=>{let l=document.activeElement,c=f=>f instanceof HTMLInputElement?`input[type=${f.type}]`:f instanceof HTMLTextAreaElement?"textarea":f.isContentEditable?"contenteditable":f.tagName.toLowerCase(),u=f=>f instanceof HTMLInputElement||f instanceof HTMLTextAreaElement?f.value:f.isContentEditable&&f.textContent||"";if(!l||l===document.body)return{success:!1,error:"No element is focused",elementType:"none",valueBefore:"",valueAfter:""};let d=c(l),h=u(l);try{if(l instanceof HTMLInputElement){let f=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;f?f.call(l,a):l.value=a,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0}))}else if(l instanceof HTMLTextAreaElement){let f=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,"value")?.set;f?f.call(l,a):l.value=a,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0}))}else if(l.isContentEditable)l.textContent=a,l.dispatchEvent(new Event("input",{bubbles:!0}));else return{success:!1,error:`Element is not editable: ${d}`,elementType:d,valueBefore:h,valueAfter:h}}catch(f){return{success:!1,error:String(f.message||f),elementType:d,valueBefore:h,valueAfter:u(l)}}return{success:!0,elementType:d,valueBefore:h,valueAfter:u(l)}},t);return{...await this.captureState(e),metadata:{elementType:o.elementType,valueBefore:o.valueBefore,valueAfter:o.valueAfter,...o.error&&{error:o.error}}}}async setSelectValue(e,t){let{page:n}=e,s=await n.evaluate(()=>{let u=document.activeElement;if(!(u instanceof HTMLSelectElement))return null;let d=u.options[u.selectedIndex]?.textContent?.trim()||"",h=Array.from(u.options).map(f=>f.textContent?.trim()||f.value);return{valueBefore:d,options:h}});if(!s)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:"",valueAfter:"",error:"No select element is focused. Use click_at on the select first."}};let i=(await n.evaluateHandle(()=>document.activeElement)).asElement();if(!i)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:s.valueBefore,valueAfter:s.valueBefore,error:"Could not get select element handle",availableOptions:s.options}};let a=!1;try{await i.selectOption({label:t}),a=!0}catch{try{await i.selectOption({value:t}),a=!0}catch{}}let l=await n.evaluate(()=>{let u=document.activeElement;return u instanceof HTMLSelectElement?u.options[u.selectedIndex]?.textContent?.trim()||u.value:""});return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:s.valueBefore,valueAfter:l,...!a&&{error:`No option matching "${t}"`},availableOptions:s.options}}}async uploadFile(e,t){let{page:n}=e;console.log(`[BasePlaywright] upload_file called with filePaths=${JSON.stringify(t)}`);let o=(await n.evaluateHandle(()=>document.querySelector('input[type="file"][data-agentiqa-file-target]')||document.activeElement)).asElement();if(!o)return{...await this.captureState(e),metadata:{elementType:"file",error:"No file input found. Use click_at on the file input first."}};let i=await n.evaluate(u=>u instanceof HTMLInputElement&&u.type==="file"?{isFileInput:!0,accept:u.accept||"*",multiple:u.multiple}:{isFileInput:!1,accept:"",multiple:!1},o);if(!i.isFileInput)return{...await this.captureState(e),metadata:{elementType:"not-file-input",error:"No file input found. Use click_at on the file input/upload area first."}};try{await o.setInputFiles(t),console.log(`[BasePlaywright] upload_file setInputFiles succeeded, count=${t.length}`)}catch(u){return console.log(`[BasePlaywright] upload_file setInputFiles failed: ${u.message}`),{...await this.captureState(e),metadata:{elementType:"file",accept:i.accept,multiple:i.multiple,error:`File upload failed: ${u.message}`}}}let a=await this.onFilesUploaded(t),l=await n.evaluate(()=>document.querySelector('input[type="file"][data-agentiqa-file-target]')?.files?.length||0);return console.log(`[BasePlaywright] upload_file result: fileCount=${l}`),await this.awaitPageReady(n),{...await this.captureState(e),metadata:{elementType:"file",accept:i.accept,multiple:i.multiple,fileCount:l,...a.length>0&&{storedAssets:a}}}}async dragAndDrop(e,t,n,s,o){let{page:i}=e;return await i.mouse.move(t,n),await i.mouse.down(),await i.mouse.move(s,o,{steps:10}),await i.mouse.up(),await this.captureState(e)}async resolveRefCenter(e,t){try{let o=await e.page.locator(`aria-ref=${t}`).boundingBox({timeout:3e3});return o?{x:Math.floor(o.x+o.width/2),y:Math.floor(o.y+o.height/2)}:null}catch{return null}}async refNotFoundError(e,t){return{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}async switchTab(e,t){if(t==="tab1"){let n=!e.tab1||e.tab1.isClosed();if(this.diagLog?.(e.sessionId,"switchTab tab1 requested",{tab1Closed:n,hasExtId:!!e.extensionId}),n&&e.extensionId){this.diagLog?.(e.sessionId,"switchTab auto-recreating tab1 at popup.html");let s=await e.context.newPage();Za(s),await s.goto(`chrome-extension://${e.extensionId}/popup.html`,{timeout:5e3}),e.tab1=s}if(!e.tab1||e.tab1.isClosed())return{...await this.captureState(e),metadata:{error:"Tab 1 is not available"}};if(e.page=e.tab1,e.activeTab="tab1",e.extensionId){let s=e.tab1.url();if(!s.startsWith(`chrome-extension://${e.extensionId}`)){this.diagLog?.(e.sessionId,"switchTab navigating to popup.html",{beforeUrl:s});try{await e.tab1.goto(`chrome-extension://${e.extensionId}/popup.html`,{timeout:5e3});let o=e.tab1.url();this.diagLog?.(e.sessionId,"switchTab popup.html loaded",{afterUrl:o})}catch(o){this.diagLog?.(e.sessionId,"switchTab popup.html failed",{error:o?.message})}}}}else{if(!e.tab2||e.tab2.isClosed())return{...await this.captureState(e),metadata:{error:"Tab 2 is not available. No second tab is open."}};e.page=e.tab2,e.activeTab="tab2"}return e.needsFullSnapshot=!0,await e.page.bringToFront(),await this.captureState(e)}async closeTab(e){if(e.activeTab==="tab1")return{...await this.captureState(e),metadata:{error:"Cannot close tab 1"}};if(e.tab2&&!e.tab2.isClosed())try{await e.tab2.close()}catch{}return e.tab2=void 0,e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0,await e.page.bringToFront(),await this.captureState(e)}static HTTP_BODY_MAX_LENGTH=5e4;async httpRequest(e,t,n,s,o){let{page:i}=e;try{let a={method:n,timeout:3e4,ignoreHTTPSErrors:!0};s&&(a.headers=s),o&&n!=="GET"&&(a.data=o);let l=await i.request.fetch(t,a),c=await l.text(),u=!1;if(c.length>r.HTTP_BODY_MAX_LENGTH&&(c=c.slice(0,r.HTTP_BODY_MAX_LENGTH),u=!0),(l.headers()["content-type"]||"").includes("application/json")&&!u)try{c=JSON.stringify(JSON.parse(c),null,2),c.length>r.HTTP_BODY_MAX_LENGTH&&(c=c.slice(0,r.HTTP_BODY_MAX_LENGTH),u=!0)}catch{}return{...await this.captureState(e),metadata:{httpResponse:{status:l.status(),statusText:l.statusText(),headers:l.headers(),body:c,...u&&{truncated:!0}}}}}catch(a){return{...await this.captureState(e),metadata:{error:`HTTP request failed: ${a.message}`}}}}static RUN_JS_INPUT_MAX_LENGTH=4e3;static RUN_JS_RESULT_MAX_LENGTH=5e3;static RUN_JS_TIMEOUT_MS=1e4;async runJs(e,t){if(t.length>r.RUN_JS_INPUT_MAX_LENGTH)return{...await this.captureState(e),metadata:{error:`run_js code exceeded ${r.RUN_JS_INPUT_MAX_LENGTH}-char cap (${t.length} chars).`}};let n=`(async () => {
1669
+ `,document.body.appendChild(i)),i.style.left=`${s}px`,i.style.top=`${o}px`},{x:t,y:n})}catch{}}getSuggestedSampleFiles(e,t){return[]}async ensureBrowser(){if(!this.browser){console.log("[BasePlaywright] Launching browser");let e=performance.now();this.browser=await this.launchBrowser();let t=Math.round(performance.now()-e);console.log(`[BasePlaywright] Browser launched in ${t}ms`),this.browser.on("disconnected",()=>{console.log("[BasePlaywright] Browser disconnected"),this.browser=null,this.sessions.clear(),this.onBrowserDisconnected?.()})}return this.browser}async ensureSession(e,t){if(this.sessions.has(e))return this.sessions.get(e);e.includes(":child-")||await this.cleanupOtherSessions(e);let n=await this.createSession(e,t);return this.sessions.set(e,n),n}async invoke(e){let t=await this.ensureSession(e.sessionId,e.config);t.lastInvokeAt=Date.now(),t.browserNavigationScope=Array.isArray(e.config?.browserNavigationScope)?e.config.browserNavigationScope.map(String):void 0,t.allowExternalNavigation=!!e.config?.allowExternalNavigation,t.testingEmailPolicy=bt(e.config)??void 0,t.isTestPlanReplay=!!e.config?.isTestPlanReplay;let n=e.config?.loginCredentialEmails;t.loginCredentialEmails=Array.isArray(n)?n.map(String):void 0,t.appUnderTestOrigin=R_(e.config);let s=e.args??{},o=performance.now(),i,a,l=this.safeCurrentUrl(t);try{let c=await this.dispatch(t,e.action,s);c=this.withSessionMetadata(t,c);let u=Math.round(performance.now()-o);if(console.log(`[BasePlaywright] ${e.action} completed in ${u}ms`),t.tab2||t.isExtensionSession){let d=t.tab1&&!t.tab1.isClosed(),h=t.tab2&&!t.tab2.isClosed(),f=[];d&&f.push(t.tab1.url()),h&&f.push(t.tab2.url()),c={...c,metadata:{activeTab:t.activeTab,tabCount:(d?1:0)+(h?1:0),tabUrls:f,...t.pendingExtensionPopup?{pendingExtensionPopup:!0}:{},...c.metadata}},t.pendingExtensionPopup=!1}i={screenshot:c.screenshot.toString("base64"),url:c.url,aiSnapshot:c.aiSnapshot,domChanges:c.domChanges,metadata:c.metadata},a=t.page}catch(c){let u=String(c?.message||"");if(c instanceof Kt)throw c;if(u.includes("Execution context was destroyed")||u.includes("most likely because of a navigation")||u.includes("navigation")){console.log(`[BasePlaywright] Navigation detected during ${e.action}, recovering`),t.needsFullSnapshot=!0;try{await t.page.waitForLoadState("load",{timeout:5e3})}catch{}let d=await this.captureState(t),h=this.withSessionMetadata(t,d);i={screenshot:h.screenshot.toString("base64"),url:h.url,aiSnapshot:h.aiSnapshot,metadata:h.metadata},a=t.page}else if(u.includes("Browser session closed")||u.includes("Target closed")||u.includes("has been closed")||u.includes("Page crashed")){console.log(`[BasePlaywright] Session closed for ${e.sessionId}, recreating`),this.sessions.delete(e.sessionId);try{let d=await this.ensureSession(e.sessionId,e.config),h=this.withSessionMetadata(d,await this.dispatch(d,e.action,s));i={screenshot:h.screenshot.toString("base64"),url:h.url,aiSnapshot:h.aiSnapshot,metadata:h.metadata},a=d.page}catch(d){console.error("[BasePlaywright] Retry after session recreation failed:",d);let h=d instanceof Error?d.message:String(d);throw new Error(`Browser session recovery failed: ${h}`)}}else throw c}return e.action!=="navigate"&&a&&(await this.assertNoCloudflareBlockAfterCrossOriginNav(a,l,i.url),await this.assertNoCaptchaGate(t,a,e.action,s,i.url),await this.assertNoCredentialRejection(t,a,e.action,s,i.url),await this.assertNoThirdPartyIdpLogin(t,e.action,s,i.url)),i.metadata={...i.metadata??{},events:this.buildAndResetDigest(a)},i}buildAndResetDigest(e){let t=e?Zo.get(e):void 0;if(!t)return{consoleErrors:[],pageErrors:[],failedRequests:[],pendingRequests:[],recentWrites:[]};let n=Date.now(),s={consoleErrors:t.rollingConsoleErrors.slice(),pageErrors:t.rollingPageErrors.slice(),failedRequests:t.rollingFailedRequests.slice(),pendingRequests:Array.from(t.pendingWrites).map(o=>({url:o.url,method:o.method,ageMs:n-o.startTs})),recentWrites:t.rollingRecentWrites.slice()};return t.rollingConsoleErrors=[],t.rollingPageErrors=[],t.rollingFailedRequests=[],t.rollingRecentWrites=[],s}async readFocusedFieldValue(e){try{return await e.evaluate(()=>{let t=document.activeElement;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)return t.value;if(t instanceof HTMLElement&&t.isContentEditable)return t.textContent??""})}catch{return}}async getFocusedFieldName(e){try{return await e.evaluate(()=>{let t=document.activeElement;if(!t||t===document.body)return;let n=u=>{let d=[];for(let h of Array.from(u.childNodes))if(h.nodeType===Node.TEXT_NODE){let f=h.textContent?.trim();f&&d.push(f)}else if(h instanceof HTMLElement&&!s(h)){let f=h.innerText?.trim();f&&d.push(f)}return d.join(" ").replace(/\s+/g," ").trim()},s=u=>u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement||u.isContentEditable,o=t.getAttribute("aria-label");if(o)return o;let i=t.getAttribute("aria-labelledby");if(i){let u=i.split(/\s+/).map(d=>document.getElementById(d)?.textContent?.trim()).filter(Boolean).join(" ");if(u)return u}if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){let u=t.labels?.[0]?.textContent?.trim();if(u)return u}let a=t.id;if(a){let u=document.querySelector(`label[for="${a}"]`);if(u)return u.textContent?.trim()}if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){if(t.placeholder)return t.placeholder;if(t.name)return t.name}let l=t.previousElementSibling;for(;l;){let u=l instanceof HTMLElement?l.innerText?.trim():void 0;if(u)return u;l=l.previousElementSibling}let c=t.parentElement;for(let u=0;c&&u<3;u+=1,c=c.parentElement){let d=n(c);if(d)return d}})}catch{return}}async awaitPageReady(e){return e.waitForLoadState("domcontentloaded",{timeout:5e3}).then(()=>!0).catch(()=>!1)}async screenshotWithSlowSiteRetry(e,t={}){let{page:n}=e,s=(e.screenshotCount??0)===0,o=15e3,i=25e3,l={type:"png",timeout:s?o:5e3};t.fullPage&&(l.fullPage=!0);try{let c=await n.screenshot(l);return e.screenshotCount=(e.screenshotCount??0)+1,c}catch(c){if(!s||!Fc(c))throw c;let u=n.url();this.diagLog?.(e.sessionId,"screenshot_first_timeout_retry",{url:u,firstTimeoutMs:o,retryTimeoutMs:i});let d={type:"png",timeout:i};t.fullPage&&(d.fullPage=!0);try{let h=await n.screenshot(d);return e.screenshotCount=(e.screenshotCount??0)+1,this.diagLog?.(e.sessionId,"screenshot_first_retry_success",{url:u}),h}catch(h){throw Fc(h)?new on({url:u,totalTimeoutMs:o+i,attemptCount:2}):h}}}async captureState(e){let{page:t}=e;e.expectDomQuiescence&&!process.env.AQ_NO_DOM_OBSERVER&&!process.env.AQ_NO_DOM_DELAY&&await nr.waitForQuiescence(t,{minQuietMs:100,maxMs:800});let n=await this.screenshotWithSlowSiteRetry(e),s=t.url(),o;try{let a=await t._snapshotForAI({track:e.sessionId+":"+e.activeTab}),l=typeof a=="string"?a:a?.full,c=typeof a=="object"?a?.incremental:void 0;!e.needsFullSnapshot&&c?o=c:(o=l,e.needsFullSnapshot=!1)}catch{o=void 0,e.needsFullSnapshot=!0}let i=process.env.AQ_NO_DOM_OBSERVER?void 0:await nr.flush(t)??void 0;return{screenshot:n,url:s,aiSnapshot:o,domChanges:i}}withSessionMetadata(e,t){return e.httpsCertFallback?{...t,metadata:{...t.metadata??{},httpsCertFallback:e.httpsCertFallback}}:t}shouldResetDomObserverBeforeAction(e){return!new Set(["open_web_browser","screenshot","snapshot","wait","wait_5_seconds","wait_for_element","full_page_screenshot","run_js"]).has(e)}shouldWaitForDomQuiescenceAfterAction(e){return this.shouldResetDomObserverBeforeAction(e)}async dispatch(e,t,n){let s=await this.dispatchPlatformAction(e,t,n);if(s)return s;let{viewportWidth:o,viewportHeight:i}=e,a=d=>Math.floor(d/1e3*o),l=d=>Math.floor(d/1e3*i),c=!process.env.AQ_NO_DOM_OBSERVER&&this.shouldResetDomObserverBeforeAction(t),u=e.expectDomQuiescence;c&&await nr.reset(e.page),e.expectDomQuiescence=this.shouldWaitForDomQuiescenceAfterAction(t);try{switch(t){case"open_web_browser":case"screenshot":return await this.captureState(e);case"snapshot":return e.needsFullSnapshot=!0,await this.captureState(e);case"click_at":{let d=Array.isArray(n.modifiers)?n.modifiers.map(String):[];if(n.label)return await this.clickByLabel(e,String(n.label),d);if(n.ref)return await this.clickByRef(e,String(n.ref),d);let h=this.requireNormalizedPoint(t,n);return await this.clickAt(e,a(h.x),l(h.y),d)}case"right_click_at":{if(n.ref)return await this.rightClickByRef(e,String(n.ref));let d=this.requireNormalizedPoint(t,n);return await this.rightClickAt(e,a(d.x),l(d.y))}case"hover_at":{if(n.ref)return await this.hoverByRef(e,String(n.ref));let d=this.requireNormalizedPoint(t,n);return await this.hoverAt(e,a(d.x),l(d.y))}case"type_text_at":{let d=n.clearBeforeTyping??n.clear_before_typing??!0;if(n.label)return await this.typeByLabel(e,String(n.label),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d);if(n.ref)return await this.typeByRef(e,String(n.ref),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d);let h=this.requireNormalizedPoint(t,n);return await this.typeTextAt(e,a(h.x),l(h.y),String(n.text??""),!!(n.pressEnter??n.press_enter??!1),d)}case"scroll_document":return await this.scrollDocument(e,String(n.direction));case"scroll_to_bottom":return await this.scrollToBottom(e);case"scroll_at":{let d=String(n.direction),h=n.magnitude!=null?Number(n.magnitude):800;if(d==="up"||d==="down"?h=l(h):(d==="left"||d==="right")&&(h=a(h)),n.ref){let p=await this.resolveRefCenter(e,String(n.ref));return p?await this.scrollAt(e,p.x,p.y,d,h):await this.refNotFoundError(e,String(n.ref))}let f=this.requireNormalizedPoint(t,n);return await this.scrollAt(e,a(f.x),l(f.y),d,h)}case"wait":return await this.waitSeconds(e,Number(n.seconds||2));case"wait_for_element":return await this.waitForElement(e,String(n.textContent??""),Number(n.timeoutSeconds||5));case"wait_5_seconds":return await this.waitSeconds(e,5);case"full_page_screenshot":return await this.fullPageScreenshot(e);case"switch_layout":{let d=Number(n.width),h=Number(n.height);return e.viewportWidth=d,e.viewportHeight=h,await this.switchLayout(e,d,h)}case"go_back":return await this.goBack(e);case"go_forward":return await this.goForward(e);case"navigate":{let d=String(n.url??n.href??"");if(e.isExtensionSession){if(d.startsWith("chrome-extension://")){if(e.tab1&&!e.tab1.isClosed())await e.tab1.goto(d),await this.awaitPageReady(e.tab1);else{let h=await e.context.newPage();Za(h),await h.goto(d),await this.awaitPageReady(h)}return e.tab1&&!e.tab1.isClosed()&&(e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0,await e.page.bringToFront()),await this.captureState(e)}e.tab2&&!e.tab2.isClosed()&&(e.page=e.tab2,e.activeTab="tab2")}else e.activeTab==="tab2"&&e.tab1&&!e.tab1.isClosed()&&(e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0);return await this.navigate(e,d)}case"key_combination":return await this.keyCombination(e,Array.isArray(n.keys)?n.keys.map(String):[]);case"set_focused_input_value":return await this.setFocusedInputValue(e,String(n.value??""));case"drag_and_drop":{let d,h;if(n.ref){let g=await this.resolveRefCenter(e,String(n.ref));if(!g)return await this.refNotFoundError(e,String(n.ref));d=g.x,h=g.y}else{let g=this.requireNormalizedPoint(t,n);d=a(g.x),h=l(g.y)}let f,p;if(n.destinationRef){let g=await this.resolveRefCenter(e,String(n.destinationRef));if(!g)return await this.refNotFoundError(e,String(n.destinationRef));f=g.x,p=g.y}else{let g=this.requireNormalizedPoint(t,n,n.destinationX!==void 0?"destinationX":"destination_x",n.destinationY!==void 0?"destinationY":"destination_y");f=a(g.x),p=l(g.y)}return await this.dragAndDrop(e,d,h,f,p)}case"upload_file":{let d=Array.isArray(n.filePaths)?n.filePaths.map(String):[String(n.filePaths??"")];return await this.uploadFile(e,d)}case"http_request":return await this.httpRequest(e,String(n.url??""),String(n.method??"GET"),n.headers,n.body!=null?String(n.body):void 0);case"run_js":return await this.runJs(e,String(n.code??""));case"switch_tab":{let d=String(n.tab??"tab1"),h=d==="main"?"tab1":d==="extension"?"tab2":d;return await this.switchTab(e,h)}case"close_tab":return await this.closeTab(e);default:return console.warn(`[BasePlaywright] Unsupported action: ${t}`),await this.captureState(e)}}finally{e.expectDomQuiescence=u}}async clickAt(e,t,n,s=[]){let{page:o}=e;try{await o.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}await this.onBeforeAction(e,t,n);let i={isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:null,clickTarget:null};try{let h=await o.evaluateHandle(g=>document.elementFromPoint(g.x,g.y),{x:t,y:n}),f=h.asElement(),p=f?await f.evaluate(C_).catch(()=>null):null;await h.dispose().catch(()=>{}),i=await o.evaluate(g=>{let m=document.elementFromPoint(g.x,g.y);if(!m)return{isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:null,clickTarget:null};let b={tag:m.tagName.toLowerCase(),text:(m.textContent||"").trim().slice(0,80),role:m.getAttribute("role")||""},w=m.closest("a[href], area[href]"),_=null,T=m.closest("select");if(T)_=T;else if(m instanceof HTMLLabelElement&&m.htmlFor){let v=document.getElementById(m.htmlFor);v instanceof HTMLSelectElement&&(_=v)}else{let v=m instanceof HTMLLabelElement?m:m.closest("label");if(v){let x=v.querySelector("select");x&&(_=x)}}if(_){_.focus();let v=_.options[_.selectedIndex]?.textContent?.trim()||"",x=Array.from(_.options).map(I=>I.textContent?.trim()||I.value);return{isSelect:!0,isMultiple:_.multiple,selectedText:v,options:x,clickedElement:null,clickTarget:null}}return{isSelect:!1,isMultiple:!1,selectedText:"",options:[],clickedElement:b,clickTarget:null,linkHref:w?.href||""}},{x:t,y:n}),i.clickTarget=p}catch(h){let f=String(h?.message||"");if(!(f.includes("Execution context was destroyed")||f.includes("navigation")))throw h}if(i.isSelect&&!i.isMultiple)return await this.awaitPageReady(o),{...await this.captureState(e),metadata:{elementType:"select",valueBefore:i.selectedText,valueAfter:i.selectedText,availableOptions:i.options,...i.clickTarget?{clickTarget:i.clickTarget}:{}}};if(i.linkHref){let h=await this.blockScopedNavigationIfNeeded(e,i.linkHref);if(h)return h}let a=o.waitForEvent("filechooser",{timeout:150}).catch(()=>null),l=o.waitForEvent("download",{timeout:500}).catch(()=>null);await nr.reset(o);for(let h of s)await o.keyboard.down(h);await o.mouse.click(t,n);for(let h of s)await o.keyboard.up(h);let c=await a;if(c){let f=await c.element().evaluate(m=>{let b=m;return document.querySelectorAll("[data-agentiqa-file-target]").forEach(w=>w.removeAttribute("data-agentiqa-file-target")),b.setAttribute("data-agentiqa-file-target","true"),b instanceof HTMLInputElement?{accept:b.accept||"*",multiple:b.multiple}:{accept:"*",multiple:!1}}),p=this.getSuggestedSampleFiles(f.accept,f.multiple);return console.log(`[BasePlaywright] FILE CHOOSER INTERCEPTED: accept="${f.accept}", multiple=${f.multiple}`),{...await this.captureState(e),metadata:{elementType:"file",accept:f.accept,multiple:f.multiple,suggestedFiles:p}}}let u=await l;if(u){let h=u.suggestedFilename(),f=u.url();console.log(`[BasePlaywright] DOWNLOAD INTERCEPTED: "${h}" from ${f}`),await u.cancel();try{await o.goBack({waitUntil:"domcontentloaded",timeout:5e3})}catch{}return{...await this.captureState(e),metadata:{elementType:"download",downloadFilename:h,downloadUrl:f,clickedElement:i.clickedElement??void 0,clickTarget:i.clickTarget??void 0}}}await this.awaitPageReady(o);let d=await this.captureState(e);return i.clickedElement||i.clickTarget?{...d,metadata:{...i.clickedElement?{clickedElement:i.clickedElement}:{},...i.clickTarget?{clickTarget:i.clickTarget}:{}}}:d}async clickByRef(e,t,n=[]){let{page:s}=e,o=3e3;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let i=s.locator(`aria-ref=${t}`),a=await i.boundingBox({timeout:o});a&&await this.onBeforeAction(e,a.x+a.width/2,a.y+a.height/2);let l=await i.evaluate(b=>({tag:b.tagName.toLowerCase(),text:(b.textContent||"").trim().slice(0,80),role:b.getAttribute("role")||"",linkHref:b.closest("a[href], area[href]")?.href})).catch(()=>null),c=await i.evaluate(C_).catch(()=>null),u=await i.evaluate(b=>{let w=b instanceof HTMLSelectElement?b:b.closest("select");if(!w)return null;w.focus();let _=w.options[w.selectedIndex]?.textContent?.trim()||"",T=Array.from(w.options).map(v=>v.textContent?.trim()||v.value);return{selectedText:_,options:T,isMultiple:w.multiple}}).catch(()=>null);if(u&&!u.isMultiple)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:u.selectedText,valueAfter:u.selectedText,availableOptions:u.options,...c?{clickTarget:c}:{}}};if(l?.linkHref){let b=await this.blockScopedNavigationIfNeeded(e,l.linkHref);if(b)return b}let d=s.waitForEvent("filechooser",{timeout:500}).catch(()=>null),h=s.waitForEvent("download",{timeout:500}).catch(()=>null),f=n.map(b=>b).filter(Boolean);await nr.reset(s),await i.click({force:!0,timeout:o,modifiers:f.length?f:void 0});let p=await d;if(p){let w=await p.element().evaluate(v=>{let x=v;return document.querySelectorAll("[data-agentiqa-file-target]").forEach(S=>S.removeAttribute("data-agentiqa-file-target")),x.setAttribute("data-agentiqa-file-target","true"),x instanceof HTMLInputElement?{accept:x.accept||"*",multiple:x.multiple}:{accept:"*",multiple:!1}}),_=this.getSuggestedSampleFiles(w.accept,w.multiple);return console.log(`[BasePlaywright] FILE CHOOSER INTERCEPTED via ref=${t}: accept="${w.accept}"`),{...await this.captureState(e),metadata:{elementType:"file",accept:w.accept,multiple:w.multiple,suggestedFiles:_}}}let g=await h;if(g){let b=g.suggestedFilename(),w=g.url();console.log(`[BasePlaywright] DOWNLOAD INTERCEPTED via ref=${t}: "${b}" from ${w}`),await g.cancel();try{await s.goBack({waitUntil:"domcontentloaded",timeout:5e3})}catch{}return{...await this.captureState(e),metadata:{elementType:"download",downloadFilename:b,downloadUrl:w,clickedElement:l??void 0,clickTarget:c??void 0}}}await this.awaitPageReady(s);let m=await this.captureState(e);return l||c?{...m,metadata:{...l?{clickedElement:l}:{},...c?{clickTarget:c}:{}}}:m}catch(i){console.warn(`[BasePlaywright] clickByRef ref=${t} failed: ${i.message}`);let a=await this.captureState(e),c=(i.message??"").includes("intercepts pointer events")?`Ref "${t}" is covered by another element (overlay/popup). Dismiss the overlay first, or try a different ref.`:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`;return{...a,metadata:{error:c}}}}async clickByLabel(e,t,n=[]){let{page:s}=e,o=3e3;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}let i=await s.evaluate(a=>{let l=x=>x.replace(/\s+/g," ").trim().replace(/[?:.!]+$/,"").toLowerCase(),c=x=>l(x).replace(/[\s:_-]+/g,""),u=l(a),d=c(a),h=(x,S)=>!!S&&x.startsWith(S)&&(x.length===S.length||/[\s:>/-]/.test(x.charAt(S.length))),f=x=>{let S=l(x);return S?S===u?0:c(x)===d?1:h(S,u)||h(u,S)?2:null:null},p=x=>{let S=window.getComputedStyle(x);if(S.display==="none"||S.visibility==="hidden")return!1;let I=x.getBoundingClientRect();return I.width>0&&I.height>0},g=x=>{let S=x.getAttribute("aria-label");if(S&&S.trim())return S.trim();let I=x.getAttribute("aria-labelledby");if(I){let L=I.split(/\s+/).map(W=>document.getElementById(W)?.textContent?.trim()).filter(Boolean).join(" ");if(L)return L}if(x instanceof HTMLInputElement&&(x.type==="submit"||x.type==="button"||x.type==="reset")&&x.value&&x.value.trim())return x.value.trim();let E=(x.innerText||x.textContent||"").trim();if(E)return E;let A=x.getAttribute("title");return A&&A.trim()?A.trim():""},m=Array.from(document.querySelectorAll('button, a[href], [role="button"], [role="link"], [role="menuitem"], [role="tab"], input[type="submit"], input[type="button"], input[type="reset"], summary')).filter(x=>p(x)),b=[];for(let x of m){let S=g(x),I=f(S);I!=null&&b.push({element:x,name:S,rank:I})}if(b.length===0)return{status:"none"};b.sort((x,S)=>x.rank-S.rank);let w=b[0].rank,_=b.filter(x=>x.rank===w);if(Array.from(new Set(_.map(x=>x.element))).length>1)return{status:"ambiguous",matches:_.slice(0,5).map(x=>x.name)};let v=`agentiqa_click_target_${Date.now()}_${Math.random().toString(36).slice(2)}`;return _[0].element.setAttribute("data-agentiqa-click-target",v),{status:"ok",marker:v,matchedName:_[0].name}},t);if(i.status!=="ok"){let a=await this.captureState(e),l=i.status==="ambiguous"?`More than one control is named "${t}" (${(i.matches||[]).join(", ")}). Click by ref from the latest snapshot, or use a more specific name.`:`No clickable control named "${t}" was found on the page. Check the latest snapshot and click by ref, or use coordinates.`;return{...a,metadata:{...a.metadata,note:l}}}try{let a=s.locator(`[data-agentiqa-click-target="${i.marker}"]`).first(),l=await a.boundingBox({timeout:o});l&&await this.onBeforeAction(e,l.x+l.width/2,l.y+l.height/2);let c=await a.evaluate(h=>({tag:h.tagName.toLowerCase(),text:(h.textContent||"").trim().slice(0,80),role:h.getAttribute("role")||"",linkHref:h.closest("a[href], area[href]")?.href})).catch(()=>null);if(c?.linkHref){let h=await this.blockScopedNavigationIfNeeded(e,c.linkHref);if(h)return h}let u=n.map(h=>h).filter(Boolean);await nr.reset(s),await a.click({force:!0,timeout:o,modifiers:u.length?u:void 0}),await this.awaitPageReady(s);let d=await this.captureState(e);return c?{...d,metadata:{...d.metadata,clickedElement:c}}:d}catch(a){let l=await this.captureState(e);return{...l,metadata:{...l.metadata,note:`Could not click "${t}": ${a?.message??"unknown error"}. Try clicking by ref from the latest snapshot.`}}}}async rightClickAt(e,t,n){let{page:s}=e;try{await s.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}return await this.onBeforeAction(e,t,n),await s.mouse.click(t,n,{button:"right"}),await this.awaitPageReady(s),await this.captureState(e)}async rightClickByRef(e,t){let{page:n}=e,s=3e3;try{await n.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let o=n.locator(`aria-ref=${t}`),i=await o.boundingBox({timeout:s});return i&&await this.onBeforeAction(e,i.x+i.width/2,i.y+i.height/2),await o.click({button:"right",force:!0,timeout:s}),await this.awaitPageReady(n),await this.captureState(e)}catch(o){console.warn(`[BasePlaywright] rightClickByRef ref=${t} failed: ${o.message}`);let i=await this.captureState(e),l=(o.message??"").includes("intercepts pointer events")?`Ref "${t}" is covered by another element (overlay/popup). Dismiss the overlay first, or try a different ref.`:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`;return{...i,metadata:{error:l}}}}async hoverAt(e,t,n){let{page:s}=e;return await this.onBeforeAction(e,t,n),await s.mouse.move(t,n),await new Promise(o=>setTimeout(o,300)),await this.captureState(e)}async hoverByRef(e,t){let{page:n}=e,s=3e3;try{await n.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let o=n.locator(`aria-ref=${t}`),i=await o.boundingBox({timeout:s});return i&&await this.onBeforeAction(e,i.x+i.width/2,i.y+i.height/2),await o.hover({force:!0,timeout:s}),await new Promise(a=>setTimeout(a,300)),await this.captureState(e)}catch(o){return console.warn(`[BasePlaywright] hoverByRef ref=${t} failed: ${o.message}`),{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}}async typeTextAt(e,t,n,s,o,i,a=!1){let{page:l}=e;await this.onBeforeAction(e,t,n),await l.mouse.click(t,n);let c;try{c=await l.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement?{type:"input",inputType:v.type}:v instanceof HTMLTextAreaElement?{type:"textarea",inputType:"textarea"}:v instanceof HTMLSelectElement?{type:"select",inputType:"select"}:v.isContentEditable?{type:"contenteditable",inputType:"contenteditable"}:{type:"other",inputType:"none"}})}catch{console.warn("[BasePlaywright] page.evaluate blocked in typeTextAt, falling back to keyboard typing"),c={type:"input",inputType:"text"}}let u=["date","time","datetime-local","month","week"],d=c.type==="input"&&u.includes(c.inputType),f=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(c.inputType),p=f&&!d?await this.readFocusedFieldValue(l):void 0,g=i===!0||i==="true";if(s=this.normalizeTestingEmailForFocusedField(e,s,{inputType:c.inputType}),d){let v=!1;try{v=await l.evaluate(x=>{let S=document.activeElement;if(S instanceof HTMLInputElement){let I=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;return I?I.call(S,x):S.value=x,S.dispatchEvent(new Event("input",{bubbles:!0})),S.dispatchEvent(new Event("change",{bubbles:!0})),!0}return!1},s)}catch{}v||(g&&f&&(await l.keyboard.press("ControlOrMeta+a"),await l.keyboard.press("Backspace")),await l.keyboard.type(s,{delay:10}))}else g&&f&&(await l.keyboard.press("ControlOrMeta+a"),await l.keyboard.press("Backspace")),await l.keyboard.type(s,{delay:10});o&&(await l.keyboard.press("Enter"),await this.awaitPageReady(l));try{await l.evaluate(()=>new Promise(v=>requestAnimationFrame(()=>setTimeout(v,50))))}catch{}let m=await this.getFocusedFieldName(l),b=f&&!d?await this.readFocusedFieldValue(l):void 0,w=Du({requested:s,valueBefore:p,valueAfter:b,pressEnter:o,sensitive:a}),_=await this.captureState(e),T={..._.metadata};return m&&(T.typedIntoField=m),!a&&p!==void 0&&(T.valueBefore=p),!a&&b!==void 0&&(T.valueAfter=b),w.valueMismatch&&(T.valueMismatch=!0,T.error=w.error),{..._,metadata:T}}async typeByRef(e,t,n,s,o,i=!1){let{page:a}=e,l=3e3;try{await a.evaluate(()=>window.getSelection()?.removeAllRanges())}catch{}try{let c=a.locator(`aria-ref=${t}`),u=await c.boundingBox({timeout:l});u&&await this.onBeforeAction(e,u.x+u.width/2,u.y+u.height/2),await c.click({force:!0,timeout:l});let d;try{d=await a.evaluate(()=>{let S=document.activeElement;return S instanceof HTMLInputElement?{inputType:S.type}:S instanceof HTMLTextAreaElement?{inputType:"textarea"}:S.isContentEditable?{inputType:"contenteditable"}:{inputType:"none"}})}catch{console.warn(`[BasePlaywright] page.evaluate blocked for typeByRef ref=${t}, falling back to keyboard typing`),d={inputType:"text"}}let f=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(d.inputType),g=["date","time","datetime-local","month","week"].includes(d.inputType);if(!f&&!g)return{...await this.captureState(e),metadata:{error:`Ref "${t}" resolved to a non-text element (${d.inputType||"unknown"}). Use a different ref that targets the text input directly.`}};let m=o===!0||o==="true";n=this.normalizeTestingEmailForFocusedField(e,n,{inputType:d.inputType});let b=g?void 0:await this.readFocusedFieldValue(a);if(g)try{await a.evaluate(S=>{let I=document.activeElement;if(I instanceof HTMLInputElement){let E=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;E?E.call(I,S):I.value=S,I.dispatchEvent(new Event("input",{bubbles:!0})),I.dispatchEvent(new Event("change",{bubbles:!0}))}},n)}catch{await a.keyboard.type(n,{delay:15})}else m&&f?(await a.keyboard.press("ControlOrMeta+a"),n?await a.keyboard.type(n,{delay:15}):await a.keyboard.press("Backspace")):n&&await a.keyboard.type(n,{delay:15});s&&await a.keyboard.press("Enter"),await this.awaitPageReady(a);try{await a.evaluate(()=>new Promise(S=>requestAnimationFrame(()=>setTimeout(S,50))))}catch{}let w=await this.getFocusedFieldName(a),_=g?void 0:await this.readFocusedFieldValue(a),T=g?{valueMismatch:!1}:Du({requested:n,valueBefore:b,valueAfter:_,pressEnter:s,sensitive:i}),v=await this.captureState(e),x={...v.metadata};return w&&(x.typedIntoField=w),!i&&b!==void 0&&(x.valueBefore=b),!i&&_!==void 0&&(x.valueAfter=_),T.valueMismatch&&(x.valueMismatch=!0,x.error=T.error),{...v,metadata:x}}catch(c){return console.warn(`[BasePlaywright] typeByRef ref=${t} failed: ${c.message}`),{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}}async typeByLabel(e,t,n,s,o){let{page:i}=e,a=3e3,l=await i.evaluate(v=>{let x=k=>k.replace(/\s+/g," ").trim().replace(/:$/,"").toLowerCase(),S=k=>x(k).replace(/[\s:_-]+/g,""),I=x(v),E=S(v),A=k=>{let N=x(k);return N?N===I?0:S(k)===E?1:N.includes(I)||I.includes(N)?2:null:null},L=k=>{let N=window.getComputedStyle(k);if(N.display==="none"||N.visibility==="hidden")return!1;let oe=k.getBoundingClientRect();return oe.width>0&&oe.height>0},W=k=>k instanceof HTMLTextAreaElement?!k.disabled:k instanceof HTMLInputElement?k.disabled||k.type==="hidden"?!1:["text","password","email","search","url","tel","number","date","time","datetime-local","month","week"].includes(k.type):k.isContentEditable,U=k=>k instanceof HTMLInputElement?`input[type=${k.type}]`:k instanceof HTMLTextAreaElement?"textarea":k.isContentEditable?"contenteditable":k.tagName.toLowerCase(),j=k=>k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement?k.value:k.isContentEditable?k.textContent??"":"",$=k=>{let N=[];for(let oe of Array.from(k.childNodes))if(oe.nodeType===Node.TEXT_NODE){let ne=oe.textContent?.trim();ne&&N.push(ne)}else if(oe instanceof HTMLElement&&!W(oe)){let ne=oe.innerText?.trim();ne&&N.push(ne)}return N.join(" ").replace(/\s+/g," ").trim()},H=k=>{let N=[],oe=(M,R,O)=>{let V=M?.replace(/\s+/g," ").trim();V&&N.push({label:V,source:R,priority:O})};oe(k.getAttribute("aria-label"),"aria-label",0);let ne=k.getAttribute("aria-labelledby");if(ne&&oe(ne.split(/\s+/).map(M=>document.getElementById(M)?.textContent?.trim()).filter(Boolean).join(" "),"aria-labelledby",0),k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement)for(let M of Array.from(k.labels??[]))oe(M.textContent,"label",0);let X=k.id;X&&oe(document.querySelector(`label[for="${X}"]`)?.textContent,"label[for]",0),(k instanceof HTMLInputElement||k instanceof HTMLTextAreaElement)&&(oe(k.placeholder,"placeholder",1),oe(k.name,"name",2),oe(k.id,"id",2));let G=k.previousElementSibling;for(;G;)oe(G instanceof HTMLElement?G.innerText:G.textContent,"previous-sibling",3),G=G.previousElementSibling;let Y=k.parentElement;for(let M=0;Y&&M<3;M+=1,Y=Y.parentElement)oe($(Y),"container-text",4+M);return N},q=[],F=Array.from(document.querySelectorAll('input:not([type="hidden"]), textarea, [contenteditable="true"]')).filter(k=>W(k)&&L(k));for(let k of F)for(let N of H(k)){let oe=A(N.label);oe!=null&&q.push({element:k,label:N.label,source:N.source,priority:N.priority,matchRank:N.priority*10+oe,elementType:U(k),valueBefore:j(k)})}if(q.length===0)return{status:"none",error:`No text input found for label "${v}". Check the latest page snapshot for the field label or use ref/x/y.`};q.sort((k,N)=>k.matchRank-N.matchRank);let re=q[0].matchRank,le=q.filter(k=>k.matchRank===re);if(Array.from(new Set(le.map(k=>k.element))).length>1)return{status:"ambiguous",error:`Multiple text inputs matched label "${v}". Use a more specific label or a direct ref.`,matches:le.map(k=>({label:k.label,source:k.source,elementType:k.elementType}))};let J=`agentiqa_label_target_${Date.now()}_${Math.random().toString(36).slice(2)}`;return le[0].element.setAttribute("data-agentiqa-label-target",J),{status:"ok",marker:J,matchedLabel:le[0].label,labelSource:le[0].source,elementType:le[0].elementType,valueBefore:le[0].valueBefore}},t);if(l.status!=="ok"){let v=await this.captureState(e);return{...v,metadata:{...v.metadata,error:l.error,...l.status==="ambiguous"&&{matches:l.matches}}}}let c=i.locator(`[data-agentiqa-label-target="${l.marker}"]`).first(),u=await c.boundingBox({timeout:a});u&&await this.onBeforeAction(e,u.x+u.width/2,u.y+u.height/2),await c.click({force:!0,timeout:a});let d=await i.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement?{inputType:v.type}:v instanceof HTMLTextAreaElement?{inputType:"textarea"}:v.isContentEditable?{inputType:"contenteditable"}:{inputType:"none"}}),f=["date","time","datetime-local","month","week"].includes(d.inputType),g=["text","password","email","search","url","tel","number","textarea","contenteditable"].includes(d.inputType);if(!g&&!f){let v=await this.captureState(e);return{...v,metadata:{...v.metadata,error:`Label "${t}" resolved to a non-text element (${d.inputType||"unknown"}). Use a different label or direct ref.`}}}let m=o===!0||o==="true";if(n=this.normalizeTestingEmailForFocusedField(e,n,{inputType:d.inputType,label:t,matchedLabel:l.matchedLabel}),f)try{await i.evaluate(v=>{let x=document.activeElement;if(x instanceof HTMLInputElement){let S=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;S?S.call(x,v):x.value=v,x.dispatchEvent(new Event("input",{bubbles:!0})),x.dispatchEvent(new Event("change",{bubbles:!0}))}},n)}catch{await i.keyboard.type(n,{delay:15})}else m&&g?(await i.keyboard.press("ControlOrMeta+a"),n?await i.keyboard.type(n,{delay:15}):await i.keyboard.press("Backspace")):n&&await i.keyboard.type(n,{delay:15});s&&await i.keyboard.press("Enter"),await this.awaitPageReady(i);try{await i.evaluate(()=>new Promise(v=>requestAnimationFrame(()=>setTimeout(v,50))))}catch{}let b=await this.getFocusedFieldName(i),w=await i.evaluate(()=>{let v=document.activeElement;return v instanceof HTMLInputElement||v instanceof HTMLTextAreaElement?v.value:v instanceof HTMLElement&&v.isContentEditable?v.textContent??"":""}),_=f?{valueMismatch:!1}:Du({requested:n,valueBefore:l.valueBefore,valueAfter:w,pressEnter:s}),T=await this.captureState(e);return{...T,metadata:{...T.metadata,typedIntoField:b??l.matchedLabel,matchedLabel:l.matchedLabel,labelSource:l.labelSource,elementType:l.elementType,valueBefore:l.valueBefore,valueAfter:w,..._.valueMismatch?{valueMismatch:!0,error:_.error}:{}}}}async scrollDocument(e,t){let{page:n,viewportHeight:s}=e,o=Math.floor(s*.8),i=t==="up"||t==="down",a=t==="up"||t==="left"?-o:o;return await n.evaluate(({vertical:l,signed:c})=>{let u=document.scrollingElement||document.documentElement,d=null,h=0;for(let m of Array.from(document.querySelectorAll("*"))){let b=l?m.scrollHeight-m.clientHeight:m.scrollWidth-m.clientWidth;if(b<=h)continue;let w=l?getComputedStyle(m).overflowY:getComputedStyle(m).overflowX;w!=="auto"&&w!=="scroll"||(h=b,d=m)}let f=l?u.scrollHeight-u.clientHeight:u.scrollWidth-u.clientWidth,p=l?0:c,g=l?c:0;!d||f>=h?window.scrollBy(p,g):d.scrollBy(p,g)},{vertical:i,signed:a}),await new Promise(l=>setTimeout(l,200)),await this.captureState(e)}async scrollToBottom(e){let{page:t}=e;return await t.evaluate(()=>{let n=document.scrollingElement||document.documentElement,s=null,o=0;for(let a of Array.from(document.querySelectorAll("*"))){let l=a.scrollHeight-a.clientHeight;if(l<=o)continue;let c=getComputedStyle(a).overflowY;c!=="auto"&&c!=="scroll"||(o=l,s=a)}let i=n.scrollHeight-n.clientHeight;!s||i>=o?window.scrollTo(0,n.scrollHeight):s.scrollTo(0,s.scrollHeight)}),await new Promise(n=>setTimeout(n,200)),await this.captureState(e)}async scrollAt(e,t,n,s,o){let{page:i}=e;await i.mouse.move(t,n);let a=0,l=0;switch(s){case"up":l=-o;break;case"down":l=o;break;case"left":a=-o;break;case"right":a=o;break}return await i.mouse.wheel(a,l),await new Promise(c=>setTimeout(c,200)),await this.captureState(e)}async waitSeconds(e,t){let n=Math.min(Math.max(t,1),30);return await new Promise(s=>setTimeout(s,n*1e3)),await this.captureState(e)}async waitForElement(e,t,n){let{page:s}=e,o=(()=>{let l=typeof process<"u"&&process?.env?Number(process.env.REPLAY_WAIT_POLL_MS):NaN;return Number.isFinite(l)&&l>0?Math.round(l/1e3):180})(),i=e.isTestPlanReplay===!0?o:30,a=Math.min(Math.max(n,1),i);try{return await s.getByText(t,{exact:!1}).first().waitFor({state:"visible",timeout:a*1e3}),await new Promise(c=>setTimeout(c,300)),await this.captureState(e)}catch{return{...await this.captureState(e),metadata:{error:`Text "${t}" not found within ${a}s. Do NOT retry \u2014 the page likely loaded with different text. Inspect the screenshot and proceed with the next action, or report_issue if blocked.`}}}}async fullPageScreenshot(e){let{page:t}=e,n=await this.screenshotWithSlowSiteRetry(e,{fullPage:!0}),s=t.url();return{screenshot:n,url:s}}async switchLayout(e,t,n){let{page:s}=e;return await s.setViewportSize({width:t,height:n}),await this.captureState(e)}async goBack(e){let{page:t}=e;return e.needsFullSnapshot=!0,await t.goBack(),await this.awaitPageReady(t),await this.captureState(e)}async goForward(e){let{page:t}=e;return e.needsFullSnapshot=!0,await t.goForward(),await this.awaitPageReady(t),await this.captureState(e)}async navigate(e,t){let{page:n}=e,s=$r(t),o=await this.blockScopedNavigationIfNeeded(e,s);if(o)return o;let i=await Nu(s,{allowPrivateNetwork:this.preflightAllowsPrivateNetwork()});if(!i.ok)throw new er(i);e.needsFullSnapshot=!0;let a=await n.goto(s,{waitUntil:"domcontentloaded"}),l=await this.awaitPageReady(n);await this.assertNoCloudflareBlock(n,a,s);let c=[],u=a?.request().redirectedFrom()??null;for(;u;)c.unshift(u.url()),u=u.redirectedFrom();let d=n.url();await this.assertNoThirdPartyIdpLogin(e,"navigate",{},d);let h=Rc(s,d,c,(p,g,m,b)=>console.warn(`[BasePlaywright] [${g}] ${m}`,b??"")),f=await this.captureState(e);return{...f,metadata:{...f.metadata??{},navigateMeta:h,pageSettled:l}}}async blockScopedNavigationIfNeeded(e,t){let n=Uv({targetUrl:t,currentUrl:e.page.url(),scope:e.browserNavigationScope,allowExternalNavigation:e.allowExternalNavigation,httpsCertFallbackHost:e.httpsCertFallback?.host});if(n.allowed)return null;let s=await this.captureState(e),o=e.browserNavigationScope?.join(", ")||"(none)";return{...s,metadata:{...s.metadata??{},error:`Navigation blocked: ${t} is outside the allowed scope (${o}). Record/report the external link without opening it unless the user explicitly asked to validate external links.`,scopedNavigation:{status:"blocked",targetUrl:t,reason:n.reason}}}}async keyCombination(e,t){let{page:n}=e,s=t.map(i=>tL[i.toLowerCase()]??i),o=s.some(i=>Lu.has(i));if(s.length===1)await n.keyboard.press(s[0]);else if(o){let i=s.filter(l=>Lu.has(l)),a=s.filter(l=>!Lu.has(l));for(let l of i)await n.keyboard.down(l);for(let l of a)await n.keyboard.press(l);for(let l of i.reverse())await n.keyboard.up(l)}else for(let i of s)await n.keyboard.press(i);return await this.awaitPageReady(n),await this.captureState(e)}async setFocusedInputValue(e,t){let{page:n}=e,s={isSelect:!1,inputType:"none"};try{s=await n.evaluate(()=>{let a=document.activeElement;return a instanceof HTMLSelectElement?{isSelect:!0,inputType:"select"}:a instanceof HTMLInputElement?{isSelect:!1,inputType:a.type}:a instanceof HTMLTextAreaElement?{isSelect:!1,inputType:"textarea"}:a?.isContentEditable?{isSelect:!1,inputType:"contenteditable"}:{isSelect:!1,inputType:"none"}})}catch{return console.warn("[BasePlaywright] page.evaluate blocked in setFocusedInputValue, falling back to keyboard typing"),t=this.normalizeTestingEmailForFocusedField(e,t,{}),await n.keyboard.press("ControlOrMeta+a"),await n.keyboard.type(t,{delay:10}),{...await this.captureState(e),metadata:{elementType:"unknown (evaluate blocked)",valueBefore:"",valueAfter:t}}}if(s.isSelect)return await this.setSelectValue(e,t);t=this.normalizeTestingEmailForFocusedField(e,t,{inputType:s.inputType});let o=await n.evaluate(a=>{let l=document.activeElement,c=f=>f instanceof HTMLInputElement?`input[type=${f.type}]`:f instanceof HTMLTextAreaElement?"textarea":f.isContentEditable?"contenteditable":f.tagName.toLowerCase(),u=f=>f instanceof HTMLInputElement||f instanceof HTMLTextAreaElement?f.value:f.isContentEditable&&f.textContent||"";if(!l||l===document.body)return{success:!1,error:"No element is focused",elementType:"none",valueBefore:"",valueAfter:""};let d=c(l),h=u(l);try{if(l instanceof HTMLInputElement){let f=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value")?.set;f?f.call(l,a):l.value=a,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0}))}else if(l instanceof HTMLTextAreaElement){let f=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,"value")?.set;f?f.call(l,a):l.value=a,l.dispatchEvent(new Event("input",{bubbles:!0})),l.dispatchEvent(new Event("change",{bubbles:!0}))}else if(l.isContentEditable)l.textContent=a,l.dispatchEvent(new Event("input",{bubbles:!0}));else return{success:!1,error:`Element is not editable: ${d}`,elementType:d,valueBefore:h,valueAfter:h}}catch(f){return{success:!1,error:String(f.message||f),elementType:d,valueBefore:h,valueAfter:u(l)}}return{success:!0,elementType:d,valueBefore:h,valueAfter:u(l)}},t);return{...await this.captureState(e),metadata:{elementType:o.elementType,valueBefore:o.valueBefore,valueAfter:o.valueAfter,...o.error&&{error:o.error}}}}async setSelectValue(e,t){let{page:n}=e,s=await n.evaluate(()=>{let u=document.activeElement;if(!(u instanceof HTMLSelectElement))return null;let d=u.options[u.selectedIndex]?.textContent?.trim()||"",h=Array.from(u.options).map(f=>f.textContent?.trim()||f.value);return{valueBefore:d,options:h}});if(!s)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:"",valueAfter:"",error:"No select element is focused. Use click_at on the select first."}};let i=(await n.evaluateHandle(()=>document.activeElement)).asElement();if(!i)return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:s.valueBefore,valueAfter:s.valueBefore,error:"Could not get select element handle",availableOptions:s.options}};let a=!1;try{await i.selectOption({label:t}),a=!0}catch{try{await i.selectOption({value:t}),a=!0}catch{}}let l=await n.evaluate(()=>{let u=document.activeElement;return u instanceof HTMLSelectElement?u.options[u.selectedIndex]?.textContent?.trim()||u.value:""});return{...await this.captureState(e),metadata:{elementType:"select",valueBefore:s.valueBefore,valueAfter:l,...!a&&{error:`No option matching "${t}"`},availableOptions:s.options}}}async uploadFile(e,t){let{page:n}=e;console.log(`[BasePlaywright] upload_file called with filePaths=${JSON.stringify(t)}`);let o=(await n.evaluateHandle(()=>{let u=document.querySelector('input[type="file"][data-agentiqa-file-target]');if(u)return u;let d=document.activeElement;if(d instanceof HTMLInputElement&&d.type==="file")return d;let h=Array.from(document.querySelectorAll('input[type="file"]'));if(h.length===0)return d;let f=h.filter(g=>g.closest('[role="dialog"], dialog, [aria-modal="true"]')),p=f.length>0?f:h;return p[p.length-1]})).asElement();if(!o)return{...await this.captureState(e),metadata:{elementType:"file",error:"No file input found. Use click_at on the file input first."}};let i=await n.evaluate(u=>u instanceof HTMLInputElement&&u.type==="file"?{isFileInput:!0,accept:u.accept||"*",multiple:u.multiple}:{isFileInput:!1,accept:"",multiple:!1},o);if(!i.isFileInput)return{...await this.captureState(e),metadata:{elementType:"not-file-input",error:"No file input found. Use click_at on the file input/upload area first."}};try{await o.setInputFiles(t),console.log(`[BasePlaywright] upload_file setInputFiles succeeded, count=${t.length}`)}catch(u){return console.log(`[BasePlaywright] upload_file setInputFiles failed: ${u.message}`),{...await this.captureState(e),metadata:{elementType:"file",accept:i.accept,multiple:i.multiple,error:`File upload failed: ${u.message}`}}}let a=await this.onFilesUploaded(t),l=await n.evaluate(()=>document.querySelector('input[type="file"][data-agentiqa-file-target]')?.files?.length||0);return console.log(`[BasePlaywright] upload_file result: fileCount=${l}`),await this.awaitPageReady(n),{...await this.captureState(e),metadata:{elementType:"file",accept:i.accept,multiple:i.multiple,fileCount:l,...a.length>0&&{storedAssets:a}}}}async dragAndDrop(e,t,n,s,o){let{page:i}=e;return await i.mouse.move(t,n),await i.mouse.down(),await i.mouse.move(s,o,{steps:10}),await i.mouse.up(),await this.captureState(e)}async resolveRefCenter(e,t){try{let o=await e.page.locator(`aria-ref=${t}`).boundingBox({timeout:3e3});return o?{x:Math.floor(o.x+o.width/2),y:Math.floor(o.y+o.height/2)}:null}catch{return null}}async refNotFoundError(e,t){return{...await this.captureState(e),metadata:{error:`Ref "${t}" not found \u2014 the page may have changed. Check the latest page snapshot for updated refs.`}}}async switchTab(e,t){if(t==="tab1"){let n=!e.tab1||e.tab1.isClosed();if(this.diagLog?.(e.sessionId,"switchTab tab1 requested",{tab1Closed:n,hasExtId:!!e.extensionId}),n&&e.extensionId){this.diagLog?.(e.sessionId,"switchTab auto-recreating tab1 at popup.html");let s=await e.context.newPage();Za(s),await s.goto(`chrome-extension://${e.extensionId}/popup.html`,{timeout:5e3}),e.tab1=s}if(!e.tab1||e.tab1.isClosed())return{...await this.captureState(e),metadata:{error:"Tab 1 is not available"}};if(e.page=e.tab1,e.activeTab="tab1",e.extensionId){let s=e.tab1.url();if(!s.startsWith(`chrome-extension://${e.extensionId}`)){this.diagLog?.(e.sessionId,"switchTab navigating to popup.html",{beforeUrl:s});try{await e.tab1.goto(`chrome-extension://${e.extensionId}/popup.html`,{timeout:5e3});let o=e.tab1.url();this.diagLog?.(e.sessionId,"switchTab popup.html loaded",{afterUrl:o})}catch(o){this.diagLog?.(e.sessionId,"switchTab popup.html failed",{error:o?.message})}}}}else{if(!e.tab2||e.tab2.isClosed())return{...await this.captureState(e),metadata:{error:"Tab 2 is not available. No second tab is open."}};e.page=e.tab2,e.activeTab="tab2"}return e.needsFullSnapshot=!0,await e.page.bringToFront(),await this.captureState(e)}async closeTab(e){if(e.activeTab==="tab1")return{...await this.captureState(e),metadata:{error:"Cannot close tab 1"}};if(e.tab2&&!e.tab2.isClosed())try{await e.tab2.close()}catch{}return e.tab2=void 0,e.page=e.tab1,e.activeTab="tab1",e.needsFullSnapshot=!0,await e.page.bringToFront(),await this.captureState(e)}static HTTP_BODY_MAX_LENGTH=5e4;async httpRequest(e,t,n,s,o){let{page:i}=e;try{let a={method:n,timeout:3e4,ignoreHTTPSErrors:!0};s&&(a.headers=s),o&&n!=="GET"&&(a.data=o);let l=await i.request.fetch(t,a),c=await l.text(),u=!1;if(c.length>r.HTTP_BODY_MAX_LENGTH&&(c=c.slice(0,r.HTTP_BODY_MAX_LENGTH),u=!0),(l.headers()["content-type"]||"").includes("application/json")&&!u)try{c=JSON.stringify(JSON.parse(c),null,2),c.length>r.HTTP_BODY_MAX_LENGTH&&(c=c.slice(0,r.HTTP_BODY_MAX_LENGTH),u=!0)}catch{}return{...await this.captureState(e),metadata:{httpResponse:{status:l.status(),statusText:l.statusText(),headers:l.headers(),body:c,...u&&{truncated:!0}}}}}catch(a){return{...await this.captureState(e),metadata:{error:`HTTP request failed: ${a.message}`}}}}static RUN_JS_INPUT_MAX_LENGTH=4e3;static RUN_JS_RESULT_MAX_LENGTH=5e3;static RUN_JS_TIMEOUT_MS=1e4;async runJs(e,t){if(t.length>r.RUN_JS_INPUT_MAX_LENGTH)return{...await this.captureState(e),metadata:{error:`run_js code exceeded ${r.RUN_JS_INPUT_MAX_LENGTH}-char cap (${t.length} chars).`}};let n=`(async () => {
1665
1670
  try {
1666
1671
  const __res = await (async () => { ${t} })();
1667
1672
  return { ok: true, value: __res };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentiqa",
3
- "version": "1.1.16-staging.e25b9fa",
3
+ "version": "1.1.16-staging.e9e8eed",
4
4
  "description": "AI-powered testing for web and mobile apps",
5
5
  "type": "module",
6
6
  "bin": "dist/cli.js",