@mearl/native-host 3.0.0-beta.3 → 3.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -1107,7 +1107,7 @@ ${installed.stderr??""}`,listed=[];if(/✓[^\n]*\(copied\)/.test(stripAnsi(outpu
1107
1107
  Run: mearl ${name} --help`:toolbox?`
1108
1108
  Open in Mearl toolbox.`:""}`)}return verifySkill(!0),0}var registrationContext,init_plugin3=__esm({"../setup/dist/plugin.js"(){"use strict";init_plugin2();init_skillSources();init_presetInstallationLease();init_pluginRuntime();init_skills();init_dist();registrationContext={options:{},invoke:async()=>{throw new Error("注册时不能执行浏览器操作")},progress:()=>{throw new Error("注册时不能报告执行进度")}}}});async function installManagedPlugins(options={},dependencies={}){let writeLine=options.writeLine??(line=>console.error(line));try{let catalog=(dependencies.readCatalog??readOfficialPluginCatalog)();if(!catalog||Object.keys(catalog.plugins).length===0)return"skipped";let registrationFor=dependencies.findPluginRegistration??findPluginRegistration,candidates=Object.entries(catalog.plugins).filter(([name])=>{let registration=registrationFor(name);return registration&&registration.managedBy!==OFFICIAL_PLUGIN_MANAGER?(writeLine(`${name} plugin: existing user registration kept.`),!1):!0});if(candidates.length===0)return"skipped";if(options.includeMearlSkill&&!await(dependencies.runSkillOperation??runGlobalMearlSkillOperation)(options.mearlSkillOperation??"install",{env:options.env,runCommand:options.runCommand,writeLine,yes:!0}))return"failed";for(let[name,definition]of candidates){let code=await(dependencies.installPlugins??installPlugins)([...definition.installArgs],options.runCommand??runCommand,{registrationManagedBy:OFFICIAL_PLUGIN_MANAGER,env:options.env});if(code!==0)return writeLine(`${name} plugin: install failed (exit ${code}).`),"failed";if(registrationFor(name)?.managedBy!==OFFICIAL_PLUGIN_MANAGER)return writeLine(`${name} plugin: installation did not create its managed registration.`),"failed";writeLine(`${name} plugin: installed and registered.`)}return"installed"}catch(error62){return writeLine(`Managed plugin batch: ${error62 instanceof Error?error62.message:String(error62)}; failed.`),"failed"}}async function uninstallManagedPlugins(options={},dependencies={}){let writeLine=options.writeLine??(line=>console.error(line));try{let catalog=(dependencies.readCatalog??readOfficialPluginCatalog)();if(!catalog)return"skipped";let registrationFor=dependencies.findPluginRegistration??findPluginRegistration;for(let name of Object.keys(catalog.plugins)){let registration=registrationFor(name);if(registration){if(registration.managedBy!==OFFICIAL_PLUGIN_MANAGER){writeLine(`${name} plugin: existing user registration kept.`);continue}if(await(dependencies.uninstallPlugin??uninstallPlugin)(name,options.runCommand??runCommand,{preserveRegistrationsOutsideManager:OFFICIAL_PLUGIN_MANAGER,env:options.env}),registrationFor(name))return writeLine(`${name} plugin: uninstall did not clear its registration.`),"failed";writeLine(`${name} plugin: uninstalled.`)}}return options.includeMearlSkill&&!await(dependencies.runSkillOperation??runGlobalMearlSkillOperation)("uninstall",{env:options.env,runCommand:options.runCommand,writeLine,yes:!0})?"failed":"uninstalled"}catch(error62){return writeLine(`Managed plugin batch: ${error62 instanceof Error?error62.message:String(error62)}; failed.`),"failed"}}var init_managedPlugins=__esm({"../setup/dist/managedPlugins.js"(){"use strict";init_plugin2();init_plugin3();init_skills();init_dist()}});import{existsSync as existsSync11,readFileSync as readFileSync15,statSync as statSync13}from"node:fs";import{join as join24,resolve as resolve10}from"node:path";function inspectProviderPackage(manifest){if(validateInstalledProviderManifest(manifest))return!1;try{let file2=join24(manifest.packageRoot,"package.json"),stat5=statSync13(file2);if(!stat5.isFile()||stat5.size>1024*1024)return!1;let metadata=JSON.parse(readFileSync15(file2,"utf8")),provider=metadata.mearl?.provider;return metadata.name===manifest.packageName&&metadata.version===manifest.version&&provider?.providerId===manifest.providerId&&provider.protocolVersion===manifest.protocolVersion&&typeof provider.entry=="string"&&resolve10(manifest.packageRoot,provider.entry)===manifest.entryPath}catch{return!1}}async function stopProviderBeforePackageChange(providerId){if(readProviderStart(providerId))throw new Error(`Provider ${providerId} is still starting`);let live=readLiveProvider(providerId);if(!live)return;let owner={pid:live.pid,processIdentity:processIdentity(live.pid)};stopProvider(providerId);let deadline=Date.now()+5e3;for(;isProcessOwnerAlive(owner);){if(Date.now()>=deadline)throw new Error(`Provider ${providerId} did not finish stopping`);await new Promise(resolve16=>setTimeout(resolve16,50))}if(readLiveProvider(providerId)||readProviderStart(providerId))throw new Error(`Provider ${providerId} restarted during shutdown`)}function parseProviderPackageSpec(value){let nameMatch=value.trim().match(/^(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)(?:@(.+))?$/i);if(!nameMatch||!nameMatch[1]||/\s/.test(nameMatch[2]??""))throw new Error(`Provider install requires an npm package name, received: ${value}`);return{packageName:nameMatch[1],target:nameMatch[2]||"latest"}}function providerManager(env,dependencies,resolvedManager){return resolvedManager??(dependencies.resolveInvokingManager??(childEnv=>resolveInvokingPackageManager({env:childEnv})))(env)}function outputPath(output3){return output3.split(/\r?\n/).map(line=>line.trim()).filter(Boolean).at(-1)??null}async function resolveGlobalProviderPackageRoot(installation,packageName,runner,env){let args,toNodeModules;switch(installation.manager){case"npm":case"pnpm":args=["root","-g"],toNodeModules=value=>value;break;case"yarn":args=["global","dir"],toNodeModules=value=>join24(value,"node_modules");break;case"bun":args=["pm","bin","-g"],toNodeModules=value=>resolve10(value,"..","install","global","node_modules");break;case"volta":throw new Error("Volta cannot manage a Provider package without a binary; use npm or pnpm")}let result=await runner(installation.command,args,{capture:!0,env:prependCommandPath(installation.command,env)}),root=outputPath(result.stdout);if(result.code!==0||!root)throw new Error(`${installation.manager} global package root lookup failed`);return join24(toNodeModules(root),...packageName.split("/"))}function findRegisteredProvider(selector,listProviders){let matches=listProviders().filter(provider=>provider.providerId===selector||provider.packageName===selector);if(matches.length===0)throw new Error(`Provider is not registered: ${selector}`);if(matches.length>1)throw new Error(`Provider selector is ambiguous: ${selector}`);return matches[0]}async function runPackageCommand(installation,operation,packageName,target,runner,env){let command=buildGlobalPackageCommand(installation,operation,[packageName],target),commandEnv=operation==="uninstall"?env:packageRegistryEnvironment(packageName,env),result=await runner(command.command,command.args,{env:prependCommandPath(command.command,commandEnv),tee:!0});if(result.code!==0)throw new Error(`${packageName} ${operation} failed (exit ${result.code})`)}async function installProviderPackage(packageSpec,dependencies={}){let{packageName,target}=parseProviderPackageSpec(packageSpec),env=process.env,runner=dependencies.runCommand??runCommand,installation=providerManager(env,dependencies),previous=(dependencies.listProviders??listInstalledProviders)().filter(provider=>provider.packageName===packageName);for(let provider of previous)await(dependencies.stopProvider??stopProviderBeforePackageChange)(provider.providerId);await runPackageCommand(installation,"install",packageName,target,runner,env);let packageRoot=await(dependencies.resolvePackageRoot??resolveGlobalProviderPackageRoot)(installation,packageName,runner,env);if(!(dependencies.existsSync??existsSync11)(join24(packageRoot,"package.json")))throw new Error(`${packageName} was installed but its package root was not found: ${packageRoot}`);return{manifest:(dependencies.registerPackage??registerProviderPackage)(packageRoot),manager:installation.manager}}async function updateProviderPackage(selector,dependencies={}){let listProviders=dependencies.listProviders??listInstalledProviders,current2=findRegisteredProvider(selector,listProviders),env=process.env,runner=dependencies.runCommand??runCommand,detectedManager=(dependencies.resolvePackageManager??resolvePackageManagerInstallation)(current2.packageRoot,current2.packageName)??void 0,installation=providerManager(env,dependencies,detectedManager);return await(dependencies.stopProvider??stopProviderBeforePackageChange)(current2.providerId),await runPackageCommand(installation,"update",current2.packageName,"latest",runner,env),{manifest:(dependencies.registerPackage??registerProviderPackage)(current2.packageRoot),manager:installation.manager}}async function uninstallProviderPackage(selector,dependencies={}){let listProviders=dependencies.listProviders??listInstalledProviders,manifest=findRegisteredProvider(selector,listProviders),env=process.env,installation=(dependencies.resolvePackageManager??resolvePackageManagerInstallation)(manifest.packageRoot,manifest.packageName)??providerManager(env,dependencies);return await(dependencies.stopProvider??stopProviderBeforePackageChange)(manifest.providerId),await runPackageCommand(installation,"uninstall",manifest.packageName,"latest",dependencies.runCommand??runCommand,env),(dependencies.unregisterProvider??uninstallProvider)(manifest.providerId),{manifest,manager:installation.manager}}var init_provider=__esm({"../setup/dist/provider.js"(){"use strict";init_host();init_packageManager();init_npmRegistry();init_dist()}});import{readdirSync as readdirSync9}from"node:fs";import{basename as basename5,join as join25,resolve as resolve11}from"node:path";import{homedir as homedir10}from"node:os";async function cleanupUninstalledRuntime(){await cleanupPluginRuntime();let root=join25(homedir10(),".mearl");if(resolve11(pluginDirectory())!==resolve11(join25(root,"plugins")))return;if(listRecordFiles(join25(root,"providers/installed")).length)throw new Error("Registered Providers still use runtime caches; caches were retained");for(let directory of["providers/live","providers/starting"])if(listRecordFiles(join25(root,directory)).some(file2=>{let record5=readRecord(file2);return record5&&isProcessOwnerAlive(record5)}))throw new Error("Providers still use runtime caches; caches were retained");pruneOrphanLogs(join25(root,"managed-browsers"),0),pruneOrphanLogs(join25(root,"providers/logs"),0);let entries;try{entries=readdirSync9(join25(root,"plugin-pages"),{withFileTypes:!0})}catch(error62){if(error62.code==="ENOENT")return;throw error62}for(let entry of entries)if(!(!entry.isDirectory()||!/^[a-f0-9]{16}\.json\.d$/.test(entry.name)))for(let file2 of listRecordFiles(join25(root,"plugin-pages",entry.name))){if(!/^session-[a-f0-9]{16}\.json$/.test(basename5(file2)))continue;let record5=readRecord(file2);record5&&!isProcessOwnerAlive(record5)&&removeRecordIfPid(file2,record5.pid)}}var init_runtimeCleanup=__esm({"../setup/dist/runtimeCleanup.js"(){"use strict";init_plugin2();init_dist()}});import{createHash as createHash14,randomUUID as randomUUID17}from"node:crypto";var digest3,PresetBrowserTransfer,init_presetBrowserTransfer=__esm({"../setup/dist/presetBrowserTransfer.js"(){"use strict";init_bundles();init_presetStore();digest3=bytes=>createHash14("sha256").update(bytes).digest("hex"),PresetBrowserTransfer=class{store;now;session;constructor(store=new PresetInstallationStore,now=Date.now){this.store=store,this.now=now}async selectedRevision(acceptedNodeRevision){let current2=await this.store.current({verifyResources:!1});return current2.receipt?.browserSyncRequired&&current2.receipt.revision===acceptedNodeRevision?current2.receipt.revision:null}async handle(input3){if(!input3||typeof input3!="object")throw new BundleError("BUNDLE_INVALID","Expected a browser transfer operation");if(this.session&&this.now()-this.session.touched>12e4&&(this.session=void 0),input3.operation==="open"){if(this.session)throw new BundleError("BUNDLE_CONFLICT","A browser preset transfer is already running");if(typeof input3.installationRevision!="string"||!/^[a-f0-9]{64}$/.test(input3.installationRevision))throw new BundleError("BUNDLE_INVALID","Expected an installation revision");let current2=await this.store.current();if(!current2.receipt||!current2.prepared||current2.receipt.revision!==input3.installationRevision)throw new BundleError("BUNDLE_CONFLICT","The selected preset changed before browser transfer");let header={schemaVersion:1,installationRevision:current2.receipt.revision,snapshot:current2.prepared.browser.snapshot,bundles:current2.prepared.browser.bundles.map(({source,revision:revision2,manifest})=>({source,revision:revision2,manifest})),overrides:current2.receipt.overrides},bytes=Buffer.from(JSON.stringify(header));if(bytes.byteLength>MAX_PRESET_TRANSFER_HEADER_BYTES)throw new BundleError("BUNDLE_QUOTA","Browser transfer metadata exceeds 16 MiB");let sha2562=digest3(bytes),resources=new Map([[sha2562,bytes]]);for(let bundle of current2.prepared.browser.bundles)for(let[name,entry]of Object.entries(bundle.manifest.resources))resources.set(entry.sha256,bundle.resources[name]);if(this.session)throw new BundleError("BUNDLE_CONFLICT","A browser preset transfer is already running");return this.session={id:randomUUID17(),revision:current2.receipt.revision,touched:this.now(),resources},{session:this.session.id,sha256:sha2562,byteLength:bytes.byteLength}}let session=this.session;if(!session||input3.session!==session.id)throw new BundleError("BUNDLE_CONFLICT","Browser preset transfer expired or changed");if(session.touched=this.now(),input3.operation==="release")return this.session=void 0,{released:!0};if(input3.operation==="confirm"){if((await this.store.current({verifyResources:!1})).receipt?.revision!==session.revision)throw new BundleError("BUNDLE_CONFLICT","The selected preset changed during browser transfer");return{installationRevision:session.revision}}if(input3.operation==="read"){let bytes=session.resources.get(input3.sha256);if(!bytes||!Number.isSafeInteger(input3.offset)||input3.offset<0||input3.offset>=bytes.byteLength||input3.offset%PRESET_TRANSFER_CHUNK_BYTES!==0)throw new BundleError("BUNDLE_INVALID","Invalid browser transfer resource or offset");return{data:Buffer.from(bytes.subarray(input3.offset,input3.offset+PRESET_TRANSFER_CHUNK_BYTES)).toString("base64")}}throw new BundleError("BUNDLE_INVALID","Unsupported browser preset transfer operation")}}}});import{lstat as lstat2,open as open3}from"node:fs/promises";import{constants as constants5}from"node:fs";import{resolve as resolve12}from"node:path";import{fileURLToPath as fileURLToPath3,pathToFileURL as pathToFileURL2}from"node:url";function createNodePresetLoader(){let network=createBundleFetchLoader();return async(source,options)=>{let url2=new URL(source);if(url2.protocol==="file:"){options.signal?.throwIfAborted();let file2=fileURLToPath3(url2),before=await lstat2(file2);if(!before.isFile()||before.isSymbolicLink()||before.size>options.maxBytes)throw new BundleError("BUNDLE_QUOTA","Invalid local preset resource file or size");let handle=await open3(file2,constants5.O_RDONLY|(constants5.O_NOFOLLOW??0));try{let stat5=await handle.stat();if(!stat5.isFile()||stat5.size>options.maxBytes)throw new BundleError("BUNDLE_QUOTA","Local preset resource exceeds its size limit");let bytes=Buffer.alloc(stat5.size+1),length=0;for(;length<bytes.byteLength;){options.signal?.throwIfAborted();let result=await handle.read(bytes,length,bytes.byteLength-length,null);if(!result.bytesRead)break;length+=result.bytesRead}if(length>stat5.size)throw new BundleError("BUNDLE_INTEGRITY","Local preset resource changed while reading");return bytes.subarray(0,length)}finally{await handle.close()}}if(url2.protocol!=="https:"&&!(url2.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(url2.hostname)))throw new BundleError("BUNDLE_INVALID","Preset sources require HTTPS, local files or loopback HTTP");return network(source,options)}}function sourceUrl2(source){try{return new URL(source).href}catch{return pathToFileURL2(resolve12(source)).href}}function localHosts(allowStart=!1){let configured=process.env.MEARL_SOCKET_PATH?.trim(),hosts=configured?[{id:"local",socketPath:configured}]:listLiveBrowsers().filter(browser=>!browser.provider).map(browser=>({id:browser.browserId,socketPath:browser.socketPath}));return hosts.length?Array.from(new Map(hosts.map(host=>[host.socketPath,host])).values()):[{id:"local",...allowStart?{}:{socketPath:SOCKET_PATH}}]}async function runPresetCommand(command,source){let installer=new PresetInstaller;return command==="status"?installer.status():command==="update"?installer.update():installer.apply(source)}var PresetInstaller,init_preset2=__esm({"../setup/dist/preset.js"(){"use strict";init_local();init_dist2();init_bundles();init_presetStore();init_presetInstallationLease();init_presetStore();init_presetBrowserTransfer();PresetInstaller=class{store;options;constructor(options={}){this.options=options,this.store=new PresetInstallationStore(options)}invokeHost(action,data2,target,timeoutSec,allowStart=!1){return!this.options.invoke&&!target.socketPath&&!allowStart?Promise.reject(new Error("HOST_UNAVAILABLE")):(this.options.invoke??(target.socketPath?invoke:invokeLocalHost))(action,data2,{socketPath:target.socketPath,timeoutSec})}async hostStatus(reload,nodeRevision,installationRevision){let targets=this.options.hosts?.()??localHosts(reload);return Promise.all(targets.map(async target=>{try{let result=await this.invokeHost(reload?"node_capabilities_reload":"node_capabilities_status",reload?{structured:!0}:{},target,15,reload);return reload&&result?.accepted===!1?{id:target.id,state:"rejected",error:typeof result.error=="string"?result.error:"Node capability activation was rejected"}:{id:target.id,state:result?.revision===nodeRevision&&result?.installationRevision===installationRevision?"active":"pending"}}catch{return{id:target.id,state:"pending",error:"HOST_UNAVAILABLE"}}}))}async browserStatus(reload,browserRevision,installationRevision){let targets=this.options.hosts?.()??localHosts(reload);return Promise.all(targets.map(async target=>{try{let result=await this.invokeHost(reload?"preset_browser_sync":"preset_browser_status",reload?{installationRevision}:{},target,reload?130:15,reload);return result?.accepted===!1?{id:target.id,state:"rejected",settingsReady:!1,error:typeof result.error=="string"?result.error:"Browser preset activation rejected"}:{id:target.id,state:result?.ready===!0&&result?.browserRevision===browserRevision&&result?.installationRevision===installationRevision?"active":"pending",settingsReady:result?.settingsReady===!0,...Array.isArray(result?.permissionPending)&&result.permissionPending.length?{error:"PRESET_DOMAIN_PERMISSION_REQUIRED: Open extension settings and authorize preset domains"}:{}}}catch{return{id:target.id,state:"pending",settingsReady:!1,error:"BROWSER_UNAVAILABLE"}}}))}async componentStatus(install,receipt){let target=(this.options.hosts?.()??localHosts(install))[0];if(!target)return{};try{let result=await this.invokeHost(install?"preset_components_install":"preset_components_status",{installationRevision:receipt.revision},target,install?900:15,install);if(result?.installationRevision!==receipt.revision||result?.nodeRevision!==receipt.nodeRevision||!Array.isArray(result?.components))throw new Error("Component installation host requires an update");if(result.catalogReady!==void 0&&typeof result.catalogReady!="boolean")throw new Error("Invalid plugin catalog readiness");let seen=new Set;for(let entry of result.components){if(!entry||!["skills","providers"].includes(entry.kind)||typeof entry.id!="string"||!["ready","pending","conflict"].includes(entry.state)||entry.reason!==void 0&&typeof entry.reason!="string")throw new Error("Invalid component installation status");let key=`${entry.kind}:${entry.id}`;if(seen.has(key))throw new Error("Duplicate component installation status");seen.add(key)}return{status:result}}catch(error62){return{error:error62 instanceof Error?error62.message:String(error62)}}}async status(){let current2=await this.store.current(),result={revision:presetSelectionRevision(current2),ready:!1,hosts:[],browsers:[],pending:[],warnings:[]};if(!current2.receipt||!current2.prepared)return result;let{preset}=current2.prepared,receipt=current2.receipt;result.id=preset.id,result.version=preset.version,result.overrides=receipt.overrides,result.settings=mergePresetDefaults(preset.defaults,receipt.overrides),result.nodeRevision=receipt.nodeRevision,result.browserRevision=receipt.browserRevision,result.browserSyncRequired=receipt.browserSyncRequired===!0,result.localComponentCount=Object.values(preset.packages).filter(entry=>entry.enabled!==!1&&entry.runtimes?.includes("node")).length+[...Object.values(preset.skills),...Object.values(preset.providers)].filter(entry=>entry.enabled!==!1).length,result.hosts=await this.hostStatus(!1,receipt.nodeRevision,receipt.revision);let nodeReady=result.hosts.length>0&&result.hosts.every(host=>host.state==="active");receipt.browserSyncRequired&&(result.browsers=await this.browserStatus(!1,receipt.browserRevision,receipt.revision));let browserReady=result.browsers.length>0&&result.browsers.every(host=>host.state==="active");for(let[id2,entry]of Object.entries(preset.packages))if(entry.enabled!==!1)for(let runtime of entry.runtimes??[])runtime==="browser"?browserReady||result.pending.push({kind:"packages",id:id2,runtime,reason:"BROWSER_SYNC_REQUIRED"}):receipt.managedModules.includes(id2)?nodeReady||result.pending.push({kind:"packages",id:id2,runtime,reason:"HOST_SYNC_REQUIRED"}):result.pending.push({kind:"packages",id:id2,runtime,reason:"NODE_HOST_NOT_SUPPORTED"});let hasExternal=["skills","providers"].some(kind=>Object.values(preset[kind]).some(entry=>entry.enabled!==!1)),hasCatalog=!!current2.activation?.bindings["plugins.catalog"],components=hasExternal||hasCatalog?await this.componentStatus(!1,receipt):{};hasCatalog&&components.status?.catalogReady!==!0&&result.pending.push({kind:"preset",id:preset.id,runtime:"node",reason:"PLUGIN_CATALOG_SYNC_REQUIRED"}),components.error&&result.warnings.push(`Component readiness check failed: ${components.error}`);for(let kind of["skills","providers"])for(let[id2,entry]of Object.entries(preset[kind])){if(entry.enabled===!1)continue;let actual=components.status?.components.find(component2=>component2.kind===kind&&component2.id===id2);actual?.state!=="ready"&&result.pending.push({kind,id:id2,runtime:"node",reason:actual?.reason??"INSTALL_REQUIRED"})}return receipt.nodeSyncRequired&&!nodeReady&&!result.pending.some(entry=>entry.reason==="HOST_SYNC_REQUIRED")&&result.pending.push({kind:"preset",id:preset.id,runtime:"node",reason:"HOST_SYNC_REQUIRED"}),receipt.browserSyncRequired&&!browserReady&&!result.pending.some(entry=>entry.reason==="BROWSER_SYNC_REQUIRED")&&result.pending.push({kind:"preset",id:preset.id,runtime:"browser",reason:"BROWSER_SYNC_REQUIRED"}),receipt.browserSyncRequired&&browserReady&&Object.keys(result.settings??{}).length&&!result.browsers.every(host=>host.settingsReady)&&result.pending.push({kind:"preset",id:preset.id,runtime:"browser",reason:"SETTINGS_APPLY_REQUIRED"}),result.ready=result.pending.length===0,result}async synchronize(receipt,previousRevision){let rejection=(await this.hostStatus(!0,receipt.nodeRevision,receipt.revision)).find(host=>host.state==="rejected");if(rejection){if(previousRevision===receipt.revision)throw new Error(`Node activation rejected; installed selection retained: ${rejection.error}`);await this.store.rollback(receipt.revision);let previous=await this.store.current({verifyResources:!1});throw await this.hostStatus(!0,previous.activation?.revision??"",previous.activation?.installationRevision??""),new Error(`Node activation rejected; previous selection restored: ${rejection.error}`)}if(receipt.browserSyncRequired){let rejected=(await this.browserStatus(!0,receipt.browserRevision,receipt.revision)).find(host=>host.state==="rejected");if(rejected)throw new Error(`Browser activation rejected; installed selection retained for retry: ${rejected.error}`)}let installed=await this.store.current({verifyResources:!1});if(installed.receipt?.revision!==receipt.revision||!installed.preset)throw new BundleError("BUNDLE_CONFLICT","The selected preset changed before component installation");let components=[...Object.values(installed.preset.skills),...Object.values(installed.preset.providers)].some(entry=>entry.enabled!==!1)||installed.activation?.bindings["plugins.catalog"]?await this.componentStatus(!0,receipt):{},status=await this.status();if(components.error&&status.warnings.push(`Component installation failed; completed assets retained for retry: ${components.error}`),status.revision!==receipt.revision)throw new BundleError("BUNDLE_CONFLICT","The selected preset changed while hosts were synchronizing");try{await this.store.collectGarbage()}catch(error62){status.warnings.push(`Preset resource cleanup failed: ${error62 instanceof Error?error62.message:String(error62)}`)}return status}async stageSource(source,expectedRevision,options){let prepared=await preparePreset(sourceUrl2(source),{mearlApiVersion:this.store.mearlApiVersion,load:this.options.load??createNodePresetLoader(),signal:options.signal});if(options.expectedId!==void 0&&prepared.preset.id!==options.expectedId)throw new BundleError("BUNDLE_INVALID","The preset identity differs from the accepted installation");let receipt=await this.store.apply(prepared,{...options,expectedRevision}),activated=!1;return{revision:receipt.revision,activate:async()=>{if(activated)throw new Error("Staged installation was already activated; use resume with its revision to retry");activated=!0;let current2=await this.store.current();if(current2.receipt?.revision!==receipt.revision)throw new BundleError("BUNDLE_CONFLICT","The selected preset changed before runtime setup completed");return this.synchronize(current2.receipt,expectedRevision)}}}async stage(source,options={}){let accepted={...options,...options.overrides===void 0?{}:{overrides:mergePresetDefaults({},options.overrides)},...options.nodeConfig===void 0?{}:{nodeConfig:mergePresetDefaults({},options.nodeConfig)}},current2=await this.store.current({verifyResources:!1}),revision2=presetSelectionRevision(current2);if(accepted.expectedRevision!==void 0&&accepted.expectedRevision!==revision2)throw new BundleError("BUNDLE_CONFLICT","The installed preset changed before preparation");return this.stageSource(source,revision2,accepted)}async apply(source,options={}){return(await this.stage(source,options)).activate()}async update(){let current2=await this.store.current({verifyResources:!1});if(!current2.receipt)throw new Error("No preset is installed");return this.apply(current2.receipt.snapshot.source,{expectedRevision:presetSelectionRevision(current2)})}async resume(expectedRevision){let current2=await this.store.current();if(!current2.receipt||current2.receipt.revision!==expectedRevision)throw new BundleError("BUNDLE_CONFLICT","The installed preset changed before resuming");return this.synchronize(current2.receipt,expectedRevision)}async configure(value){let current2=await this.store.current();if(!current2.prepared)throw new Error("No preset is installed");let receipt=await this.store.apply(current2.prepared,{expectedRevision:presetSelectionRevision(current2),overrides:value});return this.synchronize(receipt,presetSelectionRevision(current2))}}}});import{readFileSync as readFileSync16}from"node:fs";import{join as join26}from"node:path";function isLegacyPackage(installation){try{let manifest=JSON.parse(readFileSync16(join26(installation.packageRoot,"package.json"),"utf8"));return manifest.name===installation.packageName&&typeof manifest.version=="string"&&/^[0-2]\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(manifest.version)}catch{return!1}}async function prepareSetupPreset(options,dependencies={}){let env=options.env??process.env,installer=dependencies.installer??new PresetInstaller({directory:env.MEARL_CAPABILITY_DIR});if(options.source)return installer.stage(options.source);let current2=await installer.store.current();if(current2.receipt)return{revision:current2.receipt.revision,activate:()=>installer.resume(current2.receipt.revision)};if(current2.activation)return;let client2=options.installed===void 0?detectGlobalPackage("@mearl/client",["mearl"],{env}):null;if(!(options.installed??[...detectMearlProfiles({env}).values(),...client2?[client2]:[]]).some(isLegacyPackage))return;let config2=await(dependencies.configuration??(()=>{let url2=new URL(env.MEARL_INSTALLATION_CONFIG_URL||MEARL_INSTALLATION_CONFIG_URL);if(url2.protocol!=="https:"&&!(url2.protocol==="http:"&&["127.0.0.1","localhost","[::1]"].includes(url2.hostname)))throw new Error("Installation configuration requires HTTPS or loopback HTTP");return loadInstallationConfig(url2,url2.origin,AbortSignal.timeout(5e3))}))();if(!config2.legacyPreset)throw new Error("Legacy upgrade preset is unavailable. Retry when the installation catalog is available, or select a preset with --preset.");return installer.stage(config2.presets[config2.legacyPreset].source,{expectedRevision:null,expectedId:config2.legacyPreset})}var MEARL_INSTALLATION_CONFIG_URL,init_presetUpgrade=__esm({"../setup/dist/presetUpgrade.js"(){"use strict";init_installation();init_preset2();init_packageManager();init_profiles();MEARL_INSTALLATION_CONFIG_URL="https://f-loat.github.io/mearl/installation.json"}});import fs10 from"node:fs";import path12 from"node:path";import{createInterface as createInterface2}from"node:readline/promises";import{stdin as input,stdout as output}from"node:process";function autoUpdateClientInstallation(env){let root=env.MEARL_AUTO_UPDATE_CLIENT_ROOT;if(!root)return null;try{let packageRoot=fs10.realpathSync(root);return JSON.parse(fs10.readFileSync(path12.join(packageRoot,"package.json"),"utf8")).name!==UNIFIED_CLIENT?null:resolvePackageManagerInstallation(packageRoot,UNIFIED_CLIENT,{env})}catch{return null}}function autoUpdateProfiles(env){let value=env.MEARL_AUTO_UPDATE_PROFILES;if(value===void 0)return;let requested=value.split(",");if(requested.length===0||new Set(requested).size!==requested.length||requested.some(profile=>!PROFILE_ORDER.includes(profile)))throw new Error("Invalid automatic update profiles");return PROFILE_ORDER.filter(profile=>requested.includes(profile))}async function confirmInTerminal(message){if(!input.isTTY||!output.isTTY)return!1;let readline2=createInterface2({input,output});try{let answer=(await readline2.question(`${message} [y/N] `)).trim().toLowerCase();return answer==="y"||answer==="yes"}finally{readline2.close()}}function siblingExecutable(anchor2,name,existsSync14){for(let executableName of executableNames(name)){let candidate=path12.join(path12.dirname(anchor2),executableName);if(existsSync14(candidate))return candidate}return null}async function runLifecycleCommand(runner,command,args,env,label,writeLine,capture=!1){try{return await runner(command,args,{capture,env:prependCommandPath(command,env)})}catch(error62){return writeLine(`${label}: ${error62 instanceof Error?error62.message:String(error62)}`),null}}async function runPackageOperation(installation,operation,packages,targetLine,runner,env,label,writeLine){let command=buildGlobalPackageCommand(installation,operation,packages,targetLine);writeLine(`${label}: ${command.manager}`);let result=await runLifecycleCommand(runner,command.command,command.args,operation==="uninstall"?env:packages.reduce((childEnv,name)=>packageRegistryEnvironment(name,childEnv),env),label,writeLine);return result?result.code!==0?(writeLine(`${label}: failed (exit ${result.code}).`),{command,ok:!1}):{command,ok:!0}:{command,ok:!1}}async function runHook(profile,operation,detected,runner,existsSync14,env,writeLine,yes,cloudMode){let executable,args,hookEnv=env;if(profile==="local"&&operation!=="uninstall")executable=detected.executable,args=["--init","--mode","skills"];else if(profile==="mcp"&&operation==="uninstall")executable=siblingExecutable(detected.executable,"mearl-mcp-uninstall",existsSync14),args=["--managed-by-setup",...yes?["--yes"]:[]];else if(profile==="mcp"&&operation==="install")executable=siblingExecutable(detected.executable,"mearl-mcp-configure",existsSync14),args=["--global-install",...yes?["--yes"]:[]];else if(profile==="mcp"&&operation==="update")executable=siblingExecutable(detected.executable,"mearl-mcp-configure",existsSync14),args=["--skip-mcp","--global-install"];else if(profile==="cloud"){if(executable=siblingExecutable(detected.executable,"mearl-cloud-server",existsSync14),!executable)return writeLine(`Hook: ${profile} ${operation} command was not found.`),!1;let requestedMode=operation==="uninstall"?"websocket":cloudMode,commandEnv=requestedMode?{...env,MEARL_CLOUD_MODE:requestedMode}:env;if(hookEnv=commandEnv,operation==="uninstall")args=["stop"];else{let status=await runLifecycleCommand(runner,executable,["status"],commandEnv,"Hook: cloud status",writeLine,!0);if(!status)return!1;if(status.code!==0)return writeLine(`Hook: cloud status failed (exit ${status.code}).`),!1;let running=/\[CloudServer\]\s+Status:\s+running\b/.test(status.stdout),qoderMode=cloudMode==="qoder"||/\[CloudServer\]\s+Mode:\s+qoder\b/.test(status.stdout);if(operation==="update"&&!running&&!qoderMode)return writeLine("Hook: cloud update skipped because cloud-server is not running."),!0;args=[running&&!qoderMode?"restart":"start"]}}else return!0;if(!executable)return writeLine(`Hook: ${profile} ${operation} command was not found.`),!1;let result=await runLifecycleCommand(runner,executable,args,hookEnv,`Hook: ${profile} ${operation}`,writeLine);return result?result.code!==0?(writeLine(`Hook: ${profile} ${operation} failed (exit ${result.code}).`),!1):!0:!1}function selectedProfiles(operation,requested,detected){if(requested?.length){let requestedSet=new Set(requested);return PROFILE_ORDER.filter(profile=>requestedSet.has(profile))}return operation==="install"?["local"]:PROFILE_ORDER.filter(profile=>detected.has(profile))}function operationVerb(operation){return operation==="uninstall"?"uninstall":operation==="update"?"update":"install"}async function runMearlLifecycle(options,dependencies={}){if(options.targetLine&&!isMearlReleaseLine(options.targetLine))throw new Error("targetLine must be a valid Mearl release line such as 2.3.x");let env=options.env??process.env,writeLine=dependencies.writeLine??(line=>console.error(line)),existsSync14=dependencies.existsSync??fs10.existsSync,runner=dependencies.runCommand??runCommand,cleanupNativeHost=dependencies.cleanupNativeHost??uninstallNativeHostArtifacts,detectPackage=dependencies.detectPackage??detectGlobalPackage,detectProfiles=dependencies.detectProfiles??((childEnv,detectionOptions)=>detectMearlProfiles({env:childEnv,...detectionOptions})),resolveInvokingManager=dependencies.resolveInvokingManager??(childEnv=>resolveInvokingPackageManager({env:childEnv})),runSkill=dependencies.runSkillOperation??runGlobalMearlSkillOperation,confirm=dependencies.confirm??confirmInTerminal,detectionEnv=options.operation==="update"&&env.MEARL_AUTO_UPDATE_SEARCH_PATH!==void 0?{...env,PATH:env.MEARL_AUTO_UPDATE_SEARCH_PATH}:env,detected=options.operation==="uninstall"?detectProfiles(detectionEnv,{includeLinked:!0}):detectProfiles(detectionEnv),packageDetectionOptions={env,...options.operation==="uninstall"?{includeLinked:!0}:{}},requestedProfiles=options.profiles??(options.profile?[options.profile]:void 0)??(options.operation==="update"?autoUpdateProfiles(env):void 0),autoUpdateClient=options.operation==="update"?autoUpdateClientInstallation(env):null,clientOnly=options.operation==="update"&&env.MEARL_AUTO_UPDATE_CLIENT_ONLY==="1";if(clientOnly&&!autoUpdateClient)return writeLine("A valid global client installation is required for a client-only update."),!1;let selected=clientOnly?[]:selectedProfiles(options.operation,requestedProfiles,detected),lifecycleSelected=options.operation==="uninstall"?selected.filter(profile=>detected.has(profile)):selected,fullPluginNames=[],fullProviders=[];if(options.operation==="uninstall"&&options.full){try{fullPluginNames=(dependencies.listPlugins??listPluginNames)()}catch(error62){return writeLine(`CLI plugins: unable to list registrations: ${error62 instanceof Error?error62.message:String(error62)}`),!1}try{fullProviders=(dependencies.listProviders??listInstalledProviders)()}catch(error62){return writeLine(`Providers: unable to list registrations: ${error62 instanceof Error?error62.message:String(error62)}`),!1}}if(selected.length===0&&!autoUpdateClient&&!(options.operation==="uninstall"&&options.full))return writeLine("No installed Mearl environment was detected on the current PATH."),options.operation==="uninstall"?!0:(writeLine("Run: npx --@mearl:registry=https://registry.npmjs.org @mearl/setup"),!1);if(options.operation==="update"){let missing=selected.filter(profile=>!detected.has(profile));if(missing.length>0)return writeLine(`Not installed: ${missing.join(", ")}. Run the install command first.`),!1}if(options.operation==="uninstall"){if(lifecycleSelected.length===0&&!options.full)return writeLine(`Nothing to uninstall for ${selected.join(", ")}.`),!0;if(options.full&&(writeLine(`CLI plugins: ${fullPluginNames.length>0?fullPluginNames.join(", "):"none detected"}.`),writeLine(`Providers: ${fullProviders.length>0?fullProviders.map(provider=>provider.providerId).join(", "):"none detected"}.`)),!options.yes&&!await confirm(options.full?`Uninstall ${lifecycleSelected.length>0?`Mearl environments (${lifecycleSelected.join(", ")}) and `:""}all CLI plugins and Providers?`:`Uninstall Mearl environments: ${lifecycleSelected.join(", ")}?`))return writeLine("Uninstall cancelled."),!1}lifecycleSelected.length>0&&writeLine(`${operationVerb(options.operation)} targets: ${lifecycleSelected.map(profile=>MEARL_PROFILES[profile].label).join(", ")}`),autoUpdateClient&&lifecycleSelected.length===0&&writeLine("update targets: client CLI");let selectedUsesClient=autoUpdateClient!==null||lifecycleSelected.some(profile=>CLIENT_PROFILES.includes(profile)),detectedClientCandidate=selectedUsesClient&&!autoUpdateClient?detectPackage(UNIFIED_CLIENT,["mearl"],packageDetectionOptions):null,detectedClient=detectedClientCandidate?.packageName===UNIFIED_CLIENT?detectedClientCandidate:null,stagedPreset;if(options.operation!=="uninstall"&&(options.presetSource||!options.targetLine||Number(options.targetLine.split(".")[0])>=3)&&!dependencies.prepareSkills)try{stagedPreset=await(dependencies.preparePreset??prepareSetupPreset)({source:options.presetSource,env,installed:[...lifecycleSelected.flatMap(profile=>detected.get(profile)?[detected.get(profile)]:[]),...detectedClient?[detectedClient]:[],...autoUpdateClient&&env.MEARL_AUTO_UPDATE_CLIENT_ROOT?[{packageName:UNIFIED_CLIENT,packageRoot:env.MEARL_AUTO_UPDATE_CLIENT_ROOT}]:[]]})}catch(error62){return writeLine(`Preset preparation: ${error62 instanceof Error?error62.message:String(error62)}`),!1}let packagesOk=!0;if(lifecycleSelected.includes("cloud")){let legacyCloudClient=detectPackage(LEGACY_CLOUD_CLIENT,["mearl-cloud"],packageDetectionOptions);if(legacyCloudClient?.packageName===LEGACY_CLOUD_CLIENT){let command=buildGlobalPackageCommand(legacyCloudClient,"uninstall",[LEGACY_CLOUD_CLIENT]);writeLine(`Migration (cloud): removing ${LEGACY_CLOUD_CLIENT}.`);let result=await runLifecycleCommand(runner,command.command,command.args,env,"Migration (cloud)",writeLine);if(!result||result.code!==0)return result&&writeLine(`Migration (cloud): failed (exit ${result.code}).`),writeLine("Mearl setup incomplete. Review the messages above."),!1}}let remainingClientProfiles=selectedUsesClient&&options.operation==="uninstall"?CLIENT_PROFILES.filter(profile=>detected.has(profile)&&!lifecycleSelected.includes(profile)):[];if(selectedUsesClient&&options.operation!=="uninstall"){let clientInstallation=autoUpdateClient??detectedClient??detected.get("local")??detected.get("cloud")??resolveInvokingManager(env);if(!(await runPackageOperation(clientInstallation,options.operation,[UNIFIED_CLIENT],mearlPackageSelector(SETUP_VERSION,options.targetLine),runner,env,"Packages (shared client)",writeLine)).ok)return writeLine("Mearl setup incomplete. Review the messages above."),!1}for(let profile of lifecycleSelected){let config2=MEARL_PROFILES[profile],current2=detected.get(profile);if(options.operation==="uninstall"&&!current2)continue;let installation=current2??resolveInvokingManager(env);if(options.operation==="uninstall"&&profile==="local"){let keepsMcp=detected.has("mcp")&&!selected.includes("mcp"),cleanupOk=!1;try{cleanupOk=cleanupNativeHost({mode:keepsMcp?"skills":"both",silent:!0})}catch(error62){writeLine(`Native host: ${error62 instanceof Error?error62.message:String(error62)}`)}if(writeLine(`Native host: ${cleanupOk?"cleaned.":"cleanup failed."}`),!cleanupOk){packagesOk=!1;continue}}if(options.operation==="uninstall"&&!await runHook(profile,options.operation,current2,runner,existsSync14,env,writeLine,options.yes===!0,options.cloudMode)){packagesOk=!1;continue}let packageOperation=options.operation,packages=config2.packages.filter(packageName=>packageName!==UNIFIED_CLIENT);if(packages.length===0)continue;let packageResult=await runPackageOperation(installation,packageOperation,packages,mearlPackageSelector(SETUP_VERSION,options.targetLine),runner,env,`Packages (${profile})`,writeLine);if(!packageResult.ok){packagesOk=!1;continue}if(options.operation!=="uninstall"&&(profile==="local"||profile==="cloud"||profile==="mcp")){let refreshed=detectPackage(config2.anchorPackage,config2.anchorBins,{env:prependCommandPath(packageResult.command.command,env)})??current2;(!refreshed||!await runHook(profile,options.operation,refreshed,runner,existsSync14,env,writeLine,options.yes===!0,options.cloudMode))&&(packagesOk=!1)}}selectedUsesClient&&options.operation==="uninstall"&&(remainingClientProfiles.length>0?writeLine(`Packages (shared client): kept for ${remainingClientProfiles.join(", ")}.`):packagesOk&&detectedClient&&((await runPackageOperation(detectedClient,"uninstall",[UNIFIED_CLIENT],void 0,runner,env,"Packages (shared client)",writeLine)).ok||(packagesOk=!1)));let fullOk=!0;if(options.operation==="uninstall"&&options.full){let pendingPlugins=new Set(fullPluginNames),removePlugin=dependencies.uninstallPlugin??uninstallPlugin;for(;pendingPlugins.size>0;){let name=pendingPlugins.values().next().value;pendingPlugins.delete(name);try{let result=await removePlugin(name,runner);for(let removedName of result.unregisteredNames)pendingPlugins.delete(removedName);result.removed&&writeLine(`CLI plugin ${name}: uninstalled.`)}catch(error62){fullOk=!1,writeLine(`CLI plugin ${name}: ${error62 instanceof Error?error62.message:String(error62)}`)}}let removeProvider=dependencies.uninstallProvider??uninstallProviderPackage;for(let provider of fullProviders)try{await removeProvider(provider.providerId),writeLine(`Provider ${provider.providerId}: uninstalled.`)}catch(error62){fullOk=!1,writeLine(`Provider ${provider.providerId}: ${error62 instanceof Error?error62.message:String(error62)}`)}}if(packagesOk&&options.operation!=="uninstall"&&(stagedPreset||dependencies.prepareSkills))try{if(stagedPreset){let status=await stagedPreset.activate();dependencies.onPresetStatus?.(status)}else await dependencies.prepareSkills()}catch(error62){writeLine(`Skill preparation: ${error62 instanceof Error?error62.message:String(error62)}`),packagesOk=!1}let selectedUsesSkill=lifecycleSelected.some(profile=>MEARL_PROFILES[profile].usesSkill),remainingSkillProfiles=["local","cloud"].filter(profile=>detected.has(profile)&&!lifecycleSelected.includes(profile)),runStandaloneSkill=!0,skillOk=!0;if(packagesOk&&selectedUsesSkill&&selectedUsesClient&&options.skipSkill!==!0&&(options.operation==="uninstall"&&remainingSkillProfiles.length===0||options.operation!=="uninstall"&&options.targetLine===void 0))try{if(options.operation==="uninstall"){let result=await(dependencies.uninstallManagedPlugins??uninstallManagedPlugins)({env,includeMearlSkill:!0,runCommand:runner,writeLine});result==="uninstalled"?runStandaloneSkill=!1:result==="failed"&&(runStandaloneSkill=!1,skillOk=!1)}else{let result=await(dependencies.installManagedPlugins??installManagedPlugins)({env,includeMearlSkill:!0,mearlSkillOperation:options.operation,runCommand:runner,writeLine});result==="installed"?runStandaloneSkill=!1:result==="failed"&&(runStandaloneSkill=!1,skillOk=!1)}}catch(error62){writeLine(`Managed plugin batch: ${error62 instanceof Error?error62.message:String(error62)}; failed.`),runStandaloneSkill=!1,skillOk=!1}if(packagesOk&&skillOk&&selectedUsesSkill&&options.skipSkill!==!0)try{options.operation==="uninstall"?remainingSkillProfiles.length>0?(writeLine(`Skill: kept for ${remainingSkillProfiles.join(", ")}.`),writeLine(`Managed plugins: kept for ${remainingSkillProfiles.join(", ")}.`)):runStandaloneSkill&&(skillOk=await runSkill("uninstall",{env,yes:options.yes,writeLine,runCommand:runner})):runStandaloneSkill&&(skillOk=await runSkill(options.operation,{env,yes:options.yes,writeLine,runCommand:runner}))}catch(error62){writeLine(`Skill: ${error62 instanceof Error?error62.message:String(error62)}`),skillOk=!1}if(packagesOk&&skillOk&&fullOk){if(options.operation==="uninstall"&&options.full&&PROFILE_ORDER.every(profile=>!detected.has(profile)||lifecycleSelected.includes(profile)))try{await(dependencies.cleanupRuntime??cleanupUninstalledRuntime)(),writeLine("Runtime caches: cleaned; user data and browser profiles were retained.")}catch(error62){return writeLine(`Runtime caches: ${error62 instanceof Error?error62.message:String(error62)}`),!1}return writeLine("Mearl setup complete."),!0}return writeLine("Mearl setup incomplete. Review the messages above."),!1}var LEGACY_CLOUD_CLIENT,UNIFIED_CLIENT,PROFILE_ORDER,CLIENT_PROFILES,init_lifecycle=__esm({"../setup/dist/lifecycle.js"(){"use strict";init_setupVersion();init_plugin2();init_host();init_skills();init_packageManager();init_profiles();init_nativeHost2();init_dist();init_version();init_managedPlugins();init_plugin3();init_provider();init_npmRegistry();init_runtimeCleanup();init_presetUpgrade();LEGACY_CLOUD_CLIENT="@mearl/cloud-client",UNIFIED_CLIENT="@mearl/client",PROFILE_ORDER=["local","cloud","mcp"],CLIENT_PROFILES=["local","cloud"]}});var dist_exports4={};__export(dist_exports4,{BrowserExtensionConflictError:()=>BrowserExtensionConflictError,MEARL_EXTENSION_DIRECTORY_NAME:()=>MEARL_EXTENSION_DIRECTORY_NAME,MEARL_EXTENSION_DOWNLOAD_PAGE:()=>MEARL_EXTENSION_DOWNLOAD_PAGE,MEARL_EXTENSION_ID:()=>MEARL_EXTENSION_ID,MEARL_EXTENSION_REMOTE_CONFIG_URL:()=>MEARL_EXTENSION_REMOTE_CONFIG_URL,MEARL_NATIVE_HOST_NAMES:()=>MEARL_NATIVE_HOST_NAMES,MEARL_PROFILES:()=>MEARL_PROFILES,PresetInstallationStore:()=>PresetInstallationStore,PresetInstaller:()=>PresetInstaller,buildGlobalPackageCommand:()=>buildGlobalPackageCommand,compareMearlReleaseLines:()=>compareMearlReleaseLines,compareMearlReleaseVersions:()=>compareMearlReleaseVersions,createNodePresetLoader:()=>createNodePresetLoader,detectGlobalPackage:()=>detectGlobalPackage,detectMearlProfiles:()=>detectMearlProfiles,extensionArchiveUrl:()=>extensionArchiveUrl,fetchBrowserExtensionRelease:()=>fetchBrowserExtensionRelease,findLatestExtensionVersion:()=>findLatestExtensionVersion,inspectMearlExtensionViaCdp:()=>inspectMearlExtensionViaCdp,inspectProviderPackage:()=>inspectProviderPackage,installMearlBrowserExtension:()=>installMearlBrowserExtension,installProviderPackage:()=>installProviderPackage,isMearlReleaseLine:()=>isMearlReleaseLine,isMearlReleaseVersion:()=>isMearlReleaseVersion,isMearlReleaseVersionInLine:()=>isMearlReleaseVersionInLine,loadUnpackedViaCdp:()=>loadUnpackedViaCdp,mearlReleaseLineForVersion:()=>mearlReleaseLineForVersion,parseMearlReleaseLine:()=>parseMearlReleaseLine,parseMearlReleaseVersion:()=>parseMearlReleaseVersion,parseProviderPackageSpec:()=>parseProviderPackageSpec,resolveChromeUserDataDirectory:()=>resolveChromeUserDataDirectory,resolveInvokingPackageManager:()=>resolveInvokingPackageManager,resolveMearlExtensionDirectory:()=>resolveMearlExtensionDirectory,resolveNativeHostDirectory:()=>resolveNativeHostDirectory,resolvePackageManagerInstallation:()=>resolvePackageManagerInstallation,runMearlLifecycle:()=>runMearlLifecycle,runPresetCommand:()=>runPresetCommand,uninstallNativeHostArtifacts:()=>uninstallNativeHostArtifacts,uninstallProviderPackage:()=>uninstallProviderPackage,updateProviderPackage:()=>updateProviderPackage,updateSetupManagedBrowserExtensionFiles:()=>updateSetupManagedBrowserExtensionFiles});var init_dist7=__esm({"../setup/dist/index.js"(){"use strict";init_packageManager();init_browserExtension();init_lifecycle();init_profiles();init_nativeHost2();init_preset2();init_version();init_provider()}});function initBrowserRegistry(deps){logFn=deps.log}function recordFileFor(browserId){return keyedConfigFile(BROWSERS_SUBDIR2,browserId)}function persist(patch){if(!identity)return!1;let file2=recordFileFor(identity.browserId),previous=readRecord(file2);if(previous&&previous.pid!==process.pid&&isAlive(previous.pid))return!1;let record5={pid:process.pid,browserId:identity.browserId,socketPath:identity.socketPath,browserName:identity.browserName,nativeHostVersion:identity.nativeHostVersion,transport:identity.transport,startedAt:identity.startedAt,lastFocusedAt:previous?.lastFocusedAt,extensionVersion:previous?.extensionVersion,...patch,updatedAt:Date.now()};try{return previous?.pid===process.pid?writeRecordIfPid(file2,process.pid,record5):previous&&!removeRecordIfPid(file2,previous.pid)?!1:claimRecord(file2,record5)}catch(error62){return logFn("browser-registry: failed to write record:",String(error62)),!1}}async function retireUnreachableBrowserRecord(browserId,canConnect){let file2=recordFileFor(browserId),existing=readRecord(file2);return!existing||existing.pid===process.pid||!isAlive(existing.pid)||await canConnect(existing.socketPath)?!1:removeRecordIfPid(file2,existing.pid)}function registerBrowser(info){let startedAt=identity?.startedAt??new Date().toISOString();identity={browserId:info.browserId,socketPath:info.socketPath,browserName:info.browserName,nativeHostVersion:info.nativeHostVersion,transport:info.transport,startedAt};let previous=readRecord(recordFileFor(info.browserId)),registered=persist({lastFocusedAt:info.preserveFocus===!1?info.lastFocusedAt??0:previous?.lastFocusedAt??info.lastFocusedAt??Date.now(),extensionVersion:info.extensionVersion});return logFn(`browser-registry: ${registered?"registered":"registration skipped"} ${info.browserName} (${info.browserId})`),registered}function writeFocusNow(){lastFocusWriteAt=Date.now(),persist({lastFocusedAt:lastFocusWriteAt})}function touchFocus(){if(!identity)return;let elapsed=Date.now()-lastFocusWriteAt;if(elapsed>=FOCUS_WRITE_THROTTLE_MS){writeFocusNow();return}focusFlushTimer||(focusFlushTimer=setTimeout(()=>{focusFlushTimer=null,writeFocusNow()},FOCUS_WRITE_THROTTLE_MS-elapsed),focusFlushTimer.unref())}function unregisterBrowser(){if(focusFlushTimer&&(clearTimeout(focusFlushTimer),focusFlushTimer=null),!identity)return;let file2=recordFileFor(identity.browserId);removeRecordIfPid(file2,process.pid)}function pruneDeadBrowsers(){let now=Date.now();for(let file2 of listRecordFiles(keyedDir(BROWSERS_SUBDIR2))){let rec=readRecord(file2);if(!rec){removeInvalidRecord(file2);continue}isAlive(rec.pid)||now-(rec.updatedAt??0)>STALE_RECORD_TTL_MS&&removeRecordIfPid(file2,rec.pid)}}function listRegisteredBrowsers(){let records=[];for(let file2 of listRecordFiles(keyedDir(BROWSERS_SUBDIR2))){let record5=readRecord(file2);record5&&isAlive(record5.pid)&&records.push(record5)}return records.sort((a,b)=>(b.lastFocusedAt??0)-(a.lastFocusedAt??0)||(b.updatedAt??0)-(a.updatedAt??0))}var BROWSERS_SUBDIR2,STALE_RECORD_TTL_MS,FOCUS_WRITE_THROTTLE_MS,identity,logFn,lastFocusWriteAt,focusFlushTimer,init_browser_registry=__esm({"src/browser-registry.ts"(){"use strict";init_dist();BROWSERS_SUBDIR2="browsers",STALE_RECORD_TTL_MS=5*6e4,FOCUS_WRITE_THROTTLE_MS=1e3,identity=null,logFn=()=>{};lastFocusWriteAt=0,focusFlushTimer=null}});import{StringDecoder as StringDecoder3}from"node:string_decoder";var Utf8LineBuffer,init_socketLines=__esm({"src/socketLines.ts"(){"use strict";Utf8LineBuffer=class{constructor(maxBufferBytes){this.maxBufferBytes=maxBufferBytes}maxBufferBytes;buffer="";decoder=new StringDecoder3("utf8");push(chunk){if(this.buffer+=this.decoder.write(chunk),Buffer.byteLength(this.buffer,"utf8")>this.maxBufferBytes)return this.buffer="",{lines:[],overflow:!0};let lines=[],newlineIndex;for(;(newlineIndex=this.buffer.indexOf(`
