lazycodex-ai 5.1.32 → 5.1.34
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.
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/dist/cli/account-keys/commands.d.ts +9 -0
- package/dist/cli/account-keys/keyring.d.ts +16 -0
- package/dist/cli/account-keys/keystore.d.ts +34 -0
- package/dist/cli/index.js +91401 -90585
- package/dist/cli/service-auth/accounts-closed.fixture.d.ts +1 -0
- package/dist/cli/service-auth/commands.d.ts +4 -1
- package/dist/cli/worktree-sweep/types.d.ts +3 -1
- package/dist/cli-node/index.js +91521 -90705
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -0
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +6 -5
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +3 -3
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +9 -6
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +3 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -5
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +126 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +3 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-figures.mjs +33 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +3 -2
- package/packages/shared-skills/skills/browser/runtime/omowright/index.js +6 -5
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +3 -3
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +9 -6
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +3 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -5
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +1 -1
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +2 -0
- package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +126 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +3 -1
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +2 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-figures.mjs +33 -1
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.test.ts +56 -3
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/bad-report.html +4 -0
|
@@ -1481,7 +1481,7 @@ Closed tab: ${Vn.data.title??`Unknown`} (${Vn.url})`,{kind:`tab_lifecycle`,mode:
|
|
|
1481
1481
|
Viewport fallback also failed: ${formatScreenshotError(xn)}`)}}let Zn=Buffer.from(Yn,`base64`);if(xn.path)if(xn.path.startsWith(`/tmp`)){let jn=this.browser.resolvePath(`tmp`),Vn=path.join(jn,path.basename(xn.path));await mkdir(path.dirname(Vn),{recursive:true}),await writeFile(Vn,Zn),replPrint(`[WARN] Use ${jn} instead of /tmp. Saved it to ${Vn}`)}else{let jn=this.browser.resolvePath(xn.path);await mkdir(path.dirname(jn),{recursive:true}),await writeFile(jn,Zn)}return Zn}async#y(){return await this.evaluate(`window.devicePixelRatio`).catch(()=>1)}async#b(xn,jn,Vn){try{return await this._sendToTarget(`Page.captureScreenshot`,xn,{timeoutMs:jn})}catch(xn){throw rewriteCaptureScreenshotError(xn,jn,Vn)}}async snapshot(xn={}){return await takeSnapshot(this.cdp,this.frameManager,xn)}frames(){return this.frameManager.collectDescendantFrames(this.frameManager.mainFrameId).map(({frameId:xn})=>new OmOFrame(this,xn))}mainFrame(){let xn=this.frameManager.getFrame(this.frameManager.mainFrameId);if(!xn)throw Error(`Main frame is not available`);return new OmOFrame(this,xn.frameId)}async bringToFront(){this.browser.page=this,replPrint(`page is now [${await this.title().catch(()=>``)}](${this.url()})`)}async openPopupUrl(xn){await this.browser.openPopupTab(xn,this.targetId)}get console(){return this.#t??=new PageConsole(this)}video(){return this.#w()}viewportSize(){return this.#i?{...this.#i}:null}async refreshViewportSize(){let xn=await this.evaluate(()=>({width:Math.round(globalThis.visualViewport?.width??globalThis.innerWidth??0),height:Math.round(globalThis.visualViewport?.height??globalThis.innerHeight??0)}));if(xn.width<=0||xn.height<=0)throw Error(`Measured viewport size is invalid: ${JSON.stringify(xn)}`);return this.#i=xn,xn}setCachedViewportSize(xn){this.#i=xn?{...xn}:null}async pdf(xn={}){let jn=xn.path?await this.browser.ensurePath(xn.path,`write`,`page.pdf`):undefined,Vn={...xn,height:formatPdfLength(xn.height),margin:xn.margin?{bottom:formatPdfLength(xn.margin.bottom),left:formatPdfLength(xn.margin.left),right:formatPdfLength(xn.margin.right),top:formatPdfLength(xn.margin.top)}:undefined,printBackground:xn.printBackground??true,transferMode:`ReturnAsBase64`,width:formatPdfLength(xn.width)},{data:qn}=await this._sendToTarget(`Page.printToPDF`,Vn),Jn=Buffer.from(qn,`base64`);return jn&&(await mkdir(path.dirname(jn),{recursive:true}).catch(()=>{}),await writeFile(jn,Jn)),Jn}async close(xn={}){if(await this.#w().stop().catch(()=>{}),xn.runBeforeUnload){let jn=await this.resolveSessionId(this.#n);await this.cdp.send(`Page.close`,undefined,jn).catch(async()=>{await this.browser.closeTab(this,xn)});return}await this.browser.closeTab(this,xn)}async#x(){let{result:xn,exceptionDetails:jn}=await this._sendToTarget(`Runtime.evaluate`,{expression:`globalThis`,serializationOptions:{serialization:`idOnly`}});if(jn)throw Error(formatRuntimeException(jn,`Failed to resolve main world`));if(!xn.objectId)throw Error(`Main world object is not available`);return xn.objectId}async#S(xn){await this._sendToTarget(`Network.enable`).catch(()=>{});let jn=await this.resolveSessionId(this.#n);await new Promise((Vn,qn)=>{let Jn=new Set,Yn=null,Zn=null,Qn=()=>{ti(),ri(),ii(),Yn&&=(clearTimeout(Yn),null),Zn&&=(clearTimeout(Zn),null)},ei=()=>{Yn&&clearTimeout(Yn),Jn.size===0&&(Yn=setTimeout(()=>{Qn(),Vn()},NETWORK_IDLE_MS))},ti=this.cdp.on(`Network.requestWillBeSent`,(xn,Vn)=>{Vn.sessionId===jn&&(xn.request.url.startsWith(`data:`)||Jn.add(xn.requestId))}),ni=(xn)=>{Jn.delete(xn),ei()},ri=this.cdp.on(`Network.loadingFinished`,(xn,Vn)=>{Vn.sessionId===jn&&ni(xn.requestId)}),ii=this.cdp.on(`Network.loadingFailed`,(xn,Vn)=>{Vn.sessionId===jn&&ni(xn.requestId)});Zn=setTimeout(()=>{Qn(),qn(Error(`Timed out waiting for load state "networkidle"`))},xn),ei()})}async#C(){let xn=await this.resolveSessionId(this.#n);await new Promise((jn,Vn)=>{let qn=setTimeout(()=>{Jn(),Vn(Error(`Timed out waiting for navigation`))},DEFAULT_WAIT_TIMEOUT_MS),Jn=this.cdp.on(`Page.frameNavigated`,(Vn,Yn)=>{Yn.sessionId===xn&&(Vn.frame.parentId||(clearTimeout(qn),Jn(),jn()))})})}async _sendToTarget(xn,jn,Vn={}){if(this.#s)throw Error(`Page ${this.targetId} is disposed`);let{preferredSessionId:qn,...Jn}=Vn;if(qn!==undefined){if(!this.frameManager.hasSession(qn))throw Error(`Frame is stale or detached: CDP session ${qn} is unavailable`);return await this.cdp.send(xn,jn,qn,Jn)}let Yn=await this.resolveSessionId(this.#n);return await this.cdp.send(xn,jn,Yn,Jn)}#w(){return this.#o??=new OmOVideo(this),this.#o}}});init_client$6();init_session_manager();init_frame_manager();init_page();class TabRepository{#client;constructor(client){this.#client=client}async listTargets(){return this.#client.discoverTargets()}}class BrowserConnection{#client;#sessions;#pages=new Map;#storageRoot;constructor(client,options={}){this.#client=client;this.#sessions=new SessionManager(client);this.tabs=new TabRepository(client);this.#storageRoot=path2.resolve(options.storageRoot??process.cwd())}async initialize(){await this.#client.ensureConnected();return this}get cdp(){return this.#client}setDialogPolicy(policy){if(typeof this.#client.setDialogPolicy!=="function"){throw new Error("dialog policy is not supported by this transport")}this.#client.setDialogPolicy(policy)}async listTargets(){return this.tabs.listTargets()}async attachPage(targetId){if(this.#pages.has(targetId))return this.#pages.get(targetId).page;const sessionId=await this.#sessions.resolvePageSession(targetId);const frames=new FrameManager(this.#client,targetId,sessionId);await frames.initialize();const entry={frames,page:null};const host=this.#createPageHost(targetId,entry);const page=new OmOPage(this.#client,frames,targetId,sessionId,host);entry.page=page;this.#pages.set(targetId,entry);return page}#createPageHost(targetId,entry){return{daemonSession:{accountId:0,id:"standalone"},extensionBridgeRoute:{},page:null,resolvePageSession:(id,preferred)=>this.#sessions.resolvePageSession(id,preferred),resolvePath:(value)=>path2.resolve(this.#storageRoot,value),ensurePath:async(value)=>{const resolved=path2.resolve(this.#storageRoot,value);await mkdir2(path2.dirname(resolved),{recursive:true});return resolved},registerReadableDownload:(value)=>value,isReadableDownload:()=>false,openPopupTab:async()=>{throw Error("Popup tab control requires the extension bridge")},closeTab:async(_page,options={})=>{if(options.runBeforeUnload){await this.#client.send("Page.close",undefined,await this.#sessions.resolvePageSession(targetId))}else{await this.#client.send("Target.closeTarget",{targetId})}await entry.page?.dispose();await entry.frames.dispose();this.#pages.delete(targetId)}}}async close(){for(const[targetId,entry]of[...this.#pages]){await entry.page.dispose().catch(()=>{});await entry.frames.dispose().catch(()=>{});this.#pages.delete(targetId)}await this.#sessions.dispose();await this.#client.close()}}async function connect(cdpHttpUrl,options={}){if(typeof cdpHttpUrl!=="string"||cdpHttpUrl.length===0){throw new TypeError("connect(cdpHttpUrl) requires a Chromium CDP HTTP URL")}const client=new CdpClient({cdpUrl:cdpHttpUrl,...options.client});return new BrowserConnection(client,options).initialize()}import{spawn}from"node:child_process";function normalizeDialogPolicy(policy){return policy??{accept:true}}function dialogFromOpening(params={}){return{type:params.type,message:params.message,defaultPrompt:params.defaultPrompt??"",url:params.url}}function decisionFromResult(result){if(typeof result==="boolean")return{accept:result};if(result&&typeof result==="object"){const decision={accept:result.accept!==false};if(result.promptText!==undefined)decision.promptText=result.promptText;return decision}return{accept:true}}async function resolveDialogAction(policy,params={}){try{const result=typeof policy==="function"?await policy(dialogFromOpening(params)):policy;return decisionFromResult(result)}catch{return{accept:true}}}init_internal_capability();class Emittery2{#listeners=new Map;on(event,fn){const list=this.#listeners.get(event)??[];list.push(fn);this.#listeners.set(event,list);return()=>{const cur=this.#listeners.get(event)??[];this.#listeners.set(event,cur.filter((f)=>f!==fn))}}async emit(event,data){for(const fn of this.#listeners.get(event)??[])await fn(data)}}var DEFAULT_COMMAND_TIMEOUT_MS2=30000;var DEFAULT_READINESS_TIMEOUT_MS=1e4;var GRACEFUL_CLOSE_TIMEOUT_MS=2000;var STDIO_TAIL_LIMIT=16384;class PipeCdpClient{#child;#writeStream;#readStream;#buffer=Buffer.alloc(0);#seq=1;#pending=new Map;#events=new Emittery2;#connected=false;#commandTimeoutMs;#readinessTimeoutMs;#browserPath;#browserArgs;#spawnOptions;#stdioTail="";#dialogPolicy;transportEvents=new Emittery2;logId;constructor({browserPath,browserArgs=[],spawnOptions={},commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy}){if(!browserPath)throw new TypeError("PipeCdpClient requires browserPath");this.#browserPath=browserPath;this.#browserArgs=browserArgs;this.#spawnOptions=spawnOptions;this.#commandTimeoutMs=commandTimeoutMs??DEFAULT_COMMAND_TIMEOUT_MS2;this.#readinessTimeoutMs=readinessTimeoutMs??DEFAULT_READINESS_TIMEOUT_MS;this.logId=logId??`cdp-pipe-${Date.now()}`;this.#dialogPolicy=normalizeDialogPolicy(dialogPolicy)}get isConnected(){return this.#connected}get isHeadless(){return this.#browserArgs.some((arg)=>/^--headless(?:=|$)/.test(arg))}get childProcess(){return this.#child}get dialogPolicy(){return this.#dialogPolicy}setDialogPolicy(policy){this.#dialogPolicy=normalizeDialogPolicy(policy)}async ensureConnected(){if(this.#connected)return;const stdio=["ignore","pipe","pipe","pipe","pipe"];this.#child=spawn(this.#browserPath,[...this.#browserArgs,"--remote-debugging-pipe"],{...this.#spawnOptions,stdio});this.#stdioTail="";for(const stream of[this.#child.stdout,this.#child.stderr]){if(!stream)continue;stream.setEncoding("utf8");stream.on("data",(chunk)=>{this.#stdioTail=(this.#stdioTail+chunk).slice(-STDIO_TAIL_LIMIT)});stream.on("error",()=>{})}this.#writeStream=this.#child.stdio[3];this.#readStream=this.#child.stdio[4];this.#readStream.on("data",(chunk)=>this.#onData(chunk));for(const stream of[this.#writeStream,this.#readStream]){stream.on("error",(error)=>this.#failAll(Error(`CDP pipe error: ${error.code??error.message}`)))}this.#child.on("exit",()=>{this.#connected=false;this.#failAll(Error("CDP pipe browser exited"));this.transportEvents.emit("disconnected").catch(()=>{})});this.#connected=true;await this.#awaitReady();await this.send("Target.setDiscoverTargets",{discover:true}).catch(()=>{});await this.transportEvents.emit("connected")}async#awaitReady(){const deadline=Date.now()+this.#readinessTimeoutMs;let lastError;while(Date.now()<deadline){try{await this.send("Browser.getVersion",undefined,undefined,{timeoutMs:1000});return}catch(error){lastError=error;await new Promise((resolve)=>setTimeout(resolve,100))}}const reason=lastError?String(lastError.message??lastError):"no response";const tail=this.#stdioTail.trim();throw Error(`Browser did not become ready over the CDP pipe (${reason})`+(tail?`
|
|
1482
1482
|
Browser stdio tail:
|
|
1483
1483
|
${tail}`:""))}async send(method,params,sessionId,opts={}){if(method==="Target.createTarget"&&opts.capability!==targetCreationCapability)throw Error("Target.createTarget is reserved for createAgentTabs");if(String(method).startsWith("OmO.")){throw Error(`Extension commands are unavailable over pipe transport: ${method}`)}await this.ensureConnected();const id=this.#seq++;const msg={id,method};if(params!==undefined)msg.params=params;if(sessionId)msg.sessionId=sessionId;const{promise,resolve,reject}=Promise.withResolvers();const timeoutMs=opts.timeoutMs??this.#commandTimeoutMs;const timeout=timeoutMs>0?setTimeout(()=>{this.#pending.delete(id);reject(Error(`CDP command timeout: ${method}`))},timeoutMs):undefined;this.#pending.set(id,{method,resolve,reject,timeout});this.#writeStream.write(JSON.stringify(msg)+"\x00");return promise}on(method,fn){return this.#events.on(method,({data})=>fn(data.payload,data.meta))}async discoverTargets(){const{targetInfos}=await this.send("Target.getTargets");return targetInfos.map((t)=>({id:t.targetId,type:t.type,url:t.url??"",title:t.title??"",description:t.description,faviconUrl:t.faviconUrl,parentId:t.openerId}))}async close(){const child=this.#child;this.#child=undefined;if(child&&child.exitCode===null){const exited=new Promise((resolve)=>child.once("exit",resolve));if(this.#connected){await Promise.race([this.send("Browser.close",undefined,undefined,{timeoutMs:GRACEFUL_CLOSE_TIMEOUT_MS}).catch(()=>{}),exited])}this.#connected=false;this.#failAll(Error("CDP pipe client closed"));if(child.exitCode===null){await Promise.race([exited,new Promise((resolve)=>setTimeout(resolve,GRACEFUL_CLOSE_TIMEOUT_MS))])}if(child.exitCode===null){try{child.kill("SIGKILL")}catch{}await exited}return}this.#connected=false;this.#failAll(Error("CDP pipe client closed"))}#onData(chunk){this.#buffer=Buffer.concat([this.#buffer,chunk]);let idx;while((idx=this.#buffer.indexOf(0))!==-1){const raw=this.#buffer.subarray(0,idx).toString("utf8");this.#buffer=this.#buffer.subarray(idx+1);if(raw.length===0)continue;let msg;try{msg=JSON.parse(raw)}catch{continue}this._handleTransportMessage(msg)}}_handleTransportMessage(msg){if(typeof msg.id==="number"){const entry=this.#pending.get(msg.id);if(!entry)return;this.#pending.delete(msg.id);if(entry.timeout)clearTimeout(entry.timeout);if(msg.error){const err=Error(msg.error.message??`CDP command failed: ${entry.method}`);err.code=msg.error.code;err.data=msg.error.data;entry.reject(err)}else{entry.resolve(msg.result)}return}if(typeof msg.method==="string"){const handled=msg.method==="Page.javascriptDialogOpening"&&msg.sessionId?this.#handleJavaScriptDialog(msg).catch(()=>{}):undefined;this.#events.emit(msg.method,{data:{payload:msg.params,meta:{sessionId:msg.sessionId}}}).catch(()=>{});return handled}}async#handleJavaScriptDialog(msg){const params=await resolveDialogAction(this.#dialogPolicy,msg.params);await this.send("Page.handleJavaScriptDialog",params,msg.sessionId)}#failAll(err){for(const entry of this.#pending.values()){if(entry.timeout)clearTimeout(entry.timeout);entry.reject(err)}this.#pending.clear()}}async function connectPipe({browserPath,browserArgs=[],spawnOptions,storageRoot,commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy}={}){const client=new PipeCdpClient({browserPath,browserArgs,spawnOptions,commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy});await client.ensureConnected();const connection=new BrowserConnection(client,{storageRoot});await connection.initialize();connection.browserProcess=client.childProcess;connection.newTab=async(url="about:blank")=>{await Promise.resolve().then(() => init_agent_tabs());return(await createAgentTabs(connection).create(url)).page};return connection}import{randomInt}from"node:crypto";import{chmod,mkdir as mkdir3,open as open2,readFile}from"node:fs/promises";import{globSync}from"node:fs";import os from"node:os";import path3 from"node:path";var MIN_FINGERPRINT_SEED=1e4;var MAX_FINGERPRINT_SEED=99999;var METADATA_FILE=".omowright-cloak.json";var DEFAULT_PROFILE_DIR=path3.join(os.homedir(),".local","share","omowright-cloak");function normalizeFingerprintSeed(value){const seed=typeof value==="string"&&value.trim()!==""?Number(value):value;if(!Number.isInteger(seed)||seed<MIN_FINGERPRINT_SEED||seed>MAX_FINGERPRINT_SEED){throw new RangeError(`fingerprint seed must be an integer from ${MIN_FINGERPRINT_SEED} to ${MAX_FINGERPRINT_SEED}`)}return seed}function platformFingerprint(){return process.platform==="darwin"?"macos":"windows"}function identityFlag(value){return/^(--user-data-dir|--fingerprint|--fingerprint-platform)(?:=|$)/.test(value)}function buildCloakBrowserArgs({profileDir,fingerprintSeed,extraArgs=[]}={}){const profile=path3.resolve(profileDir??DEFAULT_PROFILE_DIR);const seed=normalizeFingerprintSeed(fingerprintSeed);const conflicting=extraArgs.filter(identityFlag);if(conflicting.length>0){throw new Error(`extraArgs cannot override fixed profile identity: ${conflicting.join(", ")}`)}return["--no-first-run","--no-default-browser-check","--disable-session-crashed-bubble",`--user-data-dir=${profile}`,`--fingerprint=${seed}`,`--fingerprint-platform=${platformFingerprint()}`,...extraArgs]}async function readMetadata(metadataPath){try{const parsed=JSON.parse(await readFile(metadataPath,"utf8"));return normalizeFingerprintSeed(parsed.fingerprintSeed)}catch(error){if(error?.code==="ENOENT")return null;if(error instanceof SyntaxError){throw new Error(`invalid CloakBrowser profile metadata: ${metadataPath}`)}throw error}}async function writeMetadata(metadataPath,fingerprintSeed){const handle=await open2(metadataPath,"wx",384);try{await handle.writeFile(`${JSON.stringify({fingerprintSeed},null,2)}
|
|
1484
|
-
`,"utf8")}finally{await handle.close()}await chmod(metadataPath,384)}async function resolveCloakProfile({profileDir=DEFAULT_PROFILE_DIR,fingerprintSeed,randomSeed=()=>randomInt(MIN_FINGERPRINT_SEED,MAX_FINGERPRINT_SEED+1)}={}){const resolvedProfile=path3.resolve(profileDir);const metadataPath=path3.join(resolvedProfile,METADATA_FILE);await mkdir3(resolvedProfile,{recursive:true,mode:448});await chmod(resolvedProfile,448);const requested=fingerprintSeed===undefined?null:normalizeFingerprintSeed(fingerprintSeed);let stored=await readMetadata(metadataPath);if(stored===null){const candidate=requested??normalizeFingerprintSeed(randomSeed());try{await writeMetadata(metadataPath,candidate);stored=candidate}catch(error){if(error?.code!=="EEXIST")throw error;stored=await readMetadata(metadataPath);if(stored===null){throw new Error(`CloakBrowser profile seed metadata disappeared: ${metadataPath}`)}}}if(requested!==null&&requested!==stored){throw new Error(`fingerprint seed mismatch: profile is fixed at ${stored}, requested ${requested}`)}await chmod(metadataPath,384);return{profileDir:resolvedProfile,metadataPath,fingerprintSeed:stored,browserArgs:buildCloakBrowserArgs({profileDir:resolvedProfile,fingerprintSeed:stored})}}function findCloakBrowserPath({browserRoot=path3.join(os.homedir(),".cloakbrowser")}={}){const candidates=globSync(path3.join(path3.resolve(browserRoot),"chromium-*","Chromium.app","Contents","MacOS","Chromium")).sort();const browserPath=candidates.at(-1);if(!browserPath){throw new Error(`CloakBrowser binary not found under ${path3.resolve(browserRoot)}; set CLOAKBROWSER_BIN`)}return browserPath}async function connectCloakProfile({browserPath=process.env.CLOAKBROWSER_BIN??findCloakBrowserPath(),profileDir=process.env.CLOAK_PROFILE_DIR??DEFAULT_PROFILE_DIR,fingerprintSeed=process.env.CLOAK_FINGERPRINT_SEED,browserArgs=[],storageRoot,...options}={}){const profile=await resolveCloakProfile({profileDir,fingerprintSeed});return connectPipe({browserPath,browserArgs:buildCloakBrowserArgs({profileDir:profile.profileDir,fingerprintSeed:profile.fingerprintSeed,extraArgs:browserArgs}),storageRoot:storageRoot??profile.profileDir,...options})}var SNAPSHOT_PARSE_ERROR="compactSnapshot expected a snapshot() result (JSON string or {tree, refs} object)";function compactSnapshot(snapshot){const obj=typeof snapshot==="string"?JSON.parse(snapshot):snapshot;if(!obj||typeof obj.tree!=="string")throw new TypeError(SNAPSHOT_PARSE_ERROR);return obj.tree}function snapshotTokens(snapshot){const text=typeof snapshot==="string"?snapshot:JSON.stringify(snapshot);return{bytes:Buffer.byteLength(text,"utf8"),estTokens:Math.ceil(Buffer.byteLength(text,"utf8")/4)}}var toolSchemas=[{name:"newTab",description:"Open a tab and return its page. Waits for interactivity.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"attachPage",description:"Attach to an existing tab by targetId from listTargets().",parameters:{type:"object",properties:{targetId:{type:"string"}},required:["targetId"]}},{name:"page.goto",description:"Navigate the page; resolves when the page looks interactive.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"page.snapshot",description:"PRIMARY page read. Returns a compact a11y tree; interactive elements carry [ref=eN] ids for page.locator. Options: maxDepth, maxChars, interactive, showHidden, selector, ref.",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.locator",description:"Resolve a ref id (e.g. 'e1') or CSS selector to a Locator: click, fill, press, hover, check, selectOption, dragTo, setInputFiles, textContent, isVisible, waitFor.",parameters:{type:"object",properties:{selectorOrRef:{type:"string"}},required:["selectorOrRef"]}},{name:"page.evaluate",description:"Run JS in the page and return the result.",parameters:{type:"object",properties:{expression:{type:"string"}},required:["expression"]}},{name:"page.screenshot",description:"Capture page image bytes (path, fullPage, clip, type, quality).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.pdf",description:"Print the page to PDF bytes (path, format, margin, printBackground).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"compactSnapshot",description:"Pass page.snapshot() output through this before reading: returns the raw tree without the refs map (refs resolve in-page; the map is ~54% of bytes on real pages).",parameters:{type:"object",properties:{snapshot:{}},required:["snapshot"]}},{name:"createCua",description:"Coordinate fallback for UI that refs cannot target: click/doubleClick/drag/move/scroll/type/keypress at viewport points, getVisibleScreenshot() as base64 PNG.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"createCaptcha",description:"Captcha helpers: click(bounds) and drag(from,to) return a post-action snapshot tree; readText(bounds?) OCRs a screenshot region (macOS Vision default, custom ocr injectable).",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"createChromeApi",description:"Chrome MV3-shaped APIs: tabs.query/get, windows.*, bookmarks/history/topSites (read-only from profile files), downloads.search/download. Writes throw UnsupportedOperationError.",parameters:{type:"object",properties:{connection:{},options:{type:"object"}},required:["connection"]}},{name:"injectCookies",description:"Inject raw cookie exports into a page with CDP sanitization handled (negative expires dropped, __Host- forced secure+root path+url-scoped, SameSite=None dropped when not secure, name+domain deduped).",parameters:{type:"object",properties:{page:{},cookies:{type:"array"}},required:["page","cookies"]}},{name:"createNetworkSnoop",description:"Buffer a page's CDP network traffic and return {pop,peek,popJson,waitFor,summary,dispose}; match is a predicate or {url,method,mimeType,resourceType}.",parameters:{type:"object",properties:{page:{},match:{},bodies:{type:"boolean"},maxEntries:{type:"number"},maxBodyBytes:{type:"number"}},required:["page"]}},{name:"snoop.waitFor",description:"Wait until a finished entry matches a predicate or {url,method,mimeType,resourceType}; timeoutMs defaults to 30000.",parameters:{type:"object",properties:{match:{},timeoutMs:{type:"number"}}}},{name:"snoop.popJson",description:"Drain the buffer and parse JSON bodies; skips non-JSON mime types and payloads that fail to parse.",parameters:{type:"object",properties:{}}},{name:"snoop.summary",description:"Return a compact text view of buffered entries (method, status, mimeType, size, url) capped by {max}.",parameters:{type:"object",properties:{max:{type:"number"}}}},{name:"collectWhileScrolling",description:"Async generator that scrolls the page, drains snoop.popJson(), and yields extracted items until minItems, maxScrolls, or noGrowth.",parameters:{type:"object",properties:{page:{},snoop:{},minItems:{type:"number"},maxScrolls:{type:"number"},extract:{},scroll:{type:"string",enum:["wheel","end"]},settleMs:{type:"number"}},required:["page","snoop"]}},{name:"createTrace",description:"Start a page flight recorder that writes dir/trace.jsonl, dir/trace.har, dir/summary.json and screenshots; never sends Runtime.enable.",parameters:{type:"object",properties:{page:{},dir:{type:"string"},screenshots:{type:"boolean"},network:{type:"boolean"},console:{type:"boolean"},maxBodyBytes:{type:"number"},events:{}},required:["page","dir"]}},{name:"trace.step",description:"Run named fn as a traced step with optional before/after screenshots; records the error and rethrows on failure.",parameters:{type:"object",properties:{name:{type:"string"},fn:{}},required:["name","fn"]}},{name:"trace.stop",description:"Flush listeners, write summary.json and trace.har, dispose the snoop, and return the summary for dir.",parameters:{type:"object",properties:{}}},{name:"reconcileFrames",description:"Adopt OOPIF iframe targets missing from FrameManager so later snapshots include them; returns the frames that were added.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"snapshotWithFrames",description:"Reconcile missing OOPIF frames then snapshot; refs stay page-level: page.locator(ref), never frame.locator.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"connectPipe.dialogPolicy",description:"Wire connectPipe({dialogPolicy}) and client.setDialogPolicy to {accept,promptText} or (dialog)=>boolean|{accept,promptText}; throwing policy accepts. WebSocket connect() keeps auto-accept.",parameters:{type:"object",properties:{accept:{type:"boolean"},promptText:{type:"string"}}}},{name:"requestHuman",description:"Pause for a human: inject a shadow-DOM Done banner, bringToFront first, and remove the banner on every path; until is {url}|{selector}|async predicate.",parameters:{type:"object",properties:{page:{},prompt:{type:"string"},until:{},timeoutMs:{type:"number"},pollMs:{type:"number"},signal:{}},required:["page"]}},{name:"describeLayers",description:"Sample a viewport grid and return {blocking,candidates,viewport} so overlay coverage and selectorHint are visible.",parameters:{type:"object",properties:{page:{},grid:{type:"number"}},required:["page"]}},{name:"snapshotWithLayers",description:"Prepend a @layers header to the snapshot tree and attach a layers field so overlays are visible before the a11y tree.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"emulate",description:"Apply a DEVICE_PRESETS name, a custom metrics object, or null to restore the original UA captured on first call.",parameters:{type:"object",properties:{page:{},preset:{}},required:["page","preset"]}},{name:"createRoutes",description:"Install page request interception; Fetch.enable is sent only on the first route(); interception is fingerprintable, keep it off by default.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"route.continue",description:"Continue an intercepted request, optionally overriding url, method, headers, or postData.",parameters:{type:"object",properties:{url:{type:"string"},method:{type:"string"},headers:{type:"object"},postData:{}}}},{name:"route.fulfill",description:"Fulfill an intercepted request with {status,headers,contentType,body} instead of hitting the network.",parameters:{type:"object",properties:{status:{type:"number"},headers:{type:"object"},contentType:{type:"string"},body:{}}}},{name:"route.abort",description:"Abort an intercepted request with a CDP errorReason (defaults to Failed).",parameters:{type:"object",properties:{reason:{type:"string"}}}}];var MODIFIER_KEYS2=new Set(["Alt","Control","Meta","Shift"]);function splitModifiers(keys=[]){const modifiers=keys.filter((key)=>MODIFIER_KEYS2.has(key)||/^(cmd|command|ctrl|option|controlormeta)$/i.test(key));const rest=keys.filter((key)=>!modifiers.includes(key));return{modifiers,rest}}function createCua(page){async function holdModifiers(keys,fn){const{modifiers}=splitModifiers(keys);for(const key of modifiers)await page.keyboard.down(key);try{return await fn()}finally{for(const key of[...modifiers].reverse())await page.keyboard.up(key)}}return{page,async click({x,y,button="left",keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{button}))},async doubleClick({x,y,keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{clickCount:2}))},async drag({path,keys}){if(!Array.isArray(path)||path.length<2){throw new TypeError("cua.drag requires path with at least two points")}await holdModifiers(keys,async()=>{const[first,...rest]=path;await page.mouse.move(first.x,first.y);await page.mouse.down();try{for(const point of rest)await page.mouse.move(point.x,point.y)}finally{await page.mouse.up()}})},async getVisibleScreenshot(){const buffer=await page.screenshot({type:"png"});return buffer.toString("base64")},async keypress({keys}){if(!Array.isArray(keys)||keys.length===0){throw new TypeError("cua.keypress requires a non-empty keys array")}await page.keyboard.press(keys.join("+"))},async move({x,y,keys}){await holdModifiers(keys,()=>page.mouse.move(x,y))},async scroll({x,y,scrollX,scrollY,keypress}){await holdModifiers(keypress,async()=>{await page.mouse.move(x,y);await page.mouse.wheel(scrollX,scrollY)})},async type({text}){await page.keyboard.type(text)}}}import{spawn as spawn2}from"node:child_process";import{mkdtempSync,writeFileSync,rmSync}from"node:fs";import{tmpdir as tmpdir2}from"node:os";import path4 from"node:path";var DEFAULT_DRAG_STEPS=20;var POST_ACTION_SETTLE_MS=3000;var MACOS_VISION_OCR_SWIFT=`
|
|
1484
|
+
`,"utf8")}finally{await handle.close()}await chmod(metadataPath,384)}async function resolveCloakProfile({profileDir=DEFAULT_PROFILE_DIR,fingerprintSeed,randomSeed=()=>randomInt(MIN_FINGERPRINT_SEED,MAX_FINGERPRINT_SEED+1)}={}){const resolvedProfile=path3.resolve(profileDir);const metadataPath=path3.join(resolvedProfile,METADATA_FILE);await mkdir3(resolvedProfile,{recursive:true,mode:448});await chmod(resolvedProfile,448);const requested=fingerprintSeed===undefined?null:normalizeFingerprintSeed(fingerprintSeed);let stored=await readMetadata(metadataPath);if(stored===null){const candidate=requested??normalizeFingerprintSeed(randomSeed());try{await writeMetadata(metadataPath,candidate);stored=candidate}catch(error){if(error?.code!=="EEXIST")throw error;stored=await readMetadata(metadataPath);if(stored===null){throw new Error(`CloakBrowser profile seed metadata disappeared: ${metadataPath}`)}}}if(requested!==null&&requested!==stored){throw new Error(`fingerprint seed mismatch: profile is fixed at ${stored}, requested ${requested}`)}await chmod(metadataPath,384);return{profileDir:resolvedProfile,metadataPath,fingerprintSeed:stored,browserArgs:buildCloakBrowserArgs({profileDir:resolvedProfile,fingerprintSeed:stored})}}function findCloakBrowserPath({browserRoot=path3.join(os.homedir(),".cloakbrowser")}={}){const candidates=globSync(path3.join(path3.resolve(browserRoot),"chromium-*","Chromium.app","Contents","MacOS","Chromium")).sort();const browserPath=candidates.at(-1);if(!browserPath){throw new Error(`CloakBrowser binary not found under ${path3.resolve(browserRoot)}; set CLOAKBROWSER_BIN`)}return browserPath}async function connectCloakProfile({browserPath=process.env.CLOAKBROWSER_BIN??findCloakBrowserPath(),profileDir=process.env.CLOAK_PROFILE_DIR??DEFAULT_PROFILE_DIR,fingerprintSeed=process.env.CLOAK_FINGERPRINT_SEED,browserArgs=[],storageRoot,...options}={}){const profile=await resolveCloakProfile({profileDir,fingerprintSeed});return connectPipe({browserPath,browserArgs:buildCloakBrowserArgs({profileDir:profile.profileDir,fingerprintSeed:profile.fingerprintSeed,extraArgs:browserArgs}),storageRoot:storageRoot??profile.profileDir,...options})}var SNAPSHOT_PARSE_ERROR="compactSnapshot expected a snapshot() result (JSON string or {tree, refs} object)";function compactSnapshot(snapshot){const obj=typeof snapshot==="string"?JSON.parse(snapshot):snapshot;if(!obj||typeof obj.tree!=="string")throw new TypeError(SNAPSHOT_PARSE_ERROR);return obj.tree}function snapshotTokens(snapshot){const text=typeof snapshot==="string"?snapshot:JSON.stringify(snapshot);return{bytes:Buffer.byteLength(text,"utf8"),estTokens:Math.ceil(Buffer.byteLength(text,"utf8")/4)}}var toolSchemas=[{name:"newTab",description:"Open a tab and return its page. Waits for interactivity.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"attachPage",description:"Attach to an existing tab by targetId from listTargets().",parameters:{type:"object",properties:{targetId:{type:"string"}},required:["targetId"]}},{name:"page.goto",description:"Navigate the page; resolves when the page looks interactive.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"page.snapshot",description:"PRIMARY page read. Returns a compact a11y tree; interactive elements carry [ref=eN] ids for page.locator. Options: maxDepth, maxChars, interactive, showHidden, selector, ref.",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.locator",description:"Resolve a ref id (e.g. 'e1') or CSS selector to a Locator: click, fill, press, hover, check, selectOption, dragTo, setInputFiles, textContent, isVisible, waitFor.",parameters:{type:"object",properties:{selectorOrRef:{type:"string"}},required:["selectorOrRef"]}},{name:"page.evaluate",description:"Run JS in the page and return the result.",parameters:{type:"object",properties:{expression:{type:"string"}},required:["expression"]}},{name:"page.screenshot",description:"Capture page image bytes (path, fullPage, clip, type, quality).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.pdf",description:"Print the page to PDF bytes (path, format, margin, printBackground).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"compactSnapshot",description:"Pass page.snapshot() output through this before reading: returns the raw tree without the refs map (refs resolve in-page; the map is ~54% of bytes on real pages).",parameters:{type:"object",properties:{snapshot:{}},required:["snapshot"]}},{name:"createCua",description:"Coordinate fallback for UI that refs cannot target: click/doubleClick/drag/move/scroll/type/keypress at viewport points, getVisibleScreenshot() as base64 PNG.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"createCaptcha",description:"Captcha helpers: click(bounds) and drag(from,to) return a post-action snapshot tree; readText(bounds?) OCRs a screenshot region (macOS Vision default, custom ocr injectable).",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"createChromeApi",description:"Chrome MV3-shaped APIs: tabs.query/get, windows.*, bookmarks/history/topSites (read-only from profile files), downloads.search/download. Writes throw UnsupportedOperationError.",parameters:{type:"object",properties:{connection:{},options:{type:"object"}},required:["connection"]}},{name:"injectCookies",description:"Inject raw cookie exports into a page with CDP sanitization handled (negative expires dropped, __Host- forced secure+root path+url-scoped, SameSite=None dropped when not secure, name+domain deduped).",parameters:{type:"object",properties:{page:{},cookies:{type:"array"}},required:["page","cookies"]}},{name:"createNetworkSnoop",description:"Buffer a page's CDP network traffic and return {pop,peek,popJson,waitFor,summary,dispose}; match is a predicate or {url,method,mimeType,resourceType}.",parameters:{type:"object",properties:{page:{},match:{},bodies:{type:"boolean"},maxEntries:{type:"number"},maxBodyBytes:{type:"number"}},required:["page"]}},{name:"snoop.waitFor",description:"Wait until a finished entry matches a predicate or {url,method,mimeType,resourceType}; timeoutMs defaults to 30000.",parameters:{type:"object",properties:{match:{},timeoutMs:{type:"number"}}}},{name:"snoop.popJson",description:"Drain the buffer and parse JSON bodies; skips non-JSON mime types and payloads that fail to parse.",parameters:{type:"object",properties:{}}},{name:"snoop.summary",description:"Return a compact text view of buffered entries (method, status, mimeType, size, url) capped by {max}.",parameters:{type:"object",properties:{max:{type:"number"}}}},{name:"collectWhileScrolling",description:"Async generator that scrolls the page, drains snoop.popJson(), and yields extracted items until minItems, maxScrolls, or noGrowth.",parameters:{type:"object",properties:{page:{},snoop:{},minItems:{type:"number"},maxScrolls:{type:"number"},extract:{},scroll:{type:"string",enum:["wheel","end"]},settleMs:{type:"number"}},required:["page","snoop"]}},{name:"createTrace",description:"Start a page flight recorder that writes dir/trace.jsonl, dir/trace.har, dir/summary.json and screenshots; never sends Runtime.enable.",parameters:{type:"object",properties:{page:{},dir:{type:"string"},screenshots:{type:"boolean"},network:{type:"boolean"},console:{type:"boolean"},maxBodyBytes:{type:"number"},events:{}},required:["page","dir"]}},{name:"trace.step",description:"Run named fn as a traced step with optional before/after screenshots; records the error and rethrows on failure.",parameters:{type:"object",properties:{name:{type:"string"},fn:{}},required:["name","fn"]}},{name:"trace.stop",description:"Flush listeners, write summary.json and trace.har, dispose the snoop, and return the summary for dir.",parameters:{type:"object",properties:{}}},{name:"reconcileFrames",description:"Adopt OOPIF iframe targets missing from FrameManager so later snapshots include them; returns the frames that were added.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"snapshotWithFrames",description:"Reconcile missing OOPIF frames then snapshot; refs stay page-level: page.locator(ref), never frame.locator.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"connectPipe.dialogPolicy",description:"Wire connectPipe({dialogPolicy}) and client.setDialogPolicy to {accept,promptText} or (dialog)=>boolean|{accept,promptText}; throwing policy accepts. WebSocket connect() keeps auto-accept.",parameters:{type:"object",properties:{accept:{type:"boolean"},promptText:{type:"string"}}}},{name:"requestHuman",description:"Pause for a human: inject a shadow-DOM Done banner, bringToFront first, and remove the banner on every path; until is {url}|{selector}|async predicate.",parameters:{type:"object",properties:{page:{},prompt:{type:"string"},until:{},timeoutMs:{type:"number"},pollMs:{type:"number"},signal:{}},required:["page"]}},{name:"describeLayers",description:"Sample a viewport grid and return {blocking,candidates,viewport} so overlay coverage and selectorHint are visible.",parameters:{type:"object",properties:{page:{},grid:{type:"number"}},required:["page"]}},{name:"snapshotWithLayers",description:"Prepend a @layers header to the snapshot tree and attach a layers field so overlays are visible before the a11y tree.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"emulate",description:"Apply a DEVICE_PRESETS name, a custom metrics object, or null to restore the original UA captured on first call.",parameters:{type:"object",properties:{page:{},preset:{}},required:["page","preset"]}},{name:"createRoutes",description:"Install page request interception; Fetch.enable is sent only on the first route(); interception is fingerprintable, keep it off by default.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"route.continue",description:"Continue an intercepted request, optionally overriding url, method, headers, or postData.",parameters:{type:"object",properties:{url:{type:"string"},method:{type:"string"},headers:{type:"object"},postData:{}}}},{name:"route.fulfill",description:"Fulfill an intercepted request with {status,headers,contentType,body} instead of hitting the network.",parameters:{type:"object",properties:{status:{type:"number"},headers:{type:"object"},contentType:{type:"string"},body:{}}}},{name:"route.abort",description:"Abort an intercepted request with a CDP errorReason (defaults to Failed).",parameters:{type:"object",properties:{reason:{type:"string"}}}}];var MODIFIER_KEYS2=new Set(["Alt","Control","Meta","Shift"]);function splitModifiers(keys=[]){const modifiers=keys.filter((key)=>MODIFIER_KEYS2.has(key)||/^(cmd|command|ctrl|option|controlormeta)$/i.test(key));const rest=keys.filter((key)=>!modifiers.includes(key));return{modifiers,rest}}function createCua(page){async function holdModifiers(keys,fn){const{modifiers}=splitModifiers(keys);for(const key of modifiers)await page.keyboard.down(key);try{return await fn()}finally{for(const key of[...modifiers].reverse())await page.keyboard.up(key)}}return{page,async click({x,y,button="left",keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{button}))},async doubleClick({x,y,keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{clickCount:2}))},async drag({path,keys}){if(!Array.isArray(path)||path.length<2){throw new TypeError("cua.drag requires path with at least two points")}await holdModifiers(keys,async()=>{const[first,...rest]=path;await page.mouse.move(first.x,first.y);await page.mouse.down();try{for(const point of rest)await page.mouse.move(point.x,point.y)}finally{await page.mouse.up()}})},async getVisibleScreenshot(){const buffer=await page.screenshot({type:"png"});return buffer.toString("base64")},async keypress({keys}){if(!Array.isArray(keys)||keys.length===0){throw new TypeError("cua.keypress requires a non-empty keys array")}await page.keyboard.press(keys.join("+"))},async move({x,y,keys}){await holdModifiers(keys,()=>page.mouse.move(x,y))},async scroll({x,y,scrollX,scrollY,keypress}){await holdModifiers(keypress,async()=>{await page.mouse.move(x,y);await page.mouse.wheel(scrollX,scrollY)})},async type({text}){await page.keyboard.type(text)}}}import{spawn as spawn2}from"node:child_process";import{mkdtempSync,writeFileSync,rmSync}from"node:fs";import{tmpdir as tmpdir2}from"node:os";import path4 from"node:path";var MAX_TIMER_MS=2147483647;var STOP=Symbol("wait stopped");async function waitForCaptcha(page,{until,timeoutMs=1e4,pollMs=100,signal}={}){if(typeof until!=="function")throw new TypeError("until must be a function");if(!Number.isFinite(timeoutMs)||timeoutMs<0||timeoutMs>MAX_TIMER_MS){throw new RangeError("timeoutMs must be between 0 and 2147483647")}if(!Number.isFinite(pollMs)||pollMs<=0||pollMs>MAX_TIMER_MS){throw new RangeError("pollMs must be greater than 0 and at most 2147483647")}if(signal!==undefined&&(typeof signal?.aborted!=="boolean"||typeof signal.addEventListener!=="function"||typeof signal.removeEventListener!=="function")){throw new TypeError("signal must be AbortSignal-compatible")}const started=performance.now();const deadline=started+timeoutMs;const operation=new AbortController;let deadlineTimer;let pollTimer;let stop;const stopped=new Promise((resolve)=>{stop=()=>resolve(STOP)});const terminal=()=>signal?.aborted?"cancelled":performance.now()>=deadline?"timed_out":null;const result=(outcome)=>({outcome,elapsedMs:performance.now()-started});const scheduleDeadline=()=>{const remaining=deadline-performance.now();if(remaining<=0)stop();else deadlineTimer=setTimeout(scheduleDeadline,Math.ceil(remaining))};try{const initial=terminal();if(initial)return result(initial);signal?.addEventListener("abort",stop,{once:true});scheduleDeadline();for(;;){let accepted;try{accepted=await Promise.race([Promise.resolve().then(()=>terminal()?STOP:until(page,{signal:operation.signal})),stopped])}catch(error){const outcome=terminal();if(outcome)return result(outcome);throw error}const outcome=terminal();if(outcome)return result(outcome);if(accepted===true)return result("matched");await Promise.race([new Promise((resolve)=>{pollTimer=setTimeout(resolve,Math.ceil(Math.min(pollMs,deadline-performance.now())))}),stopped]);clearTimeout(pollTimer);const afterDelay=terminal();if(afterDelay)return result(afterDelay)}}finally{clearTimeout(deadlineTimer);clearTimeout(pollTimer);signal?.removeEventListener("abort",stop);operation.abort();stop()}}var DEFAULT_DRAG_STEPS=20;var POST_ACTION_SETTLE_MS=3000;function validatePoint(point){if(!Number.isFinite(point?.x)||!Number.isFinite(point?.y)){throw new TypeError("point coordinates must be finite numbers")}}function validateSteps(steps){if(!Number.isSafeInteger(steps)||steps<=0){throw new RangeError("steps must be a positive safe integer")}}function validateSettleMs(value){if(!Number.isFinite(value)||value<0||value>2147483647){throw new RangeError("settleMs must be between 0 and 2147483647")}}var MACOS_VISION_OCR_SWIFT=`
|
|
1485
1485
|
import Vision
|
|
1486
1486
|
import AppKit
|
|
1487
1487
|
let path = CommandLine.arguments[1]
|
|
@@ -1496,7 +1496,7 @@ let text = (request.results ?? [])
|
|
|
1496
1496
|
.compactMap { $0.topCandidates(1).first?.string }
|
|
1497
1497
|
.joined(separator: " ")
|
|
1498
1498
|
print(text)
|
|
1499
|
-
`;async function macOSVisionOcr(pngBuffer){if(process.platform!=="darwin"){throw new Error("macOSVisionOcr is only available on macOS; pass a custom ocr(buffer) instead")}const dir=mkdtempSync(path4.join(tmpdir2(),"omowright-ocr-"));try{const imagePath=path4.join(dir,"region.png");const scriptPath=path4.join(dir,"ocr.swift");writeFileSync(imagePath,pngBuffer);writeFileSync(scriptPath,MACOS_VISION_OCR_SWIFT);return await new Promise((resolve,reject)=>{const child=spawn2("swift",[scriptPath,imagePath],{stdio:["ignore","pipe","pipe"]});let stdout="";let stderr="";child.stdout.on("data",(chunk)=>{stdout+=chunk});child.stderr.on("data",(chunk)=>{stderr+=chunk});child.on("error",reject);child.on("close",(code)=>{if(code===0){const text=stdout.trim();resolve(text.length>0?text:null)}else{reject(new Error(`Vision OCR failed (exit ${code}): ${stderr.trim()}`))}})})}finally{rmSync(dir,{recursive:true,force:true})}}function createCaptcha(page,options={}){const ocr=options.ocr??macOSVisionOcr;function centerOf(bounds){return{x:bounds.x+bounds.width/2,y:bounds.y+bounds.height/2}}async function settleAndSnapshot(settleMs){await new Promise((resolve)=>setTimeout(resolve,settleMs));return compactSnapshot(await page.snapshot())}return{page,async click(bounds,opts={}){const point=centerOf(bounds);await page.mouse.click(point.x,point.y);return settleAndSnapshot(opts.settleMs??POST_ACTION_SETTLE_MS)},async drag(from,to,opts={}){const steps=opts.steps??DEFAULT_DRAG_STEPS;await page.mouse.move(from.x,from.y);await page.mouse.down();try{await page.mouse.move(to.x,to.y,{steps})}finally{await page.mouse.up()}return settleAndSnapshot(opts.settleMs??POST_ACTION_SETTLE_MS)},async readText(bounds){const buffer=await page.screenshot(bounds?{type:"png",clip:bounds}:{type:"png"});return ocr(buffer)}}}var EVENT_NAMES=["tabOpened","tabClosed","popupOpened","downloadStarted","downloadProgress","downloadFinished"];var BROWSER_EVENT_NAMES=Object.freeze([...EVENT_NAMES]);var DEFAULT_TIMEOUT=30000;var TERMINAL_LIMIT=4096;function pageTarget(info){return info?.type==="page"&&info?.targetId?info:null}function normalizeTarget(info){return{targetId:String(info.targetId),type:"page",url:info.url??"about:blank",title:info.title??"",openerId:info.openerId??null,openerFrameId:info.openerFrameId??null,browserContextId:info.browserContextId??null,attached:Boolean(info.attached),subtype:info.subtype??null}}function errorValue(value){return value instanceof Error?value:Error(String(value))}class BrowserEventBridge{#cdp;#options;#listeners=new Map;#unsubscribers=[];#targetInfos=new Map;#crashes=new Map;#downloads=new Map;#terminalDownloads=new Map;#terminal=new Set;#frameToTarget=new Map;#sessionToTarget=new Map;#waits=new Set;#disposed=false;#explicitDispose=false;ready;constructor(connection,options){if(!connection?.cdp)throw new TypeError("createEvents requires a browser connection");this.#cdp=connection.cdp;this.#options=this.#validate(options);this.#register();this.ready=this.#initialize()}#validate(options){const behavior=options.downloadBehavior??"default";if(!["default","allow","deny","allowAndName"].includes(behavior))throw new TypeError("Invalid downloadBehavior");if(behavior==="allow"&&!pathIsAbsolute(options.downloadPath))throw new TypeError("downloadPath must be an absolute path for allow");return{...options,downloadBehavior:behavior}}#register(){const on=(method,fn)=>this.#unsubscribers.push(this.#cdp.on(method,fn));on("Target.targetCreated",(info)=>this.#created(info,false));on("Target.targetDestroyed",(info)=>this.#destroyed(info));on("Target.targetInfoChanged",(info)=>{const target=pageTarget(info?.targetInfo);if(target&&this.#targetInfos.has(target.targetId))this.#targetInfos.set(target.targetId,normalizeTarget(target))});on("Target.targetCrashed",(info)=>{if(info?.targetId)this.#crashes.set(info.targetId,{status:info.status??"",errorCode:info.errorCode??0})});on("Target.attachedToTarget",(info,meta)=>{const targetId=info?.targetInfo?.targetId;const sessionId=info?.sessionId??meta?.sessionId;if(targetId&&sessionId){this.#sessionToTarget.set(sessionId,targetId)}});on("Target.detachedFromTarget",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId)this.#sessionToTarget.delete(sessionId)});on("Browser.downloadWillBegin",(info)=>this.#downloadBegin(info));on("Browser.downloadProgress",(info)=>this.#downloadProgress(info));on("Page.frameAttached",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frameId)this.#mapFrame(info.frameId,sessionId)});on("Page.frameNavigated",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frame?.id)this.#mapFrame(info.frame.id,sessionId)});if(this.#cdp.transportEvents?.on){this.#unsubscribers.push(this.#cdp.transportEvents.on("disconnected",()=>{this.#disconnectDownloads();this.#cancelWaits(Error("Browser transport disconnected"))}));this.#unsubscribers.push(this.#cdp.transportEvents.on("reconnected",()=>{this.#reconcile().catch((error)=>console.warn("[events] target reconciliation failed",error));this.#configureDownloads().catch((error)=>console.warn("[events] download reconfiguration failed",error))}))}}async#initialize(){await this.#cdp.ensureConnected();await this.#cdp.send("Target.setDiscoverTargets",{discover:true});const result=await this.#cdp.send("Target.getTargets");await this.#configureDownloads();const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#reconcile(){const result=await this.#cdp.send("Target.getTargets");const current=new Set((result?.targetInfos??[]).filter((info)=>pageTarget(info)).map((info)=>String(info.targetId)));for(const id of[...this.#targetInfos.keys()])if(!current.has(id))this.#destroyed({targetId:id});const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#configureDownloads(){const params={behavior:this.#options.downloadBehavior,eventsEnabled:true};if(this.#options.downloadBehavior==="allow"||this.#options.downloadBehavior==="allowAndName")params.downloadPath=this.#options.downloadPath;await this.#cdp.send("Browser.setDownloadBehavior",params)}#mapFrame(frameId,sessionId){const targetId=this.#sessionToTarget.get(sessionId)??[...this.#targetInfos.values()].find((info)=>info.attached&&info.targetId===sessionId)?.targetId;if(targetId)this.#frameToTarget.set(frameId,targetId)}#created(info,initial){const target=pageTarget(info?.targetInfo??info);if(!target)return;const normalized=normalizeTarget(target);const id=normalized.targetId;if(this.#targetInfos.has(id))return;this.#targetInfos.set(id,normalized);const popup=Boolean(normalized.openerId&&this.#targetInfos.has(normalized.openerId));this.#dispatch("tabOpened",{...normalized,popup,initial:Boolean(initial)});if(popup)this.#dispatch("popupOpened",{targetId:id,openerTargetId:normalized.openerId,openerFrameId:normalized.openerFrameId,url:normalized.url,title:normalized.title,initial:Boolean(initial)})}#destroyed(info){const id=info?.targetId;const last=this.#targetInfos.get(id);if(!last)return;this.#targetInfos.delete(id);const crash=this.#crashes.get(id)??null;this.#crashes.delete(id);for(const[frame,targetId]of this.#frameToTarget)if(targetId===id)this.#frameToTarget.delete(frame);this.#dispatch("tabClosed",{targetId:id,reason:crash?"crashed":"destroyed",lastKnown:last,crash})}#rememberTerminal(guid){this.#terminal.delete(guid);this.#terminal.add(guid);if(this.#terminal.size>TERMINAL_LIMIT){const oldest=this.#terminal.values().next().value;this.#terminal.delete(oldest);this.#terminalDownloads.delete(oldest)}}#downloadBegin(info={}){if(!info.guid||this.#terminal.has(info.guid))return;const record={guid:info.guid,url:info.url??null,suggestedFilename:info.suggestedFilename??"",frameId:info.frameId??null,targetId:this.#frameToTarget.get(info.frameId)??null,totalBytes:0,receivedBytes:0,filePath:null};this.#downloads.set(info.guid,record);this.#dispatch("downloadStarted",{guid:record.guid,url:record.url,suggestedFilename:record.suggestedFilename,frameId:record.frameId,targetId:record.targetId})}#downloadProgress(info={}){if(!info.guid)return;const alreadyTerminal=this.#terminal.has(info.guid);const record=this.#downloads.get(info.guid)??this.#terminalDownloads.get(info.guid);if(!record)return;Object.assign(record,{totalBytes:Number(info.totalBytes??0),receivedBytes:Number(info.receivedBytes??0),filePath:info.filePath??null});this.#dispatch("downloadProgress",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,filePath:record.filePath,targetId:record.targetId});if((info.state==="completed"||info.state==="canceled")&&!alreadyTerminal){this.#rememberTerminal(info.guid);this.#downloads.delete(info.guid);this.#terminalDownloads.set(info.guid,record);this.#dispatch("downloadFinished",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,cdpState:info.state,filePath:record.filePath,targetId:record.targetId,error:null})}}#disconnectDownloads(){if(this.#explicitDispose)return;for(const record of this.#downloads.values()){this.#rememberTerminal(record.guid);this.#dispatch("downloadFinished",{guid:record.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:"failed",cdpState:null,filePath:record.filePath,targetId:record.targetId,error:{code:"browserDisconnected",message:"Browser transport disconnected"}})}this.#downloads.clear()}#dispatch(name,payload){const listeners=[...this.#listeners.get(name)??[]];Promise.allSettled(listeners.map((listener)=>Promise.resolve().then(()=>listener(payload)))).then((results)=>results.filter((r)=>r.status==="rejected").forEach((r)=>console.warn(`[events] ${name} listener failed`,r.reason)))}on(name,listener){this.#assertName(name);if(typeof listener!=="function")throw new TypeError("listener must be a function");const set=this.#listeners.get(name)??new Set;set.add(listener);this.#listeners.set(name,set);return()=>this.off(name,listener)}off(name,listener){this.#assertName(name);this.#listeners.get(name)?.delete(listener)}#cancelWaits(error){for(const cancel of this.#waits)cancel(error);this.#waits.clear()}waitForEvent(name,options={}){this.#assertName(name);if(this.#disposed)return Promise.reject(Error("Events bridge is disposed"));const timeout=options.timeout??DEFAULT_TIMEOUT;return new Promise((resolve,reject)=>{let done=false;let timer;const off=this.on(name,async(value)=>{try{if(options.predicate&&!await options.predicate(value))return;if(done)return;done=true;clearTimeout(timer);this.#waits.delete(cancel);off();resolve(value)}catch(error){if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(errorValue(error))}}});const cancel=(error)=>{if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(error??Error("Events bridge disposed"))}};this.#waits.add(cancel);timer=setTimeout(()=>cancel(Error(`Timed out waiting for browser event "${name}"`)),timeout)})}#assertName(name){if(!EVENT_NAMES.includes(name))throw new TypeError(`Unknown browser event: ${String(name)}`)}async dispose(){if(this.#disposed)return;this.#disposed=true;this.#explicitDispose=true;this.#cancelWaits();for(const off of this.#unsubscribers)off();this.#unsubscribers=[];this.#listeners.clear();await this.ready.catch(()=>{})}}function pathIsAbsolute(value){return typeof value==="string"&&(value.startsWith("/")||/^[A-Za-z]:[\\/]/.test(value))}function createEvents(connection,options={}){return new BrowserEventBridge(connection,options)}init_agent_tabs();init_viewport();import{randomUUID}from"node:crypto";import path7 from"node:path";import{mkdir as mkdir4,open as open3,readFile as readFile2,rename,lstat,chmod as chmod2}from"node:fs/promises";import{homedir}from"node:os";import{TextDecoder as TextDecoder2}from"node:util";import{mkdirSync}from"node:fs";import path6 from"node:path";import{tmpdir as tmpdir3}from"node:os";import{existsSync,readFileSync as readFileSync2}from"node:fs";import path5 from"node:path";var DatabaseCtor;async function loadSqlite(){if(DatabaseCtor)return DatabaseCtor;try{const mod=await import("node:sqlite");DatabaseCtor=(file)=>new mod.DatabaseSync(file,{readOnly:true,readBigInts:true})}catch{const mod=await import("bun:sqlite");DatabaseCtor=(file)=>new mod.Database(file,{readonly:true})}return DatabaseCtor}var CHROME_EPOCH_OFFSET_MS=11644473600000;var chromeTimeToMs=(time)=>Number(BigInt(time)/1000n)-CHROME_EPOCH_OFFSET_MS;var msToChromeTime=(ms)=>BigInt(Math.floor((ms+CHROME_EPOCH_OFFSET_MS)*1000));var EXTENSION_BRIDGE_REQUIRED="requires the extension bridge in standalone OmOWright";function createProfileApis(profilePath){function profileDir(){if(!profilePath){throw new UnsupportedOperationError("chrome.bookmarks/history/topSites need a profilePath (the browser's user-data-dir); pass it to createChromeApi(connection, { profilePath })")}return path5.join(profilePath,"Default")}async function openDb(fileName){const file=path5.join(profileDir(),fileName);if(!existsSync(file))return null;const ctor=await loadSqlite();return ctor(file)}async function withDb(fileName,fn){const db=await openDb(fileName);if(!db)return[];try{return fn(db)}finally{db.close()}}function bookmarkRoots(){const file=path5.join(profileDir(),"Bookmarks");if(!existsSync(file))return[];const parsed=JSON.parse(readFileSync2(file,"utf8"));return Object.values(parsed.roots??{}).filter((node)=>node&&typeof node==="object"&&node.id)}function walkBookmarks(nodes,visit,acc=[]){for(const node of nodes){visit(node,acc);if(Array.isArray(node.children))walkBookmarks(node.children,visit,acc)}return acc}function findBookmark(id){for(const root of bookmarkRoots()){const found=walkBookmarks([root],(node,acc)=>{if(node.id===String(id))acc.push(node)});if(found.length>0)return found[0]}return null}function toBookmark(node){const out={id:node.id,title:node.name??"",type:node.type};if(node.url)out.url=node.url;if(node.date_added)out.dateAdded=chromeTimeToMs(node.date_added);if(node.children)out.children=node.children.map(toBookmark);return out}const bookmarks={async get(idOrIdList){const ids=Array.isArray(idOrIdList)?idOrIdList:[idOrIdList];return ids.map(findBookmark).filter(Boolean).map(toBookmark)},async getChildren(id){const node=findBookmark(id);return(node?.children??[]).map(toBookmark)},async getRecent(numberOfItems){const urls=walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type==="url")acc.push(node)});return urls.sort((a,b)=>Number(b.date_added??0)-Number(a.date_added??0)).slice(0,numberOfItems).map(toBookmark)},async getSubTree(id){const node=findBookmark(id);return node?[toBookmark(node)]:[]},async getTree(){return bookmarkRoots().map(toBookmark)},async search(query){const needle=typeof query==="string"?query.toLowerCase():(query.query??"").toLowerCase();const url=typeof query==="object"?query.url?.toLowerCase():undefined;const title=typeof query==="object"?query.title?.toLowerCase():undefined;return walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type!=="url")return;const name=(node.name??"").toLowerCase();const href=(node.url??"").toLowerCase();const ok=needle&&(name.includes(needle)||href.includes(needle))||url&&href.includes(url)||title&&name.includes(title);if(ok)acc.push(node)}).map(toBookmark)},async create(){throw new UnsupportedOperationError(`chrome.bookmarks.create ${EXTENSION_BRIDGE_REQUIRED}`)},async update(){throw new UnsupportedOperationError(`chrome.bookmarks.update ${EXTENSION_BRIDGE_REQUIRED}`)},async move(){throw new UnsupportedOperationError(`chrome.bookmarks.move ${EXTENSION_BRIDGE_REQUIRED}`)},async remove(){throw new UnsupportedOperationError(`chrome.bookmarks.remove ${EXTENSION_BRIDGE_REQUIRED}`)},async removeTree(){throw new UnsupportedOperationError(`chrome.bookmarks.removeTree ${EXTENSION_BRIDGE_REQUIRED}`)}};const history={async search(query){const text=`%${query.text??""}%`;const start=query.startTime!=null?msToChromeTime(query.startTime):0n;const end=query.endTime!=null?msToChromeTime(query.endTime):99999999999999999n;const max=query.maxResults??100;return await withDb("History",(db)=>db.prepare(`SELECT id, url, title, visit_count, last_visit_time FROM urls
|
|
1499
|
+
`;async function macOSVisionOcr(pngBuffer){if(process.platform!=="darwin"){throw new Error("macOSVisionOcr is only available on macOS; pass a custom ocr(buffer) instead")}const dir=mkdtempSync(path4.join(tmpdir2(),"omowright-ocr-"));try{const imagePath=path4.join(dir,"region.png");const scriptPath=path4.join(dir,"ocr.swift");writeFileSync(imagePath,pngBuffer);writeFileSync(scriptPath,MACOS_VISION_OCR_SWIFT);return await new Promise((resolve,reject)=>{const child=spawn2("swift",[scriptPath,imagePath],{stdio:["ignore","pipe","pipe"]});let stdout="";let stderr="";child.stdout.on("data",(chunk)=>{stdout+=chunk});child.stderr.on("data",(chunk)=>{stderr+=chunk});child.on("error",reject);child.on("close",(code)=>{if(code===0){const text=stdout.trim();resolve(text.length>0?text:null)}else{reject(new Error(`Vision OCR failed (exit ${code}): ${stderr.trim()}`))}})})}finally{rmSync(dir,{recursive:true,force:true})}}function createCaptcha(page,options={}){const ocr=options.ocr??macOSVisionOcr;function centerOf(bounds){validatePoint(bounds);if(!Number.isFinite(bounds.width)||bounds.width<=0||!Number.isFinite(bounds.height)||bounds.height<=0||!Number.isFinite(bounds.x+bounds.width)||!Number.isFinite(bounds.y+bounds.height)){throw new RangeError("bounds must have positive finite dimensions and finite edges")}return{x:bounds.x+bounds.width/2,y:bounds.y+bounds.height/2}}async function settleAndSnapshot(settleMs){await new Promise((resolve)=>setTimeout(resolve,settleMs));return compactSnapshot(await page.snapshot())}return{page,waitFor(opts){return waitForCaptcha(page,opts)},async click(bounds,opts={}){const center=centerOf(bounds);const point=opts.point===undefined?center:opts.point;validatePoint(point);if(point.x<bounds.x||point.x>=bounds.x+bounds.width||point.y<bounds.y||point.y>=bounds.y+bounds.height){throw new RangeError("point must be inside bounds")}const settleMs=opts.settleMs===undefined?POST_ACTION_SETTLE_MS:opts.settleMs;validateSettleMs(settleMs);if(opts.approachSteps!==undefined){validateSteps(opts.approachSteps);await page.mouse.move(point.x,point.y,{steps:opts.approachSteps})}await page.mouse.click(point.x,point.y);return settleAndSnapshot(settleMs)},async drag(from,to,opts={}){validatePoint(from);validatePoint(to);const steps=opts.steps===undefined?DEFAULT_DRAG_STEPS:opts.steps;validateSteps(steps);const settleMs=opts.settleMs===undefined?POST_ACTION_SETTLE_MS:opts.settleMs;validateSettleMs(settleMs);await page.mouse.move(from.x,from.y);await page.mouse.down();try{await page.mouse.move(to.x,to.y,{steps})}finally{await page.mouse.up()}return settleAndSnapshot(settleMs)},async readText(bounds){const buffer=await page.screenshot(bounds?{type:"png",clip:bounds}:{type:"png"});return ocr(buffer)}}}var EVENT_NAMES=["tabOpened","tabClosed","popupOpened","downloadStarted","downloadProgress","downloadFinished"];var BROWSER_EVENT_NAMES=Object.freeze([...EVENT_NAMES]);var DEFAULT_TIMEOUT=30000;var TERMINAL_LIMIT=4096;function pageTarget(info){return info?.type==="page"&&info?.targetId?info:null}function normalizeTarget(info){return{targetId:String(info.targetId),type:"page",url:info.url??"about:blank",title:info.title??"",openerId:info.openerId??null,openerFrameId:info.openerFrameId??null,browserContextId:info.browserContextId??null,attached:Boolean(info.attached),subtype:info.subtype??null}}function errorValue(value){return value instanceof Error?value:Error(String(value))}class BrowserEventBridge{#cdp;#options;#listeners=new Map;#unsubscribers=[];#targetInfos=new Map;#crashes=new Map;#downloads=new Map;#terminalDownloads=new Map;#terminal=new Set;#frameToTarget=new Map;#sessionToTarget=new Map;#waits=new Set;#disposed=false;#explicitDispose=false;ready;constructor(connection,options){if(!connection?.cdp)throw new TypeError("createEvents requires a browser connection");this.#cdp=connection.cdp;this.#options=this.#validate(options);this.#register();this.ready=this.#initialize()}#validate(options){const behavior=options.downloadBehavior??"default";if(!["default","allow","deny","allowAndName"].includes(behavior))throw new TypeError("Invalid downloadBehavior");if(behavior==="allow"&&!pathIsAbsolute(options.downloadPath))throw new TypeError("downloadPath must be an absolute path for allow");return{...options,downloadBehavior:behavior}}#register(){const on=(method,fn)=>this.#unsubscribers.push(this.#cdp.on(method,fn));on("Target.targetCreated",(info)=>this.#created(info,false));on("Target.targetDestroyed",(info)=>this.#destroyed(info));on("Target.targetInfoChanged",(info)=>{const target=pageTarget(info?.targetInfo);if(target&&this.#targetInfos.has(target.targetId))this.#targetInfos.set(target.targetId,normalizeTarget(target))});on("Target.targetCrashed",(info)=>{if(info?.targetId)this.#crashes.set(info.targetId,{status:info.status??"",errorCode:info.errorCode??0})});on("Target.attachedToTarget",(info,meta)=>{const targetId=info?.targetInfo?.targetId;const sessionId=info?.sessionId??meta?.sessionId;if(targetId&&sessionId){this.#sessionToTarget.set(sessionId,targetId)}});on("Target.detachedFromTarget",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId)this.#sessionToTarget.delete(sessionId)});on("Browser.downloadWillBegin",(info)=>this.#downloadBegin(info));on("Browser.downloadProgress",(info)=>this.#downloadProgress(info));on("Page.frameAttached",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frameId)this.#mapFrame(info.frameId,sessionId)});on("Page.frameNavigated",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frame?.id)this.#mapFrame(info.frame.id,sessionId)});if(this.#cdp.transportEvents?.on){this.#unsubscribers.push(this.#cdp.transportEvents.on("disconnected",()=>{this.#disconnectDownloads();this.#cancelWaits(Error("Browser transport disconnected"))}));this.#unsubscribers.push(this.#cdp.transportEvents.on("reconnected",()=>{this.#reconcile().catch((error)=>console.warn("[events] target reconciliation failed",error));this.#configureDownloads().catch((error)=>console.warn("[events] download reconfiguration failed",error))}))}}async#initialize(){await this.#cdp.ensureConnected();await this.#cdp.send("Target.setDiscoverTargets",{discover:true});const result=await this.#cdp.send("Target.getTargets");await this.#configureDownloads();const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#reconcile(){const result=await this.#cdp.send("Target.getTargets");const current=new Set((result?.targetInfos??[]).filter((info)=>pageTarget(info)).map((info)=>String(info.targetId)));for(const id of[...this.#targetInfos.keys()])if(!current.has(id))this.#destroyed({targetId:id});const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#configureDownloads(){const params={behavior:this.#options.downloadBehavior,eventsEnabled:true};if(this.#options.downloadBehavior==="allow"||this.#options.downloadBehavior==="allowAndName")params.downloadPath=this.#options.downloadPath;await this.#cdp.send("Browser.setDownloadBehavior",params)}#mapFrame(frameId,sessionId){const targetId=this.#sessionToTarget.get(sessionId)??[...this.#targetInfos.values()].find((info)=>info.attached&&info.targetId===sessionId)?.targetId;if(targetId)this.#frameToTarget.set(frameId,targetId)}#created(info,initial){const target=pageTarget(info?.targetInfo??info);if(!target)return;const normalized=normalizeTarget(target);const id=normalized.targetId;if(this.#targetInfos.has(id))return;this.#targetInfos.set(id,normalized);const popup=Boolean(normalized.openerId&&this.#targetInfos.has(normalized.openerId));this.#dispatch("tabOpened",{...normalized,popup,initial:Boolean(initial)});if(popup)this.#dispatch("popupOpened",{targetId:id,openerTargetId:normalized.openerId,openerFrameId:normalized.openerFrameId,url:normalized.url,title:normalized.title,initial:Boolean(initial)})}#destroyed(info){const id=info?.targetId;const last=this.#targetInfos.get(id);if(!last)return;this.#targetInfos.delete(id);const crash=this.#crashes.get(id)??null;this.#crashes.delete(id);for(const[frame,targetId]of this.#frameToTarget)if(targetId===id)this.#frameToTarget.delete(frame);this.#dispatch("tabClosed",{targetId:id,reason:crash?"crashed":"destroyed",lastKnown:last,crash})}#rememberTerminal(guid){this.#terminal.delete(guid);this.#terminal.add(guid);if(this.#terminal.size>TERMINAL_LIMIT){const oldest=this.#terminal.values().next().value;this.#terminal.delete(oldest);this.#terminalDownloads.delete(oldest)}}#downloadBegin(info={}){if(!info.guid||this.#terminal.has(info.guid))return;const record={guid:info.guid,url:info.url??null,suggestedFilename:info.suggestedFilename??"",frameId:info.frameId??null,targetId:this.#frameToTarget.get(info.frameId)??null,totalBytes:0,receivedBytes:0,filePath:null};this.#downloads.set(info.guid,record);this.#dispatch("downloadStarted",{guid:record.guid,url:record.url,suggestedFilename:record.suggestedFilename,frameId:record.frameId,targetId:record.targetId})}#downloadProgress(info={}){if(!info.guid)return;const alreadyTerminal=this.#terminal.has(info.guid);const record=this.#downloads.get(info.guid)??this.#terminalDownloads.get(info.guid);if(!record)return;Object.assign(record,{totalBytes:Number(info.totalBytes??0),receivedBytes:Number(info.receivedBytes??0),filePath:info.filePath??null});this.#dispatch("downloadProgress",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,filePath:record.filePath,targetId:record.targetId});if((info.state==="completed"||info.state==="canceled")&&!alreadyTerminal){this.#rememberTerminal(info.guid);this.#downloads.delete(info.guid);this.#terminalDownloads.set(info.guid,record);this.#dispatch("downloadFinished",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,cdpState:info.state,filePath:record.filePath,targetId:record.targetId,error:null})}}#disconnectDownloads(){if(this.#explicitDispose)return;for(const record of this.#downloads.values()){this.#rememberTerminal(record.guid);this.#dispatch("downloadFinished",{guid:record.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:"failed",cdpState:null,filePath:record.filePath,targetId:record.targetId,error:{code:"browserDisconnected",message:"Browser transport disconnected"}})}this.#downloads.clear()}#dispatch(name,payload){const listeners=[...this.#listeners.get(name)??[]];Promise.allSettled(listeners.map((listener)=>Promise.resolve().then(()=>listener(payload)))).then((results)=>results.filter((r)=>r.status==="rejected").forEach((r)=>console.warn(`[events] ${name} listener failed`,r.reason)))}on(name,listener){this.#assertName(name);if(typeof listener!=="function")throw new TypeError("listener must be a function");const set=this.#listeners.get(name)??new Set;set.add(listener);this.#listeners.set(name,set);return()=>this.off(name,listener)}off(name,listener){this.#assertName(name);this.#listeners.get(name)?.delete(listener)}#cancelWaits(error){for(const cancel of this.#waits)cancel(error);this.#waits.clear()}waitForEvent(name,options={}){this.#assertName(name);if(this.#disposed)return Promise.reject(Error("Events bridge is disposed"));const timeout=options.timeout??DEFAULT_TIMEOUT;return new Promise((resolve,reject)=>{let done=false;let timer;const off=this.on(name,async(value)=>{try{if(options.predicate&&!await options.predicate(value))return;if(done)return;done=true;clearTimeout(timer);this.#waits.delete(cancel);off();resolve(value)}catch(error){if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(errorValue(error))}}});const cancel=(error)=>{if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(error??Error("Events bridge disposed"))}};this.#waits.add(cancel);timer=setTimeout(()=>cancel(Error(`Timed out waiting for browser event "${name}"`)),timeout)})}#assertName(name){if(!EVENT_NAMES.includes(name))throw new TypeError(`Unknown browser event: ${String(name)}`)}async dispose(){if(this.#disposed)return;this.#disposed=true;this.#explicitDispose=true;this.#cancelWaits();for(const off of this.#unsubscribers)off();this.#unsubscribers=[];this.#listeners.clear();await this.ready.catch(()=>{})}}function pathIsAbsolute(value){return typeof value==="string"&&(value.startsWith("/")||/^[A-Za-z]:[\\/]/.test(value))}function createEvents(connection,options={}){return new BrowserEventBridge(connection,options)}init_agent_tabs();init_viewport();import{randomUUID}from"node:crypto";import path7 from"node:path";import{mkdir as mkdir4,open as open3,readFile as readFile2,rename,lstat,chmod as chmod2}from"node:fs/promises";import{homedir}from"node:os";import{TextDecoder as TextDecoder2}from"node:util";import{mkdirSync}from"node:fs";import path6 from"node:path";import{tmpdir as tmpdir3}from"node:os";import{existsSync,readFileSync as readFileSync2}from"node:fs";import path5 from"node:path";var DatabaseCtor;async function loadSqlite(){if(DatabaseCtor)return DatabaseCtor;try{const mod=await import("node:sqlite");DatabaseCtor=(file)=>new mod.DatabaseSync(file,{readOnly:true,readBigInts:true})}catch{const mod=await import("bun:sqlite");DatabaseCtor=(file)=>new mod.Database(file,{readonly:true})}return DatabaseCtor}var CHROME_EPOCH_OFFSET_MS=11644473600000;var chromeTimeToMs=(time)=>Number(BigInt(time)/1000n)-CHROME_EPOCH_OFFSET_MS;var msToChromeTime=(ms)=>BigInt(Math.floor((ms+CHROME_EPOCH_OFFSET_MS)*1000));var EXTENSION_BRIDGE_REQUIRED="requires the extension bridge in standalone OmOWright";function createProfileApis(profilePath){function profileDir(){if(!profilePath){throw new UnsupportedOperationError("chrome.bookmarks/history/topSites need a profilePath (the browser's user-data-dir); pass it to createChromeApi(connection, { profilePath })")}return path5.join(profilePath,"Default")}async function openDb(fileName){const file=path5.join(profileDir(),fileName);if(!existsSync(file))return null;const ctor=await loadSqlite();return ctor(file)}async function withDb(fileName,fn){const db=await openDb(fileName);if(!db)return[];try{return fn(db)}finally{db.close()}}function bookmarkRoots(){const file=path5.join(profileDir(),"Bookmarks");if(!existsSync(file))return[];const parsed=JSON.parse(readFileSync2(file,"utf8"));return Object.values(parsed.roots??{}).filter((node)=>node&&typeof node==="object"&&node.id)}function walkBookmarks(nodes,visit,acc=[]){for(const node of nodes){visit(node,acc);if(Array.isArray(node.children))walkBookmarks(node.children,visit,acc)}return acc}function findBookmark(id){for(const root of bookmarkRoots()){const found=walkBookmarks([root],(node,acc)=>{if(node.id===String(id))acc.push(node)});if(found.length>0)return found[0]}return null}function toBookmark(node){const out={id:node.id,title:node.name??"",type:node.type};if(node.url)out.url=node.url;if(node.date_added)out.dateAdded=chromeTimeToMs(node.date_added);if(node.children)out.children=node.children.map(toBookmark);return out}const bookmarks={async get(idOrIdList){const ids=Array.isArray(idOrIdList)?idOrIdList:[idOrIdList];return ids.map(findBookmark).filter(Boolean).map(toBookmark)},async getChildren(id){const node=findBookmark(id);return(node?.children??[]).map(toBookmark)},async getRecent(numberOfItems){const urls=walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type==="url")acc.push(node)});return urls.sort((a,b)=>Number(b.date_added??0)-Number(a.date_added??0)).slice(0,numberOfItems).map(toBookmark)},async getSubTree(id){const node=findBookmark(id);return node?[toBookmark(node)]:[]},async getTree(){return bookmarkRoots().map(toBookmark)},async search(query){const needle=typeof query==="string"?query.toLowerCase():(query.query??"").toLowerCase();const url=typeof query==="object"?query.url?.toLowerCase():undefined;const title=typeof query==="object"?query.title?.toLowerCase():undefined;return walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type!=="url")return;const name=(node.name??"").toLowerCase();const href=(node.url??"").toLowerCase();const ok=needle&&(name.includes(needle)||href.includes(needle))||url&&href.includes(url)||title&&name.includes(title);if(ok)acc.push(node)}).map(toBookmark)},async create(){throw new UnsupportedOperationError(`chrome.bookmarks.create ${EXTENSION_BRIDGE_REQUIRED}`)},async update(){throw new UnsupportedOperationError(`chrome.bookmarks.update ${EXTENSION_BRIDGE_REQUIRED}`)},async move(){throw new UnsupportedOperationError(`chrome.bookmarks.move ${EXTENSION_BRIDGE_REQUIRED}`)},async remove(){throw new UnsupportedOperationError(`chrome.bookmarks.remove ${EXTENSION_BRIDGE_REQUIRED}`)},async removeTree(){throw new UnsupportedOperationError(`chrome.bookmarks.removeTree ${EXTENSION_BRIDGE_REQUIRED}`)}};const history={async search(query){const text=`%${query.text??""}%`;const start=query.startTime!=null?msToChromeTime(query.startTime):0n;const end=query.endTime!=null?msToChromeTime(query.endTime):99999999999999999n;const max=query.maxResults??100;return await withDb("History",(db)=>db.prepare(`SELECT id, url, title, visit_count, last_visit_time FROM urls
|
|
1500
1500
|
WHERE (url LIKE ? OR title LIKE ?) AND last_visit_time BETWEEN ? AND ?
|
|
1501
1501
|
ORDER BY last_visit_time DESC LIMIT ?`).all(text,text,start,end,max).map((row)=>({id:String(row.id),url:row.url,title:row.title??"",visitCount:Number(row.visit_count),lastVisitTime:chromeTimeToMs(row.last_visit_time)})))},async getVisits(details){return await withDb("History",(db)=>db.prepare(`SELECT v.id, v.visit_time, v.transition FROM visits v
|
|
1502
1502
|
JOIN urls u ON u.id = v.url WHERE u.url = ?
|
|
@@ -1519,7 +1519,8 @@ ${snapshot.tree}`}}var DEFAULT_TIMEOUT_MS=30000;var JSON_MIME=/^application\/jso
|
|
|
1519
1519
|
`)}function dispose(){if(disposed)return;disposed=true;for(const off of unsubscribers.splice(0,unsubscribers.length))off();for(const waiter of[...waiters])waiter.reject(Error("Network snoop disposed"));waiters.clear();inflight.clear()}return{pop,peek,popJson,waitFor,summary,dispose}}function globToRegExp(glob){let source="";for(const char of glob){if(char==="*")source+=".*";else if(char==="?")source+=".";else if("\\^$+{}[]()|.".includes(char))source+=`\\${char}`;else source+=char}return new RegExp(`^${source}$`)}function hasGlobChars(value){return value.includes("*")||value.includes("?")}function matches(match,request){if(typeof match==="function"){return Boolean(match({url:request.url,method:request.method,resourceType:request.resourceType,headers:request.headers}))}if(match instanceof RegExp)return match.test(request.url);if(typeof match==="string"){if(hasGlobChars(match))return globToRegExp(match).test(request.url);return request.url.includes(match)}return false}function sameMatch(left,right){if(left===right)return true;if(left instanceof RegExp&&right instanceof RegExp)return left.source===right.source&&left.flags===right.flags;return false}function toHeaderEntries(headers){if(!headers)return[];if(Array.isArray(headers)){return headers.map((header)=>({name:String(header.name),value:String(header.value??"")}))}return Object.entries(headers).map(([name,value])=>({name,value:String(value??"")}))}function encodeBody(body){if(body==null)return"";if(Buffer.isBuffer(body))return body.toString("base64");if(body instanceof Uint8Array)return Buffer.from(body).toString("base64");return Buffer.from(String(body)).toString("base64")}function buildContinue(requestId,{url,method,headers,postData}={}){const params={requestId};if(url!==undefined)params.url=url;if(method!==undefined)params.method=method;if(headers!==undefined)params.headers=toHeaderEntries(headers);if(postData!==undefined)params.postData=encodeBody(postData);return params}function buildFulfill(requestId,{status=200,headers={},contentType,body=""}={}){const responseHeaders=toHeaderEntries(headers);if(contentType!=null&&String(contentType).length>0){const hasType=responseHeaders.some((header)=>header.name.toLowerCase()==="content-type");if(!hasType)responseHeaders.push({name:"content-type",value:String(contentType)})}return{requestId,responseCode:status,responseHeaders,body:encodeBody(body)}}function createRoutes(page){if(!page?.cdp)throw new TypeError("createRoutes requires a page with cdp");const entries=[];const warned=new WeakSet;const settledIds=new Set;let enabled=false;let sessionId;let unsubscribe=null;let enablePromise=null;function warnOnce(handler,error){if(typeof handler==="function"){if(warned.has(handler))return;warned.add(handler)}if(error)console.warn("[routes] route handler failed; continuing request",error);else console.warn("[routes] route handler did not settle the request; continuing")}function findHandler(request){for(const entry of entries){if(matches(entry.match,request))return entry.handler}return}async function handlePaused(params,meta){if(!sessionId||meta?.sessionId!==sessionId)return;const requestId=params?.requestId;if(!requestId||settledIds.has(requestId))return;const request={url:params.request?.url??"",method:params.request?.method??"GET",headers:params.request?.headers??{},postData:params.request?.postData,resourceType:params.resourceType};let settled=false;const settle=(method,cdpParams)=>{if(settled)return Promise.resolve();settled=true;settledIds.add(requestId);return page.cdp.send(method,cdpParams,sessionId)};const route={request,continue(overrides={}){return settle("Fetch.continueRequest",buildContinue(requestId,overrides))},fulfill(options={}){return settle("Fetch.fulfillRequest",buildFulfill(requestId,options))},abort(errorReason="Failed"){return settle("Fetch.failRequest",{requestId,errorReason})}};const handler=findHandler(request);if(!handler){await settle("Fetch.continueRequest",{requestId});return}try{await handler(route)}catch(error){warnOnce(handler,error)}if(!settled){warnOnce(handler);await settle("Fetch.continueRequest",{requestId})}}function onPaused(params,meta){handlePaused(params,meta).catch((error)=>{console.warn("[routes] requestPaused handling failed",error)})}async function ensureEnabled(){if(enabled)return;if(!enablePromise){enablePromise=(async()=>{sessionId=await page.resolveSessionId();if(!unsubscribe)unsubscribe=page.cdp.on("Fetch.requestPaused",onPaused);await page.cdp.send("Fetch.enable",{patterns:[{urlPattern:"*",requestStage:"Request"}]},sessionId);enabled=true})()}try{await enablePromise}catch(error){enablePromise=null;throw error}}async function disableFetch(){const pending=enablePromise;enablePromise=null;if(pending)await pending.catch(()=>{});if(unsubscribe){unsubscribe();unsubscribe=null}if(!enabled)return;enabled=false;await page.cdp.send("Fetch.disable",{},sessionId)}async function route(match,handler){if(typeof handler!=="function")throw new TypeError("handler must be a function");entries.push({match,handler});await ensureEnabled()}async function unroute(match){if(match===undefined)entries.length=0;else{for(let index=entries.length-1;index>=0;index-=1){if(sameMatch(entries[index].match,match))entries.splice(index,1)}}if(entries.length===0)await disableFetch()}async function dispose(){entries.length=0;await disableFetch()}return{route,unroute,dispose,get enabled(){return enabled}}}async function viewportOf(page){return page.viewportSize()??await page.evaluate(()=>({width:innerWidth,height:innerHeight}))}async function scrollOnce(page,mode){if(mode==="end"){await page.evaluate(()=>scrollTo(0,document.scrollingElement.scrollHeight));return}const viewport=await viewportOf(page);await page.mouse.move(Math.floor(viewport.width/2),Math.floor(viewport.height/2));await page.mouse.wheel(0,Math.floor(viewport.height*0.9))}async function*collectWhileScrolling(page,snoop,options={}){const{minItems=Infinity,maxScrolls=10,extract=(json)=>Array.isArray(json)?json:[],scroll="wheel",settleMs=800}=options;if(scroll!=="wheel"&&scroll!=="end")throw new TypeError(`Unknown scroll mode: ${String(scroll)}`);let rounds=0;let total=0;let lastHeight=null;let unchanged=0;let stoppedBecause="maxScrolls";for(;;){for(const json of snoop.popJson()){for(const item of extract(json)){total+=1;yield item}}if(total>=minItems){stoppedBecause="minItems";break}if(rounds>=maxScrolls){stoppedBecause="maxScrolls";break}const height=await page.evaluate(()=>document.scrollingElement.scrollHeight);if(lastHeight!==null&&height===lastHeight){unchanged+=1;if(unchanged>=2){stoppedBecause="noGrowth";break}}else{unchanged=0}lastHeight=height;await scrollOnce(page,scroll);rounds+=1;await new Promise((resolve)=>setTimeout(resolve,settleMs))}return{rounds,total,stoppedBecause}}import{mkdir as mkdir5,writeFile as writeFile2}from"node:fs/promises";import path8 from"node:path";var SLUG_MAX=48;var SNOOP_MAX_ENTRIES=5000;function slug(name){const value=String(name??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return(value||"step").slice(0,SLUG_MAX)}function messageOf(error){return error instanceof Error?error.message:String(error)}async function readConsole(page){const api=page.console;const entries=typeof api==="function"?await api.call(page):await api?.logs?.();return Array.isArray(entries)?entries:[]}function createTrace(page,options={}){if(!page?.cdp||typeof page.resolveSessionId!=="function"){throw new TypeError("createTrace requires a page with cdp and resolveSessionId")}const{dir,screenshots=true,network=true,console:consoleLogs=true,maxBodyBytes=1e6,events}=options;if(typeof dir!=="string"||dir.length===0)throw new TypeError("createTrace requires a dir");const shotDir=path8.join(dir,"screenshots");const ready=mkdir5(shotDir,{recursive:true});const startedAt=Date.now();const records=[];const netEntries=[];const unsubscribers=[];const snoop=network?createNetworkSnoop(page,{bodies:true,maxBodyBytes,maxEntries:SNOOP_MAX_ENTRIES}):null;let seq=0;let consoleSeen=0;let sessionId=null;let stopped=false;let stopping=null;function record(event){const entry={...event,ts:Date.now(),seq:seq++};records.push(entry);return entry}const sessionReady=Promise.resolve(page.resolveSessionId()).then((id)=>{sessionId=id});sessionReady.catch((error)=>record({kind:"warning",scope:"session",message:messageOf(error)}));unsubscribers.push(page.cdp.on("Page.frameNavigated",(params,meta)=>{if(stopped||params?.frame?.parentId)return;const add=()=>record({kind:"navigation",url:params?.frame?.url??"",frameId:params?.frame?.id??null});if(sessionId===null){sessionReady.then(()=>{if(!stopped&&meta?.sessionId===sessionId)add()}).catch(()=>{});return}if(meta?.sessionId===sessionId)add()}));unsubscribers.push(page.on("dialog",(dialog)=>record({kind:"dialog",type:dialog?.type??null,message:dialog?.message??"",defaultPrompt:dialog?.defaultPrompt??"",url:dialog?.url??null})));if(events){unsubscribers.push(events.on("downloadStarted",(payload)=>record({kind:"download",phase:"started",...payload})));unsubscribers.push(events.on("downloadFinished",(payload)=>record({kind:"download",phase:"finished",...payload})))}async function capture(step,phase){const file=path8.join(shotDir,`${step.seq}-${slug(step.name)}-${phase}.png`);try{await writeFile2(file,await page.screenshot({type:"png"}));step.screenshots[phase]=file}catch(error){record({kind:"warning",scope:`screenshot:${phase}`,step:step.seq,message:messageOf(error)})}}function drainNetwork(){for(const entry of snoop?.pop()??[]){if(typeof entry.body==="string"&&Buffer.byteLength(entry.body)>maxBodyBytes){entry.body=null;entry.bodySkipped=true}netEntries.push(entry);record({kind:"network",...entry})}}async function drainConsole(){if(!consoleLogs)return;let entries;try{entries=await readConsole(page)}catch{return}if(entries.length<consoleSeen)consoleSeen=0;for(const entry of entries.slice(consoleSeen))record({kind:"console",...entry});consoleSeen=entries.length}async function drain(){drainNetwork();await drainConsole()}async function step(name,fn){if(typeof fn!=="function")throw new TypeError("trace.step requires a function");await ready;const entry=record({kind:"step",name:String(name),startedAt:Date.now(),screenshots:{}});if(screenshots)await capture(entry,"before");try{return await fn()}catch(error){entry.error=messageOf(error);throw error}finally{entry.endedAt=Date.now();entry.durationMs=entry.endedAt-entry.startedAt;if(screenshots)await capture(entry,"after");await drain()}}function mark(name,data){return record({kind:"mark",name:String(name),data:data??null})}async function finish(){await ready;await drain();stopped=true;for(const off of unsubscribers.splice(0,unsubscribers.length))off();snoop?.dispose();const ordered=[...records].sort((a,b)=>a.seq-b.seq);const summary={steps:ordered.filter((entry)=>entry.kind==="step").length,events:ordered.length,network:netEntries.length,startedAt,endedAt:Date.now(),url:page.url()};await writeFile2(path8.join(dir,"trace.jsonl"),ordered.map((entry)=>`${JSON.stringify(entry)}
|
|
1520
1520
|
`).join(""));await writeFile2(path8.join(dir,"trace.har"),`${JSON.stringify(toHar(netEntries),null,2)}
|
|
1521
1521
|
`);await writeFile2(path8.join(dir,"summary.json"),`${JSON.stringify(summary,null,2)}
|
|
1522
|
-
`);return summary}function stop(){return stopping??=finish()}return{step,mark,stop,get dir(){return dir}}}import{connect as connect2}from"node:net";import{randomBytes}from"node:crypto";import{existsSync as existsSync2,readFileSync as readFileSync3}from"node:fs";import{homedir as homedir2}from"node:os";import path9 from"node:path";import{spawn as spawn3}from"node:child_process";function resolveBskHome(env=process.env){const override=env.BSK_HOME;if(typeof override==="string"&&override.length>0)return override;const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();return path9.join(home,".bsk")}function daemonInfoPath(home){return path9.join(home,"daemon.json")}function readDaemonInfo(home){let raw;try{raw=readFileSync3(daemonInfoPath(home),"utf8")}catch{return null}let info;try{info=JSON.parse(raw)}catch{return null}if(!info||typeof info.sock_path!=="string"||info.sock_path.length===0)return null;return info}function findBskBinary(env=process.env){const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();const candidates=[env.BSK_BIN,path9.join(home,".local","bin",process.platform==="win32"?"bsk.exe":"bsk")].filter(Boolean);for(const candidate of candidates)if(existsSync2(candidate))return candidate;return"bsk"}function
|
|
1522
|
+
`);return summary}function stop(){return stopping??=finish()}return{step,mark,stop,get dir(){return dir}}}import{connect as connect2}from"node:net";import{existsSync as existsSync3}from"node:fs";import path10 from"node:path";import{randomBytes}from"node:crypto";import{existsSync as existsSync2,readFileSync as readFileSync3}from"node:fs";import{homedir as homedir2}from"node:os";import path9 from"node:path";import{spawn as spawn3}from"node:child_process";import{createHash}from"node:crypto";function resolveBskHome(env=process.env){const override=env.BSK_HOME;if(typeof override==="string"&&override.length>0)return override;const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();return path9.join(home,".bsk")}function daemonInfoPath(home){return path9.join(home,"daemon.json")}function readDaemonInfo(home){let raw;try{raw=readFileSync3(daemonInfoPath(home),"utf8")}catch{return null}let info;try{info=JSON.parse(raw)}catch{return null}if(!info||typeof info.sock_path!=="string"||info.sock_path.length===0)return null;return info}function findBskBinary(env=process.env){const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();const candidates=[env.BSK_BIN,path9.join(home,".local","bin",process.platform==="win32"?"bsk.exe":"bsk")].filter(Boolean);for(const candidate of candidates)if(existsSync2(candidate))return candidate;return"bsk"}var NO_BREAKAWAY=/Job Object/;function windowsDaemonTaskName(home){return`bsk-daemon-${createHash("sha256").update(path9.resolve(home).toLowerCase()).digest("hex").slice(0,12)}`}function windowsDaemonLaunch(home,bskBin){const literal=(value)=>`'${value.replace(/['\u2018\u2019\u201A\u201B]/g,(quote)=>quote+quote)}'`;const script=`$env:BSK_HOME = ${literal(home)}; & ${literal(bskBin)} daemon start --foreground; exit $LASTEXITCODE`;return`--headless powershell.exe -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script,"utf16le").toString("base64")}`}function startDaemonInWindowsTask({home,bskBin,timeoutMs}){const script=["$action = New-ScheduledTaskAction -Execute (Join-Path $env:SystemRoot 'System32\\conhost.exe') -Argument $env:BSK_TASK_ARGUMENT","$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew","Register-ScheduledTask -TaskName $env:BSK_TASK_NAME -Action $action -Settings $settings -Force | Out-Null","Start-ScheduledTask -TaskName $env:BSK_TASK_NAME"].join("; ");return runToExit("powershell.exe",["-NoProfile","-NonInteractive","-Command",script],{env:{...process.env,BSK_TASK_NAME:windowsDaemonTaskName(home),BSK_TASK_ARGUMENT:windowsDaemonLaunch(home,bskBin)},timeoutMs,label:"registering the bsk daemon task"})}function runToExit(command,args,{env,timeoutMs,label}){return new Promise((resolve,reject)=>{const child=spawn3(command,args,{env,stdio:["ignore","pipe","pipe"],windowsHide:true});let output="";const keep=(chunk)=>{output=(output+chunk).slice(-4096)};child.stdout.on("data",keep);child.stderr.on("data",keep);const timer=setTimeout(()=>{child.kill("SIGKILL");reject(new Error(`${label} did not return within ${timeoutMs}ms`))},timeoutMs);child.on("error",(error)=>{clearTimeout(timer);reject(new Error(`could not run ${command}: ${error.message}`))});child.on("exit",(code)=>{clearTimeout(timer);resolve({code,output:output.trim()})})})}async function startDaemonWithCli({home,env=process.env,bskBin=findBskBinary(env),timeoutMs=20000}={}){const status=await runToExit(bskBin,["status","--json"],{env:{...env,BSK_HOME:home},timeoutMs,label:"bsk status"});if(status.code===0)return;if(process.platform==="win32"&&NO_BREAKAWAY.test(status.output)){const task=await startDaemonInWindowsTask({home,bskBin,timeoutMs});if(task.code===0)return;throw new Error(`registering the bsk daemon task exited with ${task.code}${task.output?`: ${task.output}`:""}`)}throw new Error(`bsk status exited with ${status.code}${status.output?`: ${status.output}`:""}`)}var DEFAULT_TIMEOUT_MS2=35000;var DISCOVERY_RETRY_MS=250;var DISCOVERY_BUDGET_MS=15000;var TOOL_TIMEOUT_GRACE_MS=5000;var HUMAN_WAIT_METHODS=new Set(["tool.request_help","tool.tab_borrow"]);var HUMAN_WAIT_BACKSTOP_MS=3600000;function ownBudgetMs(params){const budgets=[params?.timeout_ms,params?.confirmation_timeout_ms].filter((value)=>typeof value==="number");return budgets.length>0?Math.max(...budgets)+TOOL_TIMEOUT_GRACE_MS:undefined}var PROBE_TIMEOUT_MS=2000;function defaultSocketPath(home){return path10.join(home,"run","daemon.sock")}function probeSocket(sockPath){if(process.platform==="win32"||!existsSync3(sockPath))return Promise.resolve(false);return new Promise((resolve)=>{const socket=connect2(sockPath);const done=(alive)=>{clearTimeout(timer);socket.destroy();resolve(alive)};const timer=setTimeout(()=>done(false),PROBE_TIMEOUT_MS);socket.on("error",()=>done(false));socket.on("connect",()=>socket.write(JSON.stringify({id:"probe-orphan",method:"system.ping"})+`
|
|
1523
|
+
`));socket.on("data",()=>done(true))})}class BskRpcError extends Error{constructor(code,message,data){super(message);this.name="BskRpcError";this.code=code;this.data=data}}function shortId(){return randomBytes(6).toString("hex")}function sleep2(ms){return new Promise((resolve)=>setTimeout(resolve,ms))}class BskIpcClient{#home;#sockPath;#autoStart;#startDaemon;#timeoutMs;#starting=null;constructor({home,env=process.env,sockPath,autoStart,startDaemon,timeoutMs=DEFAULT_TIMEOUT_MS2,bskBin}={}){this.#home=home??resolveBskHome(env);this.#sockPath=sockPath??null;this.#autoStart=autoStart??env.BSK_AUTO_START!=="0";this.#startDaemon=startDaemon??(()=>startDaemonWithCli({home:this.#home,env,bskBin}));this.#timeoutMs=timeoutMs}get home(){return this.#home}async resolveSocket({isAborted=()=>false}={}){if(this.#sockPath)return this.#sockPath;const found=readDaemonInfo(this.#home);if(found)return found.sock_path;const orphan=defaultSocketPath(this.#home);if(existsSync3(orphan)){if(await probeSocket(orphan))return orphan;if(isAborted())throw new BskRpcError("cancelled","cancelled before the daemon was located")}if(!this.#autoStart){throw new BskRpcError("no_daemon",`no BrowserSkill daemon published at ${this.#home}/daemon.json and automatic start is disabled (BSK_AUTO_START=0); run \`bsk daemon start\` in the environment that owns it`)}if(!this.#starting)this.#starting=this.#startDaemon().finally(()=>{this.#starting=null});await this.#starting;const deadline=Date.now()+DISCOVERY_BUDGET_MS;while(Date.now()<deadline&&!isAborted()){const info=readDaemonInfo(this.#home);if(info)return info.sock_path;await sleep2(DISCOVERY_RETRY_MS)}if(isAborted())throw new BskRpcError("cancelled","cancelled while waiting for the daemon to publish daemon.json");throw new BskRpcError("no_daemon",`daemon start was requested but ${this.#home}/daemon.json never appeared`)}call(method,params,options={}){return this.callWithHandle(method,params,options).promise}callWithHandle(method,params,{idPrefix=method.replace(/[^a-z0-9]+/gi,"-"),timeoutMs}={}){const ownBudget=ownBudgetMs(params);const humanWait=HUMAN_WAIT_METHODS.has(method);const waitMs=humanWait&&ownBudget===undefined&&timeoutMs===undefined?HUMAN_WAIT_BACKSTOP_MS:Math.max(timeoutMs??this.#timeoutMs,ownBudget??0);const rpcId=`${idPrefix}-${shortId()}`;const frame={id:rpcId,method};if(params!==undefined)frame.params=params;let aborted=false;let abortSocket=()=>{};const promise=(async()=>{const sockPath=await this.resolveSocket({isAborted:()=>aborted});if(aborted)throw new BskRpcError("cancelled",`${method} cancelled by caller`);return new Promise((resolve,reject)=>{const socket=connect2(sockPath);let buffer="";let settled=false;const finish=(fn,value)=>{if(settled)return;settled=true;clearTimeout(timer);socket.destroy();fn(value)};const timer=setTimeout(()=>{const cancels=method!=="cancel"&&!humanWait;finish(reject,new BskRpcError("timeout",`${method} did not answer within ${waitMs}ms${cancels?"; a cancel was requested at the daemon":""}`));if(cancels)this.cancel(rpcId).catch(()=>{})},waitMs);abortSocket=()=>finish(reject,new BskRpcError("cancelled",`${method} cancelled by caller`));socket.on("error",(error)=>finish(reject,new BskRpcError("no_daemon",`IPC socket ${sockPath}: ${error.code??error.message}`)));socket.on("connect",()=>socket.write(JSON.stringify(frame)+`
|
|
1523
1524
|
`));socket.on("data",(chunk)=>{buffer+=chunk.toString("utf8");const idx=buffer.indexOf(`
|
|
1524
1525
|
`);if(idx===-1)return;try{finish(resolve,decodeResponse(buffer.slice(0,idx),rpcId,method))}catch(error){finish(reject,error)}});socket.on("close",()=>finish(reject,new BskRpcError("protocol_error",`${method}: daemon closed the connection without a reply`)))})})();return{rpcId,promise,abort:()=>{aborted=true;abortSocket()}}}async cancel(rpcId){return this.call("cancel",{rpc_id:rpcId},{idPrefix:"cancel",timeoutMs:2000})}}function decodeResponse(line,rpcId,method){let reply;try{reply=JSON.parse(line)}catch{throw new BskRpcError("protocol_error",`${method}: daemon reply is not JSON: ${line.slice(0,120)}`)}const undecodableFrame=reply.id==="0"&&reply.error;if(reply.id!==rpcId&&!undecodableFrame){throw new BskRpcError("protocol_error",`${method}: reply id ${reply.id} does not match request ${rpcId}`)}if(reply.error){const{code="protocol_error",message="daemon error",data}=reply.error;throw new BskRpcError(code,message,data)}return reply.result}var REF_SHAPE=/^@?e\d+$/;function resolveTarget(target){if(typeof target==="string"){return REF_SHAPE.test(target)?{ref:target}:{selector:target}}if(target&&typeof target==="object"){if(typeof target.ref==="string")return{ref:target.ref};if(typeof target.selector==="string")return{selector:target.selector};if(typeof target.captureId==="string"){return{capture_id:target.captureId,image_x:target.x,image_y:target.y}}}throw new TypeError("target must be a ref ('e3' / '@e3'), a CSS selector, or {captureId, x, y}")}function optionalTarget(target){return target===undefined?{}:resolveTarget(target)}function compact(params){const out={};for(const[key,value]of Object.entries(params))if(value!==undefined)out[key]=value;return out}async function captureScreenshot(session,{ref,tabId,fullPage=false,scope,timeoutMs}={}){if(!fullPage){const result=await session.tool("screenshot",{ref,tab_id:tabId});return{buffer:Buffer.from(result.image_base64,"base64"),width:result.width,height:result.height,format:result.format,captureId:result.capture_id??null,captureUnavailable:result.capture_unavailable??null,tabId:result.tab_id,dialogs:result.dialogs??[]}}const started=await session.tool("screenshot_full_page",{scope,tab_id:tabId,timeout_ms:timeoutMs});const buffer=await readCapture(session,started.capture_id,started.byte_size);return{buffer,width:started.width,height:started.height,format:started.format,captureId:started.capture_id,scope:started.scope??scope??null,tabId:started.tab_id,dialogs:started.dialogs??[]}}async function readCapture(session,captureId,byteSize){const chunks=[];let offset=0;try{for(;;){const chunk=await session.tool("screenshot_read",{capture_id:captureId,offset});const bytes=Buffer.from(chunk.data_base64??"","base64");chunks.push(bytes);if(chunk.eof||bytes.length===0)break;if(chunk.next_offset<=offset)throw new Error(`screenshot_read did not advance past offset ${offset}`);offset=chunk.next_offset}}finally{await session.tool("screenshot_release",{capture_id:captureId}).catch(()=>{})}const buffer=Buffer.concat(chunks);if(typeof byteSize==="number"&&buffer.length!==byteSize){throw new Error(`screenshot capture ${captureId}: expected ${byteSize} bytes, assembled ${buffer.length}`)}return buffer}var SESSION_STOP_TIMEOUT_MS=3600000;class BskSession{#client;#stopped=false;constructor(client,{sessionId,browserInstanceId=null,agentWindowId=null,interaction=null}={}){if(typeof sessionId!=="string"||sessionId.length===0)throw new TypeError("BskSession requires a sessionId");this.#client=client;this.sessionId=sessionId;this.browserInstanceId=browserInstanceId;this.agentWindowId=agentWindowId;this.interaction=interaction}get client(){return this.#client}get stopped(){return this.#stopped}tool(name,params={}){if(this.#stopped)return Promise.reject(new Error(`BrowserSkill session ${this.sessionId} is stopped`));return this.#client.call(`tool.${name}`,compact({session_id:this.sessionId,...params}),{idPrefix:name.replace(/_/g,"-")})}navigate(url,{tabId,waitUntil,timeoutMs}={}){return this.tool("navigate",{url,tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}back({tabId,waitUntil,timeoutMs}={}){return this.tool("navigate_back",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}forward({tabId,waitUntil,timeoutMs}={}){return this.tool("navigate_forward",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}reload({tabId,waitUntil,timeoutMs,hard}={}){return this.tool("reload",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs,hard})}observe({cursor,tabId,maxDepth,maxTokens,debugSurfaces,probeHover}={}){return this.tool("observe",{cursor,tab_id:tabId,max_depth:maxDepth,max_tokens:maxTokens,debug_surfaces:debugSurfaces,probe_hover:probeHover})}snapshot({tabId,maxDepth,maxTokens}={}){return this.tool("snapshot",{tab_id:tabId,max_depth:maxDepth,max_tokens:maxTokens})}getHtml({ref,tabId,maxBytes}={}){return this.tool("get_html",{ref,tab_id:tabId,max_bytes:maxBytes})}screenshot(options){return captureScreenshot(this,options)}click(target,{button,clickCount,modifiers,tabId,timeoutMs}={}){return this.tool("click",{...resolveTarget(target),button,click_count:clickCount,modifiers,tab_id:tabId,timeout_ms:timeoutMs})}hover(target,{modifiers,settleMs,tabId,timeoutMs}={}){return this.tool("hover",{...resolveTarget(target),modifiers,settle_ms:settleMs,tab_id:tabId,timeout_ms:timeoutMs})}focus(target,{tabId,timeoutMs}={}){return this.tool("focus",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}blur(target,{tabId,timeoutMs}={}){return this.tool("blur",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}fill(target,value,{clearBefore,tabId,timeoutMs}={}){return this.tool("fill",{...resolveTarget(target),value,clear_before:clearBefore,tab_id:tabId,timeout_ms:timeoutMs})}press(key,{target,modifiers,holdMs,tabId,timeoutMs}={}){return this.tool("press",{key,...optionalTarget(target),modifiers,hold_ms:holdMs,tab_id:tabId,timeout_ms:timeoutMs})}select(target,values,{tabId,timeoutMs}={}){return this.tool("select",{...resolveTarget(target),values:Array.isArray(values)?values:[values],tab_id:tabId,timeout_ms:timeoutMs})}scrollTo(target,{tabId,timeoutMs}={}){return this.tool("scroll_to",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}wheel({deltaX=0,deltaY=0,target,modifiers,tabId,timeoutMs}={}){return this.tool("wheel",{...optionalTarget(target),delta_x:deltaX,delta_y:deltaY,modifiers,tab_id:tabId,timeout_ms:timeoutMs})}evaluate(expression,{tabId,awaitPromise,returnByValue,timeoutMs}={}){return this.tool("evaluate",{expression,tab_id:tabId,await_promise:awaitPromise,return_by_value:returnByValue,timeout_ms:timeoutMs})}tabList({scope}={}){return this.tool("tab_list",{scope})}tabCreate({url,active,index}={}){return this.tool("tab_create",{url,active,index})}tabClose(tabId){return this.tool("tab_close",{tab_id:tabId})}tabSelect(tabId){return this.tool("tab_select",{tab_id:tabId})}tabBorrow(tabId,{confirmationTimeoutMs}={}){return this.tool("tab_borrow",{tab_id:tabId,confirmation_timeout_ms:confirmationTimeoutMs})}tabReturn(tabId){return this.tool("tab_return",{tab_id:tabId})}waitForNavigation({tabId,waitUntil,timeoutMs}={}){return this.tool("wait_for_navigation",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}requestHelp({prompt,title,targets,completionCriteria,tabId,timeoutMs}={}){return this.tool("request_help",{prompt,title,tab_id:tabId,timeout_ms:timeoutMs,targets:targets?.map((t)=>resolveTarget(t)),completion_criteria:completionCriteria})}console({tabId,since,limit,maxTextChars,includeStack}={}){return this.tool("console",{tab_id:tabId,since,limit,max_text_chars:maxTextChars,include_stack:includeStack})}network({tabId,since,limit,maxTextChars}={}){return this.tool("network",{tab_id:tabId,since,limit,max_text_chars:maxTextChars})}resize(width,height){return this.tool("window_resize",{width,height})}emulate({tabId,off,overrides}={}){return this.tool("emulate",{tab_id:tabId,off,overrides})}async stop(){if(this.#stopped)return null;this.#stopped=true;return this.#client.call("session.stop",{session_id:this.sessionId},{idPrefix:"session-stop",timeoutMs:SESSION_STOP_TIMEOUT_MS})}}var DEFAULT_WAIT_FOR_BROWSER_MS=15000;async function listBrowsers(client,{waitForBrowserMs}={}){const status=await client.call("system.status",compact({wait_for_browser_ms:waitForBrowserMs}),{idPrefix:"status"});return status.browsers??[]}async function connectBrowserSkill({client,home,env,sockPath,autoStart,timeoutMs,bskBin,name,browser,width,height,focused=false,waitForBrowserMs=DEFAULT_WAIT_FOR_BROWSER_MS}={}){const ipc=client??new BskIpcClient({home,env,sockPath,autoStart,timeoutMs,bskBin});const browsers=await listBrowsers(ipc,{waitForBrowserMs});if(browsers.length===0){throw new BskRpcError("no_browser_connected","the BrowserSkill daemon is up but no browser extension is connected; open the browser that has the BrowserSkill extension enabled (bskDoctor() explains the remaining step) instead of falling back to a headless browser")}const started=await ipc.call("session.start",compact({task_name:name,browser_instance_id:browser,width,height,focused}),{idPrefix:"session-start"});return new BskSession(ipc,{sessionId:started.session_id,browserInstanceId:started.browser_instance_id??null,agentWindowId:started.agent_window_id??null,interaction:started.interaction??null})}var CSS_PATH_FN=String(function cssPath(el){if(!(el instanceof Element))return null;if(el.getRootNode()!==document)return null;const parts=[];let node=el;while(node&&node.nodeType===1&&node!==document.documentElement){let part=node.localName;if(node.id&&/^[A-Za-z_][\w-]*$/.test(node.id)&&document.querySelectorAll("#"+node.id).length===1){parts.unshift("#"+node.id);return parts.join(" > ")}const parent=node.parentElement;if(parent){const same=Array.from(parent.children).filter((c)=>c.localName===node.localName);if(same.length>1)part+=`:nth-of-type(${same.indexOf(node)+1})`}parts.unshift(part);node=parent}parts.unshift("html");return parts.join(" > ")});function buildSnapshotExpression(options={}){return`(function () {
|
|
1525
1526
|
const globalThis = {};
|
|
@@ -1530,5 +1531,5 @@ ${snapshot.tree}`}}var DEFAULT_TIMEOUT_MS=30000;var JSON_MIME=/^application\/jso
|
|
|
1530
1531
|
const css = {};
|
|
1531
1532
|
for (const ref of Object.keys(result.refs || {})) css[ref] = cssPath(api.elementRegistry.get(ref));
|
|
1532
1533
|
return { tree: result.tree, refs: result.refs || {}, css, error: result.error };
|
|
1533
|
-
})()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync4,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path11 from"node:path";import{existsSync as existsSync3}from"node:fs";import{homedir as homedir3}from"node:os";import path10 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var NON_CHROMIUM_DEFAULTS=Object.freeze([{id:"safari",label:"Safari",match:["com.apple.safari","safarihtml"]},{id:"firefox",label:"Firefox",match:["org.mozilla.firefox","firefox.desktop","firefoxurl","firefoxhtml"]},{id:"zen",label:"Zen",match:["app.zen-browser.zen","zen.desktop","zenhtml"]},{id:"orion",label:"Orion",match:["com.kagi.kagimacos"]}]);var MAC=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",app:"Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",app:"Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",label:"Brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",app:"Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",label:"Chromium",store:"chrome",userData:"Library/Application Support/Chromium",app:"Chromium",bundleId:"org.chromium.Chromium"},{id:"arc",label:"Arc",store:"chrome",userData:"Library/Application Support/Arc/User Data",app:"Arc",bundleId:"company.thebrowser.Browser"},{id:"dia",label:"Dia",store:"chrome",userData:"Library/Application Support/Dia/User Data",app:"Dia",bundleId:"company.thebrowser.dia"},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Library/Application Support/Vivaldi",app:"Vivaldi",bundleId:"com.vivaldi.Vivaldi"},{id:"opera",label:"Opera",store:"chrome",userData:"Library/Application Support/com.operasoftware.Opera",app:"Opera",bundleId:"com.operasoftware.Opera",externalExtensions:false},{id:"comet",label:"Comet",store:"chrome",userData:"Library/Application Support/Comet",app:"Comet",bundleId:"ai.perplexity.comet"},{id:"aside",label:"Aside",store:"chrome",userData:"Library/Application Support/Aside",app:"Aside",bundleId:"at.studio.AsideBrowser"},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Library/Application Support/Naver/Whale",app:"Naver Whale",bundleId:"com.naver.Whale"}];var LINUX=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false,desktopIds:["google-chrome.desktop","google-chrome-stable.desktop","com.google.Chrome.desktop"],processNames:["chrome","google-chrome"]},{id:"chromium",label:"Chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true,desktopIds:["chromium.desktop","chromium-browser.desktop","chromium_chromium.desktop","org.chromium.Chromium.desktop"],processNames:["chromium","chromium-browse"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false,desktopIds:["microsoft-edge.desktop","microsoft-edge-stable.desktop","com.microsoft.Edge.desktop"],processNames:["msedge"]},{id:"brave",label:"Brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false,desktopIds:["brave-browser.desktop","com.brave.Browser.desktop"],processNames:["brave"]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:".config/vivaldi",binaries:["/usr/bin/vivaldi","/opt/vivaldi/vivaldi"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["vivaldi-stable.desktop","vivaldi.desktop"],processNames:["vivaldi-bin"]},{id:"opera",label:"Opera",store:"chrome",userData:".config/opera",binaries:["/usr/bin/opera"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["opera.desktop"],processNames:["opera"]}];var WINDOWS=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe",progIds:["chromehtml"],processNames:["chrome.exe"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe",progIds:["msedgehtm"],processNames:["msedge.exe"]},{id:"brave",label:"Brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe",progIds:["bravehtml"],processNames:["brave.exe"]},{id:"chromium",label:"Chromium",store:"chrome",userData:"Chromium/User Data",registryKey:"HKCU\\Software\\Chromium\\Extensions",binaryRel:"Chromium/Application/chrome.exe",progIds:["chromiumhtm"],processNames:[]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Vivaldi/User Data",registryKey:null,externalExtensions:false,binaryRel:"Vivaldi/Application/vivaldi.exe",progIds:["vivaldihtm"],processNames:["vivaldi.exe"]},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Naver/Naver Whale/User Data",registryKey:null,externalExtensions:false,binaryRel:"Naver/Naver Whale/Application/whale.exe",progIds:["whalehtml"],processNames:["whale.exe"]}];var BROWSER_IDS=Object.freeze([...new Set([...MAC,...LINUX,...WINDOWS].map((b)=>b.id))]);function macRow(b,home,exists){const apps=[path10.join("/Applications",`${b.app}.app`),path10.join(home,"Applications",`${b.app}.app`)];const appPath=apps.find((p)=>exists(p))??null;const userDataDir=path10.join(home,b.userData);return{id:b.id,label:b.label,store:b.store,userDataDir,binary:path10.join(appPath??apps[0],"Contents","MacOS",b.app),bundleId:b.bundleId,processNames:apps.map((a)=>path10.join(a,"Contents","MacOS",b.app)),defaultIds:[b.bundleId.toLowerCase()],externalExtensions:b.externalExtensions!==false,installed:appPath!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function linuxRow(b,home,exists){const userDataDir=path10.join(home,b.userData);const binary=b.binaries.find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,binary,externalDir:b.externalDir,userExternalDir:b.userExternalDir,processNames:b.processNames,defaultIds:b.desktopIds.map((d)=>d.toLowerCase()),externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function windowsRow(b,env,home,exists){const localAppData=env.LOCALAPPDATA??path10.join(home,"AppData","Local");const programFiles=[localAppData,env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);const userDataDir=path10.join(localAppData,b.userData);const binary=programFiles.map((root)=>path10.join(root,b.binaryRel)).find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,registryKey:b.registryKey,binary,processNames:b.processNames,defaultIds:b.progIds,externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function catalogBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync3}={}){if(platform==="darwin")return MAC.map((b)=>macRow(b,home,exists));if(platform==="linux")return LINUX.map((b)=>linuxRow(b,home,exists));if(platform==="win32")return WINDOWS.map((b)=>windowsRow(b,env,home,exists));return[]}function detectBrowsers(options={}){return catalogBrowsers(options).filter((b)=>b.installed)}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;var storeStep=(browser,label)=>`Open ${STORE_PAGE_URLS[browser.store]} in ${label} and click the "Add" button.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.label??browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(browser.externalExtensions===false){return{kind:"store",needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:storeStep(browser,label)}}if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
|
|
1534
|
-
`;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path11.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path11.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path11.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const label=browser.label??browser.id;const storeStep=`Open ${entry.storeUrl} in ${label} and click the "Add" button.`;if(entry.kind==="store")return{...entry,registered:false,alreadyPresent:false,reason:"store_only"};if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${label} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path11.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="store")return{...entry,removed:false};if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync4(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{execFile}from"node:child_process";import{statSync}from"node:fs";import{homedir as homedir4}from"node:os";import path12 from"node:path";var PROBE_TIMEOUT_MS=3000;function runDefault(command,args){return new Promise((resolve)=>{execFile(command,args,{timeout:PROBE_TIMEOUT_MS,windowsHide:true,maxBuffer:8*1024*1024},(error,stdout)=>{resolve(error?null:String(stdout))})})}function parseMacDefaultBrowser(plistJson){let handlers;try{handlers=JSON.parse(plistJson)?.LSHandlers}catch{return null}if(!Array.isArray(handlers))return null;const pick=(scheme)=>handlers.find((h)=>h?.LSHandlerURLScheme===scheme&&typeof h.LSHandlerRoleAll==="string");const handler=pick("https")??pick("http");return handler?handler.LSHandlerRoleAll.toLowerCase():"com.apple.safari"}function parseWindowsProgId(regOutput){const match=/ProgId\s+REG_SZ\s+(\S+)/i.exec(regOutput??"");return match?match[1].toLowerCase():null}function parseProcessNames(platform,output){const names=new Set;for(const raw of String(output??"").split(/\r?\n/)){const line=raw.trim();if(!line)continue;if(platform==="win32"){const image=/^"([^"]+)"/.exec(line)?.[1];if(image)names.add(image.toLowerCase())}else if(platform==="darwin"){if(/\.app\/Contents\/MacOS\/[^/]+$/.test(line))names.add(line)}else{names.add(path12.basename(line))}}return[...names]}async function probeDefault({platform,home,run}){if(platform==="darwin"){const plist=path12.join(home,"Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist");const json=await run("plutil",["-convert","json","-o","-",plist]);return json===null?null:parseMacDefaultBrowser(json)}if(platform==="linux"){const out=await run("xdg-settings",["get","default-web-browser"]);return out?.trim()?out.trim().toLowerCase():null}if(platform==="win32"){const key="HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice";return parseWindowsProgId(await run("reg",["query",key,"/v","ProgId"]))}return null}async function probeRunning({platform,run}){const out=platform==="win32"?await run("tasklist",["/fo","csv","/nh"]):await run("ps",platform==="darwin"?["-axo","comm="]:["-eo","comm="]);return out===null?null:parseProcessNames(platform,out)}function lastUsedAt(browser,stat){let latest=null;for(const rel of["Local State",path12.join("Default","History"),path12.join("Default","Preferences")]){try{const mtime=stat(path12.join(browser.userDataDir,rel)).mtimeMs;if(latest===null||mtime>latest)latest=mtime}catch{}}return latest}async function probeBrowserSignals({platform=process.platform,home=homedir4(),browsers=[],run=runDefault,stat=statSync}={}){const[defaultBrowser,running]=await Promise.all([probeDefault({platform,home,run}),probeRunning({platform,run})]);const lastUsed=Object.fromEntries(browsers.map((b)=>[b.id,lastUsedAt(b,stat)]));return{defaultBrowser,running,lastUsed}}var RECENT_USE_MS=7*24*60*60*1000;function matchesDefault(browser,defaultId){if(!defaultId)return false;return browser.defaultIds.some((id)=>defaultId===id||defaultId.startsWith(id))}function describeDefault(defaultId,catalog){if(defaultId===null||defaultId===undefined)return{raw:null,id:null,label:null,supported:null};const hit=catalog.find((b)=>matchesDefault(b,defaultId));if(hit)return{raw:defaultId,id:hit.id,label:hit.label,supported:true};const other=NON_CHROMIUM_DEFAULTS.find((b)=>b.match.some((m)=>defaultId===m||defaultId.startsWith(m)));return other?{raw:defaultId,id:other.id,label:other.label,supported:false}:{raw:defaultId,id:null,label:defaultId,supported:false}}function candidateRow(browser,{defaultBrowser,running,lastUsed},now){const isDefault=defaultBrowser.id===browser.id;const isRunning=Array.isArray(running)?browser.processNames.some((n)=>running.includes(n)||running.includes(n.toLowerCase())):null;const last=lastUsed?.[browser.id]??null;const recentlyUsed=last!==null&&now-last<=RECENT_USE_MS;const signals=[isDefault&&"default browser",isRunning&&"running now",recentlyUsed&&"used in the last 7 days"].filter(Boolean);return{id:browser.id,label:browser.label,installed:browser.installed,isDefault,running:isRunning,lastUsedAt:last===null?null:new Date(last).toISOString(),recentlyUsed,signals,browser}}function choose(candidates,primary,confidence,reason,defaultBrowser){return{primary:primary.browser,primaryId:primary.id,confidence,needsChoice:false,reason,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function ask(candidates,reason,defaultBrowser,suggested=null){return{primary:null,primaryId:null,confidence:null,needsChoice:true,reason,suggested,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function identifyBrowser({catalog,signals={},explicit=null,now=Date.now()}){const defaultBrowser=describeDefault(signals.defaultBrowser??null,catalog);const installed=catalog.filter((b)=>b.installed||b.id===explicit);const candidates=installed.map((b)=>candidateRow(b,{defaultBrowser,running:signals.running??null,lastUsed:signals.lastUsed},now));if(explicit){const hit=candidates.find((c)=>c.id===explicit);if(hit)return choose(candidates,hit,"explicit",`${hit.label} was chosen explicitly`,defaultBrowser);return ask(candidates,`"${explicit}" is not a Chromium-family browser this library knows; pick one of the candidates`,defaultBrowser)}if(candidates.length===0)return ask(candidates,"no Chromium-family browser is installed",defaultBrowser);const inUse=candidates.filter((c)=>c.running||c.recentlyUsed);const def=candidates.find((c)=>c.isDefault);if(def&&(def.running||def.recentlyUsed))return choose(candidates,def,"default-and-in-use",`${def.label} is the default browser and ${def.signals.slice(1).join(", ")}`,defaultBrowser);if(def&&inUse.length===0)return choose(candidates,def,"default",`${def.label} is the default browser`,defaultBrowser);if(def)return ask(candidates,`${def.label} is the default browser, but ${inUse.map((c)=>c.label).join(" and ")} ${inUse.length===1?"is":"are"} the one in use`,defaultBrowser);if(defaultBrowser.supported===false){const suggested=inUse.length===1?inUse[0].id:null;return ask(candidates,`the default browser is ${defaultBrowser.label}, which BrowserSkill cannot drive; the user has to name the Chromium browser to use`,defaultBrowser,suggested)}const runningNow=inUse.filter((c)=>c.running);if(inUse.length===1)return choose(candidates,inUse[0],"only-in-use",`${inUse[0].label} is the only browser ${inUse[0].signals.join(", ")}`,defaultBrowser);if(runningNow.length===1)return choose(candidates,runningNow[0],"only-running",`${runningNow[0].label} is the only browser running now`,defaultBrowser);if(inUse.length===0&&candidates.length===1)return choose(candidates,candidates[0],"only-installed",`${candidates[0].label} is the only Chromium-family browser installed`,defaultBrowser);return ask(candidates,inUse.length>1?`several browsers are in use (${inUse.map((c)=>c.label).join(", ")})`:"no browser shows recent use",defaultBrowser)}import{existsSync as existsSync5,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir5}from"node:os";import path13 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault2(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path13.join(home,name);snapshot.set(file,existsSync5(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync5(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir5(),installDir=path13.join(home,".local","bin"),version,run=runDefault2}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path13.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync5(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir6}from"node:os";import{existsSync as existsSync6,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}var NO_BROWSER_STEP=`No Chromium-family browser was found. Install one (Chrome, Edge, Brave, Arc, ...), then add the extension from ${STORE_PAGE_URLS.chrome}.`;function withRegistration(platform,browser){const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync6(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}}async function identify({platform,home,env,exists,browser,signals,probeSignals,now}){const catalog=catalogBrowsers({platform,home,env,exists}).map((b)=>withRegistration(platform,b));const observed=signals??await probeSignals({platform,home,browsers:catalog.filter((b)=>b.hasProfile)});const explicit=browser??env.OMOWRIGHT_BROWSER??null;const{primary,...identification}=identifyBrowser({catalog,signals:observed,explicit:explicit||null,now});const browsers=catalog.filter((b)=>b.installed);const registeredElsewhere=browsers.filter((b)=>b.extensionRegistered&&b.id!==identification.primaryId).map((b)=>b.id);return{browsers,identification:{...identification,registeredElsewhere},primary}}function choiceStep(identification){const listed=identification.candidates.map((c)=>`${c.label} [${c.id}]${c.signals.length?` — ${c.signals.join(", ")}`:""}`).join("; ");const hint=identification.suggested?` Likely ${identification.suggested}, but confirm first.`:"";return`Ask the user which browser they actually use (${identification.reason}). Candidates: ${listed}.${hint} Then re-run with browser: "<id>"; register nothing until they answer.`}function nextStepFor({cli,daemon,browsers,browsersConnected,identification,primary}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0&&!primary)return NO_BROWSER_STEP;if(identification.needsChoice)return choiceStep(identification);if(!primary.extensionRegistered&&primary.registration.kind!=="store")return`Register the BrowserSkill extension for ${primary.label} (bskOnboard() does this), then let the user enable it.`;return primary.registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,primary:primary??null,identification,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const registrations=[];let humanStep=null;let needsChoice=false;if(daemon.browsers.length===0){if(browsers.length===0&&!primary){humanStep=NO_BROWSER_STEP}else if(identification.needsChoice){needsChoice=true;humanStep=choiceStep(identification)}else{const registration=await registerExternalExtension({platform,browser:primary});registrations.push({browser:primary.id,...registration});humanStep=registration.humanStep;onHumanStep({browser:primary.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(!needsChoice&&browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,primary:primary??null,identification,needsChoice,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,catalogBrowsers,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,identifyBrowser,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,probeBrowserSignals,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
|
|
1534
|
+
})()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync5,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path12 from"node:path";import{existsSync as existsSync4}from"node:fs";import{homedir as homedir3}from"node:os";import path11 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var NON_CHROMIUM_DEFAULTS=Object.freeze([{id:"safari",label:"Safari",match:["com.apple.safari","safarihtml"]},{id:"firefox",label:"Firefox",match:["org.mozilla.firefox","firefox.desktop","firefoxurl","firefoxhtml"]},{id:"zen",label:"Zen",match:["app.zen-browser.zen","zen.desktop","zenhtml"]},{id:"orion",label:"Orion",match:["com.kagi.kagimacos"]}]);var MAC=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",app:"Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",app:"Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",label:"Brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",app:"Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",label:"Chromium",store:"chrome",userData:"Library/Application Support/Chromium",app:"Chromium",bundleId:"org.chromium.Chromium"},{id:"arc",label:"Arc",store:"chrome",userData:"Library/Application Support/Arc/User Data",app:"Arc",bundleId:"company.thebrowser.Browser"},{id:"dia",label:"Dia",store:"chrome",userData:"Library/Application Support/Dia/User Data",app:"Dia",bundleId:"company.thebrowser.dia"},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Library/Application Support/Vivaldi",app:"Vivaldi",bundleId:"com.vivaldi.Vivaldi"},{id:"opera",label:"Opera",store:"chrome",userData:"Library/Application Support/com.operasoftware.Opera",app:"Opera",bundleId:"com.operasoftware.Opera",externalExtensions:false},{id:"comet",label:"Comet",store:"chrome",userData:"Library/Application Support/Comet",app:"Comet",bundleId:"ai.perplexity.comet"},{id:"aside",label:"Aside",store:"chrome",userData:"Library/Application Support/Aside",app:"Aside",bundleId:"at.studio.AsideBrowser"},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Library/Application Support/Naver/Whale",app:"Naver Whale",bundleId:"com.naver.Whale"}];var LINUX=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false,desktopIds:["google-chrome.desktop","google-chrome-stable.desktop","com.google.Chrome.desktop"],processNames:["chrome","google-chrome"]},{id:"chromium",label:"Chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true,desktopIds:["chromium.desktop","chromium-browser.desktop","chromium_chromium.desktop","org.chromium.Chromium.desktop"],processNames:["chromium","chromium-browse"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false,desktopIds:["microsoft-edge.desktop","microsoft-edge-stable.desktop","com.microsoft.Edge.desktop"],processNames:["msedge"]},{id:"brave",label:"Brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false,desktopIds:["brave-browser.desktop","com.brave.Browser.desktop"],processNames:["brave"]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:".config/vivaldi",binaries:["/usr/bin/vivaldi","/opt/vivaldi/vivaldi"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["vivaldi-stable.desktop","vivaldi.desktop"],processNames:["vivaldi-bin"]},{id:"opera",label:"Opera",store:"chrome",userData:".config/opera",binaries:["/usr/bin/opera"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["opera.desktop"],processNames:["opera"]}];var WINDOWS=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe",progIds:["chromehtml"],processNames:["chrome.exe"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe",progIds:["msedgehtm"],processNames:["msedge.exe"]},{id:"brave",label:"Brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe",progIds:["bravehtml"],processNames:["brave.exe"]},{id:"chromium",label:"Chromium",store:"chrome",userData:"Chromium/User Data",registryKey:"HKCU\\Software\\Chromium\\Extensions",binaryRel:"Chromium/Application/chrome.exe",progIds:["chromiumhtm"],processNames:[]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Vivaldi/User Data",registryKey:null,externalExtensions:false,binaryRel:"Vivaldi/Application/vivaldi.exe",progIds:["vivaldihtm"],processNames:["vivaldi.exe"]},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Naver/Naver Whale/User Data",registryKey:null,externalExtensions:false,binaryRel:"Naver/Naver Whale/Application/whale.exe",progIds:["whalehtml"],processNames:["whale.exe"]}];var BROWSER_IDS=Object.freeze([...new Set([...MAC,...LINUX,...WINDOWS].map((b)=>b.id))]);function macRow(b,home,exists){const apps=[path11.join("/Applications",`${b.app}.app`),path11.join(home,"Applications",`${b.app}.app`)];const appPath=apps.find((p)=>exists(p))??null;const userDataDir=path11.join(home,b.userData);return{id:b.id,label:b.label,store:b.store,userDataDir,binary:path11.join(appPath??apps[0],"Contents","MacOS",b.app),bundleId:b.bundleId,processNames:apps.map((a)=>path11.join(a,"Contents","MacOS",b.app)),defaultIds:[b.bundleId.toLowerCase()],externalExtensions:b.externalExtensions!==false,installed:appPath!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function linuxRow(b,home,exists){const userDataDir=path11.join(home,b.userData);const binary=b.binaries.find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,binary,externalDir:b.externalDir,userExternalDir:b.userExternalDir,processNames:b.processNames,defaultIds:b.desktopIds.map((d)=>d.toLowerCase()),externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function windowsRow(b,env,home,exists){const localAppData=env.LOCALAPPDATA??path11.join(home,"AppData","Local");const programFiles=[localAppData,env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);const userDataDir=path11.join(localAppData,b.userData);const binary=programFiles.map((root)=>path11.join(root,b.binaryRel)).find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,registryKey:b.registryKey,binary,processNames:b.processNames,defaultIds:b.progIds,externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function catalogBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync4}={}){if(platform==="darwin")return MAC.map((b)=>macRow(b,home,exists));if(platform==="linux")return LINUX.map((b)=>linuxRow(b,home,exists));if(platform==="win32")return WINDOWS.map((b)=>windowsRow(b,env,home,exists));return[]}function detectBrowsers(options={}){return catalogBrowsers(options).filter((b)=>b.installed)}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;var storeStep=(browser,label)=>`Open ${STORE_PAGE_URLS[browser.store]} in ${label} and click the "Add" button.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.label??browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(browser.externalExtensions===false){return{kind:"store",needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:storeStep(browser,label)}}if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
|
|
1535
|
+
`;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path12.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path12.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path12.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const label=browser.label??browser.id;const storeStep=`Open ${entry.storeUrl} in ${label} and click the "Add" button.`;if(entry.kind==="store")return{...entry,registered:false,alreadyPresent:false,reason:"store_only"};if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${label} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path12.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="store")return{...entry,removed:false};if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync5(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{execFile}from"node:child_process";import{statSync}from"node:fs";import{homedir as homedir4}from"node:os";import path13 from"node:path";var PROBE_TIMEOUT_MS2=3000;function runDefault(command,args){return new Promise((resolve)=>{execFile(command,args,{timeout:PROBE_TIMEOUT_MS2,windowsHide:true,maxBuffer:8*1024*1024},(error,stdout)=>{resolve(error?null:String(stdout))})})}function parseMacDefaultBrowser(plistJson){let handlers;try{handlers=JSON.parse(plistJson)?.LSHandlers}catch{return null}if(!Array.isArray(handlers))return null;const pick=(scheme)=>handlers.find((h)=>h?.LSHandlerURLScheme===scheme&&typeof h.LSHandlerRoleAll==="string");const handler=pick("https")??pick("http");return handler?handler.LSHandlerRoleAll.toLowerCase():"com.apple.safari"}function parseWindowsProgId(regOutput){const match=/ProgId\s+REG_SZ\s+(\S+)/i.exec(regOutput??"");return match?match[1].toLowerCase():null}function parseProcessNames(platform,output){const names=new Set;for(const raw of String(output??"").split(/\r?\n/)){const line=raw.trim();if(!line)continue;if(platform==="win32"){const image=/^"([^"]+)"/.exec(line)?.[1];if(image)names.add(image.toLowerCase())}else if(platform==="darwin"){if(/\.app\/Contents\/MacOS\/[^/]+$/.test(line))names.add(line)}else{names.add(path13.basename(line))}}return[...names]}async function probeDefault({platform,home,run}){if(platform==="darwin"){const plist=path13.join(home,"Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist");const json=await run("plutil",["-convert","json","-o","-",plist]);return json===null?null:parseMacDefaultBrowser(json)}if(platform==="linux"){const out=await run("xdg-settings",["get","default-web-browser"]);return out?.trim()?out.trim().toLowerCase():null}if(platform==="win32"){const key="HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice";return parseWindowsProgId(await run("reg",["query",key,"/v","ProgId"]))}return null}async function probeRunning({platform,run}){const out=platform==="win32"?await run("tasklist",["/fo","csv","/nh"]):await run("ps",platform==="darwin"?["-axo","comm="]:["-eo","comm="]);return out===null?null:parseProcessNames(platform,out)}function lastUsedAt(browser,stat){let latest=null;for(const rel of["Local State",path13.join("Default","History"),path13.join("Default","Preferences")]){try{const mtime=stat(path13.join(browser.userDataDir,rel)).mtimeMs;if(latest===null||mtime>latest)latest=mtime}catch{}}return latest}async function probeBrowserSignals({platform=process.platform,home=homedir4(),browsers=[],run=runDefault,stat=statSync}={}){const[defaultBrowser,running]=await Promise.all([probeDefault({platform,home,run}),probeRunning({platform,run})]);const lastUsed=Object.fromEntries(browsers.map((b)=>[b.id,lastUsedAt(b,stat)]));return{defaultBrowser,running,lastUsed}}var RECENT_USE_MS=7*24*60*60*1000;function matchesDefault(browser,defaultId){if(!defaultId)return false;return browser.defaultIds.some((id)=>defaultId===id||defaultId.startsWith(id))}function describeDefault(defaultId,catalog){if(defaultId===null||defaultId===undefined)return{raw:null,id:null,label:null,supported:null};const hit=catalog.find((b)=>matchesDefault(b,defaultId));if(hit)return{raw:defaultId,id:hit.id,label:hit.label,supported:true};const other=NON_CHROMIUM_DEFAULTS.find((b)=>b.match.some((m)=>defaultId===m||defaultId.startsWith(m)));return other?{raw:defaultId,id:other.id,label:other.label,supported:false}:{raw:defaultId,id:null,label:defaultId,supported:false}}function candidateRow(browser,{defaultBrowser,running,lastUsed},now){const isDefault=defaultBrowser.id===browser.id;const isRunning=Array.isArray(running)?browser.processNames.some((n)=>running.includes(n)||running.includes(n.toLowerCase())):null;const last=lastUsed?.[browser.id]??null;const recentlyUsed=last!==null&&now-last<=RECENT_USE_MS;const signals=[isDefault&&"default browser",isRunning&&"running now",recentlyUsed&&"used in the last 7 days"].filter(Boolean);return{id:browser.id,label:browser.label,installed:browser.installed,isDefault,running:isRunning,lastUsedAt:last===null?null:new Date(last).toISOString(),recentlyUsed,signals,browser}}function choose(candidates,primary,confidence,reason,defaultBrowser){return{primary:primary.browser,primaryId:primary.id,confidence,needsChoice:false,reason,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function ask(candidates,reason,defaultBrowser,suggested=null){return{primary:null,primaryId:null,confidence:null,needsChoice:true,reason,suggested,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function identifyBrowser({catalog,signals={},explicit=null,now=Date.now()}){const defaultBrowser=describeDefault(signals.defaultBrowser??null,catalog);const installed=catalog.filter((b)=>b.installed||b.id===explicit);const candidates=installed.map((b)=>candidateRow(b,{defaultBrowser,running:signals.running??null,lastUsed:signals.lastUsed},now));if(explicit){const hit=candidates.find((c)=>c.id===explicit);if(hit)return choose(candidates,hit,"explicit",`${hit.label} was chosen explicitly`,defaultBrowser);return ask(candidates,`"${explicit}" is not a Chromium-family browser this library knows; pick one of the candidates`,defaultBrowser)}if(candidates.length===0)return ask(candidates,"no Chromium-family browser is installed",defaultBrowser);const inUse=candidates.filter((c)=>c.running||c.recentlyUsed);const def=candidates.find((c)=>c.isDefault);if(def&&(def.running||def.recentlyUsed))return choose(candidates,def,"default-and-in-use",`${def.label} is the default browser and ${def.signals.slice(1).join(", ")}`,defaultBrowser);if(def&&inUse.length===0)return choose(candidates,def,"default",`${def.label} is the default browser`,defaultBrowser);if(def)return ask(candidates,`${def.label} is the default browser, but ${inUse.map((c)=>c.label).join(" and ")} ${inUse.length===1?"is":"are"} the one in use`,defaultBrowser);if(defaultBrowser.supported===false){const suggested=inUse.length===1?inUse[0].id:null;return ask(candidates,`the default browser is ${defaultBrowser.label}, which BrowserSkill cannot drive; the user has to name the Chromium browser to use`,defaultBrowser,suggested)}const runningNow=inUse.filter((c)=>c.running);if(inUse.length===1)return choose(candidates,inUse[0],"only-in-use",`${inUse[0].label} is the only browser ${inUse[0].signals.join(", ")}`,defaultBrowser);if(runningNow.length===1)return choose(candidates,runningNow[0],"only-running",`${runningNow[0].label} is the only browser running now`,defaultBrowser);if(inUse.length===0&&candidates.length===1)return choose(candidates,candidates[0],"only-installed",`${candidates[0].label} is the only Chromium-family browser installed`,defaultBrowser);return ask(candidates,inUse.length>1?`several browsers are in use (${inUse.map((c)=>c.label).join(", ")})`:"no browser shows recent use",defaultBrowser)}import{existsSync as existsSync6,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir5}from"node:os";import path14 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault2(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path14.join(home,name);snapshot.set(file,existsSync6(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync6(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir5(),installDir=path14.join(home,".local","bin"),version,run=runDefault2}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path14.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync6(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir6}from"node:os";import{existsSync as existsSync7,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}var NO_BROWSER_STEP=`No Chromium-family browser was found. Install one (Chrome, Edge, Brave, Arc, ...), then add the extension from ${STORE_PAGE_URLS.chrome}.`;function withRegistration(platform,browser){const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync7(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}}async function identify({platform,home,env,exists,browser,signals,probeSignals,now}){const catalog=catalogBrowsers({platform,home,env,exists}).map((b)=>withRegistration(platform,b));const observed=signals??await probeSignals({platform,home,browsers:catalog.filter((b)=>b.hasProfile)});const explicit=browser??env.OMOWRIGHT_BROWSER??null;const{primary,...identification}=identifyBrowser({catalog,signals:observed,explicit:explicit||null,now});const browsers=catalog.filter((b)=>b.installed);const registeredElsewhere=browsers.filter((b)=>b.extensionRegistered&&b.id!==identification.primaryId).map((b)=>b.id);return{browsers,identification:{...identification,registeredElsewhere},primary}}function choiceStep(identification){const listed=identification.candidates.map((c)=>`${c.label} [${c.id}]${c.signals.length?` — ${c.signals.join(", ")}`:""}`).join("; ");const hint=identification.suggested?` Likely ${identification.suggested}, but confirm first.`:"";return`Ask the user which browser they actually use (${identification.reason}). Candidates: ${listed}.${hint} Then re-run with browser: "<id>"; register nothing until they answer.`}function nextStepFor({cli,daemon,browsers,browsersConnected,identification,primary}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0&&!primary)return NO_BROWSER_STEP;if(identification.needsChoice)return choiceStep(identification);if(!primary.extensionRegistered&&primary.registration.kind!=="store")return`Register the BrowserSkill extension for ${primary.label} (bskOnboard() does this), then let the user enable it.`;return primary.registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0,exists=existsSync7,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,primary:primary??null,identification,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS,exists=existsSync7,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const registrations=[];let humanStep=null;let needsChoice=false;if(daemon.browsers.length===0){if(browsers.length===0&&!primary){humanStep=NO_BROWSER_STEP}else if(identification.needsChoice){needsChoice=true;humanStep=choiceStep(identification)}else{const registration=await registerExternalExtension({platform,browser:primary});registrations.push({browser:primary.id,...registration});humanStep=registration.humanStep;onHumanStep({browser:primary.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(!needsChoice&&browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,primary:primary??null,identification,needsChoice,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,catalogBrowsers,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,identifyBrowser,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,probeBrowserSignals,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.0.1",
|
|
3
|
-
"sourceDigest": "
|
|
3
|
+
"sourceDigest": "646863e33d2a1af1fdd3651ee157ada9ba24337e82430edef94342774c80f0f6",
|
|
4
4
|
"files": {
|
|
5
|
-
"index.js": "
|
|
5
|
+
"index.js": "f577c49fe18bcc78293e136e112a83861e86cca5d908ebceaaec524dff8b8ccc",
|
|
6
6
|
"page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
|
|
7
7
|
},
|
|
8
|
-
"stagedAtUtc": "2026-10-
|
|
8
|
+
"stagedAtUtc": "2026-10-16T07:26:31.363Z"
|
|
9
9
|
}
|
|
@@ -34,7 +34,7 @@ Plan and reviewer agents may run for a long time: spawn them in the background a
|
|
|
34
34
|
|
|
35
35
|
# ulw-execute
|
|
36
36
|
|
|
37
|
-
Execute a work plan until every top-level checkbox is
|
|
37
|
+
Execute a work plan until every top-level checkbox is `[x]` or `[~]`. This skill pairs with the harness's ulw-execute continuation hook, which re-injects the next turn while `.omo/boulder.json` says this `codex:<session_id>` still has unchecked plan work.
|
|
38
38
|
|
|
39
39
|
## Usage
|
|
40
40
|
|
|
@@ -53,7 +53,7 @@ Do ALL of this immediately after the plan is selected, BEFORE the first implemen
|
|
|
53
53
|
|
|
54
54
|
1. **Set the goal, in detail.** When a goal tool is available (`create_goal`), call it with a DETAILED objective: the plan name and path, the concrete end state, the phase and task counts, the delivery mode (direct, `--make-pr`, or `--ship`), and how completion will be verified. One work session = one registered goal (the goal tool holds one active goal); each phase then carries its own concrete goal — the ledger entry Phase 2 records before the wave's first dispatch, defined from the previous phase's landed and verified evidence. No goal tool -> record the same objective as the first ledger entry.
|
|
55
55
|
2. **Register every phase and task as todos.** Mirror the plan into the todo/plan tool of your harness: one phase per plan wave, one todo per column-zero checkbox (including the final verification wave). Register ALL of them up front - never keep tasks in memory only.
|
|
56
|
-
3. **Keep them current at every moment.** Mark a todo in_progress when its work dispatches and done immediately after its verification passes. Never batch-complete at the end, never execute work that is not a registered todo; discovered work inside the change's blast radius — the request not delivered, a regression this change introduces, an invalid proof, a failing test or stale doc of touched code — is appended to the plan as a checkbox, mirrored as a todo
|
|
56
|
+
3. **Keep them current at every moment.** Mark a todo in_progress when its work dispatches and done immediately after its verification passes. Never batch-complete at the end, never execute work that is not a registered todo; discovered work inside the change's blast radius — the request not delivered, a regression this change introduces, an invalid proof, a failing test or stale doc of touched code — is appended to the plan as a follow-up checkbox after the checkpoint in progress, mirrored as a todo, and fixed to the ideal state once that checkpoint closes; it joins the open checkpoint only when it blocks that checkpoint's acceptance; a defect outside it gets a tracked issue with reproduction and evidence and a line in the final report. A worker that meets a defect outside its assigned files reports it instead of fixing it; only the orchestrator appends the checkbox and dispatches it to a correctly scoped unit. The todo list, Boulder state, and plan checkboxes must always tell the same story.
|
|
57
57
|
|
|
58
58
|
## Phase 1: Select the plan
|
|
59
59
|
|
|
@@ -96,14 +96,14 @@ Write `.omo/boulder.json` before implementation starts. Prefix session ids with
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
Every phase (plan wave) runs in its own task-owned worktree with its own goal: before the wave's first dispatch, record the wave's goal — its checkboxes and their acceptance criteria — as a ledger entry, then `git worktree add <repo>-wt/<plan>-<wave> <branch off the integration base>` (or verify a `--worktree` path with `git worktree list --porcelain`), store the absolute path as `worktree_path`, run every edit, command, test, and evidence capture inside it; the wave lands on the integration base once its checkboxes
|
|
99
|
+
Every phase (plan wave) runs in its own task-owned worktree with its own goal: before the wave's first dispatch, record the wave's goal — its checkboxes and their acceptance criteria — as a ledger entry, then `git worktree add <repo>-wt/<plan>-<wave> <branch off the integration base>` (or verify a `--worktree` path with `git worktree list --porcelain`), store the absolute path as `worktree_path`, run every edit, command, test, and evidence capture inside it; the wave lands on the integration base once each of its checkboxes is verified or `[~]` (direct merge, or the PR under `--make-pr`/`--ship`; a `[~]` row's unverified changes do not land: a dispatched worker reverts them in the wave worktree before landing, and the `unmet` entry records the revert), and the next wave branches from that landed base. Use the bundled ultrawork directive’s Task resources and todo rules at wave/worktree creation; read those sections if absent from context.
|
|
100
100
|
|
|
101
101
|
## Parallel delivery lanes (teams and worktrees)
|
|
102
102
|
|
|
103
103
|
Solo orchestration with parallel background workers is the default topology. Decide once, when the wave's lanes are known, and record the verdict in the ledger:
|
|
104
104
|
|
|
105
105
|
- **Independent lanes -> parallel workers.** Separate files, no shared contract: one parallel spawn burst; no team.
|
|
106
|
-
- **Dependency-ordered lanes -> one `workflow` run per wave.** Sub-tasks with real ordering between them (C needs A and B finished first) and a harness with a native `workflow` tool: dispatch the wave as ONE run (one producer node per lane plus a verification node); recover inside it with `retry`/`amend`/`send`; let
|
|
106
|
+
- **Dependency-ordered lanes -> one `workflow` run per wave.** Sub-tasks with real ordering between them (C needs A and B finished first) and a harness with a native `workflow` tool: dispatch the wave as ONE run (one producer node per lane plus a verification node); recover inside it with `retry`/`amend`/`send`; let the run's settle wake you instead of arming per-lane watchers; the next wave is a NEW run (or `amend` when only the definition changed) — never one graph for the whole plan. Read the `mass-ulw` skill's `SKILL.md` and `references/planning.md` IN FULL before defining any graph.
|
|
107
107
|
- **Overlapping lanes -> a team.** The lanes touch the same module or contract AND running them concurrently actually finishes sooner: stand up a team (where the harness has one) so one lane's discoveries relay through you mid-flight.
|
|
108
108
|
- **PR-mode independent lanes -> a worktree per lane.** Under `--make-pr`/`--ship`, when a wave holds independent checkboxes, give each lane its own branch and task-owned worktree, delivered as its own PR.
|
|
109
109
|
|
|
@@ -181,7 +181,7 @@ For each checkbox, complete all five gates before marking it done:
|
|
|
181
181
|
4. Adversarial QA: exercise every class the Phase 3 trigger map marks applicable and capture the observable result for each.
|
|
182
182
|
5. Cleanup: register every QA resource teardown as its own todo when spawned (QA scripts, tmux assets, browser sessions, PIDs, ports, containers, temp dirs), execute each, and capture the receipt. No QA asset is left running.
|
|
183
183
|
|
|
184
|
-
Append evidence to `.omo/ulw-execute/ledger.jsonl`, one JSON object per line. Include at least `event`, `plan`, `task`, `session_id`, `commands`, `artifact`, `adversarial_classes`, and `cleanup` fields. `adversarial_classes` lists each probed class with its observable result and each ruled-out class with a one-line reason.
|
|
184
|
+
Append evidence to `.omo/ulw-execute/ledger.jsonl`, one JSON object per line. Include at least `event`, `plan`, `task`, `session_id`, `commands`, `artifact`, `adversarial_classes`, and `cleanup` fields, plus `attempt` and `checkpoint_opened_at` on every entry tied to a checkbox. `adversarial_classes` lists each probed class with its observable result and each ruled-out class with a one-line reason.
|
|
185
185
|
|
|
186
186
|
### Completion contract
|
|
187
187
|
|
|
@@ -199,6 +199,7 @@ A worker done claim is never final: each implementation sub-task returns a `Done
|
|
|
199
199
|
},
|
|
200
200
|
"AdversarialVerify": {
|
|
201
201
|
"verdict": "confirmed | false-positive | needs-fix | needs-human-review",
|
|
202
|
+
"failure_class": "none | product | harness",
|
|
202
203
|
"evidence": ["file path, command, log, artifact, or explicit not inspected"],
|
|
203
204
|
"repro": "exact command or manual steps when available",
|
|
204
205
|
"confidence": 0.0
|
|
@@ -210,7 +211,9 @@ Rules:
|
|
|
210
211
|
- `confirmed` is the only pass verdict. `false-positive`, `needs-fix`, and `needs-human-review` all block checkbox completion.
|
|
211
212
|
- The verifier must be independent from the executor: use the harness's gate reviewer (see the harness compatibility section) or a fresh reviewer worker on a strong model, or root only when root did not implement or materially rewrite that task.
|
|
212
213
|
- A worker done claim must be independently verified before it becomes checkbox completion.
|
|
213
|
-
- On
|
|
214
|
+
- On a non-confirmed verdict, append the feedback to the ledger with the attempt number and its failure signature (the failing check plus its first error line, with volatile values stripped: temp-dir paths, timestamps, process and run ids, durations). Attempts count per checkbox across all its sub-tasks and re-splits, timed from its first dispatch (`checkpoint_opened_at`). STOP when the same signature repeats on consecutive attempts, on the third failed attempt, or 4 hours after the first dispatch (a plan may change these numbers, never remove the stop). Check the 4-hour bound also when a lane's watcher fires or the turn resumes without a verdict; when it has passed, cancel that lane and stop the same way: mark the checkbox `- [~]` (the plan grammar's blocked state) with a one-line gap naming what is unmet and the last evidence, mark every checkbox that depends on it, directly or through another `[~]` row, `- [~]` with the gap `blocked by <id>`, close the todo of every row marked `[~]` here labelled `blocked: <gap>`, append an `unmet` ledger entry listing what each attempt changed, and continue in the same turn with the next checkpoint that depends on none of them, or with Completion when none remains. Otherwise leave the checkbox `- [ ]` and re-dispatch the executor with the exact failure.
|
|
215
|
+
- A `[~]` row counts as closed, never as a met dependency: Phase 3 skips it, Completion starts once every top-level row is `[x]` or `[~]`, and the `ORCHESTRATION COMPLETE` block names each `[~]` row with its gap. Final Verification Wave rows are never dependents of a `[~]` row: they verify the landed work and report each `[~]` row as a known gap, which alone never fails a final-wave row. A `[~]` gap is never re-queued as a new checkbox in this run, by the blast-radius rule or by a final-wave verdict; the one harness-repair follow-up in the next rule is the only exception.
|
|
216
|
+
- The verifier sets `failure_class`. A `harness` failure (the runner, fixture, CI job or environment failed and the product behavior was never exercised) gets one repair dispatch inside the checkpoint, which does not count as an attempt; when it recurs, stop as above with the harness gap named and append the harness repair once as a follow-up checkbox; a harness-repair follow-up that stops is reported, not re-appended.
|
|
214
217
|
- The verifier must probe the applicable adversarial keys, including `stale_state`, `dirty_worktree`, and `misleading_success_output`, before allowing `FullyDone`.
|
|
215
218
|
|
|
216
219
|
## Phase 5: Mark progress
|