mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 00:09:30 +01:00
Merge remote-tracking branch 'origin/roblou/agent-host' into connor4312/agent-host-server
This commit is contained in:
@@ -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 <N> CDP port (default: 9224)
|
||||
# --timeout <N> 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.
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 <N> CDP debugging port (default: 9224)
|
||||
# --timeout <N> 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 <N>] [--timeout <N>] [--no-kill] [--skip-launch] <message>" >&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 <other>" >&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"
|
||||
Reference in New Issue
Block a user