* launch: build copilot in compile; wait for CDP in launch.sh
Two related improvements to the agent dev-loop workflow:
- npm run compile now builds the Copilot extension in parallel with the
rest of the client, mirroring the structure of npm run watch. Previously
only the watch path included extensions/copilot, so a one-shot build
left the Copilot extension stale.
- .agents/skills/launch/scripts/launch.sh now runs preLaunch.ts in the
foreground (surfacing any failure synchronously with a log tail), then
launches Code OSS with VSCODE_SKIP_PRELAUNCH=1 and blocks until the
renderer's CDP endpoint responds (90s timeout) before printing the JSON.
Previously the script returned in ~1s while Electron was still mid-
bootstrap, which made it racy for agents driving the workbench via
@playwright/cli and could let an early-dying child go unnoticed.
SKILL.md updated to drop the now-unnecessary attach retry loop and to
document the new error-surfacing behavior.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* launch: clarify launcher's actual external deps in SKILL.md
Per code review, `launch.sh` does not invoke `lsof` or `jq` itself
those are used only by the example caller snippets. Reword the
prerequisites bullet so it reflects what the script actually requires
(`rsync`, `curl`, `nohup`, Node) and notes `jq` / `lsof` as optional
for the caller-side examples.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The command `workbench.action.chat.openNewChatSessionInPlace.agent-host-copilotcli`
was registered twice:
1. Statically in `electron-browser/chat.contribution.ts` (added in #314187)
2. Dynamically in `chatSessions.contribution.ts` via the autorun that
registers an in-place action for every contributed session type. The
agent host calls `registerChatSessionContribution` for the
`agent-host-copilotcli` type at startup, which triggers that
registration.
The static duplicate hit `CommandsRegistry.registerCommand`'s 'Cannot
register two commands with the same id' assertion. Remove the static
the dynamic one already covers it and is explicitlyregistration
designed to handle programmatically-registered contributions like
`agent-host-copilotcli`.
The neighbouring `openNewSessionSidebar` / `openNewSessionEditor`
static commands stay; they have distinct ids and serve their documented
purpose of providing stable command ids for automation that may run
before the agent host has registered.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sessions: restore X-button removal of SSH remote agent host entries (Written by Copilot)
The X button on remote agent host SSH entries in the workspace picker
disconnected the SSH tunnel but did not remove the entry from configured
storage: on the next reconcile pass (or on reload) `_reconnectSSHEntries`
found the entry still configured and immediately auto-reconnected it.
This regressed in [#316810](https://github.com/microsoft/vscode/pull/316810),
which routed SSH disconnect through the provider's `_disconnectOnDemand`
hook. The provider's `disconnect()` returns early when that hook is set,
so it no longer called `removeRemoteAgentHost` for SSH entries (the
pre-#316810 behavior). Combined with the synchronous reconcile that fires
from `_sshService.disconnect`'s own close event, the entry was reconnected
before the disconnect even returned.
Call `removeRemoteAgentHost` from `_disconnectSSHOnDemand` before the
`_sshService.disconnect` await so the entry is gone before the close-event
chain triggers `_reconnectSSHEntries`. `removeRemoteAgentHost` also runs
the SSH transport disposable, which itself tears down the main-process
SSH tunnel; the explicit `_sshService.disconnect(connectionKey)` is kept
as belt-and-suspenders.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Normalize LF to CRLF in agent host terminal tool output
The detached xterm used to render terminal tool output treats input as a
raw TTY stream where a lone \n is just LF (cursor moves down without
column reset), producing a staircase rendering.
The custom terminal tool's output goes through xterm's serialize addon
which emits proper VT with \r\n line endings. The agent host SDK path,
however, was passing the SDK's plain text through unchanged, so multi-line
output rendered with each line starting at the column where the previous
one ended.
Normalize \n to \r\n in getTerminalOutput, the adapter boundary where SDK
content is shaped into terminalCommandOutput. The replace is idempotent
on already-CRLF input.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update agentHostChatContribution tests for CRLF normalization
Two assertions checked the previous raw \n output from getTerminalOutput;
update them to expect the normalized \r\n.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Agent host: clearer worktree git timeout errors and 60s budget
The 30s timeout in addWorktree/addExistingWorktree/removeWorktree was
fine under normal conditions but bumped to 60s to absorb transient
disk-I/O contention on the remote (e.g. many concurrent npm installs
across multiple agent sessions).
When git timed out, _runGit re-threw `new Error(stderr || error.message)`,
which lost the timeout indicator. For `git worktree add`, stderr only
contains git's progress meter (`Updating files: 0% (149/14834)`), so
the surfaced error looked like git progress instead of a timeout.
Now _runGit uses its own timer to flag the timeout case definitively
and a new formatGitError helper produces messages like:
git worktree timed out after 60000ms: <stderr tail>
git worktree exited with code 128: fatal: invalid reference: ...
The underlying ExecFileException is preserved as Error.cause.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback on agent-host git error reporting
- Use child.on('exit') to clear the timeout, so 'timer' is no longer
referenced in the execFile callback before its declaration.
- Add summarizeStderrForError() to squash carriage-return-heavy progress
meter output into a single short line for the thrown error message,
with a 200-char cap.
- Log the full unmodified stderr at warn level via ILogService whenever
git exits with an error, so the raw output is still available even
though the thrown error message is summarized.
- Add unit tests for summarizeStderrForError and update the existing
formatGitError timeout test to exercise multi-line input.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* agentHost: add setting to disable worktreeCreated task auto-dispatch
Adds `chat.agentHost.runWorktreeCreatedTasks` (default `false`) which
gates `WorktreeCreatedTaskDispatcher` for agent host sessions. The
generic dispatcher now consults the setting before dispatching and skips
agent host sessions when it's disabled. Non-agent-host sessions and
manual `Run Task` invocations are unaffected.
(Written by Copilot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix new dispatcher tests: set worktree after added event
The 3 new agent-host gating tests were creating sessions that already had
a worktree at the time of the 'added' event. _trackSession returns early
in that case (the session isn't 'pending'), so the dispatcher's gate was
never exercised. Match the existing pattern: start with hasWorktree=false,
fire the added event, then set the workspace.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address review feedback
- Drop ISessionsProvidersService injection from the dispatcher; check
session.providerId directly via a new isAgentHostProviderId() helper.
- Improve setting description: quote the actual tasks.json JSON shape.
- await TestConfigurationService.setUserConfiguration in the new test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix SSH remote agent host passphrase auth
Support IdentityAgent from resolved SSH config and prompt for encrypted private key passphrases when connecting SSH remote agent hosts.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify SSH IdentityAgent config comment
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Try SSH agent before encrypted on-disk keys
When the configured agent has the identity loaded, auth should succeed before we ever read an encrypted IdentityFile from disk - otherwise the user gets a passphrase prompt for a key the agent already holds unlocked.
Also fix _isDefaultKeyPath to normalize `~` paths so the absolute IdentityFile that `ssh -G` returns is correctly recognized as a default and not promoted to an explicit (encrypted) attempt that fires the passphrase prompt before the agent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Render web_fetch invocation and completion messages with the fetched URL instead of falling back to the generic tool display name.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The memory leak fix in 1fc96f74 added cleanup of loggers on deregistration.
This created a race condition where IPC log messages from renderer processes
arrive after the logger has been removed from the map, causing an unhandled
error. Replace the throw with a silent return since log messages for a
removed logger are stale and should be discarded.
Co-authored-by: vs-code-engineering[bot] <122617954+vs-code-engineering[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The provideAltTextQuickFix method calls document.lineAt(range.start.line)
without validating that the line number is within bounds. When the document
is modified between when VS Code computes the range and when the code action
provider executes, an out-of-bounds line number causes 'Illegal value for
`line`' to be thrown.
Add a bounds check before calling lineAt() so the method returns early
instead of throwing.
Co-authored-by: vs-code-engineering[bot] <122617954+vs-code-engineering[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback: OTelSqliteStore opens the DB in WAL mode
and runs in a separate utility process, so a plain copy of the .db
file can miss uncheckpointed data still living in the -wal sidecar.
We can't safely issue 'wal_checkpoint' from the workbench process,
so instead copy the -wal and -shm sidecars alongside the main .db
when they exist. The resulting trio is a valid WAL-mode SQLite
database that any reader can open and checkpoint on demand.
Mirrors the programmatic-export contract already supported by the
chat extension's github.copilot.chat.otel.exportAgentTracesDB
command: when callers pass a destination directory, the DB is
copied to <dir>/agent-host-traces.db with no save dialog. The
interactive (command palette) flow is unchanged.
- Move USAGE_REASONING_OUTPUT_TOKENS from GitHubCopilotAttr to GenAiAttr
(its value is gen_ai.usage.reasoning.output_tokens, not a github.copilot.* key)
- Accept Anthropic-style mcp__server__tool double-underscore format in
addition to mcp_server_tool single-underscore
- Add a test for the double-underscore variant
VS Code's ToolName enum uses snake_case identifiers (create_file,
replace_string_in_file, apply_patch, insert_edit_into_file,
multi_replace_string_in_file, edit_notebook_file, read_file). The
initial CLI-derived FILE_TOOL_NAMES set only contained the Copilot
CLI/Claude camelCase variants, so tool.parameters.edit_type and
tool.parameters.file_path were never emitted on real VS Code tool
spans. Add the missing names and extend classifyEditType to handle
them. Verified end-to-end in Aspire on a trace where create_file now
correctly emits edit_type=create with the file_path gated by
captureContent.
Don't emit empty <customizationsUpdate> on steering turns
Fixes#317763
Terminal steering requests sent from runInTerminalTool.ts don't forward an
instructionContext, so chatServiceImpl short-circuits collectInstructions()
to [] and the vscode.customizations.index variable is absent for those turns.
The old freeze/drift consumer in agentPrompt.tsx treated that absence as
'all customizations removed' via 'effectiveCurrent = currentValue ?? ''',
emitting an empty <customizationsUpdate> block whose own text says it
'supersedes the system prompt' — falsely telling the model that all
skills/instructions/agents had been wiped mid-task and churning the cache
tail.
Gate drift emission on 'currentValue !== undefined' so an absent variable
preserves the frozen system-prompt listing instead of falsely signalling
removal. Also graduates the experimental
'github.copilot.chat.freezeCustomizationsIndex' setting (it was already
default true on onExp).
When tool search is active, built-in tools were being collapsed into virtual groups that tool search never surfaces, making non-deferred tools (e.g. askQuestions) uncallable. Detect tool_search from the full tool list and skip built-in grouping in that case. Fixes#316779.
The sidebar in the Agents app sometimes showed "Created 0 todos" or
similar misleading text under a session title. This came from the
`manage_todo_list` tool's `pastTenseMessage` being surfaced via
`getInProgressSessionDescription` even though the tool invocation was
already marked as `ToolInvocationPresentation.Hidden` (so it wasn't
shown in the chat view at all).
- `getInProgressSessionDescription`: skip tool invocations that are
effectively hidden, so the sidebar falls through to the previous
meaningful part instead of leaking text from a hidden tool call.
- `manageTodoListTool.generatePastTenseMessage`: don't generate
"Created 0 todos" for a no-op write (no current and no new todos);
fall through to the default "Updated todo list" message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix BYOK regression for non-OAuth Copilot token pathways
Introduce hasCopilotTokenSource on IAuthenticationService, defaulted to !!anyGitHubSession in BaseAuthenticationService and overridden to true in StaticGitHubAuthenticationService (proxy/HMAC, eval harness).
Replace PR-introduced anyGitHubSession guards added in #317428 at sites that gate on 'can we mint a Copilot token?' so the eval proxy pathway is no longer short-circuited:
- languageModelAccess._getToken
- authentication.contribution.waitForChatEnabled
- copilotCli (3 sites)
- remoteEmbeddingsComputer
Group-B sites that intentionally gate on a real signed-in GitHub user (conversationFeature search/intent provider registration, byokUtilityModel notification) keep using anyGitHubSession.
* PR feedback: clarify log message; fall back to empty embeddings in proxy scenarios
- Update languageModelAccess log to say 'Copilot token source' (matches the new gate).
- In remoteEmbeddingsComputer, return empty embeddings instead of throwing when the Dotcom path lacks a GitHub access token (proxy/HMAC eval harness), matching the rest of the function's empty-fallback behavior.
The `_backgroundSummarizers` map on `AgentIntent` is keyed only by
sessionId, so a summary kicked off against one model's prefix could be
applied unconditionally on the next render — even after the user
switched to a model with a larger context window or ran `/compact`.
The user saw a 'Compacted conversation' notice on a turn with plenty
of headroom, with content summarized against the old model's history.
* `BackgroundSummarizer` now records the `endpointModel` it was built
for.
* `AgentIntent.getOrCreateBackgroundSummarizer` cancels and recreates
the summarizer if the endpoint identity changed since last call.
* `handleSummarizeCommand` (`/compact`) cancels any pending background
summarizer once we commit to foreground compaction.
* Pre-render apply now gates on `contextRatio >= applyMinRatio` (0.65)
as defense-in-depth — covers context-size overrides and any path
that slips past the endpoint check. Stale completed results are
consumed-and-discarded so a fresh kick-off can replace them.
* Enhance telemetry in Copilot CLI chat session: include isolation mode and worktree status
* Add session tracking for Copilot CLI: implement turn count and creation time
Anthropic returns 400 (`messages.N.content.M: 'thinking' or 'redacted_thinking'
blocks in the latest assistant message cannot be modified` and `Invalid 'data'
in 'redacted_thinking' block`) when historical thinking/redacted_thinking blocks
have lost or rotated their signature/data on round-trip — most visibly on
claude-haiku-4.5 / claude-sonnet-4.6 in agent mode (~18k hits/day).
The Anthropic Extended Thinking docs explicitly allow omitting thinking blocks
from prior assistant role turns, and on Opus 4.5+/Sonnet 4.6+ omission is
neutral for quality. Pre-#315406 (≤ 0.47) we already did this; #315406 added
'messages' to apiSupportsHistoricalThinking, which is what flipped the behavior.
Reverts only the 'messages' membership; 'responses' (OpenAI Responses API)
still needs to round-trip encrypted reasoning blocks across turns and is
unaffected. Flips the matching unit test and renames model fixtures to
claude-haiku-4-5 (the dominant in-prod offender).
The legacy AgentHostEditingSession (844 LOC) only existed to translate
completed tool-call file edits into a markdownContent/codeblockUri/textEdit
cluster that CollapsedCodeBlock would then re-derive into a diff pill by
asking the editing session for stats it had just been handed.
The agent host protocol already exposes FileEdit.diff and before/after
content URIs up-front, so cut out the round-trip:
- New IChatExternalEdit progress part carries uri, editKind, originalUri,
beforeContentUri, afterContentUri, and diff stats directly.
- stateToProgressAdapter.completedToolCallToEditParts emits one
IChatExternalEdit per FileEdit, wrapping every URI via toAgentHostUri
so remote sessions resolve through the agent host file system
provider.
- Extract ChatEditPillElement base class from CollapsedCodeBlock — same
DOM / styling, no editing-session coupling. New ChatExternalEditContentPart
extends it and renders a static pill from the progress data.
- ChatThinkingContentPart.appendItem now accepts IChatExternalEdit
metadata directly (via the new ChatThinkingItemMetadata alias); it
derives the title (Created/Deleted/Renamed/Edited <filename>) and the
pencil icon without any synthesized markdown. Diff stats bubble up
through onDidChangeDiff (fired on a microtask so subscribers attach
first).
- AgentHostSnapshotController (~330 LOC) replaces the legacy editing
session: implements IChatEditingSession but only restoreSnapshot,
requestDisablement, and snapshot URIs do real work. Everything else
is a no-op so the chat-level Restore-to-checkpoint UI keeps working
without dragging in the full diff pipeline.
- ChatChangesSummaryPart is intentionally skipped for agent host
responses — per-file pills already convey the change counts inline.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 'Add screenshot to chat'
* Add telemetry
* Improve telemetry
* Address PR comment
* Move camera icon out of chevron list
* Back to flat menu. Also fix attachment on first panel expension
* PR feedback
* Move to split button
- fileTreeParser: reject node names that are empty, '.', '..', or contain '/' or '\\'; throw on unsafe project root names. Filters unsafe child node names from the parsed tree.
- newWorkspaceFollowup: replace the platform-aware path.relative destination computation (which resolved a relative projectRoot against process.cwd() on Windows) with a posix prefix-strip helper, resolveProjectFileUri. Add a runtime isUriContained guard before writeFile so any traversal that slips past the parser cannot escape the generated workspace folder.
- Tests: cover unsafe node names, the PoC tree, isUriContained edge cases (prefix collision, scheme/authority, trailing slash), and resolveProjectFileUri for both copilot and GitHub repo-template path shapes.
Some MCP OAuth flows require a client secret in addition to the public
client_id. Storing the secret in plain-text mcp.json is unacceptable, so
this change moves the secret out of config and into the workbench secret
storage service.
Changes:
- A new "Set/Replace Client Secret" codelens is shown above the
oauth.clientId property in mcp.json. The label flips between Set and
Replace based on whether a secret is already stored. The codelens is
only shown when the server has a url (OAuth requires HTTP).
- The codelens opens a QuickInput. When a secret is already stored the
input is pre-seeded with the existing value (select-all so it can be
replaced or cleared with Backspace), a trash titlebar button deletes
the stored secret, and an eye/eye-closed toggle reveals/hides the
password masking.
- Secrets are keyed by the MCP server URL plus the clientId, so two
servers with the same name in different configs (workspace vs user
mcp.json) don't collide.
- The stored secret is threaded through createDynamicAuthenticationProvider
-> IAuthenticationProviderHostDelegate.create -> $registerDynamicAuthProvider
so that DynamicAuthProvider is constructed with the resolved secret and
uses it in the token exchange. The lookup happens before provider
creation in mainThreadMcp.$getTokenFromServerMetadata.
- If an MCP auth provider was already registered with a different
client secret than the one currently stored (e.g. the user just
replaced or deleted it via the QuickInput), the provider is
unregistered and re-created so the next token exchange uses the
freshly resolved secret.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Chat sessions: add legacyResource for one-way URI state migration
Adds a backwards-compat field on ChatSessionItem so providers can declare that an item was previously known by a different URI. The host adopts archived/pinned/read state stored under the legacy URI forward on first read, then removes the legacy entry — letting providers change their URI shape without losing user state.
- vscode.proposed.chatSessionsProvider.d.ts: new `legacyResource?: Uri` field.
- IChatSessionItem: matching field.
- extHostTypeConverters.ts: propagate through ChatSessionItem.from.
- MainThreadChatSessionItem: revive and include in isEqual.
- AgentSessionsModel: new private resolveStateEntry helper that consults the legacy URI as a fallback and adopts the entry forward. isArchived and isPinned use it. Cross-scheme and self-referential mappings are rejected. Cache serialization carries legacyResource through restarts.
- agentSessionViewModel.test.ts: 7 focused tests covering migration semantics.
* Address review: route all state accessors through resolveStateEntry
isMarkedUnread and setRead were reading/writing sessionStates directly under the current resource, so an explicit unread marker on the legacy URI could be missed and setArchived's pre-call to setRead could establish an own entry under the new URI before isArchived triggered the migration — orphaning the legacy entry.
- isMarkedUnread: route through resolveStateEntry.
- isRead: read storedReadDate via resolveStateEntry.
- setRead: adopt legacy state forward before composing the new entry.
- setArchived/setPinned: read prior state via resolveStateEntry for consistency.
- New test: migrates unread marker forward (covers the full per-resource state contract claimed in the docs).