mcpField
L
launch

launch

github.com/microsoft开发194.4k298 次安装今天更新
代码仓库

关于 launch

面向 VS Code 开发者的启动技能,可运行已构建的 Code OSS 检出版本以进行界面自动化或调试。它将用户配置复制到隔离目录、分配空闲调试端口,并以 JSON 输出启动信息。

主要功能

  1. 01启动已构建的 Code OSS 检出版本
  2. 02将已认证配置复制到临时目录
  3. 03为调试接口分配端口
  4. 04以 JSON 输出启动信息
  5. 05支持 Playwright 界面自动化和 dap-cli 调试
  6. 06提供 bash 与 PowerShell 启动脚本

适用场景

  1. 01测试 Code OSS 中的聊天或 Agent 流程
  2. 02通过 CDP 使用 Playwright 自动化界面
  3. 03调试渲染进程或扩展宿主
  4. 04避免多个实例之间的端口冲突

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/vscode --skill launch

安装后在 Claude Code 中输入/launch确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

playwrightScripts/
focus-chat-input.ts
scripts/
launch.ps1
launch.sh
monaco-paste.sh
monaco-paste.ts
updateSettings.ts
waitForCdp.ts
scripts/bootstrap/
bootstrap-profile.ps1
bootstrap-profile.sh
windows-process-tree-monitor.ps1
scripts/cleanup/
cleanup.ps1
windows-process-arguments.ps1

详细介绍

Code OSS 开发 - 启动 + 调试

你正在开发 VS Code 本身,并希望:

  1. 从源代码启动一个已登录的 Code OSS 构建版本(Copilot、GitHub 等),以便端到端运行聊天 / Agent 流程。
  2. 通过 CDP 使用 @playwright/cli 驱动它(UI 自动化)。
  3. 可选:通过 dap-cli 附加调试器,以便在渲染进程、扩展宿主或主进程中设置断点。
  4. 同时运行多个实例,且不发生端口冲突。

此技能提供了一个启动器,它会将已认证用户的数据目录克隆到临时文件夹,为每个调试接口选择空闲端口,并以 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
      
  • 安装了 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-cli skill,请提示用户从 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 Storage
  • Singleton*、*.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 调用完成整个聚焦流程:

  1. 如果可见的聊天输入框已获得焦点,则不执行任何操作。
  2. 如果存在可见的聊天输入框但未获得焦点,则将焦点移至该输入框。
  3. 否则,调用平台的聊天聚焦快捷键,等待输入框出现,并且仅当该快捷键尚未聚焦输入框时才将焦点移至该输入框。

该脚本会从浏览器页面检测平台,优先使用当前激活的 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 -e heredoc,这类代码很容易被错误复制。此外,还有三个不易察觉的正确性陷阱,辅助脚本会在内部处理:

    1. Monaco 的 native-edit-context 不会响应 fill 或 type,只能响应实际的粘贴事件(或逐键 press)。
    2. Monaco 会在视图行 DOM 中将 ASCII 空格渲染为 U+00A0(NBSP),因此比较前必须进行规范化处理。
    3. Monaco 会在粘贴事件后异步更新其 DOM — 在同一次 eval 中同步读取会返回过时状态。辅助脚本会跨多个绘制周期轮询渲染后的视图行,直到出现粘贴的前缀或验证超时。
  • 逐键 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 EINVAL from @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>。

评论

支持 Markdown