1109
1109
  `))!==-1;)lines.push(this.buffer.slice(0,newlineIndex)),this.buffer=this.buffer.slice(newlineIndex+1);return{lines,overflow:!1}}}}});var browser_socket_request_exports={};__export(browser_socket_request_exports,{requestBrowserSocket:()=>requestBrowserSocket});import net3 from"node:net";function requestBrowserSocket(socketPath,action,data2,version3,timeoutMs=6e4,browserId,host,routing){let id2=`browser_rpc_${Date.now()}_${Math.random().toString(36).slice(2,10)}`;return new Promise((resolve16,reject)=>{let client2=net3.createConnection(socketPath),lines=new Utf8LineBuffer(MAX_RESPONSE_BUFFER_SIZE),settled=!1,finish=(error62,result)=>{settled||(settled=!0,clearTimeout(timer),client2.removeAllListeners(),client2.destroy(),error62?reject(error62):resolve16(result))},timer=setTimeout(()=>finish(new Error(`Browser request timeout after ${timeoutMs}ms`)),timeoutMs),deadline=Date.now()+timeoutMs;client2.on("connect",()=>{client2.write(`${JSON.stringify({id:id2,action,data:data2,version:version3,protocolVersion:3,controlSource:routing?.controlSource??"local",...routing?.providerTarget?{providerTarget:routing.providerTarget,deadline}:{},...browserId?{browserId}:{},...host?{host}:{}})}
