关于 launch
面向 VS Code 开发者的启动技能,可运行已构建的 Code OSS 检出版本以进行界面自动化或调试。它将用户配置复制到隔离目录、分配空闲调试端口,并以 JSON 输出启动信息。
主要功能
- 01启动已构建的 Code OSS 检出版本
- 02将已认证配置复制到临时目录
- 03为调试接口分配端口
- 04以 JSON 输出启动信息
- 05支持 Playwright 界面自动化和 dap-cli 调试
- 06提供 bash 与 PowerShell 启动脚本
适用场景
- 01测试 Code OSS 中的聊天或 Agent 流程
- 02通过 CDP 使用 Playwright 自动化界面
- 03调试渲染进程或扩展宿主
- 04避免多个实例之间的端口冲突
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ vscode --skill launch安装后在 Claude Code 中输入/launch确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
playwrightScripts/focus-chat-input.tsscripts/launch.ps1launch.shmonaco-paste.shmonaco-paste.tsupdateSettings.tswaitForCdp.tsscripts/bootstrap/bootstrap-profile.ps1bootstrap-profile.shwindows-process-tree-monitor.ps1scripts/cleanup/cleanup.ps1windows-process-arguments.ps1详细介绍
Code OSS 开发 - 启动 + 调试
你正在开发 VS Code 本身,并希望:
- 从源代码启动一个已登录的 Code OSS 构建版本(Copilot、GitHub 等),以便端到端运行聊天 / Agent 流程。
- 通过 CDP 使用
@playwright/cli驱动它(UI 自动化)。 - 可选:通过 dap-cli 附加调试器,以便在渲染进程、扩展宿主或主进程中设置断点。
- 同时运行多个实例,且不发生端口冲突。
此技能提供了一个启动器,它会将已认证用户的数据目录克隆到临时文件夹,为每个调试接口选择空闲端口,并以 JSON 格式输出端口,方便你通过程序获取。
此克隆是精简版:默认排除工作区存储、浏览器缓存、文件历史记录、缓存的 VSIX 备份和旧日志。在 macOS 上,身份验证令牌存储在操作系统钥匙串以及 User/globalStorage 中的少量文件里,两者都会保留。在 Windows 上,GitHub 会话则存储在共享数据目录中,启动器会另外为其填充数据(请参阅Windows 身份验证)。
先决条件
- macOS、Linux 或 Windows。
- macOS / Linux:启动器是一个 bash 脚本(
scripts/launch.sh),依赖PATH中的rsync、nohup和 Node。 - Windows:改用
scripts\launch.ps1。除PATH中的 Node 外,无需其他工具;兼容 Windows PowerShell 5.1 和 PowerShell 7+。使用ConvertFrom-Json解析启动器输出。如果通过 fnm 管理 Node,请先将其加入PATH:fnm env --use-on-cd --shell powershell | Out-String | Invoke-Expression fnm use # picks up the repo's .nvmrc
- macOS / Linux:启动器是一个 bash 脚本(
- 安装了
node_modules/的 VS Code 工作副本(如果缺少,请运行npm install——不要从相邻工作树创建符号链接;这会以不易察觉的方式导致构建出错)。 - 已构建源码的 VS Code 工作副本。运行一次
npm run compile(单次构建),或运行npm run watch进行增量构建。两者都会构建完整客户端以及extensions/下的所有内置扩展。要成功运行,必须构建完整产品;只构建客户端并不够。- 仅当缺少构建产物时,启动器才会编译;它不会检测依赖项是否过时或
out/文件是否过时。拉取代码、切换分支或更改依赖清单后,根据需要运行npm install,并在启动前重新构建。
- 仅当缺少构建产物时,启动器才会编译;它不会检测依赖项是否过时或
- 建议使用已通过身份验证的 Code OSS 配置文件,但并非必需。默认情况下,启动器会尝试从 macOS/Linux 上的
~/.vscode-oss-dev或 Windows 上的$env:USERPROFILE\.vscode-oss-dev复制初始配置;这是仓库的launch.json配置所使用的 user-data-dir。只有在需要从其他配置文件复制初始配置时(例如常规的~/Library/Application Support/Code安装),才传入--source-user-data-dir <path>(或设置$CODE_OSS_DEV_AUTHED_USER_DATA_DIR)。如果源配置文件不存在,启动器会使用空的隔离配置文件继续运行。 - 可用的
@playwright/cli(它是 vscode 仓库中的 devDependency——运行npm install后使用npx @playwright/cli)。 - 调试器相关工作:
PATH中需有dap-cli。如果需要调试器支持但没有dap-cliskill,请提示用户从 https://github.com/roblourens/dap-cli 安装。 - CSS 选择器属于内部实现细节。如果基于选择器的
eval不再生效,请重新获取snapshot、检查当前 DOM 并更新选择器,不要假定旧选择器仍然适用。 - 无法从沙盒化的 Agent 终端启动或操作 Code OSS。在 Windows 上,沙盒要么无法在没有本地网络访问权限时启动,要么启动器会报错
[launch.ps1] Access is denied;仅启用本地网络访问权限并不足够。如果你的 shell 工具可以请求在沙盒外执行,请用此方式运行启动器、@playwright/cli和清理操作。否则,启动前请用户关闭本次会话的终端沙盒(权限选择器 →“终端沙盒”),并在用户确认后继续。
启动器会将源配置文件复制到临时目录,绝不会修改原文件。每次启动都会使用独立的
--user-data-dir和--extensions-dir。
启动器始终会在启动配置文件的
User/settings.json中设置files.simpleDialog.enable: true。自动化必须启用此设置:VS Code 原生 OS 文件对话框无法通过 CDP 使用@playwright/cli操作,而且在无头 macOS 上无法通过 SSH 访问。简单(快速输入)对话框可通过press和粘贴剪贴板内容进行操作。此覆盖仅对单次启动生效,只影响临时配置文件。
从 Agent 会话启动前,请调用
get_current_session,并将其title作为--session-title传入。对于常规编辑器窗口,启动器会将该标题写入临时配置文件的window.title设置。对于 Agents 窗口,启动器会将标题传给 Command Center。此操作绝不会修改源配置文件。 对于无人值守的自动化,请传递--disable-workspace-trust,以免信任对话框阻塞流程或扩展宿主启动。此覆盖仅限当前进程,不会修改源配置文件。仅对你信任的内容使用此选项。
启动
启动脚本位于此 SKILL.md 旁边的 scripts/launch.sh(macOS/Linux)或 scripts\launch.ps1(Windows)。请根据此技能文件的安装位置解析脚本路径,不要硬编码绝对路径。
# LAUNCH=<dir-of-this-SKILL.md>/scripts/launch.sh
SESSION_TITLE=<title-from-get_current_session>
"$LAUNCH" --session-title "$SESSION_TITLE" # default: workbench
"$LAUNCH" --agents --session-title "$SESSION_TITLE"
"$LAUNCH" -- <workspace-path> # forward extra args to code.sh
"$LAUNCH" --source-user-data-dir <path> # pick a specific authed profile
"$LAUNCH" --repo <vscode-repo-root> # if not run from the repo
"$LAUNCH" --clone-extensions # start with a copy of the source extensions/ (~few seconds)
"$LAUNCH" --full # skip slim excludes; copy everything
"$LAUNCH" --skip-prelaunch # reuse already-current build outputs
"$LAUNCH" --disable-workspace-trust # avoid trust prompts for trusted automation inputs
在 Windows 上,使用相同的标志调用 PowerShell 启动脚本:
$skillDir = '<dir-of-this-SKILL.md>'
$launch = Join-Path $skillDir 'scripts\launch.ps1'
$sessionTitle = '<title-from-get_current_session>'
& $launch --session-title $sessionTitle # default: workbench
& $launch --agents --session-title $sessionTitle
& $launch -- --use-mock-keychain # forward extra args to code.bat
& $launch --source-user-data-dir C:\path\to\profile
& $launch --repo C:\path\to\vscode
& $launch --clone-extensions
& $launch --full
& $launch --skip-prelaunch
& $launch --disable-workspace-trust
如果本地执行策略阻止运行脚本,请使用 powershell -ExecutionPolicy Bypass -File <path-to-launch.ps1> 调用。Windows 实现与 bash 启动脚本具有相同的配置文件隔离、精简副本排除项、设置合并、端口分配、前台预启动和 CDP 就绪约定;区别仅在于 shell 命令和路径语法。
处理缺少登录状态的情况
如果源配置文件缺失或没有已存储的 GitHub 会话,不要停止,也不要在启动前询问。继续操作,直到隔离的 Code OSS 窗口显示正常的 GitHub/Copilot 登录界面。Code OSS 需要登录时,不要放弃。
此时,使用问题工具提供以下选项:
- 我已登录
- 初始化配置文件,以便后续启动时预先完成身份验证
如果用户已登录,请继续使用当前隔离启动。如果用户选择初始化,请清理当前隔离启动,并运行平台初始化脚本:
BOOTSTRAP=<dir-of-this-SKILL.md>/scripts/bootstrap/bootstrap-profile.sh
"$BOOTSTRAP" --repo <vscode-repo-root>
$bootstrap = Join-Path $skillDir 'scripts\bootstrap\bootstrap-profile.ps1'
& $bootstrap -Repo <vscode-repo-root>
如果失败的启动使用了自定义的 --source-user-data-dir,请在 macOS/Linux 上将相同路径作为 --user-data-dir <path> 传递,在 Windows 上则作为 -UserDataDir <path> 传递。
初始化脚本仅会在持久 Code OSS 窗口的 CDP 端点就绪后返回。脚本会报告日志路径;如果窗口无法正常使用,则会附上日志末尾内容并失败。请用户在该窗口中登录 GitHub/Copilot,然后关闭窗口,并使用我已登录作为确认选项。在 macOS 上,用户必须使用 Cmd+Q 完全退出 Code OSS;关闭最后一个窗口并不会停止应用程序。
用户确认后,请等待初始化进程树释放持久配置文件,再次启动:
"$BOOTSTRAP" --wait-for-exit <exitMarker-from-bootstrap-json>
& $bootstrap -WaitForExit -ExitMarker <exitMarker-from-bootstrap-json>
使用初始化脚本报告的相同配置文件路径;仅在使用默认配置文件时省略显式路径。等待成功后,再次运行常规启动脚本。其隔离副本将继承已存储的身份验证信息,不会与初始化窗口仍在使用的文件发生竞争。
未经用户明确选择,不要初始化配置文件。初始化窗口使用持久配置文件和共享身份验证存储,这与常规隔离启动不同。
复制内容(默认的精简模式)
排除列表与 VS Code 自身的性能测试技能(.github/skills/auto-perf-optimize)所用列表一致;已知该列表能确保 Copilot 身份验证和语言模型可用性正常工作。具体而言,WebStorage/、Service Worker/、Local Storage/、Cookies、Network Persistent State、TransportSecurity、Trust Tokens、Preferences、machineid 以及整个 User/globalStorage/(其中包含 state.vscdb)都会保留。
Windows 身份验证
Windows 没有用于这些机密的应用间共享密钥链,因此它们存储在磁盘文件中——但并非全部位于 user-data-dir 中。GitHub 会话仅在 Windows 上存储于 StorageScope.APPLICATION_SHARED(参见 src/vs/platform/secrets/common/secrets.ts 中的 useSharedStorage 和 CROSS_APP_SHARED_SECRET_KEYS),因此凭据的两部分位于不同目录中:
| 部分 | 位置 |
|---|---|
| 加密的 GitHub 会话数据块 | <shared-data-dir>/sharedStorage/state.vscdb |
DPAPI 封装的解密密钥(os_crypt.encrypted_key) | <user-data-dir>/Local State |
因此,启动器会同时进行两项准备:复制源配置文件,并且将源共享数据目录复制到本次运行的临时 shared-data 目录。源目录的解析方式与 IEnvironmentService.appSharedDataHome 相同:若设置了 $env:CODE_OSS_DEV_AUTHED_SHARED_DATA_DIR,则使用该目录;否则,若以便携模式运行,则使用 $env:VSCODE_PORTABLE\shared-data;否则使用 ~/<product.sharedDataFolderName>(即 %USERPROFILE%\.vscode-oss-shared)。它还会验证 Local State、machineid 和 Network 是否已随配置文件一并复制;如果两个数据库中都没有 GitHub 会话,则会向 stderr 发出警告。 |
在 macOS/Linux 上,这种不对称不可见,因为相同的令牌会存放在配置文件内。仅在 Windows 上出现的“始终处于登出状态”问题源于共享数据目录,而不是配置文件:使用源配置文件登录会写入有效的会话,但在加入此复制步骤之前,每次启动都会向 Code OSS 提供一个空的共享目录并将其丢弃。
引导脚本会将会话数据写入 %USERPROFILE%\.vscode-oss-shared,并将密钥写入配置文件的 Local State;后续启动会复制两者并继承该会话。
早于
APPLICATION_SHARED迁移的配置文件可能仍将密钥保存在User/globalStorage/state.vscdb中。ApplicationSharedStorageMain会将应用程序存储注册为读取回退项,因此即使不存在共享数据目录,这些配置文件也能通过身份验证;这就是为什么会将缺少共享数据目录报告为一项事实,而不是直接认定为致命问题。
排除的内容(临时、可重新生成或已知不需要):
User/workspaceStorage/- 每个工作区的状态,包括存储的聊天会话(通常有数 GB)User/History/- 本地文件编辑历史记录CachedExtensionVSIXs- VSIX 备份文件(数百 MB)logs- 配置文件根目录下的 Chromium 缓存:
Cache、Code Cache、CachedData、GPUCache、ShaderCache、Dawn*Cache、component_crx_cache;以及持久集成浏览器分区下的:Cache、Code Cache、GPUCache、Dawn*Cache Backups、blob_storage、BrowserMetrics、Crashpad、Session StorageSingleton*、*.lock、*.sock(否则会与源实例冲突)
持久集成浏览器分区会保留网站状态,例如 Cookie、本地存储和会话存储、IndexedDB、WebStorage、服务工作线程及偏好设置;仅排除可重新生成的缓存。
默认情况下,extensions/ 是一个全新的空目录——速度最快且不会冲突,但启动的实例不会安装任何第三方扩展。传入 --clone-extensions,可将源扩展目录复制到临时配置文件中,使新实例独立于源实例。若怀疑精简副本缺少所需内容,可传入 --full 跳过所有排除项。
为什么绝不直接共用源
extensions/目录? 扩展管理服务会写入一个共享的.obsolete文件;两个并发写入者会导致彼此的共享后台进程崩溃。出于同样的原因,启动器始终使用独立的扩展目录,就像它使用--shared-data-dir一样(见下文)。
脚本会在启动前执行准备工作(下载 electron、在缺少时进行编译、安装内置扩展),并在前台运行;随后将 Code OSS 分离启动,并阻塞等待渲染进程的 CDP 端点响应(最长约 90 秒),然后才在 stdout 上打印 JSON 行。如果发生任何错误——启动前准备出错、code.sh 提前退出、CDP 始终未开启——脚本都会以非零状态退出,并将相关日志末尾内容输出到 stderr。
对于同一已准备好构建版本的重复启动,在成功正常启动一次后传入 --skip-prelaunch。仅当监视任务持续更新所有输出,或源代码与构建输出均未发生变化时才使用;否则,新实例可能运行过时或不完整的代码。
{"pid":12345,"cdpPort":53111,"extHostPort":53112,"mainPort":53113,"agentHostPort":53114,"userDataDir":".../user-data","extensionsDir":".../extensions","sharedDataDir":".../shared-data","runDir":"...","logFile":".../code.log","repo":"...","agents":false,"timings":{"profileMs":231,"preLaunchMs":251,"cdpReadyMs":459,"totalMs":941}}
附加的 timings 对象使用单调递增时间,记录准备隔离配置文件、运行启动前准备工作,以及启动 Code OSS 直至 CDP 就绪所花费的时间。totalMs 涵盖启动器从开始运行到就绪的完整耗时。
使用 Node 捕获,Node 已是启动器的前置条件。由于打印 JSON 时 CDP 已经就绪,因此无需重试循环:
INFO=$("$LAUNCH" | tail -n1)
CDP=$(node -p 'JSON.parse(process.argv[1]).cdpPort' "$INFO")
EXT=$(node -p 'JSON.parse(process.argv[1]).extHostPort' "$INFO")
MAIN=$(node -p 'JSON.parse(process.argv[1]).mainPort' "$INFO")
AGENT=$(node -p 'JSON.parse(process.argv[1]).agentHostPort' "$INFO")
LOG=$(node -p 'JSON.parse(process.argv[1]).logFile' "$INFO")
PID=$(node -p 'JSON.parse(process.argv[1]).pid' "$INFO")
在 Windows 上,使用 PowerShell 捕获并解析 JSON:
$info = & $launch | Select-Object -Last 1 | ConvertFrom-Json
$cdp = $info.cdpPort
$ext = $info.extHostPort
$main = $info.mainPort
$agent = $info.agentHostPort
$log = $info.logFile
$pid = $info.pid
各端口的用途
| 端口 | 进程 | 用途 |
|---|---|---|
cdpPort (--remote-debugging-port) | 渲染器(工作台窗口) | 通过 CDP 使用 @playwright/cli,也可使用 Chrome DevTools |
extHostPort (--inspect-extensions) | 扩展宿主(Node) | dap-cli(Node 检查器协议) |
mainPort (--inspect) | Electron 主进程(Node) | dap-cli(Node 检查器协议) |
agentHostPort (--inspect-agenthost) | Agent 宿主进程(Node) | dap-cli(Node 检查器协议) |
使用 @playwright/cli 操作 UI
使用启动 JSON 中动态分配的 cdpPort。常规流程是:连接、确认目标、查看截图、获取快照、进行交互,然后在 UI 有重要变化后重新获取快照。
如果不确定 Playwright CLI 的语法,请运行 npx @playwright/cli --help 或 npx @playwright/cli <command> --help,不要猜测选项名称。
始终选择唯一的
PW_SESSION名称,并在每次调用npx @playwright/cli ...时将其作为-s=$PW_SESSION传入。 CLI 由持久守护进程(cliDaemon.js)提供支持,并以会话名称为键;如果两个 shell 都省略-s=,它们就会共用隐式的"default"会话,最近一次连接的 CDP 会成为两个 shell 后续命令的目标。启动技能采用隔离设计(每个实例独立的 UDD、端口和 shared-data-dir),此模式也能在 Playwright 操作层保持这种隔离。**关于替代方案PLAYWRIGHT_CLI_SESSION环境变量的说明:**它在软件包 README 中有说明,用于open类工作流时也能正常工作,但与attach --cdp=...配合不佳(守护进程最终会同时获得--cdp=...和--endpoint=<env-value>,后者优先,导致connect ENOENT失败)。已在@playwright/[email protected]上确认。显式使用-s=NAME在所有模式下均可正常工作。
# At the top of your script / subagent prompt:
PW_SESSION="my-uniq-$$" # any unique string; $$ is fine for one shell per agent
# launch.sh blocks until CDP is ready, so a single attach is enough.
npx @playwright/cli -s=$PW_SESSION attach --cdp=http://127.0.0.1:$CDP
npx @playwright/cli -s=$PW_SESSION tab-list
npx @playwright/cli -s=$PW_SESSION snapshot
执行 attach 后,只要持续传入相同的 -s=$PW_SESSION,后续 @playwright/cli 命令就会继续使用已连接的应用,直到你关闭连接或重新连接。
选择正确的 Electron 目标
Electron 应用可能会暴露多个窗口或 webview。如果 tab-list 显示 about:blank、webview 或其他错误目标,请先切换目标再进行交互:
npx @playwright/cli -s=$PW_SESSION tab-list
npx @playwright/cli -s=$PW_SESSION tab-select 2
npx @playwright/cli -s=$PW_SESSION snapshot
如果重新启动后目标看起来已过期,请运行 npx @playwright/cli -s=$PW_SESSION close,然后使用 $CDP 重新连接,并再次检查 tab-list。
继续操作前检查启动的窗口
每次启动或重新启动后,等待目标窗口完成渲染,然后截取屏幕截图并使用图像查看工具打开。请先目视确认预期界面、文本、图标和布局均正常,然后再与应用交互或要求用户登录。仅保存截图而不检查、读取 DOM/无障碍快照,或连接到 CDP 端点,都不满足此项检查要求。
如果截图显示图标缺失、内容空白或布局明显损坏,请停止当前流程,并在继续操作前调查观察到的问题。修复后,重新启动并再次检查;不要等用户指出 UI 已损坏。
将焦点移至聊天输入框
在常规工作台和 Agents 窗口中都使用 playwrightScripts/focus-chat-input.ts 脚本。它会通过一次 Playwright 调用完成整个聚焦流程:
- 如果可见的聊天输入框已获得焦点,则不执行任何操作。
- 如果存在可见的聊天输入框但未获得焦点,则将焦点移至该输入框。
- 否则,调用平台的聊天聚焦快捷键,等待输入框出现,并且仅当该快捷键尚未聚焦输入框时才将焦点移至该输入框。
该脚本会从浏览器页面检测平台,优先使用当前激活的 Agents 会话,并排除内联聊天输入框。如果克隆的配置文件自定义了默认快捷键,则会通过命令面板改用特定界面的命令。
LAUNCH_DIR=<dir-of-this-SKILL.md>
FOCUS_CHAT="$LAUNCH_DIR/playwrightScripts/focus-chat-input.ts"
npx @playwright/cli -s=$PW_SESSION run-code --filename="$FOCUS_CHAT"
$skillDir = '<dir-of-this-SKILL.md>'
$focusChat = Join-Path $skillDir 'playwrightScripts\focus-chat-input.ts'
npx @playwright/cli "-s=$pwSession" run-code "--filename=$focusChat"
该脚本会返回 { focused, focusChanged, focusInvoked, shortcutInvoked, commandPaletteFallbackInvoked, selector }。focusChanged 表示本次脚本调用是否将焦点移至 Chat,而 focusInvoked 表示脚本是否必须直接调用 focus()。如果聊天输入框原本已获得焦点,两者均为 false。如果脚本失败,请获取新的快照,并在重试前处理任何阻塞对话框或聊天不可用状态。
在 Monaco 中输入内容(聊天输入框、编辑器)
在 Code OSS 中,fill 和 type 会静默失败 — Monaco 的 native-edit-context 元素不会响应 Playwright 的默认输入流程。请使用以下替代方案之一:
-
scripts/monaco-paste.ts辅助脚本(推荐 — 跨平台、快速、无需系统剪贴板,可安全并行使用)。从位置参数或 stdin 读取文本,并向获得焦点的聊天输入 Monaco 编辑器分派带有DataTransfer负载的ClipboardEvent('paste')。支持--session NAME或PW_SESSION,确保它与其他所有操作处于同一个-s=会话中。Bash 包装脚本scripts/monaco-paste.sh仍可用于兼容性。LAUNCH_DIR=<dir-of-this-SKILL.md> # the same dir that holds scripts/launch.sh FOCUS_CHAT="$LAUNCH_DIR/playwrightScripts/focus-chat-input.ts" PASTE="$LAUNCH_DIR/scripts/monaco-paste.ts" export PW_SESSION # helper reads this env var # Send a prompt: npx @playwright/cli -s=$PW_SESSION run-code --filename="$FOCUS_CHAT" node "$PASTE" 'Please run `pwd && ls` using your terminal tool.' npx @playwright/cli -s=$PW_SESSION press Enter # Long / arbitrary text via stdin (avoids any shell-quoting headaches): printf 'multi-line prompt\nwith backticks `x`\nand emoji 🎉' | node "$PASTE" # Append without clearing: node "$PASTE" --append " continued text" # Skip the read-back check (useful when intentionally pasting more than the # chat input's ~600-character soft cap): node "$PASTE" --no-verify "...long text..." # Or pass the session explicitly per call (if you don't want to export PW_SESSION): node "$PASTE" --session "$PW_SESSION" "..."$paste = Join-Path $skillDir 'scripts\monaco-paste.ts' node $paste --session $pwSession 'Please respond with OK.' npx @playwright/cli "-s=$pwSession" press Enter该辅助脚本会在 stdout 输出一行 JSON:
{ok, actualLength, expectedLength, viewLineCount, firstViewLine, error?}。成功时退出码为 0,验证失败时为 1,参数错误时为 2。经测试,连续粘贴 20 次以上均可靠,包括 Unicode(中文)、表情符号(🎉)、反引号、与号、嵌入的引号和换行符。**为何使用辅助脚本,而不只是编写文档:**内联示例需要使用带有嵌入式 JS 模板字面量的多行
node -eheredoc,这类代码很容易被错误复制。此外,还有三个不易察觉的正确性陷阱,辅助脚本会在内部处理:- Monaco 的
native-edit-context不会响应fill或type,只能响应实际的粘贴事件(或逐键press)。 - Monaco 会在视图行 DOM 中将 ASCII 空格渲染为 U+00A0(NBSP),因此比较前必须进行规范化处理。
- Monaco 会在粘贴事件后异步更新其 DOM — 在同一次
eval中同步读取会返回过时状态。辅助脚本会跨多个绘制周期轮询渲染后的视图行,直到出现粘贴的前缀或验证超时。
- Monaco 的
-
逐键
press(通用但速度较慢 — 每次按键都要单独调用 CLI,并产生 Node 启动开销):npx @playwright/cli -s=$PW_SESSION press H npx @playwright/cli -s=$PW_SESSION press i npx @playwright/cli -s=$PW_SESSION press Enter -
通过
pbcopy粘贴剪贴板内容(在 macOS 上速度快,但NSPasteboard是系统级共享资源,因此任何同时操作剪贴板的 shell 都会发生冲突)。仅当粘贴期间机器上没有其他程序使用剪贴板时才使用。LAUNCH_DIR=<dir-of-this-SKILL.md> FOCUS_CHAT="$LAUNCH_DIR/playwrightScripts/focus-chat-input.ts" printf '%s' "Your prompt here" | pbcopy npx @playwright/cli -s=$PW_SESSION run-code --filename="$FOCUS_CHAT" npx @playwright/cli -s=$PW_SESSION press Meta+v npx @playwright/cli -s=$PW_SESSION press Enter
多实例并行模式
由于启动技能围绕隔离机制构建,典型工作负载是在一台机器上运行多个 Agent,每个 Agent 驱动各自的 Code OSS。这种模式的核心是为每个 Agent 分配唯一的 PW_SESSION,并在所有地方传递它:
LAUNCH_DIR=<dir-of-this-SKILL.md>
FOCUS_CHAT="$LAUNCH_DIR/playwrightScripts/focus-chat-input.ts"
PASTE="$LAUNCH_DIR/scripts/monaco-paste.ts"
export PW_SESSION
# In agent A's shell:
PW_SESSION="agent-A-$$"
INFO=$("$LAUNCH" --agents -- --use-mock-keychain | tail -n1)
CDP=$(node -p 'JSON.parse(process.argv[1]).cdpPort' "$INFO")
npx @playwright/cli -s=$PW_SESSION attach --cdp=http://127.0.0.1:$CDP
npx @playwright/cli -s=$PW_SESSION run-code --filename="$FOCUS_CHAT"
node "$PASTE" "prompt for A" # helper picks up $PW_SESSION
# In agent B's shell (running concurrently):
PW_SESSION="agent-B-$$"
INFO=$("$LAUNCH" --agents -- --use-mock-keychain | tail -n1)
CDP=$(node -p 'JSON.parse(process.argv[1]).cdpPort' "$INFO")
npx @playwright/cli -s=$PW_SESSION attach --cdp=http://127.0.0.1:$CDP
npx @playwright/cli -s=$PW_SESSION run-code --filename="$FOCUS_CHAT"
node "$PASTE" "prompt for B"
每个 Agent 都有自己的 cliDaemon,绑定到各自的 CDP,因此粘贴、点击和快照不会相互干扰。已通过两个并发实例进行实时验证。**macOS Mach 端口注意事项:**在 macOS 上,当并发运行的 Code OSS 实例超过约 2–3 个时,Crashpad 的异常处理程序往往会因 mach_port_request_notification: invalid capability 而崩溃。这是独立的操作系统级限制,不受会话名称影响。
**清理
cliDaemon进程:**使用npx @playwright/cli -s=$PW_SESSION close停止会话的守护进程;或者(先关闭所有 Code OSS 窗口)使用npx @playwright/cli kill-all清除所有过期守护进程。会话守护进程位于~/Library/Caches/ms-playwright/daemon/<hash>/下。
Agents 窗口选择器差异
Agents 窗口不使用常规工作台的 .interactive-input-editor 包装器。限定在该包装器内的选择器检查可能返回空结果,即使 Agents 聊天输入框已获得焦点也是如此。
// Regular-workbench-specific selector; do not assume this exists in Agents.
document.querySelectorAll('.interactive-input-editor .view-line')
// More useful checks in Agents.
document.querySelectorAll('.view-line')
document.activeElement?.matches('.native-edit-context, textarea.inputarea')
焦点脚本会适配这些 DOM 差异,并优先选择活动的 Agents 会话。
验证和清除聊天文本
对于常规工作台侧边栏,以下内容可以确认文本已输入到 Monaco 输入框中:
npx @playwright/cli -s=$PW_SESSION eval '
(() => {
const sidebar = document.querySelector(".part.auxiliarybar");
const viewLines = sidebar?.querySelectorAll(".interactive-input-editor .view-line") ?? [];
return Array.from(viewLines).map(viewLine => viewLine.textContent).join("|");
})()'
对于 Agents 窗口,请使用新的快照以及上述更广泛的选择器/焦点检查,不要假设常规侧边栏包装器存在。
清除已获得焦点的 Monaco 输入框:
# macOS
npx @playwright/cli -s=$PW_SESSION press Meta+a
# Linux / Windows
npx @playwright/cli -s=$PW_SESSION press Control+a
npx @playwright/cli -s=$PW_SESSION press Backspace
如果界面尚不可用,导致焦点脚本无法访问 Chat,请先截取快照并导航 UI,使聊天界面处于可用状态,然后再重试。不要把已完成的 CLI 命令当作文本已输入的证明。
截图(留存记录)
SHOTS="$PWD/screenshots/$(date +%Y-%m-%dT%H-%M-%S)"
mkdir -p "$SHOTS"
npx @playwright/cli -s=$PW_SESSION screenshot --filename="$SHOTS/after-launch.png"
请将截图保存在工作区内,而不是
/tmp,以便后续审查。
对于宽窗口,--full-page 可以让布局更易于检查;如果快照为所需面板提供了稳定的 ref,元素截图也很有用:
npx @playwright/cli -s=$PW_SESSION screenshot --full-page --filename="$SHOTS/full-window.png"
npx @playwright/cli -s=$PW_SESSION screenshot e42 --filename="$SHOTS/panel.png"
在 macOS 上,截图失败并显示 “Permission denied” 通常意味着终端没有屏幕录制权限。解决该权限问题期间,请使用文本/状态验证。
录制视频
要录制演示视频,请将附加会话用作 Playwright page,采用通用的 page.screencast 录制工作流:
- 将整个场景(包括
page.screencast.start()和stop())写成文件中的裸async page => { ... }函数,并通过单次npx @playwright/cli -s=$PW_SESSION run-code --filename=<file>调用运行。page是附加的工作台窗口。 - 输入的文本尽量简短。逐键输入 Monaco 虽然可见,但速度很慢:约 400 个字符会生成一段近四分钟的视频。请在开始录制前粘贴较长的设置文本。
- 要运行命令,请按
F1,在Type the name of a command to run.文本框中输入>Command Name,然后点击完全匹配的选项。列表会异步更新,因此输入后立即按Enter可能会运行过时的结果。
使用 dap-cli 调试
若要在窗口运行时在 VS Code 源码中设置断点,请将 dap-cli 附加到其中一个端口。如果 dap-cli 会有帮助,但相应的技能不可用,请提示用户先从 https://github.com/roblourens/dap-cli 安装,再继续执行特定于调试器的步骤。
如果 dap-cli 技能可用,请阅读该技能,了解完整的附加/断点/检查工作流——此技能只会告诉你应连接哪个端口:
- 扩展宿主(最常见——Copilot Chat 扩展、内置扩展、正在开发的自有扩展)->
extHostPort - 主进程(Electron 生命周期、窗口/菜单连接、IPC)->
mainPort - 本地 Agent 宿主(
src/vs/platform/agentHost/node/...、Agent 会话生命周期、AHP 连接、Claude/Copilot Agent 提供方)->agentHostPort - 渲染进程(工作台本身,
src/vs/workbench/...)->cdpPort
你可以同时针对同一个窗口运行 @playwright/cli 和 dap-cli——在一个终端中操作 UI,在另一个终端中命中断点并检查状态。
多个实例
每次启动都会使用新的端口和新的临时 runDir,因此你可以根据机器的承受能力,同时运行任意多个 Code OSS 窗口。每个实例的端口都会出现在各自的 JSON 数据块中——请勿混用。
启动器还会传入 --shared-data-dir=<runDir>/shared-data。这是实现多实例隔离的必要条件:Code OSS 会在 ~/.<dataFolderName>-shared/sharedStorage/state.vscdb 中使用固定路径的 SQLite 数据库,该数据库不受 --user-data-dir 管理。如果不覆盖此路径,两个并发实例就会争用同一个文件,其中一个实例会因“共享后台进程意外终止”而退出。每次启动都会获得各自的 shared-data 目录,并从源 shared-data-dir 复制数据进行初始化,以确保 Windows 上的 GitHub 会话得以保留——请参阅Windows 身份验证,了解此复制操作的原因。
源码变更后重启
工作台代码会在 Code OSS 窗口启动时加载;源码变更不会热重载到已运行的实例中。构建输出更新后,请终止已启动的进程,再次启动,并从新的 JSON 数据块中重新连接到新的 cdpPort。
kill "$PID" 2>/dev/null || true
INFO=$("$LAUNCH" | tail -n1)
CDP=$(node -p 'JSON.parse(process.argv[1]).cdpPort' "$INFO")
PID=$(node -p 'JSON.parse(process.argv[1]).pid' "$INFO")
npx @playwright/cli -s=$PW_SESSION attach --cdp=http://127.0.0.1:$CDP
npx @playwright/cli -s=$PW_SESSION tab-list
npx @playwright/cli -s=$PW_SESSION snapshot
如果你需要频繁迭代,请单独保持仓库构建/监视任务运行,这样重新启动时就能使用已生成的最新输出。正常启动成功一次后,只要这些输出仍是最新的,--skip-prelaunch 就能避免重复执行准备工作。
清理
启动器会将所有内容写入临时 runDir(显示在 JSON 中)。完成后:
# Disconnect this session's playwright daemon (leaves other sessions' daemons alone)
npx @playwright/cli -s=$PW_SESSION close
# Or nuke any stale daemons left behind by crashed callers across all sessions:
# npx @playwright/cli kill-all
# Kill the Code OSS instance
kill "$PID" 2>/dev/null || true
# Remove the throwaway profile
rm -rf "$(dirname "$LOG")"
在 Windows 上,请使用清理辅助工具。code.bat 返回的 PID 可能对应一个生命周期很短的包装进程,因此该辅助工具会查找并仅终止 --user-data-dir 指向本次启动的确切 runDir 的 Code - OSS.exe 进程,然后移除临时配置文件:
$cleanup = Join-Path $skillDir 'scripts\cleanup\cleanup.ps1'
& $cleanup -RunDir $info.runDir -PlaywrightSession $pwSession
Code OSS 是完整的 Electron 应用,很容易占用 1-4 GB 内存。请务必清理。
故障排除
Daemon pid=...: listen EINVALfrom@playwright/cli- 守护进程的套接字路径(TMPDIR+ 固定的约 33 个字符前缀 +-s=会话名称)超过了约 103 字节的 Unix 套接字限制。macOS 的默认TMPDIR只给会话名称留出约 16 个字符,因此请先缩短-s=。如果需要更长的名称,请仅为单条命令设置覆盖值(TMPDIR=/tmp npx @playwright/cli ...),不要使用export,这样启动器仍会使用你自己的每用户临时目录。- "Sent env to running instance. Terminating..." - 动态的
--user-data-dir应该可以避免此问题。如果看到这条消息,说明另一个 Code OSS 正在使用相同的配置文件路径;请为其他源传入--source-user-data-dir,或检查临时副本是否确实已创建(ls "$(node -p 'JSON.parse(process.argv[1]).userDataDir' "$INFO")")。 - Renderer ESM errors /
import { Menu } from 'electron'- 你的环境中设置了ELECTRON_RUN_AS_NODE。启动器会为子进程取消设置该变量,但如果你自行启动code.sh,也要执行相同操作。 missing config value GIT_CONFIG_VALUE_*from Git - 从 Agent 运行时继承的索引式 Git 配置在启动应用进程时丢失了一个空值。启动器会取消设置GIT_CONFIG_COUNT和GIT_CONFIG_PARAMETERS,以免仅适用于主机的 Git 覆盖项影响 Code OSS;如果直接从这类运行时调用code.sh,也要执行相同操作。- 内置扩展加载失败(
Cannot find module .../extensions/.../out/extension.js) - 扩展尚未编译。运行npm run compile(单次编译,也会重新构建所有内置扩展)或npm run watch(增量编译)。常见原因是:你为了通过单元测试运行了npm run transpile-client,这会生成out/,但不会生成extensions/*/out/,因此 preLaunch 的“out/是否缺失?”检查跳过了编译。 launch.sh以非零状态退出并显示日志末尾内容 - 可能是启动前步骤失败、code.sh在 CDP 启动前退出,或 CDP 在 90 秒内未启动。输出到 stderr 的日志末尾内容来自runDir/code.log;请阅读该文件以诊断问题。- 快照显示了错误页面或没有预期的控件 - 使用
tab-list,必要时通过tab-select <index>切换,然后重新拍摄快照再进行交互。 - CLI 输入命令执行完成,但输入框仍为空 - 运行
playwrightScripts/focus-chat-input.ts,使用press或从剪贴板粘贴,而不是使用fill/type,并在发送前确认输入框的状态。 - 启动的窗口中缺少身份验证信息 - 使用缺少登录信息的流程,让用户选择在当前隔离窗口中完成登录,或引导持久源配置文件完成初始化。在 Windows 上,请先检查共享数据目录:GitHub 会话数据存储在
%USERPROFILE%\.vscode-oss-shared\sharedStorage\state.vscdb中,而不在配置文件目录中。启动器找到该数据时会在 stderr 中记录copying shared data: <src> -> <dst>;未找到时则会警告no shared-data-dir at <path>。