Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/instructions/remoteAgentHost.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: Architecture documentation for remote agent host connections. Use when working in `src/vs/sessions/contrib/remoteAgentHost`
applyTo: src/vs/sessions/contrib/remoteAgentHost/**
---

# Remote Agent Host

The remote agent host feature connects the sessions app to agent host processes running on other machines over WebSocket.

## Key Files

- `ARCHITECTURE.md` - full architecture documentation (URI conventions, registration flow, data flow diagram)
- `REMOTE_AGENT_HOST_RECONNECTION.md` - reconnection lifecycle spec (15 numbered requirements)
- `browser/remoteAgentHost.contribution.ts` - central orchestrator
- `browser/agentHostFileSystemProvider.ts` - read-only FS provider for remote browsing

## Architecture Documentation

When making changes to this feature area, **review and update `ARCHITECTURE.md`** if your changes affect:

- Connection lifecycle (connect, disconnect, reconnect)
- Agent registration flow
- URI conventions or naming
- Session creation flow
- The data flow diagram

The doc lives at `src/vs/sessions/contrib/remoteAgentHost/ARCHITECTURE.md`.

## Related Code Outside This Folder

- `src/vs/platform/agentHost/common/remoteAgentHostService.ts` - service interface (`IRemoteAgentHostService`)
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostServiceImpl.ts` - Electron implementation
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostProtocolClient.ts` - WebSocket protocol client
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts` - session list sidebar
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts` - session content provider
87 changes: 87 additions & 0 deletions .github/skills/unit-tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
name: unit-tests
description: Use when running unit tests in the VS Code repo. Covers the runTests tool, scripts/test.sh (macOS/Linux) and scripts/test.bat (Windows), and their supported arguments for filtering, globbing, and debugging tests.
---

# Running Unit Tests

## Preferred: Use the `runTests` tool

If the `runTests` tool is available, **prefer it** over running shell commands. It provides structured output with detailed pass/fail information and supports filtering by file and test name.

- Pass absolute paths to test files via the `files` parameter.
- Pass test names via the `testNames` parameter to filter which tests run.
- Set `mode="coverage"` to collect coverage.

Example (conceptual): run tests in `src/vs/editor/test/common/model.test.ts` with test name filter `"should split lines"`.

## Fallback: Shell scripts

When the `runTests` tool is not available (e.g. in CLI environments), use the platform-appropriate script from the repo root:

- **macOS / Linux:** `./scripts/test.sh [options]`
- **Windows:** `.\scripts\test.bat [options]`

These scripts download Electron if needed and launch the Mocha test runner.

### Commonly used options

#### `--run <file>` - Run tests from a specific file

Accepts a **source file path** (starting with `src/`). The runner strips the `src/` prefix and the `.ts`/`.js` extension automatically to resolve the compiled module.

```bash
./scripts/test.sh --run src/vs/editor/test/common/model.test.ts
```

Multiple files can be specified by repeating `--run`:

```bash
./scripts/test.sh --run src/vs/editor/test/common/model.test.ts --run src/vs/editor/test/common/range.test.ts
```

#### `--grep <pattern>` (aliases: `-g`, `-f`) - Filter tests by name

Runs only tests whose full title matches the pattern (passed to Mocha's `--grep`).

```bash
./scripts/test.sh --grep "should split lines"
```

Combine with `--run` to filter tests within a specific file:

```bash
./scripts/test.sh --run src/vs/editor/test/common/model.test.ts --grep "should split lines"
```

#### `--runGlob <pattern>` (aliases: `--glob`, `--runGrep`) - Run tests matching a glob

Runs all test files matching a glob pattern against the compiled output directory. Useful for running all tests under a feature area.

```bash
./scripts/test.sh --runGlob "**/editor/test/**/*.test.js"
```

Note: the glob runs against compiled `.js` files in the output directory, not source `.ts` files.

#### `--coverage` - Generate a coverage report

```bash
./scripts/test.sh --run src/vs/editor/test/common/model.test.ts --coverage
```

#### `--timeout <ms>` - Set test timeout

Override the default Mocha timeout for long-running tests.

```bash
./scripts/test.sh --run src/vs/editor/test/common/model.test.ts --timeout 10000
```

### Integration tests

Integration tests (files ending in `.integrationTest.ts` or located in `extensions/`) are **not run** by `scripts/test.sh`. Use `scripts/test-integration.sh` (or `scripts/test-integration.bat`) instead.

### Compilation requirement

Tests run against compiled JavaScript output. Ensure the `VS Code - Build` watch task is running or that compilation has completed before running tests. Test failures caused by stale output are a common pitfall.
17 changes: 8 additions & 9 deletions .github/workflows/sessions-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
name: Sessions E2E Tests

# Disabled: Flaky
# on:
# pull_request:
# branches:
# - main
# - 'release/*'
# paths:
# - 'src/vs/sessions/**'
# - 'scripts/code-sessions-web.*'
on:
pull_request:
branches:
- main
- 'release/*'
paths:
- 'src/vs/sessions/**'
- 'scripts/code-sessions-web.*'

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion ThirdPartyNotices.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2277,7 +2277,7 @@ written authorization of the copyright holder.

---------------------------------------------------------

vscode-codicons 0.0.41 - MIT and Creative Commons Attribution 4.0
vscode-codicons 0.0.46-0 - MIT and Creative Commons Attribution 4.0
https://github.com/microsoft/vscode-codicons

Attribution 4.0 International
Expand Down
2 changes: 1 addition & 1 deletion cgmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@
}
},
"license": "MIT and Creative Commons Attribution 4.0",
"version": "0.0.41"
"version": "0.0.46-0"
},
{
"component": {
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"@microsoft/1ds-post-js": "^3.2.13",
"@parcel/watcher": "^2.5.6",
"@types/semver": "^7.5.8",
"@vscode/codicons": "^0.0.45-14",
"@vscode/codicons": "^0.0.46-0",
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/native-watchdog": "^1.4.6",
Expand Down
8 changes: 4 additions & 4 deletions remote/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion remote/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dependencies": {
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@vscode/codicons": "^0.0.45-14",
"@vscode/codicons": "^0.0.46-0",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/tree-sitter-wasm": "^0.3.0",
"@vscode/vscode-languagedetection": "1.0.23",
Expand Down
1 change: 1 addition & 0 deletions src/vs/base/common/codiconsLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,4 +656,5 @@ export const codiconsLibrary = {
claude: register('claude', 0xec82),
openInWindow: register('open-in-window', 0xec83),
newSession: register('new-session', 0xec84),
terminalSecure: register('terminal-secure', 0xec85),
} as const;
36 changes: 20 additions & 16 deletions src/vs/sessions/contrib/remoteAgentHost/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,13 @@ Remote addresses are encoded into URI-safe authority strings via
`agentHostAuthority(address)`:

- Alphanumeric addresses pass through unchanged
- Others are url-safe base64 encoded with a `b64-` prefix
- "Normal" addresses (`[a-zA-Z0-9.:-]`) get colons replaced with `__`
- Everything else is url-safe base64 encoded with a `b64-` prefix

Example: `http://127.0.0.1:3000` → `b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`
Examples:
- `localhost:8081` → `localhost__8081`
- `192.168.1.1:8080` → `192.168.1.1__8080`
- `http://127.0.0.1:3000` → `b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`