1110
- `)}),client2.on("data",chunk=>{let result=lines.push(chunk);if(result.overflow){finish(new Error("Browser response exceeded the supported size"));return}for(let line of result.lines){let response;try{response=JSON.parse(line)}catch{continue}if(response.id===id2){response.success?finish(void 0,response.data):finish(new Error(response.error||"Browser request failed"));return}}}),client2.on("error",error62=>finish(error62)),client2.on("end",()=>finish(new Error("Browser connection closed before responding"))),client2.on("close",()=>finish(new Error("Browser connection closed before responding")))})}var MAX_RESPONSE_BUFFER_SIZE,init_browser_socket_request=__esm({"src/browser-socket-request.ts"(){"use strict";init_socketLines();init_dist4();MAX_RESPONSE_BUFFER_SIZE=64*1024*1024}});var version_exports={};__export(version_exports,{NATIVE_HOST_VERSION:()=>NATIVE_HOST_VERSION});var NATIVE_HOST_VERSION,init_version2=__esm({"src/version.ts"(){"use strict";NATIVE_HOST_VERSION="3.0.0-beta.3"}});function providerRequestTimeoutMs(action,data2,deadline){if(deadline!==void 0){if(!Number.isSafeInteger(deadline)||deadline<=0)throw new Error("Invalid Provider request deadline");let remaining=deadline-Date.now();if(remaining<=0)throw new Error("Provider request deadline exceeded");if(remaining>2147483647)throw new Error("Provider request deadline exceeds the supported range");return remaining}return resolveActionTimeoutSec(action,data2,120)*1e3}var init_provider_request_timeout=__esm({"src/provider-request-timeout.ts"(){"use strict";init_actionTimeouts()}});var browser_provider_exports={};__export(browser_provider_exports,{accessProviderBrowser:()=>accessProviderBrowser,closeProviderBrowser:()=>closeProviderBrowser,configureProvider:()=>configureProvider,detachProviderBrowser:()=>detachProviderBrowser,findProviderBrowser:()=>findProviderBrowser,getProviderLaunchForm:()=>getProviderLaunchForm,launchProviderBrowser:()=>launchProviderBrowser,listExternalBrowserProviders:()=>listExternalBrowserProviders,listProviderControlSessions:()=>listProviderControlSessions,startProviderBrowser:()=>startProviderBrowser,syncProviderBrowserCookies:()=>syncProviderBrowserCookies,updateProviderControlSession:()=>updateProviderControlSession});function listExternalBrowserProviders(){return listInstalledProviders().map(provider=>({provider:{id:provider.providerId,name:provider.name,builtin:!1,version:provider.version,protocolVersion:provider.protocolVersion},browserType:provider.browserType}))}async function getProviderLaunchForm(providerId,request){if(!listInstalledProviders().find(item=>item.providerId===providerId)?.browserType.dynamicLaunchForm)throw new Error(`Provider does not support dynamic launch forms: ${providerId}`);let live=await requireLiveProvider(providerId),form=await requestBrowserSocket(live.socketPath,"browser_providers",{...request,operation:"launch_form"},NATIVE_HOST_VERSION,12e4);return validateProviderLaunchForm(form),form}async function requireLiveProvider(providerId){let warning=(await ensureInstalledProvidersRunning([providerId])).find(item=>item.startsWith(`provider ${providerId}:`));if(warning)throw new Error(warning);let live=readLiveProvider(providerId);if(!live)throw new Error(`Provider did not start: ${providerId}`);return assertProviderProtocolVersion(live.providerProtocolVersion,providerId),{socketPath:live.socketPath}}async function launchProviderBrowser(input3){let providerId=input3.provider.trim();if(!listInstalledProviders().find(item=>item.providerId===providerId))throw new Error(`Provider is not registered: ${providerId}`);let live=await requireLiveProvider(providerId),result=await(input3.host?requestBrowserSocket(live.socketPath,"browser_launch",input3.providerOptions,NATIVE_HOST_VERSION,providerRequestTimeoutMs("browser_launch",input3.providerOptions),void 0,input3.host):requestBrowserSocket(live.socketPath,"browser_launch",input3.providerOptions,NATIVE_HOST_VERSION,providerRequestTimeoutMs("browser_launch",input3.providerOptions)));return validateProviderBrowser(result),{...result,browserId:providerBrowserKey(providerId,result.browserId)}}function findProviderBrowser(selector){let normalized=selector.toLowerCase(),exact=listRegisteredBrowsers().filter(browser=>browser.transport==="provider").filter(browser=>browser.browserId.toLowerCase()===normalized||browser.providerBrowserId?.toLowerCase()===normalized||browser.browserName.toLowerCase()===normalized);if(exact.length>1)throw new Error(`Provider browser selector is ambiguous: ${selector}`);return exact[0]&&assertProviderProtocolVersion(exact[0].provider?.protocolVersion,exact[0].provider?.id??"Provider"),exact[0]??null}function requireProviderBrowser(browser){if(browser.transport!=="provider"||!browser.providerBrowserId)throw new Error(`Browser is not managed by an external Provider: ${browser.browserId}`);return assertProviderProtocolVersion(browser.provider?.protocolVersion,browser.provider?.id??"Provider"),browser.providerBrowserId}function requestControlledProvider(browser,action,data2,host){return requireProviderBrowser(browser),ensureProviderControlHost().then(controlHost=>requestBrowserSocket(controlHost.socketPath,action,data2,NATIVE_HOST_VERSION,providerRequestTimeoutMs(action,data2),void 0,host,{providerTarget:browser.browserId}))}async function listProviderControlSessions(){let host=readProviderControlHost();return host?(await requestBrowserSocket(host.socketPath,"browser_sessions",{operation:"list"},NATIVE_HOST_VERSION)).sessions??[]:[]}async function updateProviderControlSession(sessionId,operation){let host=readProviderControlHost();if(!host)throw new Error("CONTROL_SESSION_NOT_FOUND");return(await requestBrowserSocket(host.socketPath,"browser_sessions",{sessionId,operation},NATIVE_HOST_VERSION)).session}function closeProviderBrowser(browser,options={}){return requestControlledProvider(browser,"browser_close",{deleteProfile:options.deleteProfile===!0})}function detachProviderBrowser(browser){return requestControlledProvider(browser,"browser_instances",{operation:"detach"})}async function accessProviderBrowser(browser){return(await requestBrowserSocket(browser.socketPath,"get_browser_access_url",{},NATIVE_HOST_VERSION,6e4,requireProviderBrowser(browser))).browserAccess}async function startProviderBrowser(browser){let result=await requestControlledProvider(browser,"browser_instances",{operation:"start"});return validateProviderBrowser(result),{...result,browserId:browser.browserId}}function syncProviderBrowserCookies(browser,scope,host){return requestControlledProvider(browser,"browser_instances",{operation:"sync_cookies",scope},host)}async function configureProvider(providerId,options){if(!listInstalledProviders().find(item=>item.providerId===providerId)?.browserType.configuration)throw new Error(`Provider does not support configuration: ${providerId}`);let live=await requireLiveProvider(providerId);return requestBrowserSocket(live.socketPath,"browser_providers",{operation:"configure",options},NATIVE_HOST_VERSION)}var init_browser_provider=__esm({"src/browser-provider.ts"(){"use strict";init_dist4();init_host();init_runtime();init_browser_registry();init_browser_socket_request();init_version2();init_provider_request_timeout()}});import{randomBytes}from"node:crypto";var BROWSER_QUEUE,ControlSessionRegistry,init_control_session=__esm({"src/control-session.ts"(){"use strict";init_dist5();BROWSER_QUEUE=-1,ControlSessionRegistry=class{constructor(browserId){this.browserId=browserId}browserId;sessions=new Map;owners=new Map;queues=new Map;pending=new Map;browserReleasing=!1;resuming=new Set;createSessionId(){for(;;){let id2=`s_${randomBytes(6).toString("base64url")}`;if(!this.sessions.has(id2))return id2}}create(transport){if(this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");let record5={sessionId:this.createSessionId(),browserId:this.browserId(),transport,status:"active",tabs:[],generation:1};return this.sessions.set(record5.sessionId,record5),this.info(record5.sessionId)}info(id2){let record5=this.require(id2);return{...record5,tabs:record5.tabs.map(tab=>({...tab}))}}state(id2){let{tabs:_tabs,...state}=this.require(id2);return state}find(id2){return this.sessions.get(id2)?.browserId===this.browserId()?this.info(id2):void 0}list(){return[...this.sessions.values()].filter(record5=>record5.status!=="released").map(record5=>this.info(record5.sessionId))}require(id2){let record5=this.sessions.get(id2);if(!record5||record5.browserId!==this.browserId())throw new Error("CONTROL_SESSION_NOT_FOUND");return record5}assertActive(id2,generation){let record5=this.require(id2);if(record5.status!=="active"||generation!==void 0&&generation!==record5.generation)throw new Error(`CONTROL_SESSION_PAUSED: ${record5.status}; inspect the page after the user returns control`);return record5}claim(id2,tabId,source){let record5=this.require(id2),owner=this.owners.get(tabId);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION");record5.tabs.some(tab=>tab.tabId===tabId)||record5.tabs.push({tabId,source}),this.owners.set(tabId,id2)}blockedTabs(id2){return[...this.owners].filter(([,owner])=>owner!==id2).map(([tab])=>tab)}assertBrowserRelease(){if(this.list().length)throw new Error("ACTIVE_CONTROL_SESSIONS: release the owning session first")}async releaseBrowser(operation){if(this.assertBrowserRelease(),this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");this.browserReleasing=!0;try{return await Promise.allSettled([...this.pending.values()].flatMap(operations=>[...operations])),await operation()}finally{this.browserReleasing=!1}}async run(id2,action,data2,operation,transport="extension"){if(this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");let generation=id2?this.assertActive(id2).generation:void 0,tabs=new Set;typeof data2.tabId=="number"&&tabs.add(data2.tabId);for(let tab of data2.tabIds??[])typeof tab=="number"&&tabs.add(tab);typeof data2.openerTabId=="number"&&tabs.add(data2.openerTabId);let steps=action==="run_actions"?data2.actions:[{action,data:data2}];if(action==="run_actions"){validateActionBatch(data2);for(let step of data2.actions??[]){typeof step.data?.tabId=="number"&&tabs.add(step.data.tabId);for(let tab of step.data?.tabIds??[])typeof tab=="number"&&tabs.add(tab);typeof step.data?.openerTabId=="number"&&tabs.add(step.data.openerTabId)}}let opensPage=steps.some(step=>step.action==="tab_open"),changesSharedSettings=steps.some(step=>["set_rule","set_cookie"].includes(step.action)||step.action==="set_app_profile"&&transport!=="cdp"&&step.data?.mtopEnv!==void 0),assertSharedResources=()=>{if(action==="run_actions"&&!id2&&this.list().length&&steps.some(step=>["tab_open","browser_close"].includes(step.action)))throw new Error("ACTIVE_CONTROL_SESSIONS: open pages outside run_actions while sessions are active");if(changesSharedSettings&&this.list().some(record5=>record5.sessionId!==id2))throw new Error("BROWSER_RESOURCE_CONFLICT: use an isolated browser to change shared settings");if(data2.closeExisting&&!id2&&this.list().length)throw new Error("ACTIVE_CONTROL_SESSIONS")};assertSharedResources();for(let tab of tabs){if(tab<0)continue;let owner=this.owners.get(tab);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION")}let unlock,tail=new Promise(resolve16=>{unlock=resolve16}),runtimeTransport=action==="page_runtime",predecessors=runtimeTransport?[]:[...tabs].map(tab=>this.queues.get(tab)).filter(Boolean);if(!runtimeTransport&&this.queues.has(BROWSER_QUEUE)&&predecessors.push(this.queues.get(BROWSER_QUEUE)),(changesSharedSettings||action==="page_download"||opensPage)&&(predecessors.push(...this.queues.values()),tabs.add(BROWSER_QUEUE)),!runtimeTransport)for(let tab of tabs)this.queues.set(tab,tail);let promise2=(async()=>{try{await Promise.all(predecessors),id2&&this.assertActive(id2,generation),assertSharedResources();for(let tab of tabs){let owner=this.owners.get(tab);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION");id2&&tab>=0&&this.claim(id2,tab,"attached")}return await operation()}finally{unlock();for(let tab of tabs)this.queues.get(tab)===tail&&this.queues.delete(tab)}})(),pending=this.pending.get(id2)??new Set;pending.add(promise2),this.pending.set(id2,pending);try{return await promise2}finally{pending.delete(promise2),pending.size||this.pending.delete(id2)}}async waitForIdle(id2){await Promise.allSettled([...this.pending.get(id2)??[]])}assertStep(id2,generation){this.assertActive(id2,generation)}recordResult(id2,action,result,data2){action==="tab_open"&&typeof result?.tabId=="number"&&this.claim(id2,result.tabId,result.opened===!0?"created":"attached");for(let tab of result?.observation?.openedTabs??[])typeof tab.tabId=="number"&&this.claim(id2,tab.tabId,"created");let target=this.require(id2).tabs.find(tab=>tab.tabId===(result?.tabId??data2?.tabId));if(target&&typeof result?.title=="string"&&(target.title=result.title),target&&typeof result?.url=="string"&&(target.url=result.url),action==="tab_close"&&typeof data2?.tabId=="number"&&(this.owners.delete(data2.tabId),this.require(id2).tabs=this.require(id2).tabs.filter(tab=>tab.tabId!==data2.tabId)),action==="tab_close")for(let tab of this.require(id2).tabs)(result?.closedTabIds??[]).includes(tab.tabId)&&this.owners.delete(tab.tabId)}handoff(id2,reason){let record5=this.assertActive(id2);return record5.status="user",record5.generation+=1,record5.reason=reason,this.info(id2)}async resume(id2,cleanup=async()=>{}){let record5=this.require(id2);if(record5.status!=="user")throw new Error("Session is not waiting for the user");if(this.resuming.has(id2))throw new Error("CONTROL_SESSION_RESUMING");this.resuming.add(id2);try{return await this.waitForIdle(id2),await cleanup(),record5.status="active",record5.reason=void 0,record5.generation+=1,this.info(id2)}finally{this.resuming.delete(id2)}}async release(id2,params,cleanup){let record5=this.require(id2);if(this.resuming.has(id2))throw new Error("CONTROL_SESSION_RESUMING");if(record5.status==="released")return this.info(id2);if(record5.status==="releasing")throw new Error("CONTROL_SESSION_RELEASING");let previous=record5.status;record5.status="releasing",record5.generation+=1,await Promise.allSettled([...this.pending.get(id2)??[]]);let tabs=record5.tabs.map(tab=>tab.tabId),keep=new Set(params.keepTabIds??[]),close=params.closeCreatedTabs?record5.tabs.filter(tab=>tab.source==="created"&&!keep.has(tab.tabId)).map(tab=>tab.tabId):[];try{await cleanup(tabs,close);for(let tab of tabs)this.owners.get(tab)===id2&&this.owners.delete(tab);record5.status="released";let released=[...this.sessions.values()].filter(session=>session.status==="released"&&session.sessionId!==id2);for(let old of released.slice(0,Math.max(0,released.length-99)))this.sessions.delete(old.sessionId)}catch(error62){throw record5.status=previous,error62}return this.info(id2)}}}});import{rmSync as rmSync14}from"node:fs";import{randomUUID as randomUUID19}from"node:crypto";import{mkdir as mkdir2,open as open4,rm as rm3,stat as stat3,writeFile}from"node:fs/promises";import path14 from"node:path";import os5 from"node:os";var MAX_BYTES,ARTIFACT_CHUNK_BYTES,BrowserArtifactStore,init_browser_artifacts=__esm({"src/browser-artifacts.ts"(){"use strict";MAX_BYTES=256*1024*1024,ARTIFACT_CHUNK_BYTES=256*1024,BrowserArtifactStore=class{constructor(root=path14.join(os5.tmpdir(),`mearl-artifacts-${process.pid}-${randomUUID19()}`)){this.root=root}root;files=new Map;directory;sweeper;async create(filename,sessionId){if(typeof filename!="string"||!filename||[".",".."].includes(filename)||/[\\/]/.test(filename)||filename.length>255)throw new Error("Invalid artifact filename");this.sweeper||(this.sweeper=setInterval(()=>{for(let[id2,file3]of this.files)!file3.sessionId&&!file3.writing&&Date.now()-file3.accessedAt>3600*1e3&&this.remove(id2).catch(()=>{})},6e4),this.sweeper.unref()),await mkdir2(this.root,{recursive:!0,mode:448}),this.directory=this.root;let artifactId=randomUUID19(),directory=path14.join(this.root,artifactId);await mkdir2(directory,{mode:448});let file2={artifactId,filename,bytes:0,location:"browser",path:path14.join(directory,filename),sessionId,ready:!1,accessedAt:Date.now()};return await writeFile(file2.path,"",{mode:384}),this.files.set(artifactId,file2),this.info(file2)}async importFile(filename,sourcePath,sessionId){let source=await open4(sourcePath,"r"),id2;try{let metadata=await source.stat();if(!metadata.isFile()||metadata.size>MAX_BYTES)throw new Error("Download is unavailable or exceeds 256 MiB");id2=(await this.create(filename,sessionId)).artifactId;let file2=this.require(id2,sessionId),target=await open4(file2.path,"w"),copied=0;try{for await(let chunk of source.createReadStream({autoClose:!1})){if(copied+=chunk.length,copied>MAX_BYTES)throw new Error("Download exceeds 256 MiB");let written=0;for(;written<chunk.length;){let result=await target.write(chunk,written,chunk.length-written);if(!result.bytesWritten)throw new Error("Download import made no progress");written+=result.bytesWritten}}}finally{await target.close()}if(copied!==metadata.size)throw new Error("Download changed during import");return file2.bytes=(await stat3(file2.path)).size,file2.ready=!0,this.info(file2)}catch(error62){throw id2&&await this.remove(id2,sessionId),error62}finally{await source.close()}}require(id2,sessionId){let file2=this.files.get(id2);if(!file2||file2.sessionId!==void 0&&file2.sessionId!==sessionId)throw new Error("ARTIFACT_NOT_FOUND");return file2.accessedAt=Date.now(),file2}info(file2){return{artifactId:file2.artifactId,filename:file2.filename,bytes:file2.bytes,location:"browser"}}async write(id2,offset2,base643,sessionId){let file2=this.require(id2,sessionId);if(file2.ready||file2.writing||offset2!==file2.bytes)throw new Error("Invalid artifact write offset or completed transfer");if(typeof base643!="string"||base643.length>Math.ceil(ARTIFACT_CHUNK_BYTES/3)*4||base643.length%4!==0||!/^[A-Za-z0-9+/]*={0,2}$/.test(base643))throw new Error("Invalid artifact chunk");let chunk=Buffer.from(base643,"base64");if(chunk.toString("base64")!==base643)throw new Error("Invalid artifact chunk");if(file2.bytes+chunk.length>MAX_BYTES)throw new Error("Artifact exceeds 256 MiB");file2.writing=!0;try{let handle=await open4(file2.path,"r+");try{let written=0;for(;written<chunk.length;){let result=await handle.write(chunk,written,chunk.length-written,offset2+written);if(!result.bytesWritten)throw new Error("Artifact write made no progress");written+=result.bytesWritten}}finally{await handle.close()}return file2.bytes+=chunk.length,this.info(file2)}finally{file2.writing=!1}}complete(id2,bytes,sessionId){let file2=this.require(id2,sessionId);if(file2.writing||bytes!==file2.bytes)throw new Error("Artifact size mismatch");return file2.ready=!0,this.info(file2)}resolve(id2,sessionId){let file2=this.require(id2,sessionId);if(!file2.ready)throw new Error("Artifact is not ready");return file2.path}async read(id2,offset2,sessionId){let file2=this.require(id2,sessionId);if(!file2.ready||!Number.isSafeInteger(offset2)||offset2<0||offset2>file2.bytes)throw new Error("Invalid artifact read");let handle=await open4(file2.path,"r");try{let buffer=Buffer.alloc(Math.min(ARTIFACT_CHUNK_BYTES,file2.bytes-offset2)),result=await handle.read(buffer,0,buffer.length,offset2);return{base64:buffer.subarray(0,result.bytesRead).toString("base64"),bytes:result.bytesRead,totalBytes:file2.bytes}}finally{await handle.close()}}async remove(id2,sessionId){let file2=this.require(id2,sessionId);await rm3(path14.dirname(file2.path),{recursive:!0,force:!0}),this.files.delete(id2)}async release(sessionId,keep=[]){let retain=new Set(keep);for(let[id2,file2]of this.files)file2.sessionId===sessionId&&(retain.has(id2)?(file2.sessionId=void 0,file2.accessedAt=Date.now()):await this.remove(id2,sessionId))}disposeSync(){this.sweeper&&clearInterval(this.sweeper),this.directory&&rmSync14(this.directory,{recursive:!0,force:!0}),this.files.clear()}async dispose(){this.sweeper&&clearInterval(this.sweeper),this.directory&&await rm3(this.directory,{recursive:!0,force:!0}),this.files.clear()}}}});var ControlSessionManager,init_control_session_manager=__esm({"src/control-session-manager.ts"(){"use strict";init_dist5();init_control_session();init_browser_artifacts();ControlSessionManager=class{constructor(dependencies){this.dependencies=dependencies;this.registry=new ControlSessionRegistry(dependencies.browserId)}dependencies;registry;artifacts=new BrowserArtifactStore;activityQueues=new Map;async withActivity(id2,operation){let pending=(this.activityQueues.get(id2)??Promise.resolve()).catch(()=>{}).then(operation);this.activityQueues.set(id2,pending);try{return await pending}finally{this.activityQueues.get(id2)===pending&&this.activityQueues.delete(id2)}}async closeTabs(tabs,transport,sessionId){for(let tabId of tabs)try{let result=await this.dependencies.invoke("tab_close",{tabId},transport);this.registry.recordResult(sessionId,"tab_close",result,{tabId})}catch(error62){let current2=await this.dependencies.invoke("tab_list",{},transport);if(!Array.isArray(current2.tabs)||current2.tabs.some(tab=>(tab.tabId??tab.id)===tabId))throw error62;this.registry.recordResult(sessionId,"tab_close",{},{tabId})}}async userControl(id2,operation,reason){if(operation==="release")return await this.invoke("browser_release",{},id2),this.registry.info(id2);operation==="resume"&&await this.activityQueues.get(id2)?.catch(()=>{});let current2=this.registry.info(id2),info=operation==="resume"?await this.registry.resume(id2,()=>this.dependencies.cleanup(current2.tabs.map(tab=>tab.tabId),current2.transport,!0)):current2.status==="user"?current2:this.registry.handoff(id2,reason);return this.dependencies.notify(),operation==="handoff"?(await this.withActivity(id2,async()=>{await this.dependencies.deactivate?.(this.registry.info(id2).tabs.map(tab=>tab.tabId),current2.transport)}),this.registry.info(id2)):info}async invoke(action,data2,sessionId,requestedTransport,origin="local"){if(data2&&(Object.keys(data2).some(key=>key.startsWith("__mearl"))||(data2.actions??[]).some(step=>Object.keys(step.data??{}).some(key=>key.startsWith("__mearl")))))throw new Error("Internal session fields are reserved");validateRequiredActionTabId(action,data2);let created=sessionId==="new";if(sessionId==="new"){if(!["tab_open","page_snapshot","page_screenshot"].includes(action))throw new Error("A new session requires tab_open, page_snapshot or page_screenshot");sessionId=this.registry.create(requestedTransport??this.dependencies.transport()).sessionId}let info=sessionId?this.registry.info(sessionId):void 0,transport=info?.transport??requestedTransport??this.dependencies.transport();if(requestedTransport&&transport!==requestedTransport)throw new Error("A session cannot change transport");if(action==="browser_control"){if(!sessionId||!["handoff","resume"].includes(data2.operation))throw new Error("browser_control requires --session and handoff/resume");if(data2.operation==="resume")return{session:await this.userControl(sessionId,"resume")};if(data2.tabId!==void 0&&!info.tabs.some(tab=>tab.tabId===data2.tabId))throw new Error("TAB_NOT_IN_CONTROL_SESSION");return{session:await this.userControl(sessionId,"handoff",data2.reason)}}if(action==="browser_release"&&sessionId){let params=data2;return await this.registry.release(sessionId,params,async(tabs,close)=>{await this.dependencies.cleanup(tabs,transport,params.preserveEnvironment!==!1),await this.closeTabs(close,transport,sessionId),await this.artifacts.release(sessionId,params.keepArtifactIds)}),this.dependencies.notify(),{released:!0,hadActiveControl:!0,transports:[transport],affectedTabIds:info.tabs.map(tab=>tab.tabId),session:this.registry.state(sessionId)}}if((action==="browser_release"||action==="browser_close")&&this.registry.assertBrowserRelease(),(action==="browser_release"||action==="browser_close")&&!sessionId)return this.registry.releaseBrowser(()=>this.dependencies.invoke(action,data2,transport));let performStep=async(name,payload,batch=!1)=>{if(name==="tab_open"&&(sessionId&&payload.closeExisting&&(await this.closeTabs(this.registry.info(sessionId).tabs.filter(tab=>tab.source==="created").map(tab=>tab.tabId),transport,sessionId),payload={...payload,closeExisting:!1}),payload={...payload,__mearlBlockedTabIds:this.registry.blockedTabs(sessionId)}),name==="page_upload"&&payload.files){if(payload.filePaths)throw new Error("Use files or filePaths, not both");payload={...payload,filePaths:payload.files.map(file2=>this.artifacts.resolve(file2.artifactId,sessionId))},delete payload.files}if(batch){if(transport==="provider"){let result4=await this.dependencies.invoke(name,{...payload,__mearlControlSource:origin},transport);return sessionId&&this.registry.recordResult(sessionId,name,result4,payload),result4?.action?.success===!1?{...result4.action,success:!1}:result4?.success===!1?{...result4,success:!1}:{success:!0,data:result4}}let result3=await this.dependencies.invoke("run_actions",{tabId:payload.tabId,actions:[{action:name,data:payload}],__mearlControlSource:origin},transport),outcome=result3.results?.[0]??{success:!1,error:result3.error??"Batch step returned no result",stage:result3.stage,attempt:result3.attempt};return sessionId&&outcome.success&&this.registry.recordResult(sessionId,name,outcome.data,payload),outcome}let result2=await this.dependencies.invoke(name,{...payload,__mearlControlSource:origin},transport);return sessionId&&this.registry.recordResult(sessionId,name,result2,payload),result2},invokeStep=(name,payload,batch=!1)=>sessionId&&name==="tab_open"?this.withActivity(sessionId,async()=>(this.registry.assertStep(sessionId,info.generation),performStep(name,payload,batch))):performStep(name,payload,batch),result;try{result=await this.registry.run(sessionId,action,data2,async()=>{if(transport==="extension"&&this.dependencies.transport()!=="extension")throw new Error("CONTROL_SESSION_TRANSPORT_UNAVAILABLE: extension is disconnected");if(action.startsWith("__artifact_")){let store=this.artifacts;switch(action){case"__artifact_init":return{artifact:await store.create(data2.filename,sessionId),browserId:this.dependencies.browserId()};case"__artifact_write":return store.write(data2.artifactId,data2.offset,data2.base64,sessionId);case"__artifact_complete":return store.complete(data2.artifactId,data2.bytes,sessionId);case"__artifact_read":return store.read(data2.artifactId,data2.offset,sessionId);case"__artifact_remove":return await store.remove(data2.artifactId,sessionId),{removed:!0};default:throw new Error("Unknown artifact operation")}}if(action==="page_download"){let downloaded=await this.dependencies.download(data2,transport,origin);try{return sessionId&&this.registry.recordResult(sessionId,"page_click",downloaded.action,data2),{download:{...await this.artifacts.importFile(downloaded.filename,downloaded.filePath,sessionId),browserId:this.dependencies.browserId()},action:downloaded.action}}finally{if(downloaded.owned){let{rm:rm5,rmdir:rmdir2}=await import("node:fs/promises");await rm5(downloaded.filePath,{force:!0}),downloaded.directory&&await rmdir2(downloaded.directory).catch(()=>{})}}}return action==="run_actions"&&sessionId?runActionBatch(data2,(name,payload)=>invokeStep(name,payload,!0),{beforeStep:()=>this.registry.assertStep(sessionId,info.generation)}):invokeStep(action,data2)},transport)}catch(error62){throw created&&sessionId&&await this.registry.release(sessionId,{},async tabs=>{await this.dependencies.cleanup(tabs,transport,!0),await this.artifacts.release(sessionId)}),this.dependencies.notify(),error62}if(sessionId&&action==="tab_list"&&Array.isArray(result.tabs)){let blocked=new Set(this.registry.blockedTabs(sessionId));result={...result,tabs:result.tabs.filter(tab=>!blocked.has(tab.tabId??tab.id))},result.count=result.tabs.length}return sessionId?(this.dependencies.notify(),{...result,session:this.registry.state(sessionId)}):result}}}});import{isAbsolute as isAbsolute10}from"node:path";import{AsyncLocalStorage as AsyncLocalStorage2}from"node:async_hooks";function identity2(browser){return`${browser.pid}:${browser.startedAt}:${browser.socketPath}`}var ProviderControlService,init_provider_control=__esm({"src/provider-control.ts"(){"use strict";init_dist5();init_control_session_manager();init_dist4();init_provider_request_timeout();ProviderControlService=class{constructor(dependencies){this.dependencies=dependencies}dependencies;deadlines=new AsyncLocalStorage2;controls=new Map;retired=new Set;retire(manager){let cleanup=Promise.all(manager.registry.list().map(session=>manager.registry.waitForIdle(session.sessionId))).then(()=>manager.artifacts.dispose());this.retired.add(cleanup),cleanup.finally(()=>this.retired.delete(cleanup)).catch(()=>{})}reap(){let browsers=this.dependencies.browsers();for(let[browserId,control]of this.controls){let browser=browsers.find(item=>item.browserId===browserId);(!browser||identity2(browser)!==control.identity)&&(this.controls.delete(browserId),this.retire(control.manager))}}browser(browserId){let browser=this.dependencies.browsers().find(item=>item.browserId===browserId);if(browser?.transport!=="provider"||!browser.providerBrowserId)throw new Error("CONTROL_SESSION_NOT_FOUND: Provider browser is unavailable");return assertProviderProtocolVersion(browser.provider?.protocolVersion,browser.provider?.id??"Provider"),browser}list(){return this.reap(),[...this.controls.values()].flatMap(control=>control.manager.registry.list())}find(sessionId){this.reap();let sessions=[...this.controls.values()].map(control=>control.manager.registry.find(sessionId)).filter(session=>session!==void 0);if(sessions.length>1)throw new Error("CONTROL_SESSION_AMBIGUOUS: multiple browsers own this id");return sessions[0]}async userControl(sessionId,operation){this.reap();for(let[browserId,control]of this.controls){try{control.manager.registry.info(sessionId)}catch{continue}if(identity2(this.browser(browserId))!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");return control.manager.userControl(sessionId,operation)}throw new Error("CONTROL_SESSION_NOT_FOUND")}getControl(browser){let previous=this.controls.get(browser.browserId);if(previous?.identity===identity2(browser))return previous;previous&&this.retire(previous.manager);let control={identity:identity2(browser),manager:new ControlSessionManager({browserId:()=>browser.browserId,transport:()=>"provider",invoke:async(action,payload)=>{let current2=this.browser(browser.browserId);if(identity2(current2)!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");let{__mearlControlSource:origin="local",__mearlBlockedTabIds:blocked,...data2}=payload;if(action==="tab_open"&&blocked?.length&&data2.reuse!=="never"){let result=await this.request(current2,"tab_list",{},origin);if(!Array.isArray(result.tabs))throw new Error("Provider did not return its current pages");if(findMatchingTabs(result.tabs,data2.url,data2.match).some(tab=>blocked.includes(tab.tabId??tab.id)))throw new Error("TAB_CONTROLLED_BY_SESSION")}return this.request(current2,action,data2,origin)},cleanup:async(tabs,_transport,preserveEnvironment)=>{preserveEnvironment||await this.clearEnvironment(browser.browserId,tabs)},download:async(data2,_transport,origin)=>{let current2=this.browser(browser.browserId);if(identity2(current2)!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");let result=await this.request(current2,"page_download",data2,origin);if(!result||typeof result.filename!="string"||!result.filename||typeof result.filePath!="string"||!isAbsolute10(result.filePath)||!Object.hasOwn(result,"action")||result.owned!==void 0&&typeof result.owned!="boolean")throw new Error("Provider page_download must return a completed file with filename, absolute filePath and action");return{filename:result.filename,filePath:result.filePath,action:result.action,owned:result.owned===!0}},notify:this.dependencies.notify})};return this.controls.set(browser.browserId,control),control}async request(browser,action,data2,origin,host){action==="browser_release"&&(action="browser_instances",data2={operation:"detach"});let lifecycle=action==="browser_close"?data2.deleteProfile===!0?"deleteData":"close":action==="get_browser_access_url"?"access":action==="browser_instances"?data2.operation==="sync_cookies"?"syncCookies":data2.operation:void 0;if(lifecycle&&!browser.operations?.includes(lifecycle))throw new Error(`Provider browser does not support ${lifecycle}`);if(!lifecycle&&action!=="get_versions"){if(browser.status!=="connected")throw new Error("Provider browser is not connected");if(!browser.capabilities?.actions.some(capability=>capability===action))throw new Error(`Provider browser does not support ${action}`)}let deadline=this.deadlines.getStore();return deadline!==void 0?(providerRequestTimeoutMs(action,data2,deadline),this.dependencies.request(browser,action,data2,origin,host,deadline)):this.dependencies.request(browser,action,data2,origin,host)}async clearEnvironment(browserId,tabs){let browser=this.browser(browserId),actions=new Set(browser.capabilities?.actions??[]);if(actions.has("set_mock")&&!actions.has("get_mocks"))throw new Error("Provider cannot enumerate Mock state for cleanup");let clear=async(action,data2)=>{let result=await this.request(browser,action,data2,"local");if(result?.success===!1||result?.action?.success===!1)throw new Error(result.error??result.action?.error??"Provider environment cleanup failed")};for(let tabId of tabs){if(actions.has("set_mock")){let result=await this.request(browser,"get_mocks",{tabId},"local");if(!Array.isArray(result.mocks))throw new Error("Provider did not return its Mock state");let names=new Set(result.mocks.map(mock=>mock.apiName));for(let apiName of names)await clear("set_mock",{tabId,apiName,enabled:!1})}for(let[action,data2]of[["set_app_profile",{tabId,enabled:!1,mtopEnv:"default"}],["set_device_emulation",{tabId,enabled:!1}],["set_timezone",{tabId,timezone:""}]])actions.has(action)&&await clear(action,data2)}}async invoke(browserId,action,data2={},sessionId,origin="local",host,deadline){return this.deadlines.run(deadline,async()=>{deadline!==void 0&&providerRequestTimeoutMs(action,data2,deadline),this.reap();let browser=this.browser(browserId),{manager}=this.getControl(browser);if(action==="get_versions"||action==="get_browser_access_url")return this.request(browser,action,data2,origin);if(action==="browser_instances"){if(!["start","detach","sync_cookies"].includes(data2.operation))throw new Error("Invalid browser instance operation");return manager.registry.releaseBrowser(()=>this.request(browser,action,data2,origin,host))}return manager.invoke(action,data2,sessionId,void 0,origin)})}async dispose(){await Promise.all([...this.retired,...[...this.controls.values()].map(async({manager})=>{await Promise.all(manager.registry.list().map(session=>manager.registry.waitForIdle(session.sessionId))),await manager.artifacts.dispose()})])}}}});var provider_control_host_exports={};__export(provider_control_host_exports,{runProviderControlHost:()=>runProviderControlHost});import net4 from"node:net";import{chmodSync as chmodSync2,rmSync as rmSync16}from"node:fs";import{randomUUID as randomUUID27}from"node:crypto";async function runProviderControlHost(token){let file2=providerControlHostFile(),owner=readRecord(file2);if(!owner||owner.token!==token)throw new Error("Provider control host startup ownership was lost");let socketPath=providerControlSocketPath(process.pid);if(!writeRecordIfPid(file2,owner.pid,{pid:process.pid,socketPath,token,ready:!1,protocolVersion:3}))throw new Error("Provider control host startup ownership was lost");let connections=new Set,service=new ProviderControlService({browsers:listRegisteredBrowsers,request:(browser,action,data2,origin,host,deadline)=>requestBrowserSocket(browser.socketPath,action,data2,NATIVE_HOST_VERSION,providerRequestTimeoutMs(action,data2,deadline),browser.providerBrowserId,host,{controlSource:origin}),notify:()=>{for(let browser of listRegisteredBrowsers())browser.transport==="extension"&&requestBrowserSocket(browser.socketPath,"provider_control_sessions_changed",{},NATIVE_HOST_VERSION,5e3).catch(()=>{})}}),server=net4.createServer(socket=>{connections.add(socket),socket.on("error",()=>{}),socket.once("close",()=>connections.delete(socket));let lines=new Utf8LineBuffer(16*1024*1024);socket.on("data",chunk=>{let result=lines.push(chunk);if(result.overflow)return socket.destroy();for(let line of result.lines)handle(line,socket)})});async function handle(line,socket){let id2="";try{let request=JSON.parse(line);id2=typeof request.id=="string"?request.id:randomUUID27(),assertProviderProtocolVersion(request.protocolVersion,"Provider control client");let data2=request.data??{},result;if(request.action==="__control_session_resolve")result={session:service.find(data2.sessionId)??null};else if(request.action==="browser_sessions")if(data2.operation==="list")result={sessions:service.list()};else{if(!["handoff","resume","release"].includes(data2.operation)||typeof data2.sessionId!="string"||!data2.sessionId)throw new Error("Invalid browser session operation");result={session:await service.userControl(data2.sessionId,data2.operation)}}else{if(typeof request.providerTarget!="string"||typeof request.action!="string")throw new Error("Provider browser and action are required");if(!data2||typeof data2!="object"||Array.isArray(data2))throw new Error("Invalid action payload");if(request.transport&&request.transport!=="provider")throw new Error("A session cannot change transport");result=await service.invoke(request.providerTarget,request.action,data2,request.controlSessionId,request.controlSource??"local",request.host,request.deadline)}socket.destroyed||socket.write(JSON.stringify({id:id2,success:!0,data:result})+`
1110
+ `)}),client2.on("data",chunk=>{let result=lines.push(chunk);if(result.overflow){finish(new Error("Browser response exceeded the supported size"));return}for(let line of result.lines){let response;try{response=JSON.parse(line)}catch{continue}if(response.id===id2){response.success?finish(void 0,response.data):finish(new Error(response.error||"Browser request failed"));return}}}),client2.on("error",error62=>finish(error62)),client2.on("end",()=>finish(new Error("Browser connection closed before responding"))),client2.on("close",()=>finish(new Error("Browser connection closed before responding")))})}var MAX_RESPONSE_BUFFER_SIZE,init_browser_socket_request=__esm({"src/browser-socket-request.ts"(){"use strict";init_socketLines();init_dist4();MAX_RESPONSE_BUFFER_SIZE=64*1024*1024}});var version_exports={};__export(version_exports,{NATIVE_HOST_VERSION:()=>NATIVE_HOST_VERSION});var NATIVE_HOST_VERSION,init_version2=__esm({"src/version.ts"(){"use strict";NATIVE_HOST_VERSION="3.0.0-beta.4"}});function providerRequestTimeoutMs(action,data2,deadline){if(deadline!==void 0){if(!Number.isSafeInteger(deadline)||deadline<=0)throw new Error("Invalid Provider request deadline");let remaining=deadline-Date.now();if(remaining<=0)throw new Error("Provider request deadline exceeded");if(remaining>2147483647)throw new Error("Provider request deadline exceeds the supported range");return remaining}return resolveActionTimeoutSec(action,data2,120)*1e3}var init_provider_request_timeout=__esm({"src/provider-request-timeout.ts"(){"use strict";init_actionTimeouts()}});var browser_provider_exports={};__export(browser_provider_exports,{accessProviderBrowser:()=>accessProviderBrowser,closeProviderBrowser:()=>closeProviderBrowser,configureProvider:()=>configureProvider,detachProviderBrowser:()=>detachProviderBrowser,findProviderBrowser:()=>findProviderBrowser,getProviderLaunchForm:()=>getProviderLaunchForm,launchProviderBrowser:()=>launchProviderBrowser,listExternalBrowserProviders:()=>listExternalBrowserProviders,listProviderControlSessions:()=>listProviderControlSessions,startProviderBrowser:()=>startProviderBrowser,syncProviderBrowserCookies:()=>syncProviderBrowserCookies,updateProviderControlSession:()=>updateProviderControlSession});function listExternalBrowserProviders(){return listInstalledProviders().map(provider=>({provider:{id:provider.providerId,name:provider.name,builtin:!1,version:provider.version,protocolVersion:provider.protocolVersion},browserType:provider.browserType}))}async function getProviderLaunchForm(providerId,request){if(!listInstalledProviders().find(item=>item.providerId===providerId)?.browserType.dynamicLaunchForm)throw new Error(`Provider does not support dynamic launch forms: ${providerId}`);let live=await requireLiveProvider(providerId),form=await requestBrowserSocket(live.socketPath,"browser_providers",{...request,operation:"launch_form"},NATIVE_HOST_VERSION,12e4);return validateProviderLaunchForm(form),form}async function requireLiveProvider(providerId){let warning=(await ensureInstalledProvidersRunning([providerId])).find(item=>item.startsWith(`provider ${providerId}:`));if(warning)throw new Error(warning);let live=readLiveProvider(providerId);if(!live)throw new Error(`Provider did not start: ${providerId}`);return assertProviderProtocolVersion(live.providerProtocolVersion,providerId),{socketPath:live.socketPath}}async function launchProviderBrowser(input3){let providerId=input3.provider.trim();if(!listInstalledProviders().find(item=>item.providerId===providerId))throw new Error(`Provider is not registered: ${providerId}`);let live=await requireLiveProvider(providerId),result=await(input3.host?requestBrowserSocket(live.socketPath,"browser_launch",input3.providerOptions,NATIVE_HOST_VERSION,providerRequestTimeoutMs("browser_launch",input3.providerOptions),void 0,input3.host):requestBrowserSocket(live.socketPath,"browser_launch",input3.providerOptions,NATIVE_HOST_VERSION,providerRequestTimeoutMs("browser_launch",input3.providerOptions)));return validateProviderBrowser(result),{...result,browserId:providerBrowserKey(providerId,result.browserId)}}function findProviderBrowser(selector){let normalized=selector.toLowerCase(),exact=listRegisteredBrowsers().filter(browser=>browser.transport==="provider").filter(browser=>browser.browserId.toLowerCase()===normalized||browser.providerBrowserId?.toLowerCase()===normalized||browser.browserName.toLowerCase()===normalized);if(exact.length>1)throw new Error(`Provider browser selector is ambiguous: ${selector}`);return exact[0]&&assertProviderProtocolVersion(exact[0].provider?.protocolVersion,exact[0].provider?.id??"Provider"),exact[0]??null}function requireProviderBrowser(browser){if(browser.transport!=="provider"||!browser.providerBrowserId)throw new Error(`Browser is not managed by an external Provider: ${browser.browserId}`);return assertProviderProtocolVersion(browser.provider?.protocolVersion,browser.provider?.id??"Provider"),browser.providerBrowserId}function requestControlledProvider(browser,action,data2,host){return requireProviderBrowser(browser),ensureProviderControlHost().then(controlHost=>requestBrowserSocket(controlHost.socketPath,action,data2,NATIVE_HOST_VERSION,providerRequestTimeoutMs(action,data2),void 0,host,{providerTarget:browser.browserId}))}async function listProviderControlSessions(){let host=readProviderControlHost();return host?(await requestBrowserSocket(host.socketPath,"browser_sessions",{operation:"list"},NATIVE_HOST_VERSION)).sessions??[]:[]}async function updateProviderControlSession(sessionId,operation){let host=readProviderControlHost();if(!host)throw new Error("CONTROL_SESSION_NOT_FOUND");return(await requestBrowserSocket(host.socketPath,"browser_sessions",{sessionId,operation},NATIVE_HOST_VERSION)).session}function closeProviderBrowser(browser,options={}){return requestControlledProvider(browser,"browser_close",{deleteProfile:options.deleteProfile===!0})}function detachProviderBrowser(browser){return requestControlledProvider(browser,"browser_instances",{operation:"detach"})}async function accessProviderBrowser(browser){return(await requestBrowserSocket(browser.socketPath,"get_browser_access_url",{},NATIVE_HOST_VERSION,6e4,requireProviderBrowser(browser))).browserAccess}async function startProviderBrowser(browser){let result=await requestControlledProvider(browser,"browser_instances",{operation:"start"});return validateProviderBrowser(result),{...result,browserId:browser.browserId}}function syncProviderBrowserCookies(browser,scope,host){return requestControlledProvider(browser,"browser_instances",{operation:"sync_cookies",scope},host)}async function configureProvider(providerId,options){if(!listInstalledProviders().find(item=>item.providerId===providerId)?.browserType.configuration)throw new Error(`Provider does not support configuration: ${providerId}`);let live=await requireLiveProvider(providerId);return requestBrowserSocket(live.socketPath,"browser_providers",{operation:"configure",options},NATIVE_HOST_VERSION)}var init_browser_provider=__esm({"src/browser-provider.ts"(){"use strict";init_dist4();init_host();init_runtime();init_browser_registry();init_browser_socket_request();init_version2();init_provider_request_timeout()}});import{randomBytes}from"node:crypto";var BROWSER_QUEUE,ControlSessionRegistry,init_control_session=__esm({"src/control-session.ts"(){"use strict";init_dist5();BROWSER_QUEUE=-1,ControlSessionRegistry=class{constructor(browserId){this.browserId=browserId}browserId;sessions=new Map;owners=new Map;queues=new Map;pending=new Map;browserReleasing=!1;resuming=new Set;createSessionId(){for(;;){let id2=`s_${randomBytes(6).toString("base64url")}`;if(!this.sessions.has(id2))return id2}}create(transport){if(this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");let record5={sessionId:this.createSessionId(),browserId:this.browserId(),transport,status:"active",tabs:[],generation:1};return this.sessions.set(record5.sessionId,record5),this.info(record5.sessionId)}info(id2){let record5=this.require(id2);return{...record5,tabs:record5.tabs.map(tab=>({...tab}))}}state(id2){let{tabs:_tabs,...state}=this.require(id2);return state}find(id2){return this.sessions.get(id2)?.browserId===this.browserId()?this.info(id2):void 0}list(){return[...this.sessions.values()].filter(record5=>record5.status!=="released").map(record5=>this.info(record5.sessionId))}require(id2){let record5=this.sessions.get(id2);if(!record5||record5.browserId!==this.browserId())throw new Error("CONTROL_SESSION_NOT_FOUND");return record5}assertActive(id2,generation){let record5=this.require(id2);if(record5.status!=="active"||generation!==void 0&&generation!==record5.generation)throw new Error(`CONTROL_SESSION_PAUSED: ${record5.status}; inspect the page after the user returns control`);return record5}claim(id2,tabId,source){let record5=this.require(id2),owner=this.owners.get(tabId);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION");record5.tabs.some(tab=>tab.tabId===tabId)||record5.tabs.push({tabId,source}),this.owners.set(tabId,id2)}blockedTabs(id2){return[...this.owners].filter(([,owner])=>owner!==id2).map(([tab])=>tab)}assertBrowserRelease(){if(this.list().length)throw new Error("ACTIVE_CONTROL_SESSIONS: release the owning session first")}async releaseBrowser(operation){if(this.assertBrowserRelease(),this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");this.browserReleasing=!0;try{return await Promise.allSettled([...this.pending.values()].flatMap(operations=>[...operations])),await operation()}finally{this.browserReleasing=!1}}async run(id2,action,data2,operation,transport="extension"){if(this.browserReleasing)throw new Error("BROWSER_CONTROL_RELEASING");let generation=id2?this.assertActive(id2).generation:void 0,tabs=new Set;typeof data2.tabId=="number"&&tabs.add(data2.tabId);for(let tab of data2.tabIds??[])typeof tab=="number"&&tabs.add(tab);typeof data2.openerTabId=="number"&&tabs.add(data2.openerTabId);let steps=action==="run_actions"?data2.actions:[{action,data:data2}];if(action==="run_actions"){validateActionBatch(data2);for(let step of data2.actions??[]){typeof step.data?.tabId=="number"&&tabs.add(step.data.tabId);for(let tab of step.data?.tabIds??[])typeof tab=="number"&&tabs.add(tab);typeof step.data?.openerTabId=="number"&&tabs.add(step.data.openerTabId)}}let opensPage=steps.some(step=>step.action==="tab_open"),changesSharedSettings=steps.some(step=>["set_rule","set_cookie"].includes(step.action)||step.action==="set_app_profile"&&transport!=="cdp"&&step.data?.mtopEnv!==void 0),assertSharedResources=()=>{if(action==="run_actions"&&!id2&&this.list().length&&steps.some(step=>["tab_open","browser_close"].includes(step.action)))throw new Error("ACTIVE_CONTROL_SESSIONS: open pages outside run_actions while sessions are active");if(changesSharedSettings&&this.list().some(record5=>record5.sessionId!==id2))throw new Error("BROWSER_RESOURCE_CONFLICT: use an isolated browser to change shared settings");if(data2.closeExisting&&!id2&&this.list().length)throw new Error("ACTIVE_CONTROL_SESSIONS")};assertSharedResources();for(let tab of tabs){if(tab<0)continue;let owner=this.owners.get(tab);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION")}let unlock,tail=new Promise(resolve16=>{unlock=resolve16}),runtimeTransport=action==="page_runtime",predecessors=runtimeTransport?[]:[...tabs].map(tab=>this.queues.get(tab)).filter(Boolean);if(!runtimeTransport&&this.queues.has(BROWSER_QUEUE)&&predecessors.push(this.queues.get(BROWSER_QUEUE)),(changesSharedSettings||action==="page_download"||opensPage)&&(predecessors.push(...this.queues.values()),tabs.add(BROWSER_QUEUE)),!runtimeTransport)for(let tab of tabs)this.queues.set(tab,tail);let promise2=(async()=>{try{await Promise.all(predecessors),id2&&this.assertActive(id2,generation),assertSharedResources();for(let tab of tabs){let owner=this.owners.get(tab);if(owner&&owner!==id2)throw new Error("TAB_CONTROLLED_BY_SESSION");id2&&tab>=0&&this.claim(id2,tab,"attached")}return await operation()}finally{unlock();for(let tab of tabs)this.queues.get(tab)===tail&&this.queues.delete(tab)}})(),pending=this.pending.get(id2)??new Set;pending.add(promise2),this.pending.set(id2,pending);try{return await promise2}finally{pending.delete(promise2),pending.size||this.pending.delete(id2)}}async waitForIdle(id2){await Promise.allSettled([...this.pending.get(id2)??[]])}assertStep(id2,generation){this.assertActive(id2,generation)}recordResult(id2,action,result,data2){action==="tab_open"&&typeof result?.tabId=="number"&&this.claim(id2,result.tabId,result.opened===!0?"created":"attached");for(let tab of result?.observation?.openedTabs??[])typeof tab.tabId=="number"&&this.claim(id2,tab.tabId,"created");let target=this.require(id2).tabs.find(tab=>tab.tabId===(result?.tabId??data2?.tabId));if(target&&typeof result?.title=="string"&&(target.title=result.title),target&&typeof result?.url=="string"&&(target.url=result.url),action==="tab_close"&&typeof data2?.tabId=="number"&&(this.owners.delete(data2.tabId),this.require(id2).tabs=this.require(id2).tabs.filter(tab=>tab.tabId!==data2.tabId)),action==="tab_close")for(let tab of this.require(id2).tabs)(result?.closedTabIds??[]).includes(tab.tabId)&&this.owners.delete(tab.tabId)}handoff(id2,reason){let record5=this.assertActive(id2);return record5.status="user",record5.generation+=1,record5.reason=reason,this.info(id2)}async resume(id2,cleanup=async()=>{}){let record5=this.require(id2);if(record5.status!=="user")throw new Error("Session is not waiting for the user");if(this.resuming.has(id2))throw new Error("CONTROL_SESSION_RESUMING");this.resuming.add(id2);try{return await this.waitForIdle(id2),await cleanup(),record5.status="active",record5.reason=void 0,record5.generation+=1,this.info(id2)}finally{this.resuming.delete(id2)}}async release(id2,params,cleanup){let record5=this.require(id2);if(this.resuming.has(id2))throw new Error("CONTROL_SESSION_RESUMING");if(record5.status==="released")return this.info(id2);if(record5.status==="releasing")throw new Error("CONTROL_SESSION_RELEASING");let previous=record5.status;record5.status="releasing",record5.generation+=1,await Promise.allSettled([...this.pending.get(id2)??[]]);let tabs=record5.tabs.map(tab=>tab.tabId),keep=new Set(params.keepTabIds??[]),close=params.closeCreatedTabs?record5.tabs.filter(tab=>tab.source==="created"&&!keep.has(tab.tabId)).map(tab=>tab.tabId):[];try{await cleanup(tabs,close);for(let tab of tabs)this.owners.get(tab)===id2&&this.owners.delete(tab);record5.status="released";let released=[...this.sessions.values()].filter(session=>session.status==="released"&&session.sessionId!==id2);for(let old of released.slice(0,Math.max(0,released.length-99)))this.sessions.delete(old.sessionId)}catch(error62){throw record5.status=previous,error62}return this.info(id2)}}}});import{rmSync as rmSync14}from"node:fs";import{randomUUID as randomUUID19}from"node:crypto";import{mkdir as mkdir2,open as open4,rm as rm3,stat as stat3,writeFile}from"node:fs/promises";import path14 from"node:path";import os5 from"node:os";var MAX_BYTES,ARTIFACT_CHUNK_BYTES,BrowserArtifactStore,init_browser_artifacts=__esm({"src/browser-artifacts.ts"(){"use strict";MAX_BYTES=256*1024*1024,ARTIFACT_CHUNK_BYTES=256*1024,BrowserArtifactStore=class{constructor(root=path14.join(os5.tmpdir(),`mearl-artifacts-${process.pid}-${randomUUID19()}`)){this.root=root}root;files=new Map;directory;sweeper;async create(filename,sessionId){if(typeof filename!="string"||!filename||[".",".."].includes(filename)||/[\\/]/.test(filename)||filename.length>255)throw new Error("Invalid artifact filename");this.sweeper||(this.sweeper=setInterval(()=>{for(let[id2,file3]of this.files)!file3.sessionId&&!file3.writing&&Date.now()-file3.accessedAt>3600*1e3&&this.remove(id2).catch(()=>{})},6e4),this.sweeper.unref()),await mkdir2(this.root,{recursive:!0,mode:448}),this.directory=this.root;let artifactId=randomUUID19(),directory=path14.join(this.root,artifactId);await mkdir2(directory,{mode:448});let file2={artifactId,filename,bytes:0,location:"browser",path:path14.join(directory,filename),sessionId,ready:!1,accessedAt:Date.now()};return await writeFile(file2.path,"",{mode:384}),this.files.set(artifactId,file2),this.info(file2)}async importFile(filename,sourcePath,sessionId){let source=await open4(sourcePath,"r"),id2;try{let metadata=await source.stat();if(!metadata.isFile()||metadata.size>MAX_BYTES)throw new Error("Download is unavailable or exceeds 256 MiB");id2=(await this.create(filename,sessionId)).artifactId;let file2=this.require(id2,sessionId),target=await open4(file2.path,"w"),copied=0;try{for await(let chunk of source.createReadStream({autoClose:!1})){if(copied+=chunk.length,copied>MAX_BYTES)throw new Error("Download exceeds 256 MiB");let written=0;for(;written<chunk.length;){let result=await target.write(chunk,written,chunk.length-written);if(!result.bytesWritten)throw new Error("Download import made no progress");written+=result.bytesWritten}}}finally{await target.close()}if(copied!==metadata.size)throw new Error("Download changed during import");return file2.bytes=(await stat3(file2.path)).size,file2.ready=!0,this.info(file2)}catch(error62){throw id2&&await this.remove(id2,sessionId),error62}finally{await source.close()}}require(id2,sessionId){let file2=this.files.get(id2);if(!file2||file2.sessionId!==void 0&&file2.sessionId!==sessionId)throw new Error("ARTIFACT_NOT_FOUND");return file2.accessedAt=Date.now(),file2}info(file2){return{artifactId:file2.artifactId,filename:file2.filename,bytes:file2.bytes,location:"browser"}}async write(id2,offset2,base643,sessionId){let file2=this.require(id2,sessionId);if(file2.ready||file2.writing||offset2!==file2.bytes)throw new Error("Invalid artifact write offset or completed transfer");if(typeof base643!="string"||base643.length>Math.ceil(ARTIFACT_CHUNK_BYTES/3)*4||base643.length%4!==0||!/^[A-Za-z0-9+/]*={0,2}$/.test(base643))throw new Error("Invalid artifact chunk");let chunk=Buffer.from(base643,"base64");if(chunk.toString("base64")!==base643)throw new Error("Invalid artifact chunk");if(file2.bytes+chunk.length>MAX_BYTES)throw new Error("Artifact exceeds 256 MiB");file2.writing=!0;try{let handle=await open4(file2.path,"r+");try{let written=0;for(;written<chunk.length;){let result=await handle.write(chunk,written,chunk.length-written,offset2+written);if(!result.bytesWritten)throw new Error("Artifact write made no progress");written+=result.bytesWritten}}finally{await handle.close()}return file2.bytes+=chunk.length,this.info(file2)}finally{file2.writing=!1}}complete(id2,bytes,sessionId){let file2=this.require(id2,sessionId);if(file2.writing||bytes!==file2.bytes)throw new Error("Artifact size mismatch");return file2.ready=!0,this.info(file2)}resolve(id2,sessionId){let file2=this.require(id2,sessionId);if(!file2.ready)throw new Error("Artifact is not ready");return file2.path}async read(id2,offset2,sessionId){let file2=this.require(id2,sessionId);if(!file2.ready||!Number.isSafeInteger(offset2)||offset2<0||offset2>file2.bytes)throw new Error("Invalid artifact read");let handle=await open4(file2.path,"r");try{let buffer=Buffer.alloc(Math.min(ARTIFACT_CHUNK_BYTES,file2.bytes-offset2)),result=await handle.read(buffer,0,buffer.length,offset2);return{base64:buffer.subarray(0,result.bytesRead).toString("base64"),bytes:result.bytesRead,totalBytes:file2.bytes}}finally{await handle.close()}}async remove(id2,sessionId){let file2=this.require(id2,sessionId);await rm3(path14.dirname(file2.path),{recursive:!0,force:!0}),this.files.delete(id2)}async release(sessionId,keep=[]){let retain=new Set(keep);for(let[id2,file2]of this.files)file2.sessionId===sessionId&&(retain.has(id2)?(file2.sessionId=void 0,file2.accessedAt=Date.now()):await this.remove(id2,sessionId))}disposeSync(){this.sweeper&&clearInterval(this.sweeper),this.directory&&rmSync14(this.directory,{recursive:!0,force:!0}),this.files.clear()}async dispose(){this.sweeper&&clearInterval(this.sweeper),this.directory&&await rm3(this.directory,{recursive:!0,force:!0}),this.files.clear()}}}});var ControlSessionManager,init_control_session_manager=__esm({"src/control-session-manager.ts"(){"use strict";init_dist5();init_control_session();init_browser_artifacts();ControlSessionManager=class{constructor(dependencies){this.dependencies=dependencies;this.registry=new ControlSessionRegistry(dependencies.browserId)}dependencies;registry;artifacts=new BrowserArtifactStore;activityQueues=new Map;async withActivity(id2,operation){let pending=(this.activityQueues.get(id2)??Promise.resolve()).catch(()=>{}).then(operation);this.activityQueues.set(id2,pending);try{return await pending}finally{this.activityQueues.get(id2)===pending&&this.activityQueues.delete(id2)}}async closeTabs(tabs,transport,sessionId){for(let tabId of tabs)try{let result=await this.dependencies.invoke("tab_close",{tabId},transport);this.registry.recordResult(sessionId,"tab_close",result,{tabId})}catch(error62){let current2=await this.dependencies.invoke("tab_list",{},transport);if(!Array.isArray(current2.tabs)||current2.tabs.some(tab=>(tab.tabId??tab.id)===tabId))throw error62;this.registry.recordResult(sessionId,"tab_close",{},{tabId})}}async userControl(id2,operation,reason){if(operation==="release")return await this.invoke("browser_release",{},id2),this.registry.info(id2);operation==="resume"&&await this.activityQueues.get(id2)?.catch(()=>{});let current2=this.registry.info(id2),info=operation==="resume"?await this.registry.resume(id2,()=>this.dependencies.cleanup(current2.tabs.map(tab=>tab.tabId),current2.transport,!0)):current2.status==="user"?current2:this.registry.handoff(id2,reason);return this.dependencies.notify(),operation==="handoff"?(await this.withActivity(id2,async()=>{await this.dependencies.deactivate?.(this.registry.info(id2).tabs.map(tab=>tab.tabId),current2.transport)}),this.registry.info(id2)):info}async invoke(action,data2,sessionId,requestedTransport,origin="local"){if(data2&&(Object.keys(data2).some(key=>key.startsWith("__mearl"))||(data2.actions??[]).some(step=>Object.keys(step.data??{}).some(key=>key.startsWith("__mearl")))))throw new Error("Internal session fields are reserved");validateRequiredActionTabId(action,data2);let created=sessionId==="new";if(sessionId==="new"){if(!["tab_open","page_snapshot","page_screenshot"].includes(action))throw new Error("A new session requires tab_open, page_snapshot or page_screenshot");sessionId=this.registry.create(requestedTransport??this.dependencies.transport()).sessionId}let info=sessionId?this.registry.info(sessionId):void 0,transport=info?.transport??requestedTransport??this.dependencies.transport();if(requestedTransport&&transport!==requestedTransport)throw new Error("A session cannot change transport");if(action==="browser_control"){if(!sessionId||!["handoff","resume"].includes(data2.operation))throw new Error("browser_control requires --session and handoff/resume");if(data2.operation==="resume")return{session:await this.userControl(sessionId,"resume")};if(data2.tabId!==void 0&&!info.tabs.some(tab=>tab.tabId===data2.tabId))throw new Error("TAB_NOT_IN_CONTROL_SESSION");return{session:await this.userControl(sessionId,"handoff",data2.reason)}}if(action==="browser_release"&&sessionId){let params=data2;return await this.registry.release(sessionId,params,async(tabs,close)=>{await this.dependencies.cleanup(tabs,transport,params.preserveEnvironment!==!1),await this.closeTabs(close,transport,sessionId),await this.artifacts.release(sessionId,params.keepArtifactIds)}),this.dependencies.notify(),{released:!0,hadActiveControl:!0,transports:[transport],affectedTabIds:info.tabs.map(tab=>tab.tabId),session:this.registry.state(sessionId)}}if((action==="browser_release"||action==="browser_close")&&this.registry.assertBrowserRelease(),(action==="browser_release"||action==="browser_close")&&!sessionId)return this.registry.releaseBrowser(()=>this.dependencies.invoke(action,data2,transport));let performStep=async(name,payload,batch=!1)=>{if(name==="tab_open"&&(sessionId&&payload.closeExisting&&(await this.closeTabs(this.registry.info(sessionId).tabs.filter(tab=>tab.source==="created").map(tab=>tab.tabId),transport,sessionId),payload={...payload,closeExisting:!1}),payload={...payload,__mearlBlockedTabIds:this.registry.blockedTabs(sessionId)}),name==="page_upload"&&payload.files){if(payload.filePaths)throw new Error("Use files or filePaths, not both");payload={...payload,filePaths:payload.files.map(file2=>this.artifacts.resolve(file2.artifactId,sessionId))},delete payload.files}if(batch){if(transport==="provider"){let result4=await this.dependencies.invoke(name,{...payload,__mearlControlSource:origin},transport);return sessionId&&this.registry.recordResult(sessionId,name,result4,payload),result4?.action?.success===!1?{...result4.action,success:!1}:result4?.success===!1?{...result4,success:!1}:{success:!0,data:result4}}let result3=await this.dependencies.invoke("run_actions",{tabId:payload.tabId,actions:[{action:name,data:payload}],__mearlControlSource:origin},transport),outcome=result3.results?.[0]??{success:!1,error:result3.error??"Batch step returned no result",stage:result3.stage,attempt:result3.attempt};return sessionId&&outcome.success&&this.registry.recordResult(sessionId,name,outcome.data,payload),outcome}let result2=await this.dependencies.invoke(name,{...payload,__mearlControlSource:origin},transport);return sessionId&&this.registry.recordResult(sessionId,name,result2,payload),result2},invokeStep=(name,payload,batch=!1)=>sessionId&&name==="tab_open"?this.withActivity(sessionId,async()=>(this.registry.assertStep(sessionId,info.generation),performStep(name,payload,batch))):performStep(name,payload,batch),result;try{result=await this.registry.run(sessionId,action,data2,async()=>{if(transport==="extension"&&this.dependencies.transport()!=="extension")throw new Error("CONTROL_SESSION_TRANSPORT_UNAVAILABLE: extension is disconnected");if(action.startsWith("__artifact_")){let store=this.artifacts;switch(action){case"__artifact_init":return{artifact:await store.create(data2.filename,sessionId),browserId:this.dependencies.browserId()};case"__artifact_write":return store.write(data2.artifactId,data2.offset,data2.base64,sessionId);case"__artifact_complete":return store.complete(data2.artifactId,data2.bytes,sessionId);case"__artifact_read":return store.read(data2.artifactId,data2.offset,sessionId);case"__artifact_remove":return await store.remove(data2.artifactId,sessionId),{removed:!0};default:throw new Error("Unknown artifact operation")}}if(action==="page_download"){let downloaded=await this.dependencies.download(data2,transport,origin);try{return sessionId&&this.registry.recordResult(sessionId,"page_click",downloaded.action,data2),{download:{...await this.artifacts.importFile(downloaded.filename,downloaded.filePath,sessionId),browserId:this.dependencies.browserId()},action:downloaded.action}}finally{if(downloaded.owned){let{rm:rm5,rmdir:rmdir2}=await import("node:fs/promises");await rm5(downloaded.filePath,{force:!0}),downloaded.directory&&await rmdir2(downloaded.directory).catch(()=>{})}}}return action==="run_actions"&&sessionId?runActionBatch(data2,(name,payload)=>invokeStep(name,payload,!0),{beforeStep:()=>this.registry.assertStep(sessionId,info.generation)}):invokeStep(action,data2)},transport)}catch(error62){throw created&&sessionId&&await this.registry.release(sessionId,{},async tabs=>{await this.dependencies.cleanup(tabs,transport,!0),await this.artifacts.release(sessionId)}),this.dependencies.notify(),error62}if(sessionId&&action==="tab_list"&&Array.isArray(result.tabs)){let blocked=new Set(this.registry.blockedTabs(sessionId));result={...result,tabs:result.tabs.filter(tab=>!blocked.has(tab.tabId??tab.id))},result.count=result.tabs.length}return sessionId?(this.dependencies.notify(),{...result,session:this.registry.state(sessionId)}):result}}}});import{isAbsolute as isAbsolute10}from"node:path";import{AsyncLocalStorage as AsyncLocalStorage2}from"node:async_hooks";function identity2(browser){return`${browser.pid}:${browser.startedAt}:${browser.socketPath}`}var ProviderControlService,init_provider_control=__esm({"src/provider-control.ts"(){"use strict";init_dist5();init_control_session_manager();init_dist4();init_provider_request_timeout();ProviderControlService=class{constructor(dependencies){this.dependencies=dependencies}dependencies;deadlines=new AsyncLocalStorage2;controls=new Map;retired=new Set;retire(manager){let cleanup=Promise.all(manager.registry.list().map(session=>manager.registry.waitForIdle(session.sessionId))).then(()=>manager.artifacts.dispose());this.retired.add(cleanup),cleanup.finally(()=>this.retired.delete(cleanup)).catch(()=>{})}reap(){let browsers=this.dependencies.browsers();for(let[browserId,control]of this.controls){let browser=browsers.find(item=>item.browserId===browserId);(!browser||identity2(browser)!==control.identity)&&(this.controls.delete(browserId),this.retire(control.manager))}}browser(browserId){let browser=this.dependencies.browsers().find(item=>item.browserId===browserId);if(browser?.transport!=="provider"||!browser.providerBrowserId)throw new Error("CONTROL_SESSION_NOT_FOUND: Provider browser is unavailable");return assertProviderProtocolVersion(browser.provider?.protocolVersion,browser.provider?.id??"Provider"),browser}list(){return this.reap(),[...this.controls.values()].flatMap(control=>control.manager.registry.list())}find(sessionId){this.reap();let sessions=[...this.controls.values()].map(control=>control.manager.registry.find(sessionId)).filter(session=>session!==void 0);if(sessions.length>1)throw new Error("CONTROL_SESSION_AMBIGUOUS: multiple browsers own this id");return sessions[0]}async userControl(sessionId,operation){this.reap();for(let[browserId,control]of this.controls){try{control.manager.registry.info(sessionId)}catch{continue}if(identity2(this.browser(browserId))!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");return control.manager.userControl(sessionId,operation)}throw new Error("CONTROL_SESSION_NOT_FOUND")}getControl(browser){let previous=this.controls.get(browser.browserId);if(previous?.identity===identity2(browser))return previous;previous&&this.retire(previous.manager);let control={identity:identity2(browser),manager:new ControlSessionManager({browserId:()=>browser.browserId,transport:()=>"provider",invoke:async(action,payload)=>{let current2=this.browser(browser.browserId);if(identity2(current2)!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");let{__mearlControlSource:origin="local",__mearlBlockedTabIds:blocked,...data2}=payload;if(action==="tab_open"&&blocked?.length&&data2.reuse!=="never"){let result=await this.request(current2,"tab_list",{},origin);if(!Array.isArray(result.tabs))throw new Error("Provider did not return its current pages");if(findMatchingTabs(result.tabs,data2.url,data2.match).some(tab=>blocked.includes(tab.tabId??tab.id)))throw new Error("TAB_CONTROLLED_BY_SESSION")}return this.request(current2,action,data2,origin)},cleanup:async(tabs,_transport,preserveEnvironment)=>{preserveEnvironment||await this.clearEnvironment(browser.browserId,tabs)},download:async(data2,_transport,origin)=>{let current2=this.browser(browser.browserId);if(identity2(current2)!==control.identity)throw new Error("CONTROL_SESSION_NOT_FOUND");let result=await this.request(current2,"page_download",data2,origin);if(!result||typeof result.filename!="string"||!result.filename||typeof result.filePath!="string"||!isAbsolute10(result.filePath)||!Object.hasOwn(result,"action")||result.owned!==void 0&&typeof result.owned!="boolean")throw new Error("Provider page_download must return a completed file with filename, absolute filePath and action");return{filename:result.filename,filePath:result.filePath,action:result.action,owned:result.owned===!0}},notify:this.dependencies.notify})};return this.controls.set(browser.browserId,control),control}async request(browser,action,data2,origin,host){action==="browser_release"&&(action="browser_instances",data2={operation:"detach"});let lifecycle=action==="browser_close"?data2.deleteProfile===!0?"deleteData":"close":action==="get_browser_access_url"?"access":action==="browser_instances"?data2.operation==="sync_cookies"?"syncCookies":data2.operation:void 0;if(lifecycle&&!browser.operations?.includes(lifecycle))throw new Error(`Provider browser does not support ${lifecycle}`);if(!lifecycle&&action!=="get_versions"){if(browser.status!=="connected")throw new Error("Provider browser is not connected");if(!browser.capabilities?.actions.some(capability=>capability===action))throw new Error(`Provider browser does not support ${action}`)}let deadline=this.deadlines.getStore();return deadline!==void 0?(providerRequestTimeoutMs(action,data2,deadline),this.dependencies.request(browser,action,data2,origin,host,deadline)):this.dependencies.request(browser,action,data2,origin,host)}async clearEnvironment(browserId,tabs){let browser=this.browser(browserId),actions=new Set(browser.capabilities?.actions??[]);if(actions.has("set_mock")&&!actions.has("get_mocks"))throw new Error("Provider cannot enumerate Mock state for cleanup");let clear=async(action,data2)=>{let result=await this.request(browser,action,data2,"local");if(result?.success===!1||result?.action?.success===!1)throw new Error(result.error??result.action?.error??"Provider environment cleanup failed")};for(let tabId of tabs){if(actions.has("set_mock")){let result=await this.request(browser,"get_mocks",{tabId},"local");if(!Array.isArray(result.mocks))throw new Error("Provider did not return its Mock state");let names=new Set(result.mocks.map(mock=>mock.apiName));for(let apiName of names)await clear("set_mock",{tabId,apiName,enabled:!1})}for(let[action,data2]of[["set_app_profile",{tabId,enabled:!1,mtopEnv:"default"}],["set_device_emulation",{tabId,enabled:!1}],["set_timezone",{tabId,timezone:""}]])actions.has(action)&&await clear(action,data2)}}async invoke(browserId,action,data2={},sessionId,origin="local",host,deadline){return this.deadlines.run(deadline,async()=>{deadline!==void 0&&providerRequestTimeoutMs(action,data2,deadline),this.reap();let browser=this.browser(browserId),{manager}=this.getControl(browser);if(action==="get_versions"||action==="get_browser_access_url")return this.request(browser,action,data2,origin);if(action==="browser_instances"){if(!["start","detach","sync_cookies"].includes(data2.operation))throw new Error("Invalid browser instance operation");return manager.registry.releaseBrowser(()=>this.request(browser,action,data2,origin,host))}return manager.invoke(action,data2,sessionId,void 0,origin)})}async dispose(){await Promise.all([...this.retired,...[...this.controls.values()].map(async({manager})=>{await Promise.all(manager.registry.list().map(session=>manager.registry.waitForIdle(session.sessionId))),await manager.artifacts.dispose()})])}}}});var provider_control_host_exports={};__export(provider_control_host_exports,{runProviderControlHost:()=>runProviderControlHost});import net4 from"node:net";import{chmodSync as chmodSync2,rmSync as rmSync16}from"node:fs";import{randomUUID as randomUUID27}from"node:crypto";async function runProviderControlHost(token){let file2=providerControlHostFile(),owner=readRecord(file2);if(!owner||owner.token!==token)throw new Error("Provider control host startup ownership was lost");let socketPath=providerControlSocketPath(process.pid);if(!writeRecordIfPid(file2,owner.pid,{pid:process.pid,socketPath,token,ready:!1,protocolVersion:3}))throw new Error("Provider control host startup ownership was lost");let connections=new Set,service=new ProviderControlService({browsers:listRegisteredBrowsers,request:(browser,action,data2,origin,host,deadline)=>requestBrowserSocket(browser.socketPath,action,data2,NATIVE_HOST_VERSION,providerRequestTimeoutMs(action,data2,deadline),browser.providerBrowserId,host,{controlSource:origin}),notify:()=>{for(let browser of listRegisteredBrowsers())browser.transport==="extension"&&requestBrowserSocket(browser.socketPath,"provider_control_sessions_changed",{},NATIVE_HOST_VERSION,5e3).catch(()=>{})}}),server=net4.createServer(socket=>{connections.add(socket),socket.on("error",()=>{}),socket.once("close",()=>connections.delete(socket));let lines=new Utf8LineBuffer(16*1024*1024);socket.on("data",chunk=>{let result=lines.push(chunk);if(result.overflow)return socket.destroy();for(let line of result.lines)handle(line,socket)})});async function handle(line,socket){let id2="";try{let request=JSON.parse(line);id2=typeof request.id=="string"?request.id:randomUUID27(),assertProviderProtocolVersion(request.protocolVersion,"Provider control client");let data2=request.data??{},result;if(request.action==="__control_session_resolve")result={session:service.find(data2.sessionId)??null};else if(request.action==="browser_sessions")if(data2.operation==="list")result={sessions:service.list()};else{if(!["handoff","resume","release"].includes(data2.operation)||typeof data2.sessionId!="string"||!data2.sessionId)throw new Error("Invalid browser session operation");result={session:await service.userControl(data2.sessionId,data2.operation)}}else{if(typeof request.providerTarget!="string"||typeof request.action!="string")throw new Error("Provider browser and action are required");if(!data2||typeof data2!="object"||Array.isArray(data2))throw new Error("Invalid action payload");if(request.transport&&request.transport!=="provider")throw new Error("A session cannot change transport");result=await service.invoke(request.providerTarget,request.action,data2,request.controlSessionId,request.controlSource??"local",request.host,request.deadline)}socket.destroyed||socket.write(JSON.stringify({id:id2,success:!0,data:result})+`
1111
1111
  `)}catch(error62){socket.destroyed||socket.write(JSON.stringify({id:id2,success:!1,error:error62 instanceof Error?error62.message:String(error62)})+`
