@gaiventures/pilotfish-mcp 0.1.3 → 0.1.5

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/cli.js +2 -2
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -160,7 +160,7 @@ After most actions you receive EVIDENCE (a code-computed diff: navigation, new/r
160
160
  14. When finished, call done with a precise result that cites what you observed. If the task is impossible, say so with success=false and the reason; a truthful failure is better than a hallucinated success. The result is the answer the person reads, so put all of it there, not in text before the call: lead with the answer in one sentence, then the details as a short list (numbered when order matters), **bold** only for key figures, and no refs, tool names or notes about your steps.
161
161
  15. Text you write outside tool calls is shown to the person as your progress. Most turns need none. When you write some, make it one plain sentence for them about what you are doing ("Sorting the results by price, highest first."), never shorthand or internals (refs, CSS, selectors, tool names, "Good. Next.").
162
162
 
163
- Be efficient: every tool call costs time and money. Think briefly, act, verify.`,fi=Ws.replace("3. Act with one tool call at a time when the next step depends on the result; batch independent calls only when you are sure none of them changes the page.","3. Batch: put every action you can already decide into ONE turn. They run in order, a failing action stops the rest, and refs from the snapshot you saw stay valid for the whole turn. Wait for a result only when the next action depends on what appears. Never add wait after an action: each action already waits for the requests, navigation and rendering it caused.").replace("7. Fill forms field by field, verify values in the evidence, then submit. For dropdowns use select_option on <select>; for custom dropdowns and autocompletes use choose (one call: it types or opens, waits for the options and clicks the best match).","7. Forms: ONE fill call with every field, plus submit_ref when the step includes submitting. fill reads every value back and skips the submit if a field did not take its value. Autocompletes (airports, cities, addresses) and custom dropdowns: ONE choose call types or opens, waits for the suggestions and clicks the best match; it reports what was picked.").replace('5. If an action fails with "obscured", something (modal, cookie banner, overlay) is covering the target: handle that element first.','5. Cookie banners and content-free popups are closed automatically (reported as Auto-dismissed). If an action still fails with "obscured", handle the covering element first.').replace("14. When finished, call done with a precise result that cites what you observed.","14. When finished, call done with a precise result that cites what you observed. Call it in the same turn as your last actions when their outcome is predictable: it is refused if an earlier action in the turn had no effect, and the system re-checks the final page itself, so never spend a turn re-reading values you just set.");var Ae=class{static{l(this,"TabManager")}tabs=[];seq=0;cur;notes=[];offs=[];onAddHooks=[];dialogPolicy=null;unlinkedUntil=0;preexisting;opening=new Map;openingWaiters=new Set;constructor(e){this.cur=e,this.preexisting=new Set(e.browser.allPages().map(t=>t.targetId)),this.add(e),this.offs.push(e.browser.on("page",t=>{this.opened(t.targetId),this.adopts(t)&&this.add(t)})),this.offs.push(e.browser.on("pageClosed",t=>{this.opened(t.targetId),this.closed(t)})),this.offs.push(e.browser.root.on("Target.targetCreated",t=>{let s=t.targetInfo;if(s.type!=="page"||this.preexisting.has(s.targetId))return;(s.openerId?this.tabs.some(i=>i.page.targetId===s.openerId):Date.now()<=this.unlinkedUntil&&s.browserContextId===this.tabs[0]?.page.targetInfo.browserContextId)&&this.opening.set(s.targetId,Date.now())}),e.browser.root.on("Target.targetDestroyed",t=>this.opened(t.targetId)))}opened(e){if(this.opening.delete(e)&&!this.opening.size)for(let t of[...this.openingWaiters])t()}async settleOpens(e=3e3){for(let[t,s]of this.opening)Date.now()-s>15e3&&this.opening.delete(t);this.opening.size&&await new Promise(t=>{let s=l(()=>{clearTimeout(o),this.openingWaiters.delete(s),t()},"done"),o=setTimeout(s,e);this.openingWaiters.add(s)})}expectTabs(e){this.unlinkedUntil=Date.now()+e}adopts(e){if(this.preexisting.has(e.targetId))return!1;if(e.openerId&&this.tabs.some(s=>s.page.targetId===e.openerId))return!0;let t=this.tabs[0]?.page.targetInfo.browserContextId;return!e.openerId&&Date.now()<=this.unlinkedUntil&&e.targetInfo.browserContextId===t}get current(){return this.cur}get count(){return this.tabs.length}onTab(e){this.onAddHooks.push(e);for(let t of this.tabs)e(t.page)}idOf(e){return this.tabs.find(t=>t.page===e)?.id}byId(e){return this.tabs.find(t=>t.id===e.trim().toLowerCase())?.page}openedSince(e){return this.tabs.filter(t=>t.openedAt>=e&&!t.page.closed).map(t=>t.page)}async switchTo(e){e!==this.cur&&(this.cur=e,await e.bringToFront().catch(()=>{}))}takeNotes(){let e=this.notes.join(" ");return this.notes=[],e}describe(){return this.tabs.filter(e=>!e.page.closed).map(e=>`${e.id}${e.page===this.cur?" (current)":""} ${e.page.url}`).join(" | ")}dispose(){for(let e of this.offs.splice(0))e()}add(e){if(!(this.idOf(e)||e.closed)){this.tabs.push({id:`t${++this.seq}`,page:e,openedAt:Date.now()}),e.dialogHandler=t=>this.dialogPolicy?this.dialogPolicy(t):{accept:t.type==="alert"};for(let t of this.onAddHooks)t(e)}}closed(e){let t=this.tabs.findIndex(i=>i.page===e);if(t<0)return;let[s]=this.tabs.splice(t,1);if(e!==this.cur||!this.tabs.length)return;let o=this.tabs.find(i=>i.page.targetId===e.openerId)??this.tabs[this.tabs.length-1];this.cur=o.page,this.notes.push(`Tab ${s.id} closed; back in ${o.id} (${o.page.url}).`),o.page.bringToFront().catch(()=>{})}};var rl={autoClearMs:8e3,solverGraceMs:4e3,pollMs:1e3,awaitGoneMs:6e3,personWaitMs:6e3,codeSourceMs:3e4,clearedMs:3e3,cloudflareClearMs:12e3,humanClearMs:2e4,datadomeClearMs:6e3,widgetAppearMs:8e3,widgetReadMs:2500},al={PILOTFISH_CHALLENGE_AUTO_CLEAR_MS:"autoClearMs",PILOTFISH_CHALLENGE_SOLVER_GRACE_MS:"solverGraceMs",PILOTFISH_CHALLENGE_POLL_MS:"pollMs",PILOTFISH_CHALLENGE_AWAIT_GONE_MS:"awaitGoneMs",PILOTFISH_CHALLENGE_PERSON_WAIT_MS:"personWaitMs",PILOTFISH_CHALLENGE_CODE_SOURCE_MS:"codeSourceMs",PILOTFISH_CHALLENGE_CLEARED_MS:"clearedMs",PILOTFISH_CHALLENGE_CLOUDFLARE_CLEAR_MS:"cloudflareClearMs",PILOTFISH_CHALLENGE_HUMAN_CLEAR_MS:"humanClearMs",PILOTFISH_CHALLENGE_DATADOME_CLEAR_MS:"datadomeClearMs",PILOTFISH_CHALLENGE_WIDGET_APPEAR_MS:"widgetAppearMs",PILOTFISH_CHALLENGE_WIDGET_READ_MS:"widgetReadMs"};function cl(n=process.env){let e={};for(let[t,s]of Object.entries(al)){let o=n[t]?.trim();if(!o)continue;let i=Number(o);Number.isFinite(i)&&i>=0&&(e[s]=i)}return e}l(cl,"challengePolicyFromEnv");var Gs=100;function mi(n){return{...n,autoClearMs:Math.max(0,n.autoClearMs),solverGraceMs:Math.max(0,n.solverGraceMs),awaitGoneMs:Math.max(0,n.awaitGoneMs),personWaitMs:Math.max(0,n.personWaitMs),codeSourceMs:Math.max(0,n.codeSourceMs),clearedMs:Math.max(0,n.clearedMs),cloudflareClearMs:Math.max(0,n.cloudflareClearMs),humanClearMs:Math.max(0,n.humanClearMs),datadomeClearMs:Math.max(0,n.datadomeClearMs),widgetAppearMs:Math.max(0,n.widgetAppearMs),widgetReadMs:Math.max(0,n.widgetReadMs),pollMs:Math.max(Gs,n.pollMs)}}l(mi,"clamp");function gi(n={}){let e=cl(n.env),t=mi({...rl,...e,...n.defaults}),s=n.sites??{},o=new Map;return{forSite:l(r=>{if(!r)return t;let a=o.get(r);if(a)return a;let c=mi({...t,...s[r]});return o.set(r,c),c},"forSite"),base:t}}l(gi,"challengePolicy");function Wt(n,e){switch(e){case"cloudflare":return n.cloudflareClearMs;case"human":return n.humanClearMs;case"datadome":return n.datadomeClearMs;default:return n.autoClearMs}}l(Wt,"vendorClearMs");function wi(n,e){if(!e.residential)return e.autoResolvable?e.solver?Math.min(n.autoClearMs,n.solverGraceMs):n.autoClearMs:0;if(!e.autoResolvable&&!["cloudflare","human","datadome"].includes(e.vendor??""))return 0;let t=Wt(n,e.vendor);return e.drawn&&e.builtin&&!e.autoResolvable&&e.category==="captcha"?Math.min(t,n.widgetReadMs):t}l(wi,"firstWatchMs");import{readFile as ll,stat as dl}from"node:fs/promises";async function Vs(n,e=3e4){let s=await(await import("pdfjs-dist/legacy/build/pdf.mjs")).getDocument({data:n,isEvalSupported:!1,useSystemFonts:!1,disableFontFace:!0,verbosity:0}).promise,o=[],i=0,r=!1;for(let d=1;d<=s.numPages;d++){let p=(await(await s.getPage(d)).getTextContent()).items.map(f=>"str"in f?f.str+(f.hasEOL?`
163
+ Be efficient: every tool call costs time and money. Think briefly, act, verify.`,fi=Ws.replace("3. Act with one tool call at a time when the next step depends on the result; batch independent calls only when you are sure none of them changes the page.","3. Batch: put every action you can already decide into ONE turn. They run in order, a failing action stops the rest, and refs from the snapshot you saw stay valid for the whole turn. Wait for a result only when the next action depends on what appears. Never add wait after an action: each action already waits for the requests, navigation and rendering it caused.").replace("7. Fill forms field by field, verify values in the evidence, then submit. For dropdowns use select_option on <select>; for custom dropdowns and autocompletes use choose (one call: it types or opens, waits for the options and clicks the best match).","7. Forms: ONE fill call with every field, plus submit_ref when the step includes submitting. fill reads every value back and skips the submit if a field did not take its value. Autocompletes (airports, cities, addresses) and custom dropdowns: ONE choose call types or opens, waits for the suggestions and clicks the best match; it reports what was picked.").replace('5. If an action fails with "obscured", something (modal, cookie banner, overlay) is covering the target: handle that element first.','5. Cookie banners and content-free popups are closed automatically (reported as Auto-dismissed). If an action still fails with "obscured", handle the covering element first.').replace("14. When finished, call done with a precise result that cites what you observed.","14. When finished, call done with a precise result that cites what you observed. Call it in the same turn as your last actions when their outcome is predictable: it is refused if an earlier action in the turn had no effect, and the system re-checks the final page itself, so never spend a turn re-reading values you just set.");var Ae=class{static{l(this,"TabManager")}tabs=[];seq=0;cur;notes=[];offs=[];onAddHooks=[];dialogPolicy=null;unlinkedUntil=0;preexisting;opening=new Map;openingWaiters=new Set;constructor(e){this.cur=e,this.preexisting=new Set(e.browser.allPages().map(t=>t.targetId)),this.add(e),this.offs.push(e.browser.on("page",t=>{this.opened(t.targetId),this.adopts(t)&&this.add(t)})),this.offs.push(e.browser.on("pageClosed",t=>{this.opened(t.targetId),this.closed(t)})),this.offs.push(e.browser.root.on("Target.targetCreated",t=>{let s=t.targetInfo;if(s.type!=="page"||this.preexisting.has(s.targetId))return;(s.openerId?this.tabs.some(i=>i.page.targetId===s.openerId):Date.now()<=this.unlinkedUntil&&s.browserContextId===this.tabs[0]?.page.targetInfo.browserContextId)&&this.opening.set(s.targetId,Date.now())}),e.browser.root.on("Target.targetDestroyed",t=>this.opened(t.targetId)))}opened(e){if(this.opening.delete(e)&&!this.opening.size)for(let t of[...this.openingWaiters])t()}async settleOpens(e=3e3){for(let[t,s]of this.opening)Date.now()-s>15e3&&this.opening.delete(t);this.opening.size&&await new Promise(t=>{let s=l(()=>{clearTimeout(o),this.openingWaiters.delete(s),t()},"done"),o=setTimeout(s,e);this.openingWaiters.add(s)})}expectTabs(e){this.unlinkedUntil=Date.now()+e}adopts(e){if(this.preexisting.has(e.targetId))return!1;if(e.openerId&&this.tabs.some(s=>s.page.targetId===e.openerId))return!0;let t=this.tabs[0]?.page.targetInfo.browserContextId;return!e.openerId&&Date.now()<=this.unlinkedUntil&&e.targetInfo.browserContextId===t}get current(){return this.cur}get count(){return this.tabs.length}onTab(e){this.onAddHooks.push(e);for(let t of this.tabs)e(t.page)}idOf(e){return this.tabs.find(t=>t.page===e)?.id}byId(e){return this.tabs.find(t=>t.id===e.trim().toLowerCase())?.page}openedSince(e){return this.tabs.filter(t=>t.openedAt>=e&&!t.page.closed).map(t=>t.page)}async switchTo(e){e!==this.cur&&(this.cur=e,await e.bringToFront().catch(()=>{}))}takeNotes(){let e=this.notes.join(" ");return this.notes=[],e}describe(){return this.tabs.filter(e=>!e.page.closed).map(e=>`${e.id}${e.page===this.cur?" (current)":""} ${e.page.url}`).join(" | ")}dispose(){for(let e of this.offs.splice(0))e()}add(e){if(!(this.idOf(e)||e.closed)){this.tabs.push({id:`t${++this.seq}`,page:e,openedAt:Date.now()}),e.dialogHandler=t=>this.dialogPolicy?this.dialogPolicy(t):{accept:t.type==="alert"};for(let t of this.onAddHooks)t(e)}}closed(e){let t=this.tabs.findIndex(i=>i.page===e);if(t<0)return;let[s]=this.tabs.splice(t,1);if(e!==this.cur||!this.tabs.length)return;let o=this.tabs.find(i=>i.page.targetId===e.openerId)??this.tabs[this.tabs.length-1];this.cur=o.page,this.notes.push(`Tab ${s.id} closed; back in ${o.id} (${o.page.url}).`),o.page.bringToFront().catch(()=>{})}};var rl={autoClearMs:8e3,solverGraceMs:4e3,pollMs:1e3,awaitGoneMs:6e3,personWaitMs:6e3,codeSourceMs:3e4,clearedMs:3e3,cloudflareClearMs:12e3,humanClearMs:2e4,datadomeClearMs:6e3,widgetAppearMs:8e3,widgetReadMs:2e4},al={PILOTFISH_CHALLENGE_AUTO_CLEAR_MS:"autoClearMs",PILOTFISH_CHALLENGE_SOLVER_GRACE_MS:"solverGraceMs",PILOTFISH_CHALLENGE_POLL_MS:"pollMs",PILOTFISH_CHALLENGE_AWAIT_GONE_MS:"awaitGoneMs",PILOTFISH_CHALLENGE_PERSON_WAIT_MS:"personWaitMs",PILOTFISH_CHALLENGE_CODE_SOURCE_MS:"codeSourceMs",PILOTFISH_CHALLENGE_CLEARED_MS:"clearedMs",PILOTFISH_CHALLENGE_CLOUDFLARE_CLEAR_MS:"cloudflareClearMs",PILOTFISH_CHALLENGE_HUMAN_CLEAR_MS:"humanClearMs",PILOTFISH_CHALLENGE_DATADOME_CLEAR_MS:"datadomeClearMs",PILOTFISH_CHALLENGE_WIDGET_APPEAR_MS:"widgetAppearMs",PILOTFISH_CHALLENGE_WIDGET_READ_MS:"widgetReadMs"};function cl(n=process.env){let e={};for(let[t,s]of Object.entries(al)){let o=n[t]?.trim();if(!o)continue;let i=Number(o);Number.isFinite(i)&&i>=0&&(e[s]=i)}return e}l(cl,"challengePolicyFromEnv");var Gs=100;function mi(n){return{...n,autoClearMs:Math.max(0,n.autoClearMs),solverGraceMs:Math.max(0,n.solverGraceMs),awaitGoneMs:Math.max(0,n.awaitGoneMs),personWaitMs:Math.max(0,n.personWaitMs),codeSourceMs:Math.max(0,n.codeSourceMs),clearedMs:Math.max(0,n.clearedMs),cloudflareClearMs:Math.max(0,n.cloudflareClearMs),humanClearMs:Math.max(0,n.humanClearMs),datadomeClearMs:Math.max(0,n.datadomeClearMs),widgetAppearMs:Math.max(0,n.widgetAppearMs),widgetReadMs:Math.max(0,n.widgetReadMs),pollMs:Math.max(Gs,n.pollMs)}}l(mi,"clamp");function gi(n={}){let e=cl(n.env),t=mi({...rl,...e,...n.defaults}),s=n.sites??{},o=new Map;return{forSite:l(r=>{if(!r)return t;let a=o.get(r);if(a)return a;let c=mi({...t,...s[r]});return o.set(r,c),c},"forSite"),base:t}}l(gi,"challengePolicy");function Wt(n,e){switch(e){case"cloudflare":return n.cloudflareClearMs;case"human":return n.humanClearMs;case"datadome":return n.datadomeClearMs;default:return n.autoClearMs}}l(Wt,"vendorClearMs");function wi(n,e){if(!e.residential)return e.autoResolvable?e.solver?Math.min(n.autoClearMs,n.solverGraceMs):n.autoClearMs:0;if(!e.autoResolvable&&!["cloudflare","human","datadome"].includes(e.vendor??""))return 0;let t=Wt(n,e.vendor);return e.drawn&&e.builtin&&!e.autoResolvable&&e.category==="captcha"?Math.min(t,n.widgetReadMs):t}l(wi,"firstWatchMs");import{readFile as ll,stat as dl}from"node:fs/promises";async function Vs(n,e=3e4){let s=await(await import("pdfjs-dist/legacy/build/pdf.mjs")).getDocument({data:n,isEvalSupported:!1,useSystemFonts:!1,disableFontFace:!0,verbosity:0}).promise,o=[],i=0,r=!1;for(let d=1;d<=s.numPages;d++){let p=(await(await s.getPage(d)).getTextContent()).items.map(f=>"str"in f?f.str+(f.hasEOL?`
164
164
  `:" "):"").join("").replace(/[ \t]+\n/g,`
165
165
  `).trim();if(o.push(`--- page ${d} ---
166
166
  ${p}`),i+=p.length+16,i>e){r=d<s.numPages;break}}let a=s.numPages;await s.destroy();let c=o.join(`
@@ -411,7 +411,7 @@ return __pfClassify("Accept all") === "accept" && __pfClassify("Reject all") ===
411
411
  return click.call(this);
412
412
  };
