diff --git a/.agents/skills/launch/SKILL.md b/.agents/skills/launch/SKILL.md index 3f8e1a6a3fc..20f207907ac 100644 --- a/.agents/skills/launch/SKILL.md +++ b/.agents/skills/launch/SKILL.md @@ -372,20 +372,3 @@ Verify it's gone: # Confirm no process is listening on the debug port lsof -i :9224 # should return nothing ``` - -## Quick Test: Send a Chat Message via Agent Host - -There's a helper script that automates the full flow — launch Code OSS, switch to agent host mode, send a message, and print the response: - -```bash -# From the repo root: -./src/vs/platform/agentHost/test/node/createAndSendMessageAsLocalAgent.sh "Hello, what can you do?" - -# Options: -# --port CDP port (default: 9224) -# --timeout Response wait in seconds (default: 30) -# --no-kill Keep Code OSS running after -# --skip-launch Connect to already-running instance -``` - -This uses the JS mouse-event focus + `press`-per-key approach internally, handles session target switching, and cleans up on exit. diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index f0c868857e5..c772efa8e4d 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -61,8 +61,8 @@ async function main(buildDir?: string) { crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(base, '@github', `copilot-${plat}`)); // @github/copilot/prebuilds/{platform} (pty.node, spawn-helper) crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(base, '@github', 'copilot', 'prebuilds', plat)); - // @github/copilot/sdk/ripgrep/bin/{platform} (rg binary) - crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(base, '@github', 'copilot', 'sdk', 'ripgrep', 'bin', plat)); + // @github/copilot/ripgrep/bin/{platform} (rg binary) + crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(base, '@github', 'copilot', 'ripgrep', 'bin', plat)); } } @@ -76,8 +76,8 @@ async function main(buildDir?: string) { '**/node_modules.asar.unpacked/@github/copilot-darwin-arm64/**', '**/node_modules/@github/copilot/prebuilds/darwin-x64/**', '**/node_modules/@github/copilot/prebuilds/darwin-arm64/**', - '**/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-x64/**', - '**/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-arm64/**', + '**/node_modules/@github/copilot/ripgrep/bin/darwin-x64/**', + '**/node_modules/@github/copilot/ripgrep/bin/darwin-arm64/**', ]; await makeUniversalApp({ @@ -87,7 +87,7 @@ async function main(buildDir?: string) { outAppPath, force: true, mergeASARs: true, - x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node,**/node_modules/@github/copilot-darwin-*/copilot,**/node_modules/@github/copilot/prebuilds/darwin-*/*,**/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-*/*}', + x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node,**/node_modules/@github/copilot-darwin-*/copilot,**/node_modules/@github/copilot/prebuilds/darwin-*/*,**/node_modules/@github/copilot/ripgrep/bin/darwin-*/*}', filesToSkipComparison: (file: string) => { for (const expected of filesToSkip) { if (minimatch(file, expected)) { diff --git a/build/darwin/verify-macho.ts b/build/darwin/verify-macho.ts index 3043d1c1e34..d83d2499056 100644 --- a/build/darwin/verify-macho.ts +++ b/build/darwin/verify-macho.ts @@ -33,7 +33,7 @@ const FILES_TO_SKIP = [ '**/node_modules.asar.unpacked/@github/copilot-darwin-arm64/**', // Copilot prebuilds and ripgrep: single-arch binaries in per-platform directories '**/node_modules/@github/copilot/prebuilds/darwin-*/**', - '**/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-*/**', + '**/node_modules/@github/copilot/ripgrep/bin/darwin-*/**', ]; function isFileSkipped(file: string): boolean { diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index a851cdc15ff..f74f2b06c10 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -721,7 +721,7 @@ function patchWin32DependenciesTask(destinationFolderName: string) { * new system dependency requirements. * * node-pty: `prebuilds/{platform}-{arch}/` (pty.node + spawn-helper) - * ripgrep: `sdk/ripgrep/bin/{platform}-{arch}/` (rg binary) + * ripgrep: `ripgrep/bin/{platform}-{arch}/` (rg binary) */ function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFolderName: string) { const outputDir = path.join(path.dirname(root), destinationFolderName); @@ -731,6 +731,17 @@ function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFo ? path.join(outputDir, `${product.nameLong}.app`, 'Contents', 'Resources', 'app') : path.join(outputDir, 'resources', 'app'); + // On stable builds the copilot SDK is stripped entirely -- nothing to copy into. + const copilotDir = path.join(appBase, 'node_modules', '@github', 'copilot'); + if (!fs.existsSync(copilotDir)) { + const quality = (product as { quality?: string }).quality; + if (quality && quality !== 'stable') { + throw new Error(`[copyCopilotNativeDeps] Copilot SDK directory not found at ${copilotDir} -- unexpected for ${quality} build`); + } + console.log(`[copyCopilotNativeDeps] Skipping -- copilot SDK not present (stable build)`); + return; + } + const platformArch = `${platform === 'win32' ? 'win32' : platform}-${arch}`; // Copy node-pty (pty.node + spawn-helper) into copilot prebuilds @@ -743,10 +754,10 @@ function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFo console.log(`[copyCopilotNativeDeps] Copied node-pty to ${copilotPrebuildsDir}`); } - // Copy ripgrep (rg binary) into copilot sdk/ripgrep + // Copy ripgrep (rg binary) into copilot ripgrep const rgBinary = platform === 'win32' ? 'rg.exe' : 'rg'; const ripgrepSource = path.join(appBase, 'node_modules', '@vscode', 'ripgrep', 'bin', rgBinary); - const copilotRipgrepDir = path.join(appBase, 'node_modules', '@github', 'copilot', 'sdk', 'ripgrep', 'bin', platformArch); + const copilotRipgrepDir = path.join(appBase, 'node_modules', '@github', 'copilot', 'ripgrep', 'bin', platformArch); if (fs.existsSync(ripgrepSource)) { fs.mkdirSync(copilotRipgrepDir, { recursive: true }); diff --git a/src/vs/platform/agentHost/test/node/createAndSendMessageAsLocalAgent.sh b/src/vs/platform/agentHost/test/node/createAndSendMessageAsLocalAgent.sh deleted file mode 100755 index 8a55277df90..00000000000 --- a/src/vs/platform/agentHost/test/node/createAndSendMessageAsLocalAgent.sh +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env bash -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -# Launches Code OSS, switches to Local Agent mode, sends a chat message, -# waits for the response, and prints it to stdout. -# -# Usage: -# ./createAndSendMessageAsLocalAgent.sh "Hello, what can you do?" -# ./createAndSendMessageAsLocalAgent.sh --port 9225 "Explain this code" -# -# Options: -# --port CDP debugging port (default: 9224) -# --timeout Seconds to wait for response (default: 30) -# --no-kill Don't kill Code OSS after the test -# --skip-launch Assume Code OSS is already running on the given port -# -# Requires: agent-browser (npm install -g agent-browser, or use npx) - -set -e - -ROOT="$(cd "$(dirname "$0")/../../../../../.." && pwd)" -CDP_PORT=9224 -RESPONSE_TIMEOUT=30 -KILL_AFTER=true -SKIP_LAUNCH=false -MESSAGE="" - -# Parse arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --port) - CDP_PORT="$2" - shift 2 - ;; - --timeout) - RESPONSE_TIMEOUT="$2" - shift 2 - ;; - --no-kill) - KILL_AFTER=false - shift - ;; - --skip-launch) - SKIP_LAUNCH=true - shift - ;; - -*) - echo "Unknown option: $1" >&2 - exit 1 - ;; - *) - MESSAGE="$1" - shift - ;; - esac -done - -if [ -z "$MESSAGE" ]; then - echo "Usage: $0 [--port ] [--timeout ] [--no-kill] [--skip-launch] " >&2 - exit 1 -fi - -AB="npx agent-browser" - -cleanup() { - if [ "$KILL_AFTER" = true ] && [ "$SKIP_LAUNCH" = false ]; then - $AB close 2>/dev/null || true - local PID - PID=$(lsof -t -i :"$CDP_PORT" 2>/dev/null || true) - if [ -n "$PID" ]; then - kill "$PID" 2>/dev/null || true - fi - fi -} -trap cleanup EXIT - -# ---- Step 1: Launch Code OSS ------------------------------------------------ - -if [ "$SKIP_LAUNCH" = false ]; then - # Check if already running - if lsof -i :"$CDP_PORT" >/dev/null 2>&1; then - echo "ERROR: Port $CDP_PORT already in use. Use --skip-launch or --port " >&2 - exit 1 - fi - - echo "Launching Code OSS on CDP port $CDP_PORT..." >&2 - cd "$ROOT" - VSCODE_SKIP_PRELAUNCH=1 ./scripts/code.sh --remote-debugging-port="$CDP_PORT" &>/dev/null & - - # Wait for it to start - echo "Waiting for Code OSS to start..." >&2 - for i in $(seq 1 20); do - if $AB connect "$CDP_PORT" 2>/dev/null; then - break - fi - sleep 2 - if [ "$i" -eq 20 ]; then - echo "ERROR: Code OSS did not start within 40 seconds" >&2 - exit 1 - fi - done -else - echo "Connecting to existing Code OSS on port $CDP_PORT..." >&2 - $AB connect "$CDP_PORT" 2>/dev/null || { - echo "ERROR: Cannot connect to Code OSS on port $CDP_PORT" >&2 - exit 1 - } -fi - -echo "Connected to Code OSS" >&2 - -# ---- Step 2: Switch to Local Agent mode ------------------------------------- - -# Check current session target -CURRENT_TARGET=$($AB snapshot -i 2>&1 | grep "Set Session Target" | head -1) - -if ! echo "$CURRENT_TARGET" | grep -q "Local Agent"; then - echo "Switching to Local Agent mode..." >&2 - - # Find and click the session target button - TARGET_REF=$($AB snapshot -i 2>&1 | grep "Set Session Target" | head -1 | grep -o 'ref=e[0-9]*' | head -1 | sed 's/ref=//') - if [ -z "$TARGET_REF" ]; then - echo "ERROR: Cannot find session target button" >&2 - exit 1 - fi - $AB click "@$TARGET_REF" 2>/dev/null - sleep 0.5 - - # Navigate to Local Agent via arrow keys - # Menu items: Local (checked), Copilot CLI, Cloud, Local Agent, ... - $AB press ArrowDown 2>/dev/null # Copilot CLI - $AB press ArrowDown 2>/dev/null # Cloud - $AB press ArrowDown 2>/dev/null # Local Agent - $AB press Enter 2>/dev/null - sleep 0.5 - - # Verify - VERIFY=$($AB snapshot -i 2>&1 | grep "Set Session Target" | head -1) - if echo "$VERIFY" | grep -q "Local Agent"; then - echo "Switched to Local Agent mode" >&2 - else - echo "WARNING: Could not confirm Local Agent mode. Current: $VERIFY" >&2 - fi -else - echo "Already in Local Agent mode" >&2 -fi - -# ---- Step 3: Focus chat input and type message ------------------------------ - -echo "Sending message: $MESSAGE" >&2 - -# Focus chat input via JavaScript mouse events (universal approach) -$AB eval ' -(() => { - const sidebar = document.querySelector(".part.auxiliarybar"); - if (!sidebar) return "no sidebar"; - const inputPart = sidebar.querySelector(".interactive-input-part"); - if (!inputPart) return "no input part"; - const editor = inputPart.querySelector(".monaco-editor"); - if (!editor) return "no editor"; - const rect = editor.getBoundingClientRect(); - const x = rect.x + rect.width / 2; - const y = rect.y + rect.height / 2; - editor.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, clientX: x, clientY: y })); - editor.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, clientX: x, clientY: y })); - editor.dispatchEvent(new MouseEvent("click", { bubbles: true, clientX: x, clientY: y })); - return "focused"; -})()' >/dev/null 2>&1 - -sleep 0.3 - -# Clear any existing text -$AB press Meta+a 2>/dev/null -$AB press Backspace 2>/dev/null - -# Type message character by character -for (( i=0; i<${#MESSAGE}; i++ )); do - CHAR="${MESSAGE:$i:1}" - case "$CHAR" in - " ") $AB press Space 2>/dev/null ;; - "?") $AB press Shift+/ 2>/dev/null ;; - "!") $AB press Shift+1 2>/dev/null ;; - ",") $AB press , 2>/dev/null ;; - ".") $AB press . 2>/dev/null ;; - "'") $AB press "'" 2>/dev/null ;; - '"') $AB press 'Shift+'"'" 2>/dev/null ;; - *) $AB press "$CHAR" 2>/dev/null ;; - esac -done - -# Verify text entered -ENTERED=$($AB eval ' -(() => { - const sidebar = document.querySelector(".part.auxiliarybar"); - const viewLines = sidebar?.querySelectorAll(".interactive-input-editor .view-line"); - return Array.from(viewLines || []).map(vl => vl.textContent).join(""); -})()' 2>&1 | tr -d '"') - -echo "Entered text: $ENTERED" >&2 - -# Send the message -$AB press Enter 2>/dev/null - -# ---- Step 4: Wait for response ---------------------------------------------- - -echo "Waiting for response (timeout: ${RESPONSE_TIMEOUT}s)..." >&2 - -RESPONSE="" -for i in $(seq 1 "$RESPONSE_TIMEOUT"); do - sleep 1 - RESPONSE=$($AB eval ' -(() => { - const sidebar = document.querySelector(".part.auxiliarybar"); - if (!sidebar) return ""; - const items = sidebar.querySelectorAll(".interactive-item-container"); - if (items.length < 2) return ""; - // Last item is the response - const lastItem = items[items.length - 1]; - const text = lastItem.textContent || ""; - // Check if it looks like a complete response (has content beyond the header) - if (text.length > 20) return text; - return ""; -})()' 2>&1 | sed 's/^"//;s/"$//') - - if [ -n "$RESPONSE" ]; then - break - fi -done - -if [ -z "$RESPONSE" ]; then - echo "ERROR: No response received within ${RESPONSE_TIMEOUT}s" >&2 - exit 1 -fi - -# ---- Step 5: Output response ------------------------------------------------ - -echo "---" >&2 -echo "$RESPONSE"