1112
1112
  `)}}try{if(await new Promise((resolve16,reject)=>{server.once("error",reject),server.listen(socketPath,()=>{server.removeListener("error",reject),resolve16()})}),process.platform!=="win32"&&chmodSync2(socketPath,384),!writeRecordIfPid(file2,process.pid,{pid:process.pid,socketPath,token,ready:!0,protocolVersion:3}))throw new Error("Provider control host startup ownership was lost");await new Promise(resolve16=>{let stop=()=>{removeRecordIfPid(file2,process.pid);for(let socket of connections)socket.destroy();server.close(()=>resolve16())};process.once("SIGTERM",stop),process.once("SIGINT",stop)})}finally{removeRecordIfPid(file2,process.pid);for(let socket of connections)socket.destroy();server.close(),process.platform!=="win32"&&rmSync16(socketPath,{force:!0}),await service.dispose()}}var init_provider_control_host=__esm({"src/provider-control-host.ts"(){"use strict";init_dist();init_host();init_browser_registry();init_browser_socket_request();init_provider_control();init_socketLines();init_version2();init_dist4();init_provider_request_timeout()}});import fs17 from"node:fs";import path22 from"node:path";function resolveLauncherNodePaths(execPath){let nodePath=path22.resolve(execPath),homebrewNode=nodePath.match(/^(.*)\/Cellar\/(node(?:@\d+(?:\.\d+)*)?)\/[^/]+\/bin\/node$/);if(!homebrewNode)return[nodePath];let[,prefix,formula]=homebrewNode,stablePath=path22.join(prefix,"opt",formula,"bin","node");try{if(fs17.accessSync(stablePath,fs17.constants.X_OK),fs17.statSync(stablePath).isFile()&&fs17.realpathSync(stablePath)===fs17.realpathSync(nodePath))return[stablePath,nodePath]}catch{}return[nodePath,stablePath]}function shellQuote(value){return`'${value.replaceAll("'","'\\''")}'`}function createLauncherScript(hostDir,hostPath,hostName,execPath=process.execPath){let nodePaths=process.platform==="win32"?[path22.resolve(execPath)]:resolveLauncherNodePaths(execPath),metadataLine=`mearl-native-host-launcher: ${JSON.stringify({version:1,nodePaths,hostPath})}`;if(process.platform==="win32"){let launcherPath2=path22.join(hostDir,`${hostName}-launcher.cmd`),content2=`@echo off\r