## Agent Registration

Expand All @@ -110,10 +114,10 @@ When `_registerAgent()` is called for a discovered copilot agent from address `X

| Concept | Value | Example |
|---------|-------|---------|
| **Authority** | `agentHostAuthority(address)` | `b64-aHR0cA` |
| **Session type** | `remote-${authority}-${provider}` | `remote-b64-aHR0cA-copilot` |
| **Agent ID** | same as session type | `remote-b64-aHR0cA-copilot` |
| **Vendor** | same as session type | `remote-b64-aHR0cA-copilot` |
| **Authority** | `agentHostAuthority(address)` | `localhost__8081` |
| **Session type** | `remote-${authority}-${provider}` | `remote-localhost__8081-copilot` |
| **Agent ID** | same as session type | `remote-localhost__8081-copilot` |
| **Vendor** | same as session type | `remote-localhost__8081-copilot` |
| **Display name** | `configuredName \|\| "${displayName} (${address})"` | `dev-box` |

### Four Registrations Per Agent
Expand All @@ -134,23 +138,23 @@ When `_registerAgent()` is called for a discovered copilot agent from address `X

4. **Language model provider** - `AgentHostLanguageModelProvider` registers
models under the vendor descriptor. Model IDs are prefixed with the session
type (e.g., `remote-b64-xxx-copilot:claude-sonnet-4-20250514`).
type (e.g., `remote-localhost__8081-copilot:claude-sonnet-4-20250514`).

