diff --git a/.agents/skills/launch/SKILL.md b/.agents/skills/launch/SKILL.md index ddfd717cc96..9786ed96412 100644 --- a/.agents/skills/launch/SKILL.md +++ b/.agents/skills/launch/SKILL.md @@ -18,8 +18,8 @@ The clone is **slim**: workspace storage, browser caches, file history, cached V ## Prerequisites -- macOS or Linux. The launcher is a bash script and depends on `rsync`, `lsof`, `jq`, `nohup`, and Node on `PATH`. -- A VS Code checkout with sources built. Run `npm run watch` in another terminal (or rely on `./scripts/code.sh` to compile the first time). +- macOS or Linux. The launcher is a bash script and depends on `rsync`, `curl`, `nohup`, and Node on `PATH`. The example caller snippets below also use `jq` (parse the JSON output) and `lsof` (kill-by-port fallback) — install those if you plan to use them, but the launcher itself does not require them. +- A VS Code checkout with sources built. Run `npm run compile` once (one-shot) or `npm run watch` for incremental rebuilds. Both build the full client **and** the Copilot extension. The launcher also runs `node build/lib/preLaunch.ts` before starting Code OSS, which auto-runs `npm run compile` if `out/` is missing and downloads Electron + built-in extensions. - An **authenticated** Code OSS profile to seed from. By default the launcher uses `~/.vscode-oss-dev`, which is the user-data-dir the repo's `launch.json` configs use - if the user has ever signed in to Copilot in a dev build, this should work. Only pass `--source-user-data-dir ` (or set `$CODE_OSS_DEV_AUTHED_USER_DATA_DIR`) when you specifically want to seed from a different profile (e.g. your regular `~/Library/Application Support/Code` install). - `@playwright/cli` available (it's a devDependency in the vscode repo - `npm install` then use `npx @playwright/cli`). - For debugger work: `dap-cli` on `PATH`. If debugger support would be useful but the `dap-cli` skill is not present, prompt the user to install it from https://github.com/roblourens/dap-cli. @@ -61,13 +61,13 @@ Excluded (transient, regenerable, or known-not-needed): > If the launched window says "language model unavailable" or otherwise looks unauthed, ask the user to sign in. -The script prints one JSON line on stdout (logs go to stderr): +The script runs pre-launch (electron download, compile-if-missing, built-in extensions) **in the foreground**, then starts Code OSS detached and **blocks until the renderer's CDP endpoint is responding** (up to ~90s) before printing the JSON line on stdout. If anything fails — preLaunch errors, code.sh exits early, CDP never opens — the script exits non-zero and dumps the relevant log tail to stderr. ```json {"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} ``` -Capture it with `jq`: +Capture it with `jq` — no retry loop needed, CDP is already up when the JSON is printed: ```bash INFO=$("$LAUNCH" | tail -n1) @@ -93,11 +93,8 @@ PID=$(jq -r .pid <<<"$INFO") Use the dynamic `cdpPort` from the launch JSON. The normal loop is: attach, confirm the target, snapshot, interact, then re-snapshot after meaningful UI changes. ```bash -# Wait for Code OSS to start, retry until attached -for i in 1 2 3 4 5; do - npx @playwright/cli attach --cdp=http://127.0.0.1:$CDP 2>/dev/null && break || sleep 3 -done - +# launch.sh blocks until CDP is ready, so a single attach is enough. +npx @playwright/cli attach --cdp=http://127.0.0.1:$CDP npx @playwright/cli tab-list npx @playwright/cli snapshot ``` @@ -234,9 +231,7 @@ kill "$PID" 2>/dev/null || true INFO=$("$LAUNCH" | tail -n1) CDP=$(jq -r .cdpPort <<<"$INFO") PID=$(jq -r .pid <<<"$INFO") -for i in 1 2 3 4 5; do - npx @playwright/cli attach --cdp=http://127.0.0.1:$CDP 2>/dev/null && break || sleep 3 -done +npx @playwright/cli attach --cdp=http://127.0.0.1:$CDP npx @playwright/cli tab-list npx @playwright/cli snapshot ``` @@ -266,8 +261,8 @@ Code OSS is a full Electron app and easily eats 1-4 GB. Always clean up. - **"Sent env to running instance. Terminating..."** - The dynamic `--user-data-dir` should prevent this. If you see it, another Code OSS is using the same profile path; pass `--source-user-data-dir` to a different source or check that the temp copy actually happened (`ls "$(jq -r .userDataDir <<<"$INFO")"`). - **Renderer ESM errors / `import { Menu } from 'electron'`** - `ELECTRON_RUN_AS_NODE` is set in your env. The launcher unsets it for the child, but if you spawn `code.sh` yourself, do the same. -- **Built-in extension fails to load (`Cannot find module .../extensions/.../out/extension.js`)** - extensions weren't compiled. Run `npm run watch-extensions` (or `npm run compile-extensions`). -- **CDP connect refused** - give it a few seconds; the launcher returns before the renderer is ready. Use the retry loop above. +- **Built-in extension fails to load (`Cannot find module .../extensions/.../out/extension.js`)** - extensions weren't compiled. Run `npm run compile` (one-shot, also rebuilds the Copilot extension) or `npm run watch` (incremental). +- **`launch.sh` exits non-zero with a log tail** - either pre-launch failed, `code.sh` died before CDP came up, or CDP never opened within 90s. The tail printed to stderr is from `runDir/code.log` - read it to diagnose. - **Snapshot shows the wrong page or no expected controls** - use `tab-list`, switch with `tab-select ` if needed, then re-snapshot before interacting. - **CLI typing commands complete but the input stays empty** - focus chat with the platform shortcut, use `press` or clipboard paste rather than `fill` / `type`, then verify the input state before sending. - **Auth missing in the launched window** - confirm the source profile is actually authed (`ls "$SOURCE_UDD"` should contain `User/`, and `ls "$SOURCE_UDD/User/globalStorage"` should show persisted extension state). Some auth lives in the OS keychain - that's per-user, so it follows automatically as long as you're running as the same user. diff --git a/.agents/skills/launch/scripts/launch.sh b/.agents/skills/launch/scripts/launch.sh index 644389620cb..7d4aff965f3 100755 --- a/.agents/skills/launch/scripts/launch.sh +++ b/.agents/skills/launch/scripts/launch.sh @@ -150,8 +150,48 @@ LOG_FILE="$RUN_DIR/code.log" echo "[launch.sh] launching: $CODE_SH ${ARGS[*]}" >&2 echo "[launch.sh] logs: $LOG_FILE" >&2 -nohup "$CODE_SH" "${ARGS[@]}" >"$LOG_FILE" 2>&1 & +# Run pre-launch (electron download, compile-if-missing, built-in extensions) in the +# foreground so any errors surface synchronously. Then skip code.sh's own pre-launch. +echo "[launch.sh] running pre-launch (ensures electron + compiled output + built-ins)..." >&2 +if ! ( cd "$REPO" && node build/lib/preLaunch.ts ) >>"$LOG_FILE" 2>&1; then + echo "[launch.sh] pre-launch FAILED. Log tail:" >&2 + tail -n 80 "$LOG_FILE" >&2 + exit 1 +fi + +# Launch code.sh in the background. Detaching with `nohup ... & disown` is +# sufficient: by the time we return below, CDP is up and Electron is fully +# forked into its own process tree, so it's robust to its launching shell +# going away. (Earlier failures came from returning while Electron was still +# mid-bootstrap, not from process-group concerns.) +nohup env VSCODE_SKIP_PRELAUNCH=1 "$CODE_SH" "${ARGS[@]}" \ + >"$LOG_FILE" 2>&1 & PID=$! +disown $PID 2>/dev/null || true + +# Block until the renderer's CDP endpoint is responding so the caller can attach +# immediately. If code.sh dies or we time out, dump the log so the failure is +# visible. +echo "[launch.sh] waiting for CDP on port $CDP_PORT (timeout 90s)..." >&2 +READY=0 +for i in $(seq 1 90); do + if ! kill -0 "$PID" 2>/dev/null; then + echo "[launch.sh] code.sh (PID $PID) exited before CDP came up. Log tail:" >&2 + tail -n 80 "$LOG_FILE" >&2 + exit 1 + fi + if curl -sf -o /dev/null --max-time 1 "http://127.0.0.1:$CDP_PORT/json/version" 2>/dev/null; then + READY=1 + echo "[launch.sh] CDP ready after ${i}s" >&2 + break + fi + sleep 1 +done +if [[ "$READY" != "1" ]]; then + echo "[launch.sh] timed out waiting for CDP on port $CDP_PORT. Log tail:" >&2 + tail -n 80 "$LOG_FILE" >&2 + exit 1 +fi node -e ' console.log(JSON.stringify({ diff --git a/package.json b/package.json index 11c810a57da..4a4ea3726c6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "check-cyclic-dependencies": "node build/lib/checkCyclicDependencies.ts out", "preinstall": "node build/npm/preinstall.ts", "postinstall": "node build/npm/postinstall.ts", - "compile": "npm run gulp compile", + "compile": "npm-run-all2 -lp compile-client compile-copilot", + "compile-client": "npm run gulp compile", + "compile-copilot": "npm --prefix extensions/copilot run compile", "compile-check-ts-native": "tsgo --project ./src/tsconfig.json --noEmit --skipLibCheck", "watch": "npm-run-all2 -lp watch-client-transpile watch-client watch-extensions watch-copilot", "watchd": "deemon npm run watch",