1113
1113
  rem ${metadataLine}\r
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/native-host",
3
- "version": "3.0.0-beta.3",
3
+ "version": "3.0.0-beta.4",
4
4
  "description": "Native Messaging Host for Mearl — bridges Chrome Extension and local socket server",
5
5
  "type": "module",
6
6
  "engines": {
@@ -48,14 +48,14 @@
48
48
  "ws": "^8.18.3",
49
49
  "@types/node": "^24.9.1",
50
50
  "@types/ws": "^8.18.1",
51
- "@mearl/browser-core": "3.0.0-beta.3",
52
- "@mearl/daemon-core": "3.0.0-beta.3",
53
- "@mearl/setup": "3.0.0-beta.3",
54
- "@mearl/provider": "3.0.0-beta.3",
55
- "@mearl/client": "3.0.0-beta.3",
56
- "@mearl/agent-runtime": "3.0.0-beta.3",
57
- "@mearl/preset": "3.0.0-beta.3",
58
- "@mearl/app-profiles": "3.0.0-beta.3"
51
+ "@mearl/app-profiles": "3.0.0-beta.4",
52
+ "@mearl/browser-core": "3.0.0-beta.4",
53
+ "@mearl/client": "3.0.0-beta.4",
54
+ "@mearl/daemon-core": "3.0.0-beta.4",
55
+ "@mearl/provider": "3.0.0-beta.4",
56
+ "@mearl/setup": "3.0.0-beta.4",
57
+ "@mearl/agent-runtime": "3.0.0-beta.4",
58
+ "@mearl/preset": "3.0.0-beta.4"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/semver": "^7.7.1",