## URI Conventions

| Context | Scheme | Format | Example |
|---------|--------|--------|---------|
| New session resource | `<sessionType>` | `<sessionType>:/untitled-<uuid>` | `remote-b64-xxx-copilot:/untitled-abc` |
| Existing session | `<sessionType>` | `<sessionType>:/<rawId>` | `remote-b64-xxx-copilot:/abc-123` |
| New session resource | `<sessionType>` | `<sessionType>:/untitled-<uuid>` | `remote-localhost__8081-copilot:/untitled-abc` |
| Existing session | `<sessionType>` | `<sessionType>:/<rawId>` | `remote-localhost__8081-copilot:/abc-123` |
| Backend session state | `<provider>` | `<provider>:/<rawId>` | `copilot:/abc-123` |
| Root state subscription | (string) | `agenthost:/root` | - |
| Remote filesystem | `agenthost` | `agenthost://<authority>/<path>` | `agenthost://b64-aHR0cA/home/user/project` |
| Language model ID | - | `<sessionType>:<rawModelId>` | `remote-b64-xxx-copilot:claude-sonnet-4-20250514` |
| Remote filesystem | `agenthost` | `agenthost://<authority>/<path>` | `agenthost://localhost__8081/home/user/project` |
| Language model ID | - | `<sessionType>:<rawModelId>` | `remote-localhost__8081-copilot:claude-sonnet-4-20250514` |

### Key distinction: session resource vs backend session URI

- The **session resource** URI uses the session type as its scheme
(e.g., `remote-b64-xxx-copilot:/untitled-abc`). This is the URI visible to
(e.g., `remote-localhost__8081-copilot:/untitled-abc`). This is the URI visible to
the chat UI and session management.
- The **backend session** URI uses the provider as its scheme
(e.g., `copilot:/abc-123`). This is sent over the agent host protocol to the
Expand All @@ -173,8 +177,8 @@ remote host, then picks a folder on the remote filesystem. This produces a
`SessionWorkspace` with an `agenthost://` URI:

```
agenthost://b64-aHR0cA/home/user/myproject
↑ authority ↑ remote filesystem path
agenthost://localhost__8081/home/user/myproject
↑ authority ↑ remote filesystem path
```

### 2. Session Target Resolution
Expand All @@ -184,7 +188,7 @@ resolves the matching session type via `getRemoteAgentHostSessionTarget()`
(defined in `remoteAgentHost.contribution.ts`):

```typescript
// authority "b64-aHR0cA" → find connection → "remote-b64-aHR0cA-copilot"
// authority "localhost__8081" → find connection → "remote-localhost__8081-copilot"
const target = getRemoteAgentHostSessionTarget(connections, authority);
```

Expand All @@ -194,7 +198,7 @@ const target = getRemoteAgentHostSessionTarget(connections, authority);

```typescript
URI.from({ scheme: target, path: `/untitled-${generateUuid()}` })
// → remote-b64-aHR0cA-copilot:/untitled-abc-123
// → remote-localhost__8081-copilot:/untitled-abc-123
```