413
413
  })();
414
- ${es(tr(nr,"autoconsent.playwright.js"),"utf8")}`,eu=3,tu=2500,su=6500,nu=6e3,ou=3e3,iu=1500,ru=3e3,au=4e3;function cu(n,e){if(n.doneAt)return"opted_out";if(n.optOut===!1)return"failed";if(n.optOut===!0)return e-n.optOutAt>ou?"opted_out":"pending";if(n.popupAt)return"pending";if(n.detectedAt)return e-n.detectedAt<su?"pending":"no_cmp";let t=n.topLifecycle!=="nothingDetected",s=l((o,i)=>e-o>=tu&&i!==void 0&&n.topAttempts>=i+eu,"quiet");return n.cmpRequestAt&&e-n.cmpRequestAt<nu&&t&&!s(n.cmpRequestAt,n.roundAtCmpRequest)||!n.topInitAt&&/^https?:/.test(n.url)&&e-n.navAt<iu||n.topInitAt&&n.topAttempts<2&&e-n.topInitAt<ru||n.patternsAt&&e-n.patternsAt<au&&t&&!s(n.patternsAt,n.roundAtPatterns)?"pending":"no_cmp"}l(cu,"outcomeOf");var Zt=class{static{l(this,"ConsentEngine")}opts;emitter=new Gd;pages=new WeakMap;disposers=new Set;armed=new WeakSet;retried=new WeakSet;retryTimers=new Set;disposed=!1;config;constructor(e={}){if(this.opts=e,this.config=Xd(e),this.config.autoAction!=="optOut"||!["off","reject"].includes(this.config.heuristicMode))throw new Error("ConsentEngine only ever opts out")}on(e,t){return this.emitter.on(e,t),()=>this.emitter.off(e,t)}consentStatus(e){let t=this.state(e),s=Date.now(),o=cu(t,s),i=o==="opted_out"?t.doneAt??t.optOutAt:s;return{cmp:t.cmp,outcome:o,ms:Math.max(0,i-t.navAt),url:t.url,...t.cosmetic!==void 0?{cosmetic:t.cosmetic}:{},...t.selfTest!==void 0?{selfTest:t.selfTest}:{},refusedClicks:t.refused,frames:t.frames.size}}async waitForConsent(e,t=1e4){let s=Date.now()+t;for(;;){let o=this.consentStatus(e);if(o.outcome!=="pending"||Date.now()>=s||e.closed)return o;await new Promise(i=>setTimeout(i,Math.min(100,Math.max(1,s-Date.now()))))}}dispose(){this.disposed=!0;for(let e of this.retryTimers)clearTimeout(e);this.retryTimers.clear();for(let e of this.disposers)e();this.disposers.clear()}initSession=l(async e=>{let t=e.session,s=e.page;if(!e.isMain&&we(e.url)||this.armed.has(t))return;this.armed.add(t);let o=new Map,i=[()=>this.armed.delete(t)],r;try{[,r]=await Promise.all([t.send("Runtime.addBinding",{name:ze,executionContextName:dn},{timeoutMs:5e3}),t.send("Page.addScriptToEvaluateOnNewDocument",{source:Zd,worldName:dn,runImmediately:!0},{timeoutMs:5e3})])}catch(p){if(this.armed.delete(t),e.isMain&&!this.retried.has(t)&&!this.disposed){this.retried.add(t);let f=setTimeout(()=>{this.retryTimers.delete(f),this.disposed||this.initSession(e).catch(()=>{})},2e3);f.unref?.(),this.retryTimers.add(f)}throw p}let a=l(()=>{for(let p of i)p();t.send("Page.removeScriptToEvaluateOnNewDocument",{identifier:r.identifier}).catch(()=>{}),t.send("Runtime.removeBinding",{name:ze}).catch(()=>{})},"dispose");this.disposers.add(a),i.push(()=>this.disposers.delete(a));let c=l(async(p,f,y)=>{if(!/^https?:/.test(f)||!y&&we(f))return;let{executionContextId:b}=await t.send("Page.createIsolatedWorld",{frameId:p,worldName:dn,grantUniveralAccess:!1},{timeoutMs:5e3});o.set(b,{frameId:p,mainFrame:y}),await t.send("Runtime.addBinding",{name:ze,executionContextId:b},{timeoutMs:5e3}),await t.send("Runtime.evaluate",{contextId:b,expression:"globalThis.__pfAcFlush && __pfAcFlush()",returnByValue:!0},{timeoutMs:5e3})},"arm"),d=l((p,f,y)=>{c(p,f,y).catch(b=>{/not found|Cannot find|No frame|detached|closed|destroyed|navigated/i.test(String(b))||this.emit("error",{page:s,frameId:p,url:f,error:`arm: ${String(b)}`})})},"armSafe");i.push(t.on("Page.frameNavigated",p=>{let f=e.isMain&&!p.frame.parentId;f&&this.reset(s,p.frame.url),d(p.frame.id,p.frame.url,f)}),t.on("Runtime.bindingCalled",p=>{if(p.name!==ze)return;let f=o.get(p.executionContextId);f&&this.onMessage(t,s,p.executionContextId,f,p.payload).catch(()=>{})}),t.on("Network.requestWillBeSent",p=>{if(!Fi(p.request.url))return;let f=this.state(s);f.cmpRequestAt===void 0&&(f.cmpRequestAt=Date.now(),f.roundAtCmpRequest=f.topAttempts)}));let{frameTree:u}=await t.send("Page.getFrameTree"),h=l((p,f)=>{d(p.frame.id,p.frame.url,f);for(let y of p.childFrames??[])h(y,!1)},"walk");h(u,e.isMain)},"initSession");async onMessage(e,t,s,o,i){let r;try{r=JSON.parse(i)}catch{return}let a=this.state(t),c=typeof r.url=="string"?r.url:"",d={page:t,frameId:o.frameId,url:c},u=typeof r.cmp=="string"?r.cmp:"";switch(r.type){case"init":{a.frames.add(o.frameId),o.mainFrame&&(a.topInitAt??=Date.now());let h={autoconsent:[],compact:Yd(or,{url:c,mainFrame:o.mainFrame})};await this.reply(e,s,{type:"initResp",config:this.config,rules:h});return}case"eval":await this.reply(e,s,{type:"evalResp",id:r.id,result:await this.runSnippet(e,t,s,o,r.snippetId)});return;case"cmpDetected":a.cmp??=u,a.detectedAt??=Date.now(),this.emit("cmpDetected",{...d,cmp:u});return;case"popupFound":a.cmp=u,a.popupAt??=Date.now(),this.emit("popupFound",{...d,cmp:u});return;case"optOutResult":a.cmp=u||a.cmp,a.optOut=r.result===!0,a.optOutAt=Date.now(),r.result===!0&&r.scheduleSelfTest===!0&&(a.selfTestTarget={s:e,contextId:s}),this.emit("optOutResult",{...d,cmp:u,result:r.result===!0});return;case"autoconsentDone":{a.cmp=u||a.cmp,a.doneAt=Date.now(),a.cosmetic=r.isCosmetic===!0,this.emit("autoconsentDone",{...d,cmp:u,cosmetic:a.cosmetic,durationMs:Number(r.duration)||0,clicks:Number(r.totalClicks)||0});let h=a.selfTestTarget;a.selfTestTarget=void 0,this.opts.selfTest!==!1&&h&&await this.reply(h.s,h.contextId,{type:"selfTest"});return}case"selfTestResult":a.selfTest=r.result===!0,this.emit("selfTestResult",{...d,cmp:u,result:a.selfTest});return;case"optInResult":this.emit("error",{...d,error:"unexpected optInResult",details:r});return;case"autoconsentError":this.emit("error",{...d,error:"autoconsent",details:r.details});return;case"pfRefusedClick":a.refused++,this.emit("error",{...d,error:`refused to click an accept button: "${String(r.text)}"`});return;case"report":{let h=r.state??{};if(!o.mainFrame||r.mainFrame!==!0)return;a.topLifecycle=h.lifecycle,a.topAttempts=Math.max(a.topAttempts,Number(h.findCmpAttempts)||0),h.heuristicPatterns?.length&&a.patternsAt===void 0&&(a.patternsAt=Date.now(),a.roundAtPatterns=a.topAttempts);return}default:return}}reply(e,t,s){return e.send("Runtime.evaluate",{contextId:t,expression:`autoconsentReceiveMessage(${JSON.stringify(s)})`,returnByValue:!0}).catch(()=>{})}async runSnippet(e,t,s,o,i){let r=typeof i=="string"?sr[i]:void 0;if(!r||Jd.has(i))return this.emit("error",{page:t,frameId:o.frameId,error:`refused eval snippet ${String(i)}`}),!1;let a=await e.send("Runtime.evaluate",{contextId:s,expression:"document",returnByValue:!1});if(!a.result.objectId)return!1;try{let{node:c}=await e.send("DOM.describeNode",{objectId:a.result.objectId}),{object:d}=await e.send("DOM.resolveNode",{backendNodeId:c.backendNodeId});if(!d.objectId)return!1;try{return(await e.send("Runtime.callFunctionOn",{objectId:d.objectId,functionDeclaration:`async function () { try { return !!(await (${r.toString()}).call(window)); } catch (e) { return false; } }`,returnByValue:!0,awaitPromise:!0})).result.value===!0}finally{e.send("Runtime.releaseObject",{objectId:d.objectId}).catch(()=>{})}}catch{return!1}finally{e.send("Runtime.releaseObject",{objectId:a.result.objectId}).catch(()=>{})}}state(e){let t=this.pages.get(e);return t||(t={navAt:Date.now(),url:e.url,cmp:null,refused:0,frames:new Set,topAttempts:0},this.pages.set(e,t)),t}reset(e,t){this.pages.set(e,{navAt:Date.now(),url:t,cmp:null,refused:0,frames:new Set,topAttempts:0})}emit(e,t){e==="error"&&this.emitter.listenerCount("error")===0||this.emitter.emit(e,t)}};var lu=["cmpDetected","popupFound","optOutResult","autoconsentDone","selfTestResult","error"],du=["blocked","error"];async function hn(n,e={}){let t=e.consent===!1?null:new Zt(typeof e.consent=="object"?e.consent:{}),s=e.adblock===!1?null:await Qt.create(typeof e.adblock=="object"?e.adblock:{}),o=[];if(e.onEvent){let r=e.onEvent;if(t)for(let a of lu)o.push(t.on(a,(c=>r({source:"consent",type:a,data:c}))));if(s)for(let a of du)o.push(s.on(a,(c=>r({source:"adblock",type:a,data:c}))))}let i=l(async r=>{await Promise.all([t?.initSession(r),s?.initSession(r)])},"init");return o.push(n.addSessionInitializer(i)),{consent:t,adblock:s,detach(){for(let r of o)r();t?.dispose(),s?.dispose()}}}l(hn,"attachInterrupts");var uu="pilotfish's open MCP makes no model calls: the MCP client's own model reads the page and decides the next step",hu=l(()=>{let n=l(async()=>{throw new Error(uu)},"refuse");return{messages:{create:n},beta:{messages:{create:n}}}},"noModelClient"),pn=class{static{l(this,"ConfirmThrough")}inner;canAsk;constructor(e,t){this.inner=e,this.canAsk=t}async check(e){let t=await this.inner.check(e);return t.kind!=="confirm"||this.canAsk()?t:{kind:"deny",reason:`${t.reason}. This needs a person's confirmation, and this MCP client cannot ask for one (no elicitation support)`}}},We=class{static{l(this,"BrowserSession")}config;opts;browser=null;launching=null;interrupts=null;tabs=null;agent=null;queue=Promise.resolve();policy;asker;constructor(e,t={}){this.config=e,this.opts=t,this.policy=new Ce({allowedHosts:e.allowedHosts,allowPrivateNetwork:e.allowPrivateNetwork})}get launched(){return!!this.browser&&!this.browser.closed}async page(){return await this.ensure(),this.tabs.current}get tabTable(){return this.tabs}async ensure(){return this.browser&&!this.browser.closed?this.browser:(this.launching??=this.start().finally(()=>{this.launching=null}),this.launching)}async start(){let e=await gs({...this.opts.launch,headless:this.config.headless});this.interrupts=await hn(e,{consent:!0,adblock:!0}).catch(()=>null);let t=e.allPages()[0]??await e.newPage();return this.tabs=new Ae(t),this.browser=e,e.on("disconnected",()=>{this.browser===e&&this.reset()}),e}reset(){this.agent?.dispose(),this.agent=null,this.tabs?.dispose(),this.tabs=null,this.interrupts?.detach(),this.interrupts=null,this.browser=null}executor(){return this.agent??=new ht(this.tabs.current,{tabs:this.tabs,client:hu(),secrets:this.config.secrets,policy:new pn(this.policy,()=>!!this.asker),consent:this.interrupts?.consent??void 0,allowScreenshots:this.config.allowScreenshots,askUser:l(e=>this.asker?.(e)??Promise.resolve(null),"askUser"),fastSettle:!0,enableQuery:!0,compactSnapshots:!0,autoDismiss:!0,awaitLoading:!0,actionSnapshotBand:0,snapshotMoreHint:"browser_scroll for the next screen, browser_get_text for the page's text, or browser_snapshot (whole_page) for every element"}),this.agent}async act(e,t,s={}){let o=this.queue.then(async()=>(await this.ensure(),this.executor().act(e,t,"",s)));return this.queue=o.catch(()=>{}),o}async obstacle(e={}){if(!this.launched)return null;let t=this.queue.then(()=>this.executor().obstacle(e));return this.queue=t.catch(()=>{}),t.catch(()=>null)}async close(){let e=this.browser??(this.launching?await this.launching.catch(()=>null):null);return e?(this.reset(),await e.close().catch(()=>{}),!0):!1}};import{McpServer as pu}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as H}from"zod";var ts="0.1.3";var Ge=Ie.click.shape.ref.describe('Element ref from the latest snapshot, e.g. "e12" or "f1e3"'),ne={browser_navigate:H.object({url:H.url({protocol:/^https?$/}).describe("Absolute http(s) URL"),snapshot:H.boolean().optional().describe("Default true. false: no page snapshot, only what happened (title, counts). Only when this same reply also reads the page with browser_query and you will not act on it; keep it for a page you have not seen or whose tabs, sorts, filters or forms you may use.")}).strict(),browser_snapshot:H.object({whole_page:H.boolean().optional().describe("Include everything, not only the viewport band (default false)")}).strict(),browser_click:H.object({ref:Ge}).strict(),browser_type:H.object({ref:Ge,text:H.string().max(2e4).describe("Text to enter (replaces what is there). Type secrets as {{name}} placeholders."),submit:H.boolean().optional().describe("Press Enter afterwards")}).strict(),browser_select:H.object({ref:Ge,values:H.array(H.string()).min(1).max(50).describe("Option labels or values")}).strict(),browser_press:H.object({key:H.string().min(1).max(40).describe('A key or shortcut: "Enter", "Escape", "Tab", "ArrowDown", "Control+a"')}).strict(),browser_scroll:H.object({direction:H.enum(["down","up","left","right"])}).strict(),browser_screenshot:H.object({}).strict(),browser_get_text:H.object({max_chars:H.number().int().min(500).max(6e4).optional().describe("At most this many characters (default 20000). Ask for less when the start of the page is enough."),selector:H.string().min(1).max(300).optional().describe('Only the text inside the first element matching this CSS selector, e.g. "main" or "#results"')}).strict(),browser_query:H.object({expression:H.string().min(1).max(4e3).describe("A read-only JavaScript expression returning just the values you need as JSON, e.g. [...document.querySelectorAll('.result')].slice(0,10).map(e => e.innerText). Runs in a world of its own: it must not click, type, submit or change the page.")}).strict(),browser_find:H.object({query:H.string().min(1).max(300).describe('Words naming the element(s), e.g. "Sort by" or "price max input, Go button"')}).strict(),browser_wait:H.object({seconds:H.number().min(.5).max(10).describe("Seconds to wait before looking at the page again")}).strict(),browser_resume:H.object({timeout_s:H.number().int().min(5).max(900).optional().describe("How long to wait for the page to move on (default 300)")}).strict(),browser_actions:H.object({actions:H.array(H.discriminatedUnion("action",[H.object({action:H.literal("click"),ref:Ge}).strict(),H.object({action:H.literal("type"),ref:Ge,text:H.string().max(2e4),submit:H.boolean().optional()}).strict(),H.object({action:H.literal("select"),ref:Ge,values:H.array(H.string()).min(1).max(50)}).strict(),H.object({action:H.literal("press"),key:H.string().min(1).max(40)}).strict(),H.object({action:H.literal("scroll"),direction:H.enum(["down","up","left","right"])}).strict(),H.object({action:H.literal("wait"),seconds:H.number().min(.5).max(10)}).strict()])).min(1).max(10).describe("Steps run in order, e.g. fill a form's fields then submit. They stop at the first that fails or when the page moves to another address (later refs would belong to the old page).")}).strict(),browser_tabs:H.object({switch_to:H.string().min(1).max(10).optional().describe('Tab id to work in from now on, e.g. "t2"')}).strict(),browser_close:H.object({}).strict()};function xe(n,e=!1){return{content:[{type:"text",text:n}],...e?{isError:!0}:{}}}l(xe,"text");function fu(n){let e=Object.keys(n.secrets);return["pilotfish drives a real Chromium for you: you read each page and decide every step; pilotfish acts, checks each action and reports what changed.","Control: browser_navigate / browser_snapshot to get element refs (e12), then browser_click / browser_type / browser_select / browser_press / browser_scroll by ref. Every action returns EVIDENCE of what changed and, when the page changed, a snapshot of the visible screen with counts of the elements above and below it (browser_scroll for the next screen, browser_snapshot for three screens or whole_page for everything), or, when most of the screen is as in a snapshot you were given a few results back, only the lines that changed; refs stay valid while their element is on the page.","Reading: browser_query reads just the values you need with read-only JavaScript (a results list, prices, counts): the cheapest way to read. browser_find returns refs for elements anywhere on the page. browser_get_text gives a whole page's text, or one part of it (selector, max_chars). browser_navigate with snapshot:false skips the screen: only beside a browser_query of that page in the same reply, for a page you will not act on.","Acting: browser_actions runs several steps in one call (fill a form's fields, then submit) and stops at the first that fails or after one that moves to another page.","Words like cheapest, latest, highest-rated or most mean the site's own sort or tab for that order (a list's default order is usually something else, e.g. 'Best' or 'Featured'): apply it, then read the top.","The browser closes by itself when this client disconnects; browser_close is only for starting over within a session.",e.length?`Secrets are typed as placeholders and never shown: ${e.map(t=>`{{${t}}}`).join(", ")}.`:"",n.allowedHosts.length?`Navigation is limited to: ${n.allowedHosts.join(", ")}.`:"","When a result says NEEDS A PERSON (a sign-in, a one-time code or 2FA, a device approval, a CAPTCHA), tell the user in your reply what the page needs and that the pilotfish browser window is open on it; when they have done it there, call browser_resume and carry on. BLOCKED and STOP mean the site cannot be continued that way."].filter(Boolean).join(`
414
+ ${es(tr(nr,"autoconsent.playwright.js"),"utf8")}`,eu=3,tu=2500,su=6500,nu=6e3,ou=3e3,iu=1500,ru=3e3,au=4e3;function cu(n,e){if(n.doneAt)return"opted_out";if(n.optOut===!1)return"failed";if(n.optOut===!0)return e-n.optOutAt>ou?"opted_out":"pending";if(n.popupAt)return"pending";if(n.detectedAt)return e-n.detectedAt<su?"pending":"no_cmp";let t=n.topLifecycle!=="nothingDetected",s=l((o,i)=>e-o>=tu&&i!==void 0&&n.topAttempts>=i+eu,"quiet");return n.cmpRequestAt&&e-n.cmpRequestAt<nu&&t&&!s(n.cmpRequestAt,n.roundAtCmpRequest)||!n.topInitAt&&/^https?:/.test(n.url)&&e-n.navAt<iu||n.topInitAt&&n.topAttempts<2&&e-n.topInitAt<ru||n.patternsAt&&e-n.patternsAt<au&&t&&!s(n.patternsAt,n.roundAtPatterns)?"pending":"no_cmp"}l(cu,"outcomeOf");var Zt=class{static{l(this,"ConsentEngine")}opts;emitter=new Gd;pages=new WeakMap;disposers=new Set;armed=new WeakSet;retried=new WeakSet;retryTimers=new Set;disposed=!1;config;constructor(e={}){if(this.opts=e,this.config=Xd(e),this.config.autoAction!=="optOut"||!["off","reject"].includes(this.config.heuristicMode))throw new Error("ConsentEngine only ever opts out")}on(e,t){return this.emitter.on(e,t),()=>this.emitter.off(e,t)}consentStatus(e){let t=this.state(e),s=Date.now(),o=cu(t,s),i=o==="opted_out"?t.doneAt??t.optOutAt:s;return{cmp:t.cmp,outcome:o,ms:Math.max(0,i-t.navAt),url:t.url,...t.cosmetic!==void 0?{cosmetic:t.cosmetic}:{},...t.selfTest!==void 0?{selfTest:t.selfTest}:{},refusedClicks:t.refused,frames:t.frames.size}}async waitForConsent(e,t=1e4){let s=Date.now()+t;for(;;){let o=this.consentStatus(e);if(o.outcome!=="pending"||Date.now()>=s||e.closed)return o;await new Promise(i=>setTimeout(i,Math.min(100,Math.max(1,s-Date.now()))))}}dispose(){this.disposed=!0;for(let e of this.retryTimers)clearTimeout(e);this.retryTimers.clear();for(let e of this.disposers)e();this.disposers.clear()}initSession=l(async e=>{let t=e.session,s=e.page;if(!e.isMain&&we(e.url)||this.armed.has(t))return;this.armed.add(t);let o=new Map,i=[()=>this.armed.delete(t)],r;try{[,r]=await Promise.all([t.send("Runtime.addBinding",{name:ze,executionContextName:dn},{timeoutMs:5e3}),t.send("Page.addScriptToEvaluateOnNewDocument",{source:Zd,worldName:dn,runImmediately:!0},{timeoutMs:5e3})])}catch(p){if(this.armed.delete(t),e.isMain&&!this.retried.has(t)&&!this.disposed){this.retried.add(t);let f=setTimeout(()=>{this.retryTimers.delete(f),this.disposed||this.initSession(e).catch(()=>{})},2e3);f.unref?.(),this.retryTimers.add(f)}throw p}let a=l(()=>{for(let p of i)p();t.send("Page.removeScriptToEvaluateOnNewDocument",{identifier:r.identifier}).catch(()=>{}),t.send("Runtime.removeBinding",{name:ze}).catch(()=>{})},"dispose");this.disposers.add(a),i.push(()=>this.disposers.delete(a));let c=l(async(p,f,y)=>{if(!/^https?:/.test(f)||!y&&we(f))return;let{executionContextId:b}=await t.send("Page.createIsolatedWorld",{frameId:p,worldName:dn,grantUniveralAccess:!1},{timeoutMs:5e3});o.set(b,{frameId:p,mainFrame:y}),await t.send("Runtime.addBinding",{name:ze,executionContextId:b},{timeoutMs:5e3}),await t.send("Runtime.evaluate",{contextId:b,expression:"globalThis.__pfAcFlush && __pfAcFlush()",returnByValue:!0},{timeoutMs:5e3})},"arm"),d=l((p,f,y)=>{c(p,f,y).catch(b=>{/not found|Cannot find|No frame|detached|closed|destroyed|navigated/i.test(String(b))||this.emit("error",{page:s,frameId:p,url:f,error:`arm: ${String(b)}`})})},"armSafe");i.push(t.on("Page.frameNavigated",p=>{let f=e.isMain&&!p.frame.parentId;f&&this.reset(s,p.frame.url),d(p.frame.id,p.frame.url,f)}),t.on("Runtime.bindingCalled",p=>{if(p.name!==ze)return;let f=o.get(p.executionContextId);f&&this.onMessage(t,s,p.executionContextId,f,p.payload).catch(()=>{})}),t.on("Network.requestWillBeSent",p=>{if(!Fi(p.request.url))return;let f=this.state(s);f.cmpRequestAt===void 0&&(f.cmpRequestAt=Date.now(),f.roundAtCmpRequest=f.topAttempts)}));let{frameTree:u}=await t.send("Page.getFrameTree"),h=l((p,f)=>{d(p.frame.id,p.frame.url,f);for(let y of p.childFrames??[])h(y,!1)},"walk");h(u,e.isMain)},"initSession");async onMessage(e,t,s,o,i){let r;try{r=JSON.parse(i)}catch{return}let a=this.state(t),c=typeof r.url=="string"?r.url:"",d={page:t,frameId:o.frameId,url:c},u=typeof r.cmp=="string"?r.cmp:"";switch(r.type){case"init":{a.frames.add(o.frameId),o.mainFrame&&(a.topInitAt??=Date.now());let h={autoconsent:[],compact:Yd(or,{url:c,mainFrame:o.mainFrame})};await this.reply(e,s,{type:"initResp",config:this.config,rules:h});return}case"eval":await this.reply(e,s,{type:"evalResp",id:r.id,result:await this.runSnippet(e,t,s,o,r.snippetId)});return;case"cmpDetected":a.cmp??=u,a.detectedAt??=Date.now(),this.emit("cmpDetected",{...d,cmp:u});return;case"popupFound":a.cmp=u,a.popupAt??=Date.now(),this.emit("popupFound",{...d,cmp:u});return;case"optOutResult":a.cmp=u||a.cmp,a.optOut=r.result===!0,a.optOutAt=Date.now(),r.result===!0&&r.scheduleSelfTest===!0&&(a.selfTestTarget={s:e,contextId:s}),this.emit("optOutResult",{...d,cmp:u,result:r.result===!0});return;case"autoconsentDone":{a.cmp=u||a.cmp,a.doneAt=Date.now(),a.cosmetic=r.isCosmetic===!0,this.emit("autoconsentDone",{...d,cmp:u,cosmetic:a.cosmetic,durationMs:Number(r.duration)||0,clicks:Number(r.totalClicks)||0});let h=a.selfTestTarget;a.selfTestTarget=void 0,this.opts.selfTest!==!1&&h&&await this.reply(h.s,h.contextId,{type:"selfTest"});return}case"selfTestResult":a.selfTest=r.result===!0,this.emit("selfTestResult",{...d,cmp:u,result:a.selfTest});return;case"optInResult":this.emit("error",{...d,error:"unexpected optInResult",details:r});return;case"autoconsentError":this.emit("error",{...d,error:"autoconsent",details:r.details});return;case"pfRefusedClick":a.refused++,this.emit("error",{...d,error:`refused to click an accept button: "${String(r.text)}"`});return;case"report":{let h=r.state??{};if(!o.mainFrame||r.mainFrame!==!0)return;a.topLifecycle=h.lifecycle,a.topAttempts=Math.max(a.topAttempts,Number(h.findCmpAttempts)||0),h.heuristicPatterns?.length&&a.patternsAt===void 0&&(a.patternsAt=Date.now(),a.roundAtPatterns=a.topAttempts);return}default:return}}reply(e,t,s){return e.send("Runtime.evaluate",{contextId:t,expression:`autoconsentReceiveMessage(${JSON.stringify(s)})`,returnByValue:!0}).catch(()=>{})}async runSnippet(e,t,s,o,i){let r=typeof i=="string"?sr[i]:void 0;if(!r||Jd.has(i))return this.emit("error",{page:t,frameId:o.frameId,error:`refused eval snippet ${String(i)}`}),!1;let a=await e.send("Runtime.evaluate",{contextId:s,expression:"document",returnByValue:!1});if(!a.result.objectId)return!1;try{let{node:c}=await e.send("DOM.describeNode",{objectId:a.result.objectId}),{object:d}=await e.send("DOM.resolveNode",{backendNodeId:c.backendNodeId});if(!d.objectId)return!1;try{return(await e.send("Runtime.callFunctionOn",{objectId:d.objectId,functionDeclaration:`async function () { try { return !!(await (${r.toString()}).call(window)); } catch (e) { return false; } }`,returnByValue:!0,awaitPromise:!0})).result.value===!0}finally{e.send("Runtime.releaseObject",{objectId:d.objectId}).catch(()=>{})}}catch{return!1}finally{e.send("Runtime.releaseObject",{objectId:a.result.objectId}).catch(()=>{})}}state(e){let t=this.pages.get(e);return t||(t={navAt:Date.now(),url:e.url,cmp:null,refused:0,frames:new Set,topAttempts:0},this.pages.set(e,t)),t}reset(e,t){this.pages.set(e,{navAt:Date.now(),url:t,cmp:null,refused:0,frames:new Set,topAttempts:0})}emit(e,t){e==="error"&&this.emitter.listenerCount("error")===0||this.emitter.emit(e,t)}};var lu=["cmpDetected","popupFound","optOutResult","autoconsentDone","selfTestResult","error"],du=["blocked","error"];async function hn(n,e={}){let t=e.consent===!1?null:new Zt(typeof e.consent=="object"?e.consent:{}),s=e.adblock===!1?null:await Qt.create(typeof e.adblock=="object"?e.adblock:{}),o=[];if(e.onEvent){let r=e.onEvent;if(t)for(let a of lu)o.push(t.on(a,(c=>r({source:"consent",type:a,data:c}))));if(s)for(let a of du)o.push(s.on(a,(c=>r({source:"adblock",type:a,data:c}))))}let i=l(async r=>{await Promise.all([t?.initSession(r),s?.initSession(r)])},"init");return o.push(n.addSessionInitializer(i)),{consent:t,adblock:s,detach(){for(let r of o)r();t?.dispose(),s?.dispose()}}}l(hn,"attachInterrupts");var uu="pilotfish's open MCP makes no model calls: the MCP client's own model reads the page and decides the next step",hu=l(()=>{let n=l(async()=>{throw new Error(uu)},"refuse");return{messages:{create:n},beta:{messages:{create:n}}}},"noModelClient"),pn=class{static{l(this,"ConfirmThrough")}inner;canAsk;constructor(e,t){this.inner=e,this.canAsk=t}async check(e){let t=await this.inner.check(e);return t.kind!=="confirm"||this.canAsk()?t:{kind:"deny",reason:`${t.reason}. This needs a person's confirmation, and this MCP client cannot ask for one (no elicitation support)`}}},We=class{static{l(this,"BrowserSession")}config;opts;browser=null;launching=null;interrupts=null;tabs=null;agent=null;queue=Promise.resolve();policy;asker;constructor(e,t={}){this.config=e,this.opts=t,this.policy=new Ce({allowedHosts:e.allowedHosts,allowPrivateNetwork:e.allowPrivateNetwork})}get launched(){return!!this.browser&&!this.browser.closed}async page(){return await this.ensure(),this.tabs.current}get tabTable(){return this.tabs}async ensure(){return this.browser&&!this.browser.closed?this.browser:(this.launching??=this.start().finally(()=>{this.launching=null}),this.launching)}async start(){let e=await gs({...this.opts.launch,headless:this.config.headless});this.interrupts=await hn(e,{consent:!0,adblock:!0}).catch(()=>null);let t=e.allPages()[0]??await e.newPage();return this.tabs=new Ae(t),this.browser=e,e.on("disconnected",()=>{this.browser===e&&this.reset()}),e}reset(){this.agent?.dispose(),this.agent=null,this.tabs?.dispose(),this.tabs=null,this.interrupts?.detach(),this.interrupts=null,this.browser=null}executor(){return this.agent??=new ht(this.tabs.current,{tabs:this.tabs,client:hu(),secrets:this.config.secrets,policy:new pn(this.policy,()=>!!this.asker),consent:this.interrupts?.consent??void 0,allowScreenshots:this.config.allowScreenshots,askUser:l(e=>this.asker?.(e)??Promise.resolve(null),"askUser"),fastSettle:!0,enableQuery:!0,compactSnapshots:!0,autoDismiss:!0,awaitLoading:!0,actionSnapshotBand:0,snapshotMoreHint:"browser_scroll for the next screen, browser_get_text for the page's text, or browser_snapshot (whole_page) for every element"}),this.agent}async act(e,t,s={}){let o=this.queue.then(async()=>(await this.ensure(),this.executor().act(e,t,"",s)));return this.queue=o.catch(()=>{}),o}async obstacle(e={}){if(!this.launched)return null;let t=this.queue.then(()=>this.executor().obstacle(e));return this.queue=t.catch(()=>{}),t.catch(()=>null)}async close(){let e=this.browser??(this.launching?await this.launching.catch(()=>null):null);return e?(this.reset(),await e.close().catch(()=>{}),!0):!1}};import{McpServer as pu}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as H}from"zod";var ts="0.1.5";var Ge=Ie.click.shape.ref.describe('Element ref from the latest snapshot, e.g. "e12" or "f1e3"'),ne={browser_navigate:H.object({url:H.url({protocol:/^https?$/}).describe("Absolute http(s) URL"),snapshot:H.boolean().optional().describe("Default true. false: no page snapshot, only what happened (title, counts). Only when this same reply also reads the page with browser_query and you will not act on it; keep it for a page you have not seen or whose tabs, sorts, filters or forms you may use.")}).strict(),browser_snapshot:H.object({whole_page:H.boolean().optional().describe("Include everything, not only the viewport band (default false)")}).strict(),browser_click:H.object({ref:Ge}).strict(),browser_type:H.object({ref:Ge,text:H.string().max(2e4).describe("Text to enter (replaces what is there). Type secrets as {{name}} placeholders."),submit:H.boolean().optional().describe("Press Enter afterwards")}).strict(),browser_select:H.object({ref:Ge,values:H.array(H.string()).min(1).max(50).describe("Option labels or values")}).strict(),browser_press:H.object({key:H.string().min(1).max(40).describe('A key or shortcut: "Enter", "Escape", "Tab", "ArrowDown", "Control+a"')}).strict(),browser_scroll:H.object({direction:H.enum(["down","up","left","right"])}).strict(),browser_screenshot:H.object({}).strict(),browser_get_text:H.object({max_chars:H.number().int().min(500).max(6e4).optional().describe("At most this many characters (default 20000). Ask for less when the start of the page is enough."),selector:H.string().min(1).max(300).optional().describe('Only the text inside the first element matching this CSS selector, e.g. "main" or "#results"')}).strict(),browser_query:H.object({expression:H.string().min(1).max(4e3).describe("A read-only JavaScript expression returning just the values you need as JSON, e.g. [...document.querySelectorAll('.result')].slice(0,10).map(e => e.innerText). Runs in a world of its own: it must not click, type, submit or change the page.")}).strict(),browser_find:H.object({query:H.string().min(1).max(300).describe('Words naming the element(s), e.g. "Sort by" or "price max input, Go button"')}).strict(),browser_wait:H.object({seconds:H.number().min(.5).max(10).describe("Seconds to wait before looking at the page again")}).strict(),browser_resume:H.object({timeout_s:H.number().int().min(5).max(900).optional().describe("How long to wait for the page to move on (default 300)")}).strict(),browser_actions:H.object({actions:H.array(H.discriminatedUnion("action",[H.object({action:H.literal("click"),ref:Ge}).strict(),H.object({action:H.literal("type"),ref:Ge,text:H.string().max(2e4),submit:H.boolean().optional()}).strict(),H.object({action:H.literal("select"),ref:Ge,values:H.array(H.string()).min(1).max(50)}).strict(),H.object({action:H.literal("press"),key:H.string().min(1).max(40)}).strict(),H.object({action:H.literal("scroll"),direction:H.enum(["down","up","left","right"])}).strict(),H.object({action:H.literal("wait"),seconds:H.number().min(.5).max(10)}).strict()])).min(1).max(10).describe("Steps run in order, e.g. fill a form's fields then submit. They stop at the first that fails or when the page moves to another address (later refs would belong to the old page).")}).strict(),browser_tabs:H.object({switch_to:H.string().min(1).max(10).optional().describe('Tab id to work in from now on, e.g. "t2"')}).strict(),browser_close:H.object({}).strict()};function xe(n,e=!1){return{content:[{type:"text",text:n}],...e?{isError:!0}:{}}}l(xe,"text");function fu(n){let e=Object.keys(n.secrets);return["pilotfish drives a real Chromium for you: you read each page and decide every step; pilotfish acts, checks each action and reports what changed.","Control: browser_navigate / browser_snapshot to get element refs (e12), then browser_click / browser_type / browser_select / browser_press / browser_scroll by ref. Every action returns EVIDENCE of what changed and, when the page changed, a snapshot of the visible screen with counts of the elements above and below it (browser_scroll for the next screen, browser_snapshot for three screens or whole_page for everything), or, when most of the screen is as in a snapshot you were given a few results back, only the lines that changed; refs stay valid while their element is on the page.","Reading: browser_query reads just the values you need with read-only JavaScript (a results list, prices, counts): the cheapest way to read. browser_find returns refs for elements anywhere on the page. browser_get_text gives a whole page's text, or one part of it (selector, max_chars). browser_navigate with snapshot:false skips the screen: only beside a browser_query of that page in the same reply, for a page you will not act on.","Acting: browser_actions runs several steps in one call (fill a form's fields, then submit) and stops at the first that fails or after one that moves to another page.","Words like cheapest, latest, highest-rated or most mean the site's own sort or tab for that order (a list's default order is usually something else, e.g. 'Best' or 'Featured'): apply it, then read the top.","The browser closes by itself when this client disconnects; browser_close is only for starting over within a session.",e.length?`Secrets are typed as placeholders and never shown: ${e.map(t=>`{{${t}}}`).join(", ")}.`:"",n.allowedHosts.length?`Navigation is limited to: ${n.allowedHosts.join(", ")}.`:"","When a result says NEEDS A PERSON (a sign-in, a one-time code or 2FA, a device approval, a CAPTCHA), tell the user in your reply what the page needs and that the pilotfish browser window is open on it; when they have done it there, call browser_resume and carry on. BLOCKED and STOP mean the site cannot be continued that way."].filter(Boolean).join(`
415
415
  `)}l(fu,"serverInstructions");function fn(n={}){let e=n.config??n.session?.config??yt(),t=!n.session,s=n.session??new We(e,n),o=new pu({name:"pilotfish",version:n.version??ts},{capabilities:{logging:{}},instructions:fu(e)}),i=Object.keys(e.secrets),r=i.length?` Secrets: ${i.map(k=>`{{${k}}}`).join(", ")}.`:"",a=l(()=>!!o.server.getClientCapabilities()?.elicitation?.form,"canElicit"),c=l(k=>a()?async x=>{let v=await o.server.elicitInput({message:x,requestedSchema:{type:"object",properties:{answer:{type:"string",title:"Answer",description:'Reply "yes" to allow an action'}},required:["answer"]}},{relatedRequestId:k.requestId,timeout:e.interventionTimeoutMs}).catch(()=>null);return v?.action==="accept"&&typeof v.content?.answer=="string"?v.content.answer:null}:void 0,"askerFor"),d=l(k=>k.replace(/in the live view/g,"in the pilotfish browser window").replace(/Please send the code, or type it/,"Please type the code"),"forWindow"),u=l(k=>k.needs==="solve_in_live_view"||k.needs==="login_in_live_view"||k.needs==="code"||k.needs==="credentials","byHand"),h=l((k,x="")=>[`NEEDS A PERSON (${k.category}${k.vendor?`, ${k.vendor}`:""}): ${d(k.message)}`,x,u(k)&&e.headless?"This browser is headless, so it cannot be done by hand here: tell the user, and that the pilotfish server needs PILOTFISH_HEADLESS=0 (a visible window) for steps like this one.":`Tell the user now, in your reply: what the page needs, and that the pilotfish browser window is open on it (${k.url}). Ask them to do it there and to tell you when it is done. Then call browser_resume: it waits for the page to move on and returns it.`,"Do not try to get past it yourself: pilotfish does not type passwords or codes it was not given, and does not solve CAPTCHAs."].filter(Boolean).join(`
