* agentHost: Respect telemetry disablement during initialization
Send each client's effective telemetry level with initialize and reconnect so the host applies consent before connection telemetry or queued actions. Keep the host disabled until a client level arrives, and propagate process-level restrictions to generic remote telemetry.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: Preserve telemetry wrapper defaults for direct callers
Keep fail-closed startup explicit to the production factory while preserving the established constructor behavior used by isolated Agent Host components and tests.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: Gate telemetry on client consent
Keep seeded root configuration from enabling telemetry before initialize or reconnect provides a client telemetry level. Preserve existing direct-construction defaults for isolated callers and tests.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: Propagate telemetry level at process launch
Start controlled Agent Host processes with the launcher's effective telemetry level so opted-in clients retain early diagnostics while opted-out clients disable telemetry before startup. Keep initialize and reconnect updates as a monotonic multi-client clamp across local, remote-server, SSH, WSL, and CLI-supervised hosts.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update generated extension point cache
Include the link presentation provider extension point generated by hygiene after merging origin/main.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: Derive SSH telemetry in shared process
Use the shared process telemetry service when launching SSH and WSL Agent Hosts instead of threading the telemetry level through renderer IPC contracts.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agent host: keep the idle timeout guard for the full websocket session
The `--idle-timeout` activity guard was released when a connection upgraded to a WebSocket, not
when the client disconnected. `serve_connection_with_upgrades` completes as soon as it hands the
transport to `hyper::upgrade::on`, so the task that held the guard ended a few milliseconds after
a WebSocket started. The idle timer then counted zero clients, armed, and 300 seconds later
stopped a supervisor that was still serving that client. The shutdown calls `kill_tree`, which
also stopped the agent host server and its child processes, so in-flight turns were lost.
Remote hosts showed this as a continuous restart loop. Agent host logs from one report contain 10
process starts in 50 minutes, each 297-299 seconds after its tunnel connection was established,
with no graceful shutdown message and a tool completion as the last line written. Connection
lifetime did not change with traffic: connections that carried 200 and 7,866 messages both ended
at approximately 305 seconds, and local (non-tunnel) connections in the same logs stayed up for
more than 2,900 seconds.
- Adds `idle_timeout::GuardedStream`, which holds the `ClientGuard` in the connection transport
instead of the task that serves the connection. `hyper::upgrade::Upgraded` keeps the wrapped
stream, so the guard now lives until the socket closes.
- Uses the wrapper in `AgentHostSidecar::serve` and in `serve_tunnel_connection`, so local and
tunnel-relayed clients both report activity for their full connection.
- Takes an `Option<ClientGuard>` so callers wrap unconditionally and keep one stream type whether
or not the idle timeout is enabled.
- Adds a regression test that upgrades a real WebSocket, exchanges a message, and makes sure that
no disconnect is reported while the session is open and that the disconnect is still reported
when the socket closes. The existing activity test used a bare TCP connection and could not
find this problem.
(Commit message generated by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agent host: guard the dev-tunnel-hosted gateway connections
Addresses review feedback: the first change covered `AgentHostSidecar::serve_tunnel_connection`,
which has no call sites. The live `--tunnel` path in `run_supervisor` hands each relayed socket
straight to `serve_agent_host_tunnel_connection`, so those connections were still unguarded.
The gateway's inner dial back into our own listener does not close the gap. It only applies when a
selection resolves to this supervisor. A client that is still deciding what to select, or whose
selection resolves to a different live endpoint or a new dedicated host, never reaches our accept
loop, so the supervisor that owns the tunnel counted zero clients and could stop itself while it
was actively proxying that client.
- Adds `AgentHostSidecar::activity_tracker`, which gives callers that serve connections the
sidecar did not accept a handle to report activity.
- Wraps each relayed tunnel socket in `run_supervisor` with `idle_timeout::GuardedStream`, so the
guard survives the WebSocket upgrade and lasts for the full proxied session.
- Corrects the comment that said the inner self-dial was sufficient.
- Adds a regression test that selects a different endpoint through the real tunnel router and
exchanges frames, which fails if the guard is held by the serving task instead of the transport.
- Adds a persistent echo endpoint helper so a test can tell "session still live" apart from "the
target hung up".
(Commit message generated by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Distinct dev tunnels can share a display name while having different
`tunnelId`/`clusterId` values, so matching the hosted tunnel by name could
hide, reset the reconnect state of, or disconnect an unrelated remote tunnel.
`ITunnelHostInfo` was slimmed to just the name, leaving nothing stable to
compare, so the tunnel's dev tunnels ID is now carried from the CLI through
the machine-status stream to `sharingInfo`. Attaching clients read it from
the singleton's status, alongside the capability flag.
Both comparison sites share one `isTunnelHosted` helper. The ID is optional
throughout: when it is absent, because an older CLI reported the tunnel, the
helper falls back to the previous name comparison rather than silently failing
to match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`has_editor_link` described the attaching invocation rather than the
singleton it attached to, so a full-access invocation attaching to an
agent-host-only singleton advertised an editor link that singleton does not
serve — in both the human log and the machine status.
The singleton now reports what it actually serves in its status, and clients
use that. The field is optional so a client attached to a server predating it
falls back to describing its own invocation rather than losing a valid link.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* tunnels: host the agent host with the code-tunnel CLI
Replaces the TypeScript dev-tunnels SDK hosting path with the `code-tunnel` binary, and gives the
shared process a single owner for the tunnel process. The editor no longer creates, adopts, or
reconciles dev tunnels itself: the CLI owns naming, reuse, and lifetime, and the editor supplies
only intent.
- Adds `--agent-host-only` to `code tunnel`, which serves the agent-host port without the control
port, so remote session sharing does not also grant full remote editor access.
- Adds `--delegate-to-editor`, which pins the selection gateway to the live editor agent host and
stops it from starting a dedicated agent host. A dedicated host behind an editor-bound tunnel
outlives the tunnel and cannot be reached. Clients that do not send `delegatedInstanceId`, which
includes older editors and every background reconnect, get the bound host instead of an error.
- Adds `--user-data-dir` to `code tunnel`. The gateway read the platform default registry, so it
could not see the editor agent host in portable, custom, or development installations.
- Adds a machine-readable status stream, enabled with `VSCODE_CLI_MACHINE_STATUS`, and removes the
matching of human-readable output. The editor matched a string the CLI no longer prints, so
Remote Tunnel Access never became connected.
- Makes registry liveness require a reachable endpoint, not only a running process ID. Operating
systems reuse process IDs, so a dead entry could look alive and be selected in preference to the
live one.
- Adds `TunnelProcessCoordinator`, which owns the single tunnel process, the tunnel name, and the
CLI login. Both services previously started their own process with the same name, which made the
CLI fall back to a random name, and both logged in to the same credential store.
- Stops the editor from connecting to the tunnel that it hosts.
- Raises the Windows stack size for development builds only. The default 1MB main thread stack
overflows before `code tunnel` finishes starting.
Fixes https://github.com/microsoft/vscode/issues/319297
Fixes https://github.com/microsoft/vscode/issues/329985
(Commit message generated by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* cli: fix clippy lints and a registry read race on Windows
CI runs `cargo clippy -- -D warnings` without `--all-targets`, so lib-only
warnings fail the build. Fix the four it reported:
- use `?` instead of a match in `get_tunnel_web_url`
- drop a redundant rebinding of `delegate_to_editor`
- group `serve()`'s agent-host parameters into `AgentHostServeOptions`
- box both `GatewayTargetWs` variants (boxing only the larger one just
inverts the imbalance)
Separately, `read_registry` failed intermittently on Windows with
`PermissionDenied`. A file removed by a concurrent prune stays listed in the
directory until its last handle closes, and opening it in that window fails
with `PermissionDenied` rather than the `NotFound` the code already
handled. The error propagated out of `read_entry_file` and aborted the whole
read, so one unreadable entry hid every other endpoint. Per-entry read and
directory-enumeration failures are now logged and skipped instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* tunnels: address review feedback on CLI-hosted tunnels
Four fixes from PR review:
- Agent host sharing hard-rejected every non-GitHub request, but
`remote.tunnels.access.enableMicrosoftAuth` still exposes Microsoft
accounts and the renderer prefers them when enabled. Carry `authProvider`
through to `tunnel user login` instead of hard-coding GitHub.
- The pending service uninstall lived in one queued generation, so a
concurrent sharing update could preempt the reconcile that owed it and
leave the tunnel service installed. Persist it on the coordinator until an
uninstall succeeds.
- `getTunnelName()` is also called while access is inactive, to compare the
name this machine would use against a previously used one. Returning the
running tunnel's name yielded undefined and permanently skipped the
remote-extension recommendation. Expose the coordinator's intended name.
- Machine-status events were written straight to the emitting process's
stdout, so when the editor attached to an existing tunnel the singleton
server's token errors never reached it and token expiry was never
surfaced. Events are now always generated, relayed to attached clients
over a new singleton notification, and printed only where a process-global
stdout toggle is set.
Also converts a runtime protocol-version assertion added by this branch into
a const assertion, which `clippy --all-targets` rejects as an assertion on
a constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: refresh OSS SSH CLI and improve errors
Refresh non-pinned remote CLIs before reuse, preserve machine-readable endpoint output, tolerate legacy CLI log noise, and persist SSH setup failures to the shared log.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* cli: distinguish current version from update errors
Treat an already-current CLI as a successful update result so SSH refresh failures can be identified and logged reliably.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route directly hosted tunnels through the protocol-v6 endpoint gateway with the correct registry. Notify users after automatic editor-to-dedicated recovery over SSH or tunnels.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Discover and select editor or dedicated agent hosts over SSH and dev tunnels. Add structured CLI discovery and relay commands, tunnel protocol-v6 selection, and idle shutdown for remotely spawned standalone hosts.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Publish editor and standalone agent hosts through a shared user-data registry with cross-process locking. Update CLI discovery and management commands to work across all registered hosts without the legacy lockfile.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(server): propagate --enable-proposed-api in serve-web
The `code serve-web` CLI accepts `--enable-proposed-api EXTENSION_ID`
but never propagated the allowlist to the workbench environment service
or the server-side extension scanner. As a result, extensions declaring
`enabledApiProposals` in their package.json had their proposals wiped
at runtime by ExtensionsProposedApi and failed to activate.
This wires the flag through two paths so it matches desktop behavior:
1. Server scanner: extend IProductService.extensionsEnabledWithApiProposalVersion
in setupServerServices so the node extension scanner keeps the manifest's
enabledApiProposals when --enable-proposed-api is passed.
2. Workbench env service: add a new `enabledExtensionProposedApi` field to
IWorkbenchConstructionOptions, populate it from the CLI args in
webClientServer, and surface it via BrowserWorkbenchEnvironmentService
.extensionEnabledProposedApi so the runtime allowlist in
ExtensionsProposedApi matches the requested IDs.
Fixes#228781
* cli: forward --enable-proposed-api to serve-web subprocess
The Rust CLI parses --enable-proposed-api as a global EditorOptions flag
but was not forwarding it to the node server subprocess spawned by
serve-web. As a result, the flag never reached the server's argv parser
and the server-side plumbing in serverServices.ts and webClientServer.ts
had no input to act on.
Capture the flag in ConnectionManager from CommandContext and pass it
through StartArgs, then append --enable-proposed-api=<id> for each
requested extension ID when spawning the server process.
* fix(server): update proposed API propagation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Martin Aeschlimann <martinae@microsoft.com>
Co-authored-by: Dmitriy Vasyura <dmitriv@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AHP 0.4 moves turns/active_turn off SessionState onto per-chat ChatState
(SessionState now carries a `chats` catalog), and SessionStatus is now a
newtype bitset instead of a repr enum.
- agent_ps: use SessionStatus::bits()/from_bits().contains() instead of
`as u32` casts
- agent_stop: resolve in-progress chats from the session catalog, subscribe
to each chat to find its active turn, and dispatch the renamed
StateAction::ChatTurnCancelled to the chat URI
- agent_logs: print the session's chats catalog instead of the removed
turns/active_turn
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Kill full server process tree in agent host kill_running_server
The CLI agent host supervisor launches the server via a shell shim
(`<server>/bin/code-server-<quality>`) that spawns `node ... server-main.js`,
which in turn spawns a `bootstrap-fork` agent host process. The previous
`child.kill()` in `kill_running_server` only terminated the shim, leaving
the node descendants reparented to PID 1.
Route the shutdown through `kill_tree` instead, and have the non-Windows
`kill_tree` walk the full descendant tree via `pgrep -P` rather than just
the direct children. Add a depth-3 process-tree unit test on Unix.
Fixes#319516
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix flaky kill_tree test by reaping spawned child process
The previous test relied on `process_exists` to confirm pids were gone,
but on Linux the outer sh remains a zombie until its parent (the test
runtime) reaps it. Await `child.wait()` immediately after `kill_tree`
so the outer sh is reaped, and rely on init to reap the orphaned
descendants. Also bumps the deadline to 10s to absorb init latency.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bound kill_running_server wait and check pgrep exit status
Addresses Copilot review feedback:
- kill_running_server: bound child reap with a 5s timeout; escalate to
SIGKILL via Child::kill if a process ignores SIGTERM, so a misbehaving
server can't wedge the supervisor's shutdown or upgrade path.
- children_of: distinguish pgrep exit codes — 0/1 are expected (matches
vs no matches), 2/3 are surfaced as CommandFailed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* cli: support pinning initial agent host version for upgrade testing
- There was no way to exercise the agent host upgrade flow locally, since
the CLI always starts the latest server version and has nothing older to
upgrade from.
- Adds a VSCODE_CLI_INITIAL_AH_VERSION env var that pins the commit the
agent host is first downloaded and started at, while leaving subsequent
upgrades to resolve the real latest version, so the full download +
restart upgrade path can be driven on demand.
(Commit message generated by Copilot)
* cli: restrict initial agent host version override to hex digits
Addresses PR review: the env var value flows into URL and filesystem
paths, so restrict it to hex digits (and trim whitespace) to prevent
path separators or traversal sequences.
* cli: drop ahp-ws dependency in favor of direct tungstenite
- Removes the external ahp-ws crate so the CLI owns its WebSocket
transport directly, avoiding a thin wrapper dependency and keeping the
transport logic alongside the existing tunnel transport.
- Updates the agent commands to the ahp 0.2 API (channel-based params,
optional snapshots, and the two-argument dispatch signature) so the
CLI compiles against the pinned crate versions again.
(Commit message generated by Copilot)
* cli: deduplicate WebSocket transport adapter
- Replaces the separate WsTransport and TunnelWsTransport implementations
with a single generic WsTransport over the stream type plus an optional
keep-alive guard, so the direct and tunnel connections share one
send/recv/close adapter and cannot drift apart.
(Commit message generated by Copilot)
- ensure_supervisor_running now runs as a background task driven by
tokio::spawn, so the tunnel control server and command-shell can
start accepting connections immediately instead of waiting for the
supervisor to come up.
- handle_serve (and the agent-host port forwarder) await the shared
future on demand and mix the bridge endpoint into the per-request
code_server_args. Supervisor failure is logged as a warning so editing
and the extension host keep working; the renderer just misses
agentHostProxy.
- This eliminates the startup stall that was sporadically tripping
remote SSH's 450ms command-shell ready timeout.
Fixes#317714
(Commit message generated by Copilot)
When the VS Code server binary is extracted without execute permissions
or loses them (e.g. on network filesystems, after interrupted downloads,
or when copied without preserving permissions), spawning the server fails
with 'Permission denied'. The previous code treated this as generic
'ServerUnexpectedExit' corruption, triggering a useless re-download loop
that never fixes the actual problem.
Changes:
- Add ensure_executable() helper that checks and restores +x on Unix
before attempting to spawn the server binary.
- Add ServerNotExecutable error variant so permission failures are
reported clearly instead of being lumped into ServerUnexpectedExit.
- Skip the 'evict on corruption' logic for permission errors, since
re-downloading cannot fix a filesystem permission issue.
Fixes the root cause where VS Code Remote-SSH shows generic connection
failure popups while the actual error ('Permission denied on server
binary') is hidden in remote logs.
Replaces the exact-match version check in `ProtocolServerHandler._handleInitialize`
with a semver-caret negotiator (`negotiateProtocolVersion`) that picks the
highest offered version compatible with the server.
When negotiation still fails and the agent host was spawned by a managing
VS Code CLI (signalled by the new `VSCODE_AGENT_HOST_MANAGEMENT_SOCKET`
env var), the `UnsupportedProtocolVersion` error advertises a
`_meta.vscodeUpgradeMethod = "_vscodeUpgrade"` hint. The client can then
invoke that method on the same transport (callable pre-`initialize`) to
request a server upgrade.
The CLI runs a hyper-based HTTP control server on a unix socket / named
pipe. `POST /upgrade` synchronously downloads the latest release, returns
a serde-derived response (`{ok, upgradeNeeded, upgradeStarted,
runningCommit, latestCommit, restartDelayMs, error}`), and then schedules
a kill+respawn after a 3 s drain delay so the response can hop back
through the proxy before the transport drops. Single-flight via
`upgrade_in_progress: AtomicBool`; the listener is started lazily from
`AgentHostManager::start_server`.
UI:
- `RemoteAgentHostConnectionStatus.incompatible` carries `vscodeUpgradeMethod`
(read from `_meta`).
- A `watchForIncompatibleNotifications` autorun on each provider raises a
one-shot warning notification on transition into `incompatible`, with
"Update Server" (when the host advertised it) and "Show Options"
primary actions.
- `runServerUpgrade` is shared between the notification action and the
per-host quickpick. It drives a progress notification with a per-second
"Restarting in Ns..." countdown and observes `connectionStatus` so it
bails out if some other code path is already reconnecting.
The serve-web command's websocket proxy spawned the client-side hyper connection without `.with_upgrades()`, so `hyper::upgrade::on(&mut res)` rejected the upgrade with "upgrade expected but low level API in use" and the websocket failed to establish.
- Spawn `connection.with_upgrades()` in `forward_ws_req_to_server` to mirror the server side and the equivalent agent-host proxy.
Fixes https://github.com/microsoft/vscode/issues/315448
(Commit message generated by Copilot)
When the archive contained fewer than 20 entries, `archive.len() / 20`
evaluated to 0 and the subsequent `i % report_progress_every` operation
panicked with 'attempt to calculate the remainder with a divisor of zero'.
Clamp the divisor to a minimum of 1.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Shorten background update check interval from 24h to 6h so newly
published server versions are picked up sooner after auto-shutdown.
- Don't store the child into self.running when it exits before signaling
ready. Previously a failed startup left a dead child in running
forever, wedging ensure_server() so it could never restart the server.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Mark agent-host > agent as a native cli command
- Update to final tunnel URI format
- Lock to Github auth for agent host tunnels (matching agents app logic)