### 4. Session Object Creation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,21 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
* Encode a remote address into an identifier that is safe for use in
* both URI schemes and URI authorities, and is collision-free.
*
* If the address contains only alphanumeric characters it is returned as-is.
* Otherwise it is url-safe base64-encoded (no padding) to guarantee the
* result contains only `[A-Za-z0-9_-]`.
* Three tiers:
* 1. Purely alphanumeric addresses are returned as-is.
* 2. "Normal" addresses containing only `[a-zA-Z0-9.:-]` get colons
* replaced with `__` (double underscore) for human readability.
* Addresses containing `_` skip this tier to keep the encoding
* collision-free (`__` can only appear from colon replacement).
* 3. Everything else is url-safe base64-encoded with a `b64-` prefix.
*/
export function agentHostAuthority(address: string): string {
if (/^[a-zA-Z0-9]+$/.test(address)) {
return address;
}
if (/^[a-zA-Z0-9.:\-]+$/.test(address)) {
return address.replaceAll(':', '__');
}
return 'b64-' + encodeBase64(VSBuffer.fromString(address), false, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,26 @@ suite('AgentHostAuthority - encoding', () => {
assert.strictEqual(agentHostAuthority('localhost'), 'localhost');
});

test('address with special characters is base64-encoded', () => {
const authority = agentHostAuthority('localhost:8081');
assert.ok(authority.startsWith('b64-'));
test('normal host:port address uses human-readable encoding', () => {
assert.strictEqual(agentHostAuthority('localhost:8081'), 'localhost__8081');
assert.strictEqual(agentHostAuthority('192.168.1.1:8080'), '192.168.1.1__8080');
assert.strictEqual(agentHostAuthority('my-host:9090'), 'my-host__9090');
assert.strictEqual(agentHostAuthority('host.name:80'), 'host.name__80');
});

test('address with underscore falls through to base64', () => {
const authority = agentHostAuthority('host_name:8080');
assert.ok(authority.startsWith('b64-'), `expected base64 for underscore address, got: ${authority}`);
});

test('address with exotic characters is base64-encoded', () => {
assert.ok(agentHostAuthority('user@host:8080').startsWith('b64-'));
assert.ok(agentHostAuthority('host with spaces').startsWith('b64-'));
assert.ok(agentHostAuthority('http://myhost:3000').startsWith('b64-'));
});

test('different addresses produce different authorities', () => {
const cases = ['localhost:8080', 'localhost:8081', '192.168.1.1:8080', 'host-name:80', 'host.name:80'];
const cases = ['localhost:8080', 'localhost:8081', '192.168.1.1:8080', 'host-name:80', 'host.name:80', 'host_name:80', 'user@host:8080'];
const results = cases.map(agentHostAuthority);
const unique = new Set(results);
assert.strictEqual(unique.size, cases.length, 'all authorities must be unique');
Expand Down
1 change: 1 addition & 0 deletions src/vs/sessions/sessions.desktop.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ import './contrib/workspace/browser/workspace.contribution.js';
import './contrib/welcome/browser/welcome.contribution.js';

// Remote Agent Host
import '../platform/agentHost/electron-browser/agentHostService.js';
import '../platform/agentHost/electron-browser/remoteAgentHostService.js';
import './contrib/remoteAgentHost/browser/remoteAgentHost.contribution.js';

Expand Down
1 change: 1 addition & 0 deletions src/vs/sessions/sessions.web.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ import '../workbench/contrib/issue/browser/issue.contribution.js';
import '../workbench/contrib/splash/browser/splash.contribution.js';
import '../workbench/contrib/remote/browser/remoteStartEntry.contribution.js';
import '../workbench/contrib/processExplorer/browser/processExplorer.web.contribution.js';
import '../workbench/contrib/browserView/browser/browserView.contribution.js';

//#endregion

Expand Down
Loading
Loading