@code-yeongyu/senpi 2026.9.28-2 → 2026.9.28-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/bundle/chunks/{app-server-command-H6LFICZ2.js → app-server-command-JTHJ2U7J.js} +1 -1
- package/dist/bundle/chunks/{chunk-LSW7QSLV.js → chunk-6XBT6OGS.js} +36 -34
- package/dist/bundle/chunks/chunk-OP3QMR6B.js +8 -0
- package/dist/bundle/chunks/{cli-main-GYRTYPMG.js → cli-main-Z2Y55XHU.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-LX665HTP.js → interactive-mode-A3EYB6DW.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-MFTUM6XQ.js → multi-session-host-HM2UJ4ER.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-6IAE5WG3.js → package-manager-cli-T7Q4AZKX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-XN3HNZMH.js → rpc-mode-ROB6IG6J.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +140 -138
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/webview/bun-chrome.d.ts +15 -0
- package/dist/core/webview/bun-chrome.d.ts.map +1 -0
- package/dist/core/webview/bun-chrome.js +91 -0
- package/dist/core/webview/bun-chrome.js.map +1 -0
- package/dist/core/webview/native-webview.d.ts +16 -0
- package/dist/core/webview/native-webview.d.ts.map +1 -0
- package/dist/core/webview/native-webview.js +15 -0
- package/dist/core/webview/native-webview.js.map +1 -0
- package/dist/core/webview/webview-broker.d.ts +27 -0
- package/dist/core/webview/webview-broker.d.ts.map +1 -0
- package/dist/core/webview/webview-broker.js +120 -0
- package/dist/core/webview/webview-broker.js.map +1 -0
- package/dist/core/webview/webview-client.d.ts +19 -0
- package/dist/core/webview/webview-client.d.ts.map +1 -0
- package/dist/core/webview/webview-client.js +135 -0
- package/dist/core/webview/webview-client.js.map +1 -0
- package/dist/core/webview/webview-service.d.ts +26 -0
- package/dist/core/webview/webview-service.d.ts.map +1 -0
- package/dist/core/webview/webview-service.js +102 -0
- package/dist/core/webview/webview-service.js.map +1 -0
- package/dist/core/webview/webview-wire.d.ts +76 -0
- package/dist/core/webview/webview-wire.d.ts.map +1 -0
- package/dist/core/webview/webview-wire.js +57 -0
- package/dist/core/webview/webview-wire.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/modes/rpc/session-worker-client.d.ts +2 -0
- package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
- package/dist/modes/rpc/session-worker-client.js +7 -1
- package/dist/modes/rpc/session-worker-client.js.map +1 -1
- package/dist/modes/rpc/session-worker.js +3 -1
- package/dist/modes/rpc/session-worker.js.map +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +14 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +14 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/webview-host.ts +59 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +17 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-host.ts +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +14 -5
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-webview-client.js +79 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-webview.js +204 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/runtime-apis.md +1 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/chunk-KOPTADA3.js +0 -8
package/dist/bundle/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
3
|
-
import{enableStartupCompileCache}from"./chunks/chunk-F73COJ7R.js";import{getLatestPiRelease,hasInheritedInspectorOption,isNewerPackageVersion,processBunRuntimeOptions,releaseInheritedInspectorForChild,resolveBunReexec}from"./chunks/chunk-4LVZQRDG.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import{APP_NAME,DISPLAY_VERSION,PACKAGE_NAME,VERSION,brandProfile,getAgentDir,getPackageDir,getSelfUpdateCommand,getSelfUpdateUnavailableInstruction,isBundledNode}from"./chunks/chunk-PBWQK23Z.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-IKOFCODM.js";import{spawn as spawn2,spawnSync}from"node:child_process";import{existsSync,realpathSync}from"node:fs";import{join as join2}from"node:path";import{fileURLToPath}from"node:url";import{spawn}from"node:child_process";import{readFileSync}from"node:fs";import{join}from"node:path";function parseBootstrapSelfUpdateArgs(args2){let[command,...rest]=args2;if(command!=="update")return;let force=!1,selfFlag=!1,extensionsFlag=!1,extensionFlag=!1,source;for(let index=0;index<rest.length;index++){let arg=rest[index];if(arg==="-h"||arg==="--help")return;if(arg==="--force"){force=!0;continue}if(arg==="--self"){selfFlag=!0;continue}if(arg==="--extensions"){extensionsFlag=!0;continue}if(arg==="--extension"){extensionFlag=!0,index++;continue}if(arg.startsWith("-")||source)return;source=arg}if(extensionFlag)return;let sourceIsSelf=source==="self"||source==="pi"||source===APP_NAME;if(!(source&&!sourceIsSelf)&&!(!selfFlag&&!sourceIsSelf&&extensionsFlag))return{force}}function getObjectProperty(value,key){if(!(!value||typeof value!="object"))return Object.getOwnPropertyDescriptor(value,key)?.value}function readConfiguredNpmCommand(){try{let parsed=JSON.parse(readFileSync(join(getAgentDir(),"settings.json"),"utf-8")),npmCommand=getObjectProperty(parsed,"npmCommand");return!Array.isArray(npmCommand)||npmCommand.some(part=>typeof part!="string")?void 0:npmCommand.length>0?npmCommand:void 0}catch{return}}async function runCommand(step){await new Promise((resolve,reject)=>{let child=spawn(step.command,step.args,{stdio:"inherit"});child.on("error",error=>{reject(error)}),child.on("close",(code,signal)=>{code===0?resolve():reject(signal?new Error(`${step.display} terminated by signal ${signal}`):new Error(`${step.display} exited with code ${code??"unknown"}`))})})}async function handleBootstrapSelfUpdate(args2,dependencies={}){let parsed=parseBootstrapSelfUpdateArgs(args2);if(!parsed)return!1;let writeStdout=dependencies.writeStdout??(line=>console.log(line)),writeStderr=dependencies.writeStderr??(line=>console.error(line)),readNpmCommand=dependencies.readNpmCommand??readConfiguredNpmCommand,resolveSelfUpdateCommand=dependencies.getSelfUpdateCommand??getSelfUpdateCommand,resolveUnavailableInstruction=dependencies.getUnavailableInstruction??getSelfUpdateUnavailableInstruction,executeCommand=dependencies.runCommand??runCommand,brandUpdate=brandProfile()?.update;if(brandUpdate)return writeStdout(`${APP_NAME} ships inside ${brandUpdate.packageName}. Update it with: ${brandUpdate.command}`),!0;let loadLatestRelease=dependencies.getLatestRelease??getLatestPiRelease,latestRelease;if(!parsed.force)try{latestRelease=await loadLatestRelease(VERSION)}catch{latestRelease=void 0}let updatePackageName=latestRelease?.packageName??PACKAGE_NAME;if(!(parsed.force||!latestRelease||updatePackageName!==PACKAGE_NAME||isNewerPackageVersion(latestRelease.version,VERSION)))return writeStdout(`${APP_NAME} is already up to date (v${VERSION})`),!0;let npmCommand=readNpmCommand(),selfUpdateCommand=resolveSelfUpdateCommand(PACKAGE_NAME,npmCommand,updatePackageName);if(!selfUpdateCommand)return writeStderr(`error: ${APP_NAME} cannot self-update this installation.`),writeStderr(resolveUnavailableInstruction(PACKAGE_NAME,npmCommand,updatePackageName)),process.argv[1]&&(writeStderr(""),writeStderr(`Location of ${APP_NAME} executable: ${process.argv[1]}`)),process.exitCode=1,!0;latestRelease?.note?.trim()&&(writeStdout(""),writeStdout("Update note"),writeStdout(latestRelease.note.trim()),writeStdout("")),writeStdout(`Updating ${APP_NAME} with ${selfUpdateCommand.display}...`);for(let step of selfUpdateCommand.steps??[selfUpdateCommand])try{await executeCommand(step)}catch(error){let message=error instanceof Error?error.message:"Unknown package command error";return writeStderr(`Error: ${message}`),writeStderr(`If this keeps failing, run this command yourself: ${selfUpdateCommand.display}`),process.exitCode=1,!0}return writeStdout(`Updated ${APP_NAME}`),!0}function reexecUnderBunIfNeeded(){let options=processBunRuntimeOptions(existsSync,realpathSync),scriptRealPath=process.argv[1]??fileURLToPath(import.meta.url);try{scriptRealPath=realpathSync(scriptRealPath)}catch{}let decision=resolveBunReexec({scriptRealPath,versions:process.versions,hasInheritedInspectorOption:hasInheritedInspectorOption(),options});if(decision.action==="stay")return!1;let result=spawnSync(decision.bunPath,[scriptRealPath,...process.argv.slice(2)],{stdio:"inherit",windowsHide:!0});return result.signal?(process.kill(process.pid,result.signal),!0):(process.exitCode=result.status??1,!0)}reexecUnderBunIfNeeded()&&process.exit();enableStartupCompileCache();process.title=APP_NAME;process.env.PI_CODING_AGENT="true";process.env.AI_AGENT=APP_NAME;process.emitWarning=(()=>{});var args=process.argv.slice(2),PACKAGE_COMMANDS=new Set(["install","remove","uninstall","update","list","config"]);function isRootCommand(args2){let firstArg=args2[0];return firstArg===void 0||!PACKAGE_COMMANDS.has(firstArg)}function isPackageManagerInstall(packageDir){return packageDir.replace(/\\/g,"/").includes("/node_modules/@code-yeongyu/senpi")}function isMissingBundledWorkspaceDependencies(packageDir){return isPackageManagerInstall(packageDir)?["pi-agent-core","pi-ai","pi-tui"].some(name=>!existsSync(join2(packageDir,"node_modules","@earendil-works",name,"dist","index.js"))):!1}var ISOLATED_CHILD_ENV="SENPI_CLI_ISOLATED_CHILD";function requiresIsolatedProcess(){return process.env[ISOLATED_CHILD_ENV]==="1"?!1:process.execArgv.length>0||hasInheritedInspectorOption()}async function spawnFullCli(){let extension=import.meta.url.endsWith(".ts")?".ts":".js",fullCliPath=isBundledNode?fileURLToPath(import.meta.url):fileURLToPath(new URL(`./cli-main${extension}`,import.meta.url));releaseInheritedInspectorForChild();let childEnvironment=isBundledNode?{...process.env,[ISOLATED_CHILD_ENV]:"1"}:process.env;return await new Promise((resolve,reject)=>{let child=spawn2(process.execPath,[...process.execArgv,fullCliPath,...args],{env:childEnvironment,stdio:"inherit"});child.on("error",error=>{reject(error)}),child.on("close",(code,signal)=>{if(signal){process.kill(process.pid,signal),resolve(1);return}resolve(code??1)})})}isRootCommand(args)&&(args.includes("--version")||args.includes("-v"))&&(console.log(DISPLAY_VERSION),process.exit());if(isRootCommand(args)&&args.some(arg=>arg==="--help"||arg==="-h")){let{tryPrintHelpWithoutEngine}=await import("./chunks/help-fast-path-3U3YDEZ6.js");tryPrintHelpWithoutEngine(args)&&process.exit()}isMissingBundledWorkspaceDependencies(getPackageDir())&&await handleBootstrapSelfUpdate(args)&&process.exit();requiresIsolatedProcess()?process.exitCode=await spawnFullCli():await import("./chunks/cli-main-
|
|
3
|
+
import{enableStartupCompileCache}from"./chunks/chunk-F73COJ7R.js";import{getLatestPiRelease,hasInheritedInspectorOption,isNewerPackageVersion,processBunRuntimeOptions,releaseInheritedInspectorForChild,resolveBunReexec}from"./chunks/chunk-4LVZQRDG.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import{APP_NAME,DISPLAY_VERSION,PACKAGE_NAME,VERSION,brandProfile,getAgentDir,getPackageDir,getSelfUpdateCommand,getSelfUpdateUnavailableInstruction,isBundledNode}from"./chunks/chunk-PBWQK23Z.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-IKOFCODM.js";import{spawn as spawn2,spawnSync}from"node:child_process";import{existsSync,realpathSync}from"node:fs";import{join as join2}from"node:path";import{fileURLToPath}from"node:url";import{spawn}from"node:child_process";import{readFileSync}from"node:fs";import{join}from"node:path";function parseBootstrapSelfUpdateArgs(args2){let[command,...rest]=args2;if(command!=="update")return;let force=!1,selfFlag=!1,extensionsFlag=!1,extensionFlag=!1,source;for(let index=0;index<rest.length;index++){let arg=rest[index];if(arg==="-h"||arg==="--help")return;if(arg==="--force"){force=!0;continue}if(arg==="--self"){selfFlag=!0;continue}if(arg==="--extensions"){extensionsFlag=!0;continue}if(arg==="--extension"){extensionFlag=!0,index++;continue}if(arg.startsWith("-")||source)return;source=arg}if(extensionFlag)return;let sourceIsSelf=source==="self"||source==="pi"||source===APP_NAME;if(!(source&&!sourceIsSelf)&&!(!selfFlag&&!sourceIsSelf&&extensionsFlag))return{force}}function getObjectProperty(value,key){if(!(!value||typeof value!="object"))return Object.getOwnPropertyDescriptor(value,key)?.value}function readConfiguredNpmCommand(){try{let parsed=JSON.parse(readFileSync(join(getAgentDir(),"settings.json"),"utf-8")),npmCommand=getObjectProperty(parsed,"npmCommand");return!Array.isArray(npmCommand)||npmCommand.some(part=>typeof part!="string")?void 0:npmCommand.length>0?npmCommand:void 0}catch{return}}async function runCommand(step){await new Promise((resolve,reject)=>{let child=spawn(step.command,step.args,{stdio:"inherit"});child.on("error",error=>{reject(error)}),child.on("close",(code,signal)=>{code===0?resolve():reject(signal?new Error(`${step.display} terminated by signal ${signal}`):new Error(`${step.display} exited with code ${code??"unknown"}`))})})}async function handleBootstrapSelfUpdate(args2,dependencies={}){let parsed=parseBootstrapSelfUpdateArgs(args2);if(!parsed)return!1;let writeStdout=dependencies.writeStdout??(line=>console.log(line)),writeStderr=dependencies.writeStderr??(line=>console.error(line)),readNpmCommand=dependencies.readNpmCommand??readConfiguredNpmCommand,resolveSelfUpdateCommand=dependencies.getSelfUpdateCommand??getSelfUpdateCommand,resolveUnavailableInstruction=dependencies.getUnavailableInstruction??getSelfUpdateUnavailableInstruction,executeCommand=dependencies.runCommand??runCommand,brandUpdate=brandProfile()?.update;if(brandUpdate)return writeStdout(`${APP_NAME} ships inside ${brandUpdate.packageName}. Update it with: ${brandUpdate.command}`),!0;let loadLatestRelease=dependencies.getLatestRelease??getLatestPiRelease,latestRelease;if(!parsed.force)try{latestRelease=await loadLatestRelease(VERSION)}catch{latestRelease=void 0}let updatePackageName=latestRelease?.packageName??PACKAGE_NAME;if(!(parsed.force||!latestRelease||updatePackageName!==PACKAGE_NAME||isNewerPackageVersion(latestRelease.version,VERSION)))return writeStdout(`${APP_NAME} is already up to date (v${VERSION})`),!0;let npmCommand=readNpmCommand(),selfUpdateCommand=resolveSelfUpdateCommand(PACKAGE_NAME,npmCommand,updatePackageName);if(!selfUpdateCommand)return writeStderr(`error: ${APP_NAME} cannot self-update this installation.`),writeStderr(resolveUnavailableInstruction(PACKAGE_NAME,npmCommand,updatePackageName)),process.argv[1]&&(writeStderr(""),writeStderr(`Location of ${APP_NAME} executable: ${process.argv[1]}`)),process.exitCode=1,!0;latestRelease?.note?.trim()&&(writeStdout(""),writeStdout("Update note"),writeStdout(latestRelease.note.trim()),writeStdout("")),writeStdout(`Updating ${APP_NAME} with ${selfUpdateCommand.display}...`);for(let step of selfUpdateCommand.steps??[selfUpdateCommand])try{await executeCommand(step)}catch(error){let message=error instanceof Error?error.message:"Unknown package command error";return writeStderr(`Error: ${message}`),writeStderr(`If this keeps failing, run this command yourself: ${selfUpdateCommand.display}`),process.exitCode=1,!0}return writeStdout(`Updated ${APP_NAME}`),!0}function reexecUnderBunIfNeeded(){let options=processBunRuntimeOptions(existsSync,realpathSync),scriptRealPath=process.argv[1]??fileURLToPath(import.meta.url);try{scriptRealPath=realpathSync(scriptRealPath)}catch{}let decision=resolveBunReexec({scriptRealPath,versions:process.versions,hasInheritedInspectorOption:hasInheritedInspectorOption(),options});if(decision.action==="stay")return!1;let result=spawnSync(decision.bunPath,[scriptRealPath,...process.argv.slice(2)],{stdio:"inherit",windowsHide:!0});return result.signal?(process.kill(process.pid,result.signal),!0):(process.exitCode=result.status??1,!0)}reexecUnderBunIfNeeded()&&process.exit();enableStartupCompileCache();process.title=APP_NAME;process.env.PI_CODING_AGENT="true";process.env.AI_AGENT=APP_NAME;process.emitWarning=(()=>{});var args=process.argv.slice(2),PACKAGE_COMMANDS=new Set(["install","remove","uninstall","update","list","config"]);function isRootCommand(args2){let firstArg=args2[0];return firstArg===void 0||!PACKAGE_COMMANDS.has(firstArg)}function isPackageManagerInstall(packageDir){return packageDir.replace(/\\/g,"/").includes("/node_modules/@code-yeongyu/senpi")}function isMissingBundledWorkspaceDependencies(packageDir){return isPackageManagerInstall(packageDir)?["pi-agent-core","pi-ai","pi-tui"].some(name=>!existsSync(join2(packageDir,"node_modules","@earendil-works",name,"dist","index.js"))):!1}var ISOLATED_CHILD_ENV="SENPI_CLI_ISOLATED_CHILD";function requiresIsolatedProcess(){return process.env[ISOLATED_CHILD_ENV]==="1"?!1:process.execArgv.length>0||hasInheritedInspectorOption()}async function spawnFullCli(){let extension=import.meta.url.endsWith(".ts")?".ts":".js",fullCliPath=isBundledNode?fileURLToPath(import.meta.url):fileURLToPath(new URL(`./cli-main${extension}`,import.meta.url));releaseInheritedInspectorForChild();let childEnvironment=isBundledNode?{...process.env,[ISOLATED_CHILD_ENV]:"1"}:process.env;return await new Promise((resolve,reject)=>{let child=spawn2(process.execPath,[...process.execArgv,fullCliPath,...args],{env:childEnvironment,stdio:"inherit"});child.on("error",error=>{reject(error)}),child.on("close",(code,signal)=>{if(signal){process.kill(process.pid,signal),resolve(1);return}resolve(code??1)})})}isRootCommand(args)&&(args.includes("--version")||args.includes("-v"))&&(console.log(DISPLAY_VERSION),process.exit());if(isRootCommand(args)&&args.some(arg=>arg==="--help"||arg==="-h")){let{tryPrintHelpWithoutEngine}=await import("./chunks/help-fast-path-3U3YDEZ6.js");tryPrintHelpWithoutEngine(args)&&process.exit()}isMissingBundledWorkspaceDependencies(getPackageDir())&&await handleBootstrapSelfUpdate(args)&&process.exit();requiresIsolatedProcess()?process.exitCode=await spawnFullCli():await import("./chunks/cli-main-Z2Y55XHU.js");
|
package/dist/bundle/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{AgentSession,AgentSessionRuntime,ArminComponent,AssistantMessageComponent,BashExecutionComponent,BorderedLoader,BranchSummaryMessageComponent,CompactionSummaryMessageComponent,CredentialSynchronizationError,CustomEditor,CustomMessageComponent,DEFAULT_COMPACTION_SETTINGS,DefaultResourceLoader,ExtensionEditorComponent,ExtensionRunner,FooterComponent,InteractiveMode,LoginDialogComponent,ModelRegistry,ModelRuntime,ModelSelectorComponent,OAuthSelectorComponent,SettingsSelectorComponent,ShowImagesSelectorComponent,SkillInvocationMessageComponent,ThemeSelectorComponent,ThinkingSelectorComponent,ToolExecutionComponent,TreeSelectorComponent,UserMessageComponent,UserMessageSelectorComponent,buildNoticeBox,calculateContextTokens,classifyRead,collectEntriesForBranchSummary,compact,convertToPng,copyToClipboard,createAgentSession,createAgentSessionFromServices,createAgentSessionRuntime,createAgentSessionServices,createBashTool,createBashToolDefinition,createCodingTools,createEditTool,createEditToolDefinition,createEventBus,createExtensionRuntime,createFindTool,createFindToolDefinition,createGrepTool,createGrepToolDefinition,createLocalBashOperations,createLsTool,createLsToolDefinition,createPowerShellTool,createReadOnlyTools,createReadTool,createReadToolDefinition,createSyntheticSourceInfo,createWriteTool,createWriteToolDefinition,defineTool,detectSupportedImageMimeTypeFromFile,discoverAndLoadExtensions,estimateTokens,findCutPoint,findTurnStartIndex,formatDimensionNote,formatSkillsForPrompt,generateBranchSummary,generateDiffString,generateSummary,generateSummaryWithUsage,generateUnifiedPatch,getLastAssistantUsage,isBashToolResult,isEditToolResult,isFindToolResult,isGrepToolResult,isLsToolResult,isPowerShellToolResult,isReadToolResult,isToolCallEventType,isWriteToolResult,kernelToolsStorage,loadProjectContextFiles,loadSkills,loadSkillsFromDir,main,noticeEntryRenderer,noticeMessageRenderer,parseFrontmatter,parseSkillBlock,prepareBranchEntries,readStoredCredential,registerReadClassifier,renderDiff,resizeImage,resolveCliModel,resolveModelScopeWithDiagnostics,runPrintMode,serializeConversation,shouldCompact,stripFrontmatter,truncateToVisualLines,withFileMutationQueue,wrapRegisteredTool,wrapRegisteredTools}from"./chunks/chunk-
|
|
2
|
+
import{AgentSession,AgentSessionRuntime,ArminComponent,AssistantMessageComponent,BashExecutionComponent,BorderedLoader,BranchSummaryMessageComponent,CompactionSummaryMessageComponent,CredentialSynchronizationError,CustomEditor,CustomMessageComponent,DEFAULT_COMPACTION_SETTINGS,DefaultResourceLoader,ExtensionEditorComponent,ExtensionRunner,FooterComponent,InteractiveMode,LoginDialogComponent,ModelRegistry,ModelRuntime,ModelSelectorComponent,OAuthSelectorComponent,SettingsSelectorComponent,ShowImagesSelectorComponent,SkillInvocationMessageComponent,ThemeSelectorComponent,ThinkingSelectorComponent,ToolExecutionComponent,TreeSelectorComponent,UserMessageComponent,UserMessageSelectorComponent,buildNoticeBox,calculateContextTokens,classifyRead,collectEntriesForBranchSummary,compact,connectWebViewService,convertToPng,copyToClipboard,createAgentSession,createAgentSessionFromServices,createAgentSessionRuntime,createAgentSessionServices,createBashTool,createBashToolDefinition,createCodingTools,createEditTool,createEditToolDefinition,createEventBus,createExtensionRuntime,createFindTool,createFindToolDefinition,createGrepTool,createGrepToolDefinition,createLocalBashOperations,createLsTool,createLsToolDefinition,createPowerShellTool,createReadOnlyTools,createReadTool,createReadToolDefinition,createSyntheticSourceInfo,createWriteTool,createWriteToolDefinition,defineTool,detectSupportedImageMimeTypeFromFile,discoverAndLoadExtensions,estimateTokens,findCutPoint,findTurnStartIndex,formatDimensionNote,formatSkillsForPrompt,generateBranchSummary,generateDiffString,generateSummary,generateSummaryWithUsage,generateUnifiedPatch,getLastAssistantUsage,isBashToolResult,isEditToolResult,isFindToolResult,isGrepToolResult,isLsToolResult,isPowerShellToolResult,isReadToolResult,isToolCallEventType,isWriteToolResult,kernelToolsStorage,loadProjectContextFiles,loadSkills,loadSkillsFromDir,main,noticeEntryRenderer,noticeMessageRenderer,parseFrontmatter,parseSkillBlock,prepareBranchEntries,readStoredCredential,registerReadClassifier,renderDiff,resizeImage,resolveCliModel,resolveModelScopeWithDiagnostics,runPrintMode,serializeConversation,shouldCompact,stripFrontmatter,truncateToVisualLines,withFileMutationQueue,wrapRegisteredTool,wrapRegisteredTools}from"./chunks/chunk-6XBT6OGS.js";import{DefaultPackageManager,DynamicBorder,ExtensionInputComponent,ExtensionSelectorComponent,SessionSelectorComponent,keyHint,keyText,rawKeyHint}from"./chunks/chunk-SICBVLRH.js";import"./chunks/chunk-A4UMPXQF.js";import"./chunks/chunk-TFA3YAJK.js";import{CURRENT_SESSION_VERSION,DEFAULT_MAX_BYTES,DEFAULT_MAX_LINES,RpcClient,RpcClientOpenInFlightError,RpcTransportGoneError,SessionManager,buildContextEntries,buildSessionContext,convertToLlm,formatSize,getLatestCompactionEntry,isTransportGoneError,migrateSessionEntries,parseSessionEntries,sessionEntryToContextMessages,truncateHead,truncateLine,truncateTail}from"./chunks/chunk-DRN4FQCE.js";import"./chunks/chunk-YS5YEX4S.js";import{runRpcMode}from"./chunks/chunk-OP3QMR6B.js";import{Theme,getLanguageFromPath,getMarkdownTheme,getSelectListTheme,getSettingsListTheme,highlightCode,initTheme}from"./chunks/chunk-6VD3EBQE.js";import{sanitizeTerminalLabel}from"./chunks/chunk-6BWCPOCV.js";import"./chunks/chunk-CNQ4JB45.js";import{SettingsManager}from"./chunks/chunk-SN4ZHPMN.js";import"./chunks/chunk-YU2P3CAR.js";import"./chunks/chunk-BMX2U22F.js";import{DEFAULT_HOST_LAUNCH_SPEC,HOST_EXIT_ERROR,HOST_EXIT_FALLBACK,HOST_EXIT_OK,HOST_EXIT_REFUSED,HOST_EXIT_USAGE,HostLaunchSpecError,loadHostLaunchSpec,parseHostLaunchSpec,readHostStatus,runHostCommand,runHostRequest}from"./chunks/chunk-7YUGCTEV.js";import{GENERATION_HANDOFF_CAPABILITY,HOST_PROTOCOL_VERSION,HostEnsureRefusedError,PINNED_HOST_CLIENT_CAPABILITIES,REQUIRED_HOST_CAPABILITIES,decideHostAction,ensureHost,handoffHost,parseHostProtocolInfo,probeHost,stopHost}from"./chunks/chunk-FHJYIU3U.js";import"./chunks/chunk-HH3KJDWA.js";import{createHostDaemonPaths,daemonDirectoryName,shardKey,shardSocketPath,shardSocketPathForKey}from"./chunks/chunk-GJUCSTAG.js";import{compareEngineOrdinal,engineBuildIdentity,engineBuildIdentityFrom}from"./chunks/chunk-QOVUQZO4.js";import"./chunks/chunk-DA4W77J7.js";import"./chunks/chunk-H4QOEVS5.js";import"./chunks/chunk-HB35RBP4.js";import"./chunks/chunk-VFSXUT7K.js";import"./chunks/chunk-62XHQXCZ.js";import{getPowerShellConfig,getShellConfig}from"./chunks/chunk-TRSQVD6V.js";import"./chunks/chunk-36ACNYQR.js";import"./chunks/chunk-7X72IACK.js";import"./chunks/chunk-KUPQO7MC.js";import"./chunks/chunk-EHSZ7UCW.js";import{ProjectTrustStore,hasTrustRequiringProjectResources}from"./chunks/chunk-Y5A6TKBN.js";import{parseArgs}from"./chunks/chunk-LUPPSEQW.js";import"./chunks/chunk-26UBFF6N.js";import"./chunks/chunk-3GMFUEU7.js";import"./chunks/chunk-GZVCRV7I.js";import"./chunks/chunk-U3VOXCGP.js";import"./chunks/chunk-RFB5CBW7.js";import"./chunks/chunk-2VZJTDUX.js";import"./chunks/chunk-ELFCNCBY.js";import"./chunks/chunk-BBK6SSYT.js";import"./chunks/chunk-4LVZQRDG.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import{CONFIG_DIR_NAME,VERSION,getAgentDir,getDocsPath,getExamplesPath,getPackageDir,getReadmePath}from"./chunks/chunk-PBWQK23Z.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-RBCQOIAD.js";import"./chunks/chunk-WASCMT6Y.js";import"./chunks/chunk-X4Q5YSTE.js";import"./chunks/chunk-IXAUDPOK.js";import"./chunks/chunk-L6QV7NZI.js";import"./chunks/chunk-45SZOLCU.js";import"./chunks/chunk-WVGCQOLJ.js";import"./chunks/chunk-BPX6QWPD.js";import"./chunks/chunk-WL5EQYNL.js";import"./chunks/chunk-WDTJ6JKE.js";import"./chunks/chunk-5ROMKDLK.js";import"./chunks/chunk-3W47LTXQ.js";import"./chunks/chunk-DMI55M2N.js";import"./chunks/chunk-BQVVQ2LQ.js";import"./chunks/chunk-IKOFCODM.js";export{AgentSession,AgentSessionRuntime,ArminComponent,AssistantMessageComponent,BashExecutionComponent,BorderedLoader,BranchSummaryMessageComponent,CONFIG_DIR_NAME,CURRENT_SESSION_VERSION,CompactionSummaryMessageComponent,CredentialSynchronizationError,CustomEditor,CustomMessageComponent,DEFAULT_COMPACTION_SETTINGS,DEFAULT_HOST_LAUNCH_SPEC,DEFAULT_MAX_BYTES,DEFAULT_MAX_LINES,DefaultPackageManager,DefaultResourceLoader,DynamicBorder,ExtensionEditorComponent,ExtensionInputComponent,ExtensionRunner,ExtensionSelectorComponent,FooterComponent,GENERATION_HANDOFF_CAPABILITY,HOST_EXIT_ERROR,HOST_EXIT_FALLBACK,HOST_EXIT_OK,HOST_EXIT_REFUSED,HOST_EXIT_USAGE,HOST_PROTOCOL_VERSION,HostEnsureRefusedError,HostLaunchSpecError,InteractiveMode,LoginDialogComponent,ModelRegistry,ModelRuntime,ModelSelectorComponent,OAuthSelectorComponent,PINNED_HOST_CLIENT_CAPABILITIES,ProjectTrustStore,REQUIRED_HOST_CAPABILITIES,RpcClient,RpcClientOpenInFlightError,RpcTransportGoneError,SessionManager,SessionSelectorComponent,SettingsManager,SettingsSelectorComponent,ShowImagesSelectorComponent,SkillInvocationMessageComponent,Theme,ThemeSelectorComponent,ThinkingSelectorComponent,ToolExecutionComponent,TreeSelectorComponent,UserMessageComponent,UserMessageSelectorComponent,VERSION,buildContextEntries,buildNoticeBox,buildSessionContext,calculateContextTokens,classifyRead,collectEntriesForBranchSummary,compact,compareEngineOrdinal,connectWebViewService,convertToLlm,convertToPng,copyToClipboard,createAgentSession,createAgentSessionFromServices,createAgentSessionRuntime,createAgentSessionServices,createBashTool,createBashToolDefinition,createCodingTools,createEditTool,createEditToolDefinition,createEventBus,createExtensionRuntime,createFindTool,createFindToolDefinition,createGrepTool,createGrepToolDefinition,createHostDaemonPaths,createLocalBashOperations,createLsTool,createLsToolDefinition,createPowerShellTool,createReadOnlyTools,createReadTool,createReadToolDefinition,createSyntheticSourceInfo,createWriteTool,createWriteToolDefinition,daemonDirectoryName,decideHostAction,defineTool,detectSupportedImageMimeTypeFromFile,discoverAndLoadExtensions,engineBuildIdentity,engineBuildIdentityFrom,ensureHost,estimateTokens,findCutPoint,findTurnStartIndex,formatDimensionNote,formatSize,formatSkillsForPrompt,generateBranchSummary,generateDiffString,generateSummary,generateSummaryWithUsage,generateUnifiedPatch,getAgentDir,getDocsPath,getExamplesPath,getLanguageFromPath,getLastAssistantUsage,getLatestCompactionEntry,getMarkdownTheme,getPackageDir,getPowerShellConfig,getReadmePath,getSelectListTheme,getSettingsListTheme,getShellConfig,handoffHost,hasTrustRequiringProjectResources,highlightCode,initTheme,isBashToolResult,isEditToolResult,isFindToolResult,isGrepToolResult,isLsToolResult,isPowerShellToolResult,isReadToolResult,isToolCallEventType,isTransportGoneError,isWriteToolResult,kernelToolsStorage,keyHint,keyText,loadHostLaunchSpec,loadProjectContextFiles,loadSkills,loadSkillsFromDir,main,migrateSessionEntries,noticeEntryRenderer,noticeMessageRenderer,parseArgs,parseFrontmatter,parseHostLaunchSpec,parseHostProtocolInfo,parseSessionEntries,parseSkillBlock,prepareBranchEntries,probeHost,rawKeyHint,readHostStatus,readStoredCredential,registerReadClassifier,renderDiff,resizeImage,resolveCliModel,resolveModelScopeWithDiagnostics,runHostCommand,runHostRequest,runPrintMode,runRpcMode,sanitizeTerminalLabel,serializeConversation,sessionEntryToContextMessages,shardKey,shardSocketPath,shardSocketPathForKey,shouldCompact,stopHost,stripFrontmatter,truncateHead,truncateLine,truncateTail,truncateToVisualLines,withFileMutationQueue,wrapRegisteredTool,wrapRegisteredTools};
|
package/dist/bundle/rpc-entry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
3
|
-
import{main}from"./chunks/chunk-
|
|
3
|
+
import{main}from"./chunks/chunk-6XBT6OGS.js";import"./chunks/chunk-SICBVLRH.js";import"./chunks/chunk-A4UMPXQF.js";import"./chunks/chunk-TFA3YAJK.js";import"./chunks/chunk-DRN4FQCE.js";import"./chunks/chunk-YS5YEX4S.js";import"./chunks/chunk-OP3QMR6B.js";import"./chunks/chunk-6VD3EBQE.js";import"./chunks/chunk-6BWCPOCV.js";import"./chunks/chunk-CNQ4JB45.js";import{configureHttpDispatcher}from"./chunks/chunk-SN4ZHPMN.js";import"./chunks/chunk-YU2P3CAR.js";import"./chunks/chunk-BMX2U22F.js";import"./chunks/chunk-7YUGCTEV.js";import"./chunks/chunk-FHJYIU3U.js";import"./chunks/chunk-HH3KJDWA.js";import"./chunks/chunk-GJUCSTAG.js";import"./chunks/chunk-QOVUQZO4.js";import"./chunks/chunk-DA4W77J7.js";import"./chunks/chunk-H4QOEVS5.js";import"./chunks/chunk-HB35RBP4.js";import"./chunks/chunk-VFSXUT7K.js";import"./chunks/chunk-62XHQXCZ.js";import"./chunks/chunk-TRSQVD6V.js";import"./chunks/chunk-36ACNYQR.js";import"./chunks/chunk-7X72IACK.js";import"./chunks/chunk-KUPQO7MC.js";import"./chunks/chunk-EHSZ7UCW.js";import"./chunks/chunk-Y5A6TKBN.js";import"./chunks/chunk-LUPPSEQW.js";import"./chunks/chunk-26UBFF6N.js";import"./chunks/chunk-3GMFUEU7.js";import"./chunks/chunk-GZVCRV7I.js";import"./chunks/chunk-U3VOXCGP.js";import"./chunks/chunk-RFB5CBW7.js";import"./chunks/chunk-2VZJTDUX.js";import"./chunks/chunk-ELFCNCBY.js";import"./chunks/chunk-BBK6SSYT.js";import"./chunks/chunk-4LVZQRDG.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import{APP_NAME,scrubBrandFromEnvironment}from"./chunks/chunk-PBWQK23Z.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-RBCQOIAD.js";import"./chunks/chunk-WASCMT6Y.js";import"./chunks/chunk-X4Q5YSTE.js";import"./chunks/chunk-IXAUDPOK.js";import"./chunks/chunk-L6QV7NZI.js";import"./chunks/chunk-45SZOLCU.js";import"./chunks/chunk-WVGCQOLJ.js";import"./chunks/chunk-BPX6QWPD.js";import"./chunks/chunk-WL5EQYNL.js";import"./chunks/chunk-WDTJ6JKE.js";import"./chunks/chunk-5ROMKDLK.js";import"./chunks/chunk-3W47LTXQ.js";import"./chunks/chunk-DMI55M2N.js";import"./chunks/chunk-BQVVQ2LQ.js";import"./chunks/chunk-IKOFCODM.js";process.title=`${APP_NAME}-rpc`;scrubBrandFromEnvironment();process.env.PI_CODING_AGENT="true";process.env.AI_AGENT=APP_NAME;process.emitWarning=(()=>{});configureHttpDispatcher();main(["--mode","rpc",...process.argv.slice(2)]);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NativeWebViewClass } from "./native-webview.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves once no killed-but-unreaped Chrome child is left (bounded). A Chrome that died (crash,
|
|
4
|
+
* kill) stays a zombie until Bun reaps it, and Chrome's profile lock still names that pid, so a
|
|
5
|
+
* Chrome Bun launches in that window exits at once ("Chrome process closed the pipe").
|
|
6
|
+
*/
|
|
7
|
+
export declare function settleDeadBunChrome(): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Ends the Chrome Bun spawned once no proxied view needs it; the next Chrome-backed view respawns it.
|
|
10
|
+
* `WebView.closeAll()` would do this, but on macOS it also kills the shared WebKit host that native
|
|
11
|
+
* worker views (the macOS default backend) run on, so there only Bun's own Chrome child is killed.
|
|
12
|
+
* Resolves once those processes are gone (bounded), so a released kernel leaves no Chrome behind.
|
|
13
|
+
*/
|
|
14
|
+
export declare function retireBunChrome(webViewClass: NativeWebViewClass): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=bun-chrome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun-chrome.d.ts","sourceRoot":"","sources":["../../../src/core/webview/bun-chrome.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA6D9D;;;;GAIG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzD;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBrF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
// Bun launches its Chrome with exactly this default flag run (see `Bun.WebView` backend docs);
|
|
3
|
+
// together with the parent pid it tells Bun's browser apart from any other Chrome we spawned.
|
|
4
|
+
const BUN_CHROME_FLAGS = "--remote-debugging-pipe --headless --no-first-run --no-default-browser-check";
|
|
5
|
+
const EXIT_DEADLINE_MS = 5_000;
|
|
6
|
+
const EXIT_POLL_MS = 25;
|
|
7
|
+
function run(file, args) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
execFile(file, [...args], { maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (_error, stdout) => resolve(String(stdout ?? "")));
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function positivePids(texts) {
|
|
13
|
+
return texts.map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
14
|
+
}
|
|
15
|
+
async function bunChromePids() {
|
|
16
|
+
const pids = [];
|
|
17
|
+
for (const line of (await run("ps", ["-axo", "pid=,ppid=,command="])).split("\n")) {
|
|
18
|
+
const [pidText = "", ppidText, ...command] = line.trim().split(/\s+/u);
|
|
19
|
+
if (Number(ppidText) === process.pid && command.join(" ").includes(BUN_CHROME_FLAGS))
|
|
20
|
+
pids.push(pidText);
|
|
21
|
+
}
|
|
22
|
+
return positivePids(pids);
|
|
23
|
+
}
|
|
24
|
+
function alive(pid) {
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, 0);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return error instanceof Error && Reflect.get(error, "code") === "EPERM";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function waitForExit(pids) {
|
|
34
|
+
const deadline = Date.now() + EXIT_DEADLINE_MS;
|
|
35
|
+
let remaining = pids.filter(alive);
|
|
36
|
+
while (remaining.length > 0 && Date.now() < deadline) {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, EXIT_POLL_MS));
|
|
38
|
+
remaining = remaining.filter(alive);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// A zombie keeps no command line, so a dead browser child is recognized by its name alone.
|
|
42
|
+
const BROWSER_NAME = /chrom|msedge|brave/iu;
|
|
43
|
+
async function deadBrowserChildren() {
|
|
44
|
+
if (process.platform === "win32")
|
|
45
|
+
return [];
|
|
46
|
+
const pids = [];
|
|
47
|
+
for (const line of (await run("ps", ["-axo", "pid=,ppid=,stat=,comm="])).split("\n")) {
|
|
48
|
+
const [pidText = "", ppidText, stat = "", ...command] = line.trim().split(/\s+/u);
|
|
49
|
+
if (Number(ppidText) === process.pid && stat.startsWith("Z") && BROWSER_NAME.test(command.join(" ")))
|
|
50
|
+
pids.push(pidText);
|
|
51
|
+
}
|
|
52
|
+
return positivePids(pids);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves once no killed-but-unreaped Chrome child is left (bounded). A Chrome that died (crash,
|
|
56
|
+
* kill) stays a zombie until Bun reaps it, and Chrome's profile lock still names that pid, so a
|
|
57
|
+
* Chrome Bun launches in that window exits at once ("Chrome process closed the pipe").
|
|
58
|
+
*/
|
|
59
|
+
export async function settleDeadBunChrome() {
|
|
60
|
+
await waitForExit(await deadBrowserChildren());
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Ends the Chrome Bun spawned once no proxied view needs it; the next Chrome-backed view respawns it.
|
|
64
|
+
* `WebView.closeAll()` would do this, but on macOS it also kills the shared WebKit host that native
|
|
65
|
+
* worker views (the macOS default backend) run on, so there only Bun's own Chrome child is killed.
|
|
66
|
+
* Resolves once those processes are gone (bounded), so a released kernel leaves no Chrome behind.
|
|
67
|
+
*/
|
|
68
|
+
export async function retireBunChrome(webViewClass) {
|
|
69
|
+
// Windows has no WebKit backend, so `closeAll()` can only end Chrome, and it terminates the process
|
|
70
|
+
// before returning; listing processes there costs seconds (PowerShell), so it is skipped.
|
|
71
|
+
if (process.platform === "win32") {
|
|
72
|
+
webViewClass.closeAll();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const pids = await bunChromePids();
|
|
76
|
+
if (process.platform === "darwin") {
|
|
77
|
+
for (const pid of pids) {
|
|
78
|
+
try {
|
|
79
|
+
process.kill(pid, "SIGKILL");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Already gone between the listing and the kill.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
webViewClass.closeAll();
|
|
88
|
+
}
|
|
89
|
+
await waitForExit(pids);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=bun-chrome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun-chrome.js","sourceRoot":"","sources":["../../../src/core/webview/bun-chrome.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport type { NativeWebViewClass } from \"./native-webview.ts\";\n\n// Bun launches its Chrome with exactly this default flag run (see `Bun.WebView` backend docs);\n// together with the parent pid it tells Bun's browser apart from any other Chrome we spawned.\nconst BUN_CHROME_FLAGS = \"--remote-debugging-pipe --headless --no-first-run --no-default-browser-check\";\nconst EXIT_DEADLINE_MS = 5_000;\nconst EXIT_POLL_MS = 25;\n\nfunction run(file: string, args: readonly string[]): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\texecFile(file, [...args], { maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (_error, stdout) =>\n\t\t\tresolve(String(stdout ?? \"\")),\n\t\t);\n\t});\n}\n\nfunction positivePids(texts: readonly string[]): number[] {\n\treturn texts.map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);\n}\n\nasync function bunChromePids(): Promise<number[]> {\n\tconst pids: string[] = [];\n\tfor (const line of (await run(\"ps\", [\"-axo\", \"pid=,ppid=,command=\"])).split(\"\\n\")) {\n\t\tconst [pidText = \"\", ppidText, ...command] = line.trim().split(/\\s+/u);\n\t\tif (Number(ppidText) === process.pid && command.join(\" \").includes(BUN_CHROME_FLAGS)) pids.push(pidText);\n\t}\n\treturn positivePids(pids);\n}\n\nfunction alive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn error instanceof Error && Reflect.get(error, \"code\") === \"EPERM\";\n\t}\n}\n\nasync function waitForExit(pids: readonly number[]): Promise<void> {\n\tconst deadline = Date.now() + EXIT_DEADLINE_MS;\n\tlet remaining = pids.filter(alive);\n\twhile (remaining.length > 0 && Date.now() < deadline) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, EXIT_POLL_MS));\n\t\tremaining = remaining.filter(alive);\n\t}\n}\n\n// A zombie keeps no command line, so a dead browser child is recognized by its name alone.\nconst BROWSER_NAME = /chrom|msedge|brave/iu;\n\nasync function deadBrowserChildren(): Promise<number[]> {\n\tif (process.platform === \"win32\") return [];\n\tconst pids: string[] = [];\n\tfor (const line of (await run(\"ps\", [\"-axo\", \"pid=,ppid=,stat=,comm=\"])).split(\"\\n\")) {\n\t\tconst [pidText = \"\", ppidText, stat = \"\", ...command] = line.trim().split(/\\s+/u);\n\t\tif (Number(ppidText) === process.pid && stat.startsWith(\"Z\") && BROWSER_NAME.test(command.join(\" \")))\n\t\t\tpids.push(pidText);\n\t}\n\treturn positivePids(pids);\n}\n\n/**\n * Resolves once no killed-but-unreaped Chrome child is left (bounded). A Chrome that died (crash,\n * kill) stays a zombie until Bun reaps it, and Chrome's profile lock still names that pid, so a\n * Chrome Bun launches in that window exits at once (\"Chrome process closed the pipe\").\n */\nexport async function settleDeadBunChrome(): Promise<void> {\n\tawait waitForExit(await deadBrowserChildren());\n}\n\n/**\n * Ends the Chrome Bun spawned once no proxied view needs it; the next Chrome-backed view respawns it.\n * `WebView.closeAll()` would do this, but on macOS it also kills the shared WebKit host that native\n * worker views (the macOS default backend) run on, so there only Bun's own Chrome child is killed.\n * Resolves once those processes are gone (bounded), so a released kernel leaves no Chrome behind.\n */\nexport async function retireBunChrome(webViewClass: NativeWebViewClass): Promise<void> {\n\t// Windows has no WebKit backend, so `closeAll()` can only end Chrome, and it terminates the process\n\t// before returning; listing processes there costs seconds (PowerShell), so it is skipped.\n\tif (process.platform === \"win32\") {\n\t\twebViewClass.closeAll();\n\t\treturn;\n\t}\n\tconst pids = await bunChromePids();\n\tif (process.platform === \"darwin\") {\n\t\tfor (const pid of pids) {\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Already gone between the listing and the kill.\n\t\t\t}\n\t\t}\n\t} else {\n\t\twebViewClass.closeAll();\n\t}\n\tawait waitForExit(pids);\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The slice of Bun's `Bun.WebView` the main-thread service drives (Bun ships no Node types for it). */
|
|
2
|
+
export interface NativeWebView extends EventTarget {
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly loading: boolean;
|
|
6
|
+
onNavigated: ((url: string, title: string) => void) | null;
|
|
7
|
+
onNavigationFailed: ((error: Error) => void) | null;
|
|
8
|
+
close(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface NativeWebViewClass {
|
|
11
|
+
new (options: Readonly<Record<string, unknown>>): NativeWebView;
|
|
12
|
+
closeAll(): void;
|
|
13
|
+
}
|
|
14
|
+
/** `Bun.WebView` when this thread may construct Chrome-backed views: Bun runtime, process main thread. */
|
|
15
|
+
export declare function mainThreadWebViewClass(): NativeWebViewClass | undefined;
|
|
16
|
+
//# sourceMappingURL=native-webview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-webview.d.ts","sourceRoot":"","sources":["../../../src/core/webview/native-webview.ts"],"names":[],"mappings":"AAEA,wGAAwG;AACxG,MAAM,WAAW,aAAc,SAAQ,WAAW;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3D,kBAAkB,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC;IAChE,QAAQ,IAAI,IAAI,CAAC;CACjB;AAMD,0GAA0G;AAC1G,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,SAAS,CAMvE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isMainThread } from "node:worker_threads";
|
|
2
|
+
function isNativeWebViewClass(value) {
|
|
3
|
+
return typeof value === "function" && typeof Reflect.get(value, "closeAll") === "function";
|
|
4
|
+
}
|
|
5
|
+
/** `Bun.WebView` when this thread may construct Chrome-backed views: Bun runtime, process main thread. */
|
|
6
|
+
export function mainThreadWebViewClass() {
|
|
7
|
+
if (!isMainThread)
|
|
8
|
+
return undefined;
|
|
9
|
+
const bun = Reflect.get(globalThis, "Bun");
|
|
10
|
+
if (typeof bun !== "object" || bun === null)
|
|
11
|
+
return undefined;
|
|
12
|
+
const webView = Reflect.get(bun, "WebView");
|
|
13
|
+
return isNativeWebViewClass(webView) ? webView : undefined;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=native-webview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-webview.js","sourceRoot":"","sources":["../../../src/core/webview/native-webview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAiBnD,SAAS,oBAAoB,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,UAAU,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,UAAU,CAAC;AAC5F,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,sBAAsB;IACrC,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,MAAM,GAAG,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,OAAO,GAAY,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC","sourcesContent":["import { isMainThread } from \"node:worker_threads\";\n\n/** The slice of Bun's `Bun.WebView` the main-thread service drives (Bun ships no Node types for it). */\nexport interface NativeWebView extends EventTarget {\n\treadonly url: string;\n\treadonly title: string;\n\treadonly loading: boolean;\n\tonNavigated: ((url: string, title: string) => void) | null;\n\tonNavigationFailed: ((error: Error) => void) | null;\n\tclose(): void;\n}\n\nexport interface NativeWebViewClass {\n\tnew (options: Readonly<Record<string, unknown>>): NativeWebView;\n\tcloseAll(): void;\n}\n\nfunction isNativeWebViewClass(value: unknown): value is NativeWebViewClass {\n\treturn typeof value === \"function\" && typeof Reflect.get(value, \"closeAll\") === \"function\";\n}\n\n/** `Bun.WebView` when this thread may construct Chrome-backed views: Bun runtime, process main thread. */\nexport function mainThreadWebViewClass(): NativeWebViewClass | undefined {\n\tif (!isMainThread) return undefined;\n\tconst bun: unknown = Reflect.get(globalThis, \"Bun\");\n\tif (typeof bun !== \"object\" || bun === null) return undefined;\n\tconst webView: unknown = Reflect.get(bun, \"WebView\");\n\treturn isNativeWebViewClass(webView) ? webView : undefined;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type MessagePort } from "node:worker_threads";
|
|
2
|
+
export interface WebViewServiceConnection {
|
|
3
|
+
readonly clientId: string;
|
|
4
|
+
/** Transfer this into the kernel worker; it is the only handle to the client's views. */
|
|
5
|
+
readonly port: MessagePort;
|
|
6
|
+
/** Closes every view created through `port`; afterwards the port is dead. */
|
|
7
|
+
release(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface WebViewBroker {
|
|
10
|
+
/** Hand this to exactly one worker thread (workerData + transferList). */
|
|
11
|
+
readonly port: MessagePort;
|
|
12
|
+
dispose(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Main-thread side of one worker thread's route to the WebView service (RPC session workers run the
|
|
16
|
+
* eval kernel host off the main thread). Clients connected through a broker belong to it: it can
|
|
17
|
+
* release only those, and they are all released when the worker exits or the broker is disposed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createWebViewBroker(): WebViewBroker | undefined;
|
|
20
|
+
/** Worker-thread side: install the broker port the main thread passed in `workerData`. */
|
|
21
|
+
export declare function registerWebViewBroker(port: unknown): void;
|
|
22
|
+
/**
|
|
23
|
+
* Connects an eval kernel to the main-thread WebView service: directly on the main thread, through
|
|
24
|
+
* the thread's broker in a worker. Rejects when neither route exists (e.g. not running on Bun).
|
|
25
|
+
*/
|
|
26
|
+
export declare function connectWebViewService(): Promise<WebViewServiceConnection>;
|
|
27
|
+
//# sourceMappingURL=webview-broker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-broker.d.ts","sourceRoot":"","sources":["../../../src/core/webview/webview-broker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGrF,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,GAAG,SAAS,CA+B/D;AA6CD,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAGzD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAa/E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { isMainThread, MessageChannel } from "node:worker_threads";
|
|
2
|
+
import { mainThreadWebViewService } from "./webview-service.js";
|
|
3
|
+
const BROKER_KEY = Symbol.for("senpi.webview.broker");
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null;
|
|
6
|
+
}
|
|
7
|
+
function parseBrokerRequest(value) {
|
|
8
|
+
if (!isRecord(value) || typeof value.id !== "number")
|
|
9
|
+
return undefined;
|
|
10
|
+
if (value.kind === "connect")
|
|
11
|
+
return { kind: "connect", id: value.id };
|
|
12
|
+
if (value.kind === "release" && typeof value.clientId === "string") {
|
|
13
|
+
return { kind: "release", id: value.id, clientId: value.clientId };
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Main-thread side of one worker thread's route to the WebView service (RPC session workers run the
|
|
19
|
+
* eval kernel host off the main thread). Clients connected through a broker belong to it: it can
|
|
20
|
+
* release only those, and they are all released when the worker exits or the broker is disposed.
|
|
21
|
+
*/
|
|
22
|
+
export function createWebViewBroker() {
|
|
23
|
+
const service = mainThreadWebViewService();
|
|
24
|
+
if (!service)
|
|
25
|
+
return undefined;
|
|
26
|
+
const owner = {};
|
|
27
|
+
const channel = new MessageChannel();
|
|
28
|
+
const local = channel.port1;
|
|
29
|
+
const reply = (message, transfer = []) => local.postMessage(message, transfer);
|
|
30
|
+
local.on("message", async (value) => {
|
|
31
|
+
const request = parseBrokerRequest(value);
|
|
32
|
+
if (!request)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
if (request.kind === "connect") {
|
|
36
|
+
const grant = service.connect(owner);
|
|
37
|
+
reply({ kind: "connected", id: request.id, clientId: grant.clientId, port: grant.port }, [grant.port]);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
await service.release(request.clientId, owner);
|
|
41
|
+
reply({ kind: "released", id: request.id });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
reply({ kind: "failed", id: request.id, message: error instanceof Error ? error.message : String(error) });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
local.on("close", () => void service.releaseOwner(owner));
|
|
49
|
+
local.unref();
|
|
50
|
+
return {
|
|
51
|
+
port: channel.port2,
|
|
52
|
+
dispose: async () => {
|
|
53
|
+
local.close();
|
|
54
|
+
await service.releaseOwner(owner);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
class WorkerBrokerClient {
|
|
59
|
+
#port;
|
|
60
|
+
#pending = new Map();
|
|
61
|
+
#nextId = 1;
|
|
62
|
+
constructor(port) {
|
|
63
|
+
this.#port = port;
|
|
64
|
+
port.on("message", (reply) => {
|
|
65
|
+
const settle = this.#pending.get(reply.id);
|
|
66
|
+
this.#pending.delete(reply.id);
|
|
67
|
+
settle?.(reply);
|
|
68
|
+
});
|
|
69
|
+
port.unref();
|
|
70
|
+
}
|
|
71
|
+
async connect() {
|
|
72
|
+
const reply = await this.#request({ kind: "connect", id: this.#nextId++ });
|
|
73
|
+
if (reply.kind !== "connected")
|
|
74
|
+
throw new Error(reply.kind === "failed" ? reply.message : "unexpected broker reply");
|
|
75
|
+
const clientId = reply.clientId;
|
|
76
|
+
return {
|
|
77
|
+
clientId,
|
|
78
|
+
port: reply.port,
|
|
79
|
+
release: async () => void (await this.#request({ kind: "release", id: this.#nextId++, clientId })),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
#request(request) {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
this.#pending.set(request.id, resolve);
|
|
85
|
+
this.#port.postMessage(request);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function isBrokerClient(value) {
|
|
90
|
+
return value instanceof WorkerBrokerClient;
|
|
91
|
+
}
|
|
92
|
+
function isMessagePort(value) {
|
|
93
|
+
return isRecord(value) && typeof value.postMessage === "function" && typeof value.on === "function";
|
|
94
|
+
}
|
|
95
|
+
/** Worker-thread side: install the broker port the main thread passed in `workerData`. */
|
|
96
|
+
export function registerWebViewBroker(port) {
|
|
97
|
+
if (isMainThread || !isMessagePort(port))
|
|
98
|
+
return;
|
|
99
|
+
Reflect.set(globalThis, BROKER_KEY, new WorkerBrokerClient(port));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Connects an eval kernel to the main-thread WebView service: directly on the main thread, through
|
|
103
|
+
* the thread's broker in a worker. Rejects when neither route exists (e.g. not running on Bun).
|
|
104
|
+
*/
|
|
105
|
+
export async function connectWebViewService() {
|
|
106
|
+
if (isMainThread) {
|
|
107
|
+
const service = mainThreadWebViewService();
|
|
108
|
+
if (!service)
|
|
109
|
+
throw new Error("Bun.WebView is not available in this runtime");
|
|
110
|
+
const owner = {};
|
|
111
|
+
const grant = service.connect(owner);
|
|
112
|
+
return { ...grant, release: () => service.release(grant.clientId, owner) };
|
|
113
|
+
}
|
|
114
|
+
const broker = Reflect.get(globalThis, BROKER_KEY);
|
|
115
|
+
if (!isBrokerClient(broker)) {
|
|
116
|
+
throw new Error("no route from this worker thread to the main-thread WebView service");
|
|
117
|
+
}
|
|
118
|
+
return await broker.connect();
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=webview-broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-broker.js","sourceRoot":"","sources":["../../../src/core/webview/webview-broker.ts"],"names":[],"mappings":"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","sourcesContent":["import { isMainThread, MessageChannel, type MessagePort } from \"node:worker_threads\";\nimport { mainThreadWebViewService } from \"./webview-service.ts\";\n\nexport interface WebViewServiceConnection {\n\treadonly clientId: string;\n\t/** Transfer this into the kernel worker; it is the only handle to the client's views. */\n\treadonly port: MessagePort;\n\t/** Closes every view created through `port`; afterwards the port is dead. */\n\trelease(): Promise<void>;\n}\n\nexport interface WebViewBroker {\n\t/** Hand this to exactly one worker thread (workerData + transferList). */\n\treadonly port: MessagePort;\n\tdispose(): Promise<void>;\n}\n\ntype BrokerRequest =\n\t| { readonly kind: \"connect\"; readonly id: number }\n\t| { readonly kind: \"release\"; readonly id: number; readonly clientId: string };\ntype BrokerReply =\n\t| { readonly kind: \"connected\"; readonly id: number; readonly clientId: string; readonly port: MessagePort }\n\t| { readonly kind: \"released\"; readonly id: number }\n\t| { readonly kind: \"failed\"; readonly id: number; readonly message: string };\n\nconst BROKER_KEY = Symbol.for(\"senpi.webview.broker\");\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction parseBrokerRequest(value: unknown): BrokerRequest | undefined {\n\tif (!isRecord(value) || typeof value.id !== \"number\") return undefined;\n\tif (value.kind === \"connect\") return { kind: \"connect\", id: value.id };\n\tif (value.kind === \"release\" && typeof value.clientId === \"string\") {\n\t\treturn { kind: \"release\", id: value.id, clientId: value.clientId };\n\t}\n\treturn undefined;\n}\n\n/**\n * Main-thread side of one worker thread's route to the WebView service (RPC session workers run the\n * eval kernel host off the main thread). Clients connected through a broker belong to it: it can\n * release only those, and they are all released when the worker exits or the broker is disposed.\n */\nexport function createWebViewBroker(): WebViewBroker | undefined {\n\tconst service = mainThreadWebViewService();\n\tif (!service) return undefined;\n\tconst owner = {};\n\tconst channel = new MessageChannel();\n\tconst local = channel.port1;\n\tconst reply = (message: BrokerReply, transfer: MessagePort[] = []): void => local.postMessage(message, transfer);\n\tlocal.on(\"message\", async (value: unknown) => {\n\t\tconst request = parseBrokerRequest(value);\n\t\tif (!request) return;\n\t\ttry {\n\t\t\tif (request.kind === \"connect\") {\n\t\t\t\tconst grant = service.connect(owner);\n\t\t\t\treply({ kind: \"connected\", id: request.id, clientId: grant.clientId, port: grant.port }, [grant.port]);\n\t\t\t} else {\n\t\t\t\tawait service.release(request.clientId, owner);\n\t\t\t\treply({ kind: \"released\", id: request.id });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treply({ kind: \"failed\", id: request.id, message: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t});\n\tlocal.on(\"close\", () => void service.releaseOwner(owner));\n\tlocal.unref();\n\treturn {\n\t\tport: channel.port2,\n\t\tdispose: async () => {\n\t\t\tlocal.close();\n\t\t\tawait service.releaseOwner(owner);\n\t\t},\n\t};\n}\n\nclass WorkerBrokerClient {\n\treadonly #port: MessagePort;\n\treadonly #pending = new Map<number, (reply: BrokerReply) => void>();\n\t#nextId = 1;\n\n\tconstructor(port: MessagePort) {\n\t\tthis.#port = port;\n\t\tport.on(\"message\", (reply: BrokerReply) => {\n\t\t\tconst settle = this.#pending.get(reply.id);\n\t\t\tthis.#pending.delete(reply.id);\n\t\t\tsettle?.(reply);\n\t\t});\n\t\tport.unref();\n\t}\n\n\tasync connect(): Promise<WebViewServiceConnection> {\n\t\tconst reply = await this.#request({ kind: \"connect\", id: this.#nextId++ });\n\t\tif (reply.kind !== \"connected\")\n\t\t\tthrow new Error(reply.kind === \"failed\" ? reply.message : \"unexpected broker reply\");\n\t\tconst clientId = reply.clientId;\n\t\treturn {\n\t\t\tclientId,\n\t\t\tport: reply.port,\n\t\t\trelease: async () => void (await this.#request({ kind: \"release\", id: this.#nextId++, clientId })),\n\t\t};\n\t}\n\n\t#request(request: BrokerRequest): Promise<BrokerReply> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.#pending.set(request.id, resolve);\n\t\t\tthis.#port.postMessage(request);\n\t\t});\n\t}\n}\n\nfunction isBrokerClient(value: unknown): value is WorkerBrokerClient {\n\treturn value instanceof WorkerBrokerClient;\n}\n\nfunction isMessagePort(value: unknown): value is MessagePort {\n\treturn isRecord(value) && typeof value.postMessage === \"function\" && typeof value.on === \"function\";\n}\n\n/** Worker-thread side: install the broker port the main thread passed in `workerData`. */\nexport function registerWebViewBroker(port: unknown): void {\n\tif (isMainThread || !isMessagePort(port)) return;\n\tReflect.set(globalThis, BROKER_KEY, new WorkerBrokerClient(port));\n}\n\n/**\n * Connects an eval kernel to the main-thread WebView service: directly on the main thread, through\n * the thread's broker in a worker. Rejects when neither route exists (e.g. not running on Bun).\n */\nexport async function connectWebViewService(): Promise<WebViewServiceConnection> {\n\tif (isMainThread) {\n\t\tconst service = mainThreadWebViewService();\n\t\tif (!service) throw new Error(\"Bun.WebView is not available in this runtime\");\n\t\tconst owner = {};\n\t\tconst grant = service.connect(owner);\n\t\treturn { ...grant, release: () => service.release(grant.clientId, owner) };\n\t}\n\tconst broker: unknown = Reflect.get(globalThis, BROKER_KEY);\n\tif (!isBrokerClient(broker)) {\n\t\tthrow new Error(\"no route from this worker thread to the main-thread WebView service\");\n\t}\n\treturn await broker.connect();\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MessagePort } from "node:worker_threads";
|
|
2
|
+
import type { NativeWebView } from "./native-webview.ts";
|
|
3
|
+
export interface WebViewClientHost {
|
|
4
|
+
createView(options: Readonly<Record<string, unknown>>, onConsole: ((...args: unknown[]) => void) | undefined): Promise<NativeWebView>;
|
|
5
|
+
onClientClosed(client: WebViewServiceClient): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One kernel's capability: the views it created and the only port that can reach them. Nothing is
|
|
9
|
+
* addressable across clients, so one session's cell cannot see, drive, or close another's views.
|
|
10
|
+
*/
|
|
11
|
+
export declare class WebViewServiceClient {
|
|
12
|
+
#private;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly owner: object;
|
|
15
|
+
constructor(id: string, owner: object, port: MessagePort, host: WebViewClientHost);
|
|
16
|
+
get viewCount(): number;
|
|
17
|
+
release(): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=webview-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-client.d.ts","sourceRoot":"","sources":["../../../src/core/webview/webview-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,MAAM,WAAW,iBAAiB;IACjC,UAAU,CACT,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC1C,SAAS,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,GACnD,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1B,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACnD;AAcD;;;GAGG;AACH,qBAAa,oBAAoB;;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAMvB,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAQhF;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,IAAI,IAAI,CAMd;CA4FD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { parseClientMessage, wireError, } from "./webview-wire.js";
|
|
2
|
+
function stateOf(view) {
|
|
3
|
+
return { url: view.url, title: view.title, loading: view.loading };
|
|
4
|
+
}
|
|
5
|
+
function closeQuietly(view) {
|
|
6
|
+
try {
|
|
7
|
+
view.close();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// A view whose browser already died has nothing left to close.
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One kernel's capability: the views it created and the only port that can reach them. Nothing is
|
|
15
|
+
* addressable across clients, so one session's cell cannot see, drive, or close another's views.
|
|
16
|
+
*/
|
|
17
|
+
export class WebViewServiceClient {
|
|
18
|
+
#port;
|
|
19
|
+
#host;
|
|
20
|
+
#views = new Map();
|
|
21
|
+
#released = false;
|
|
22
|
+
constructor(id, owner, port, host) {
|
|
23
|
+
this.id = id;
|
|
24
|
+
this.owner = owner;
|
|
25
|
+
this.#port = port;
|
|
26
|
+
this.#host = host;
|
|
27
|
+
port.on("message", (message) => void this.#handle(parseClientMessage(message)));
|
|
28
|
+
port.on("close", () => this.#host.onClientClosed(this));
|
|
29
|
+
port.unref();
|
|
30
|
+
}
|
|
31
|
+
get viewCount() {
|
|
32
|
+
return this.#views.size;
|
|
33
|
+
}
|
|
34
|
+
release() {
|
|
35
|
+
if (this.#released)
|
|
36
|
+
return;
|
|
37
|
+
this.#released = true;
|
|
38
|
+
for (const view of this.#views.values())
|
|
39
|
+
closeQuietly(view);
|
|
40
|
+
this.#views.clear();
|
|
41
|
+
this.#port.close();
|
|
42
|
+
}
|
|
43
|
+
async #handle(message) {
|
|
44
|
+
if (!message || this.#released)
|
|
45
|
+
return;
|
|
46
|
+
switch (message.kind) {
|
|
47
|
+
case "create":
|
|
48
|
+
await this.#reply(message.id, async () => {
|
|
49
|
+
const view = await this.#create(message.viewId, message.options, message.captureConsole);
|
|
50
|
+
return { value: undefined, view };
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
case "call":
|
|
54
|
+
await this.#reply(message.id, async () => {
|
|
55
|
+
const view = this.#view(message.viewId);
|
|
56
|
+
const method = Reflect.get(view, message.method);
|
|
57
|
+
if (typeof method !== "function")
|
|
58
|
+
throw new TypeError(`WebView.${message.method} is not available`);
|
|
59
|
+
const value = await Reflect.apply(method, view, [...message.args]);
|
|
60
|
+
return { value, view };
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
case "close": {
|
|
64
|
+
const view = this.#views.get(message.viewId);
|
|
65
|
+
this.#views.delete(message.viewId);
|
|
66
|
+
if (view)
|
|
67
|
+
closeQuietly(view);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
case "listen": {
|
|
71
|
+
const view = this.#views.get(message.viewId);
|
|
72
|
+
view?.addEventListener(message.event, (event) => {
|
|
73
|
+
const data = Reflect.get(event, "data");
|
|
74
|
+
this.#emit(message.viewId, { type: "dom", name: message.event, data });
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
case "close-all":
|
|
79
|
+
for (const view of this.#views.values())
|
|
80
|
+
closeQuietly(view);
|
|
81
|
+
this.#views.clear();
|
|
82
|
+
this.#post({ kind: "reply", id: message.id, ok: true, value: undefined });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async #create(viewId, options, captureConsole) {
|
|
87
|
+
if (this.#views.has(viewId))
|
|
88
|
+
throw new Error(`WebView ${viewId} already exists`);
|
|
89
|
+
const onConsole = captureConsole
|
|
90
|
+
? (...args) => this.#emit(viewId, { type: "console", args })
|
|
91
|
+
: undefined;
|
|
92
|
+
const view = await this.#host.createView(options, onConsole);
|
|
93
|
+
if (this.#released) {
|
|
94
|
+
closeQuietly(view);
|
|
95
|
+
throw new Error("WebView client released");
|
|
96
|
+
}
|
|
97
|
+
view.onNavigated = (url, title) => this.#emit(viewId, { type: "navigated", url, title });
|
|
98
|
+
view.onNavigationFailed = (error) => this.#emit(viewId, { type: "navigation-failed", error: wireError(error) });
|
|
99
|
+
this.#views.set(viewId, view);
|
|
100
|
+
return view;
|
|
101
|
+
}
|
|
102
|
+
#view(viewId) {
|
|
103
|
+
const view = this.#views.get(viewId);
|
|
104
|
+
if (view)
|
|
105
|
+
return view;
|
|
106
|
+
const error = new Error(`Unknown WebView ${viewId}: it was closed or belongs to another kernel`);
|
|
107
|
+
Reflect.set(error, "code", "ERR_INVALID_STATE");
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
async #reply(id, work) {
|
|
111
|
+
try {
|
|
112
|
+
const { value, view } = await work();
|
|
113
|
+
this.#post({ kind: "reply", id, ok: true, value, state: stateOf(view) });
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.#post({ kind: "reply", id, ok: false, error: wireError(error) });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
#emit(viewId, event) {
|
|
120
|
+
this.#post({ kind: "event", viewId, event });
|
|
121
|
+
}
|
|
122
|
+
#post(message) {
|
|
123
|
+
if (this.#released)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
this.#port.postMessage(message);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (message.kind !== "reply" || !message.ok)
|
|
130
|
+
return;
|
|
131
|
+
this.#port.postMessage({ kind: "reply", id: message.id, ok: false, error: wireError(error) });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=webview-client.js.map
|