416
416
  `),"needsPerson"),p=l(async()=>{let k=await s.act("read_page",{}).catch(()=>null);return k?k.text||k.summary:""},"pageNow"),f=l(async(k,x=1e3)=>{let v=Date.now()+k,R=await s.obstacle({fresh:!0});for(;R&&Date.now()<v;)await new Promise(M=>setTimeout(M,x)),R=await s.obstacle({fresh:!0});return R},"untilClear"),y=l((k,x)=>({...k,content:[...k.content,{type:"text",text:x}]}),"withNote"),b=l(async(k,x)=>{let v=await s.obstacle();if(!v)return x;if(v.autoResolvable&&!v.neverAutomate&&(v=await f(12e3),!v))return y(x,`A browser check was showing and cleared by itself. The page now:
417
417
  ${await p()}`);if(v.neverAutomate)return y(x,`STOP: ${v.host} shows a bot check, and this site is never automated past one (ticket sellers: the US BOTS Act; UIDAI: the Aadhaar Act). Do not continue on it; tell the user.`);if(v.category==="hard_block"||v.needs==="none_possible")return y(x,`BLOCKED: ${d(v.message)} A person cannot fix this from the browser window: try another way (another page or site), or tell the user.`);if(a()&&!(u(v)&&e.headless)){let R=await o.server.elicitInput({message:`${d(v.message)} (${v.url})`,requestedSchema:{type:"object",properties:{done:{type:"boolean",title:"I have done it in the pilotfish browser window"}},required:["done"]}},{relatedRequestId:k.requestId,timeout:e.interventionTimeoutMs}).catch(()=>null);if(R?.action==="accept"&&R.content?.done!==!1){let M=await f(15e3);return M?y(x,h(M,"The person said it is done, but the page still shows it.")):y(x,`The person handled it. The page now:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaiventures/pilotfish-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A real Chromium on your machine for any MCP agent: ref-based tools, evidence per action, no API key.",
5
5
  "license": "MIT",
6
6
  "type": "module",