Tyler James Leonhardt
90d · built 2026-08-09
90-day totals
- Commits
- 78
- Grow
- 19.7
- Maintenance
- 20.0
- Fixes
- 5.6
- Total ETV
- 45.4
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 48 %
- By Growth share
- Top 31 %
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
↓-59.0 %
vs 39 prior
↑+30.9 pp
recent vs prior
↑+12.4 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 20 by ETV in the 90-day window.
- 3.8ETVConditional agent-window auth for signed-out users (#328990) * Conditional agent-window auth for signed-out users Behind the experimentation setting `chat.agentHost.allowSignedOutWhenUsable` (default off), the Agents window no longer unconditionally forces GitHub sign-in. When the user is signed out and some registered session type can run on its own credentials — Claude in native mode with an existing local setup — the window opens and a calm chat-input notification explains what happened, offering sign-in for anyone who meant to use a Copilot subscription. Whether a type requires GitHub is derived from the agent's advertised protected resources rather than a static flag, and surfaces on the provider-agnostic `ISessionType.authRequirement` as `none | github | unusable`. The third state is load-bearing: a Claude pinned to native by an explicit `claudeUseCopilotProxy: false` with no credentials advertises the Copilot resource as `required: false`, so a boolean would report it usable. Its model catalog is what distinguishes it, which is why Claude now publishes an empty catalog in that case instead of the SDK's static `supportedModels()` list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR feedback: startup race, provider identity, storage target - The window gate's inputs resolve asynchronously (the agent host advertises Claude at `AfterRestored`), but the only subscription was installed after setup completed. A signed-out startup could therefore show the non-dismissible sign-in modal and never reconsider. The subscription is now lifetime-scoped and retires an open modal when the answer flips to usable. - `getAllSessionTypes()` deduplicates by id (first provider wins), so a usable type from a second provider was invisible to the gate. Added `getAllProviderSessionTypes()` and used it for the gate and the notification lookup; the signed-out session-type fallback now matches on provider too. - "Don't Show Again" used `StorageTarget.USER`, which settings sync carries across machines, contradicting the intended machine-local scope. Now `StorageTarget.MACHINE`. Also fixes two claudeAgent tests that asserted native enumeration without a credential present, and adds coverage for the empty-catalog case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 59bca39f · 2026-08-04
- 2.4ETVPhase 16: editable Claude customization resolution via disk scan (#322501) * Phase 16: editable Claude customization resolution via disk scan Resolve a Claude session's customizations (agents, skills, slash commands, rules, MCP servers) by scanning the file system for the real customization files and shipping each item's real editable file: URI, instead of synthesizing stub files from the SDK query payloads. - Add disk-scan resolver + per-scope (Workspace/User) container mapping (customizations/claudeSessionCustomizationDiscovery.ts + scan/). - Add read-only built-in agents/skills tier (claudeBuiltinCommands.ts); curated pre-materialize, SDK-derived post-materialize. - Inline the projection into getSessionCustomizations; delete the synthetic-stub bundler and the standalone projector. - Watcher-backed live refresh of the customization list. - Update phase16-plan.md, roadmap.md, and CONTEXT.md to match. * Address PR review: depth-cap recursive rule scan, encode built-in URIs, fix roadmap bold markers * Fix claudeAgent integration test: register IFileService + INativeEnvironmentService ClaudeAgentSession now reads userHome at construction (Phase 16 watcher); the integration test's ServiceCollections lacked the env/file services, so session construction threw 'Cannot read properties of undefined (reading userHome)'. Add the same in-memory file + mock env services the unit harness uses.github.com-microsoft-vscode · dd66c66b · 2026-06-23
- 2.3ETVClaude agent — Phase 11: customizations / plugins (#318113) * Claude agent — Phase 11: customizations/plugins Workbench-pushed customizations (setClientCustomizations / setCustomizationEnabled) flow through IAgentPluginManager into Options.plugins for the Claude SDK Query. Server-side (SDK-discovered) commands / agents / MCP servers are projected as a single "Discovered in Claude" Open Plugins-conformant on-disk bundle. Notable design notes: - The SDK's Query.reloadPlugins() is parameterless and cannot change the plugin URI set after startup, so any client-side customization change triggers a yield-restart through the same rematerializer path used for client-tool changes. send()'s pre-flight runs a single rebind when either toolDiff or clientCustomizationsDiff is dirty. - SessionClientCustomizationsDiff drives dirty from the model state observable (not just enabledPluginPaths), so nonce bumps and metadata refreshes at the same URI are detected. - setClientCustomizations runs inside the per-session sequencer so a fire-and-forget call from AgentSideEffects cannot race a first sendMessage. - ClaudeSdkCustomizationBundler writes a hashed, content-addressed on-disk tree under the plugin manager's basePath. Repeated calls with the same SDK snapshot are nonce-stable and skip the rewrite. The on-disk tree is intentionally a cross-session warm cache. Tests: - New customizations/ test folder mirrors the source structure: SessionClientCustomizationsDiff (URI list, nonce, metadata, enablement, dirty semantics), projector (client+server merge), bundler (write layout, nonce stability, name sanitisation, namespacing, delete-on-change). - claudeAgent.test.ts: sync-and-toggle dispatch, sequencer serialisation, rebind on customizations dirty, mid-turn race coverage, swallowed-SDK-snapshot fallback in getSessionCustomizations. * fix: address customizations lint and review feedback Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * test: fix customizations enablement key mismatch Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Fix Windows path assertions in Phase 11 tests URI.file('/p/a').fsPath is '\p\a' on Windows, so the literal POSIX string comparisons fail there. Compute expected via URI.file().fsPath so the same path round-trip drives both sides of the assertion. * Phase 11 docs: reflect shipped rebind-always architecture The original plan described setCustomizationEnabled as defer-and-coalesce via Query.reloadPlugins() with a tool-set-divergence escalation to rebind. Council review during PR #318113 verified Query.reloadPlugins() is parameterless in @anthropic-ai/claude-agent-sdk and cannot change the plugin URI set captured into Options.plugins at startup, so any client- pushed customization change ships as a yield-restart through the same rematerializer path that client-tool changes use. Rewrites the Phase 11 sections of roadmap.md and phase11-plan.md so the docs match what was merged. Historical "original plan called for X" notes preserved for context. Phase 11 marked DONE on the roadmap. * Claude phase 11: agent picker plumbing + on-disk URIs - Add IAgent.changeAgent for Claude: pre-materialize stash, post-materialize rebind via dirty bit (SDK has no working runtime control to swap agent in place — applyFlagSettings({ agent }) exists but doesn't actually swap). - Thread Options.agent through buildOptions / materialize / rematerializer and persist selection in the per-session metadata overlay so resume picks it up. - ClaudeSdkCustomizationBundler now publishes CustomizationAgentRef.uri as the on-disk `agents/<name>.md` path (was a synthetic `claude-sdk-agent:/` scheme). The workbench customization harness needs a real file URI to parse via promptsService.parseNew — without it the agents never reached the picker. - Hide 'general-purpose' (SDK default) from the picker via shared CLAUDE_SDK_DEFAULT_AGENT_NAME constant. - Tests: 3 changeAgent cases (provisional / mid-session rebind / clear-to-undefined), bundler agent-URI shape. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 36c57182 · 2026-05-27
- 2.2ETVclaude: Phase 10 — workbench client tools via in-process MCP server (#317685)github.com-microsoft-vscode · 54eb46af · 2026-05-21
- 2.1ETVagentHost/claude: extract Materializer, MetadataStore, FileEditObserver (#315915)github.com-microsoft-vscode · 015fd2ae · 2026-05-12
- 2.1ETVagentHost/claude: Phase 9 — abort + steering + changeModel + crash recovery (#316292) * agentHost/claude: Phase 9 — abort + steering + changeModel + crash recovery Implements the runtime mutation surface for live Claude sessions: - abortSession via _abortController.abort() (mirrors production reference) - setPendingMessages steering via priority:'now' SDKUserMessage with steering_consumed signal on iterable yield - changeModel hot-swap via Query.setModel + Query.applyFlagSettings, with 'max' effort runtime clamp to 'xhigh' (single seam in claudeModelConfig) - Yield-restart primitive: ClaudeMaterializer gains 'fresh'|'resume' start modes and a sibling materializeResume() for crash/abort recovery without re-firing onDidMaterializeSession - Subprocess crash recovery without permanent _fatalError latch — sessions stay reusable across aborts and crashes via _rebindQuery + bijective state replay (model + effort + permissionMode) Refactored claudeAgentSession into three focused units: - ClaudePromptQueue — owns the SDKUserMessage iterable handed to query(), parks/wakes via DeferredPromise, batches turn completion at full drain (M10), fires steering_consumed on yield - ClaudeSdkMessageRouter — dispatches each SDK message through the per-turn mapper, swallowing handler failures - ClaudeSdkPipeline — orchestrates the WarmQuery + AbortController + queue + router lifecycle, including rebind on abort/crash with bijective state replay Each new class has standalone unit test coverage: - claudePromptQueue.test.ts (13 tests) - claudeSdkMessageRouter.test.ts (5 tests) - claudeSdkPipeline.test.ts (10 tests, synchronous lifecycle surface) Plus 10 new Phase 9 integration tests in claudeAgent.test.ts covering abort/resend, steering preempt + consumed signal, changeModel provisional + materialized + 'max' clamp + id-only paths, crash recovery via resume, and bijective state survival across restart. Live E2E (smoke.md Scenarios A-D) verified 2026-05-13. See src/vs/platform/agentHost/node/claude/phase9-plan.md for the full contract, decisions, deviations, and risks. * feedbackgithub.com-microsoft-vscode · 47523f72 · 2026-05-13
- 1.9ETVagentHost: Claude per-session provider selection (#329331) * agentHost: add provider-qualified Claude model-selection id codec Pure precursor for per-session provider selection in the Claude harness. Mirrors Codex's `@provider=` convention: toClaudeModelSelectionId encodes a provider + model id into one opaque ModelSelection.id; parseClaudeModelSelection splits it back, with a bare/malformed/legacy id defaulting to the Copilot (proxy) provider so nothing needs a data migration. claudeTransportForProvider maps the token to a transport (anthropic -> native, everything else -> proxy). Dead code until the merged-catalog + per-session routing core wires it in; landed first because it's a leaf with zero behavioral risk. Fully unit-tested with no mocks, mirroring codexModelSelection.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: add merged-catalog + per-session transport resolvers for Claude Two pure precursors for per-session provider selection, co-located with the model-selection id codec and covered by the same test suite: - mergeClaudeModelCatalogs(proxy, native): flattens the two provider catalogs into one picker list, proxy-first (preserving models[0]-is-default), each id provider-qualified so a row carries its transport and the same model under both providers yields two non-colliding rows. Either side may be empty so one source failing to fetch never blanks the other. - resolveClaudeSessionTransport({ perSessionProviderEnabled, model, defaultMode }): the per-session counterpart to the host-global resolver — off, or no model, inherits the host default (identical to today); on, the selected model's provider decides. Both are dead code until the flag-gated wiring lands; kept as a separately reviewable, fully-tested unit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: wire Claude per-session provider flag into the backend read-path Slice 2a (flag plumbing) + 2b (backend read-path) of per-session provider selection for the Claude agent host, gated behind the off-by-default experimentation flag `chat.agentHost.claude.perSessionProvider`. Flag plumbing (2a): register the boolean setting (APPLICATION scope, experimental/advanced), its customization-config key, and the forwarder contribution. Backend read-path (2b): ClaudeAgent merges the proxy (Copilot-CAPI) and native (Anthropic) model catalogs into one provider-qualified picker list, and resolves each session's transport from its selected model's provider when the flag is on (inheriting the host default when off, or when no model is selected). Review fixes folded in: - A: gate the constructor / hydration model-refresh on the flag so the native catalog bootstraps signed-out without a manual refresh. - B: _resolveParentSession inherits a never-materialized parent's provisional model so a forked peer chat keeps its native transport. - C: a runtime flag toggle re-enumerates and repopulates the merged catalog. - D: a failing proxy start no longer fails native-default sign-in. - E: toClaudeSdkModelId strips the `@provider=` qualification before the SDK / CAPI boundary — the wrapper is unparseable downstream and would 400 both transports whenever the flag is on and a model is explicitly selected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: guard Claude model refresh against a mid-flight per-session provider toggle The refresh stale-write guards captured only the transport token/mode, not the per-session provider flag. Because `_startModelRefresh` supersedes an in-flight refresh as the coalescing target but never cancels it, a false-flip of the flag mid-refresh left the superseded refresh running; when it settled it published a stale wrong-mode catalog (merged provider-qualified over the bare single-transport list the flag-off refresh had already published, or vice versa), clobbering the correct one. Capture `_perSessionProviderEnabled` at the start of both `_refreshModelsSingle` and `_refreshModelsMerged` and bail in the stale-write guard when it moved, so the superseded refresh drops its result. Tests: a flag-on→off toggle mid-merge (native half parked on a gate so the merged refresh is provably still in-flight) asserts the bare single catalog survives the straggler; and a flag-off regression that a forked peer chat still inherits its never-materialized parent's explicit model (the inheritance in `_resolveParentSession` is intentionally not flag-gated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: group Claude models by transport provider in the picker With per-session provider selection on, the merged catalog now stamps each model's `provider` with its transport token (`copilot` for the Copilot-CAPI proxy, `anthropic` for the user's own Anthropic account) alongside the provider-qualified id, so the chat model picker — which buckets by `provider` — splits Claude into a Copilot group and an Anthropic group. The same model offered by both transports yields two distinct, separately selectable rows. - common/claudeProviders.ts (new): the two transport-provider tokens live in one `common` module so the backend that stamps `model.provider` and the frontend vendor descriptor that names the group are a single, compile-checked source of truth rather than two literals that can drift. - claudeModelSelection.ts: `withQualifiedProvider` re-stamps each model's `provider` with its transport token; re-exports the tokens for node callers. - agentHostChatContribution.ts: register the `anthropic` group vendor (localized "Anthropic"), mirroring the Codex `chatgpt` second-vendor registration, so the native group resolves a clean label. Copilot-routed Claude models keep grouping under the global `copilot` vendor. Dormant while the flag is off — no Claude model carries the `anthropic` provider then. - claudeAgent.ts: correct the stale `toAgentModelInfo` doc — the picker *groups* (does not filter) by `provider`. Flag-off path is unchanged: the single-catalog refresh still stamps the harness provider and no second vendor is registered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: live per-session provider switching, unconditional Complete the Claude per-session provider feature. Switching a live session's model to one on a different transport now re-routes the running subprocess, and the feature ships unconditionally (no experimental flag). Live provider switch: - Refactor IMaterializeContext.transport into a resolveTransport callback so the transport is re-resolved inside materialize and on every rebuild — a provider switch re-routes the rebuilt subprocess onto the new transport. - A cross-transport model change defers via a pending-switch flag; the next send() rebuilds onto the newly-selected transport and commits it once the new subprocess is live. Same-transport changes still hot-swap in place. Remove the experimental flag: - Delete chat.agentHost.claude.perSessionProvider, revert the generic flag plumbing, and delete the setting-to-root-config forwarder contribution. - Collapse the per-session-provider gates in claudeAgent.ts to always-on and remove the now-dead members. - Drop the perSessionProviderEnabled parameter from resolveClaudeSessionTransport; bare/legacy ids still resume on the host default transport with no migration. - Add modelProviders to the e2e CLAUDE_CONFIG and de-flag the unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: simplify Claude per-session transport resolution Follow-up cleanup on the per-session-provider read-path, now that the feature is unconditional and the single-catalog path is gone. - Collapse the cached `_transportMode` and its reactive `_resolveTransportMode`/`_applyTransportModeChange` machinery into a read-on-demand `_defaultTransportMode()`. The host default is only the fallback for model-less / bare-id sessions, so it needs no caching or config/sign-in re-resolve — the next session reads live availability. - `authenticate()`: a Copilot proxy-start failure is now uniformly soft. GitHub sign-in still succeeds and both `_githubToken` and `_proxyHandle` stay uncommitted, so a retry re-attempts `start()`; a Copilot-routed model re-drives sign-in on its first send. Drops the native/proxy default special-casing and the mid-flight transport-mode flip. - Replace `IMaterializeContext.resolveTransport` (a callback the session re-invoked on every rebuild) with a `transport` value the agent pins at materialize. A per-session provider switch is pushed in through `send`'s new `switchTransport` (staged in `_pendingSwitchTransport`); ordinary and SDK-recover rebuilds reuse the materialized transport. A throwing guard replaces the defensive re-resolve. - Move cross-transport switch detection into `ClaudeAgentSession.setModel` (the session owns it); drop the agent-computed `deferForTransportSwitch` option and expose `hasPendingTransportSwitch` in place of `transportKind`. - Inline the one-off `_settledCatalog` helper into `_refreshModels` and drop the `_refreshModels` -> `_refreshModelsMerged` forwarder. - Drop the now-unused provider-token re-export from `claudeModelSelection`; tests import the tokens from `common/claudeProviders` directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: address Claude per-session provider review feedback Fixes five review comments on the per-session provider slice: - Keep IAgentModelInfo.provider as the `claude` routing owner and carry the transport/group token in `_meta.modelGroupId`, so a model-selected create_session no longer misroutes to a `copilot`/`anthropic` agent that the node provider registry can't resolve. - Drop the agent-host `anthropic` picker vendor that clobbered the Copilot extension's shared `anthropic` vendor on dispose; reuse the shared one. - On a replacement-token proxy start() failure, tear down the stale account (handle, token, and merged catalog) instead of leaving it live behind a "successful" sign-in that would silently serve the superseded account. - Guard setModel's cross-transport detection on an explicit provider so a bare/legacy id (parser-fallback `copilot`) can't spuriously reroute a native session. - Condense the over-long rematerializer transport-pinning comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>github.com-microsoft-vscode · 511fd2d2 · 2026-08-07
- 1.7ETVagentHost: route GitHub protected resources through a GitHub Enterprise endpoint service (#324159)github.com-microsoft-vscode · 62c7ad83 · 2026-07-07
- 1.7ETVDistribute Claude and Codex agent SDKs via product.json (#320709) * Add tar to REH dependencies and eslint allowlist The agent SDK downloader uses node-tar to extract the per-platform SDK tarballs it downloads from product.json — pure JS, zero native deps, so the agent host works on every server install footprint without relying on a system tar binary. * Distribute Claude and Codex agent SDKs via product.json Adds IAgentSdkDownloader, which fetches the per-platform Claude and Codex SDKs from a CDN configured through product.agentSdks (populated by vscode-distro), verifies the sha256 anchored in product.json, and caches the extracted root under userDataPath. Providers register iff the SDK is available — either a dev override env var or a product.agentSdks entry whose sha256 declares the current platform. Falls through to today's no-op behavior in OSS builds with neither. Tracks microsoft/vscode-internalbacklog#7885. * Fix lockfiles after merge resolve The previous merge took upstream's @anthropic-ai/sdk@0.82.0 in package.json but left the lock file's nested resolution tree pointing at 0.102.0, so npm ci rejected the workspace. Re-resolve via npm install. Also adds the remote/package-lock.json entry for tar that was missed in the first commit. * Address PR review feedback - nodeAgentHostStarter: pass process.env (not the local shell-env snapshot) to buildAgentSdkEnv so a developer's env-var dev override actually wins over a settings value. Matches electronAgentHostStarter. - agentSdkDownloader: write the .complete sentinel inside tmpDir BEFORE the move so cache publish is atomic. A crash between move and sentinel-write previously left a wedged cacheDir that subsequent runs could not recover from (rename-loser path requires a valid sentinel). - agentHostBootstrap: register RequestService with the DisposableStore so its config-change listener is cleaned up at shutdown. - agentSdkDownloader.test: build the fixture tarball via node-tar (already a dep) instead of spawning the host tar binary; drops the bsdtar/gnutar portability surface. - agentHostMain: comment referenced the renamed VSCODE_AGENT_HOST_*_PATH env var; corrected to VSCODE_AGENT_HOST_*_SDK_ROOT. * Drop test-stub fields not present in @anthropic-ai/sdk 0.82 The earlier merge took upstream's @anthropic-ai/sdk@0.82.0 over the stash's 0.102.0; some test stubs had been authored on a branch using 0.102.0 and reference fields that don't exist in 0.82 (output_tokens_details, estimated_tokens, diagnostics on BetaMessage). Strip the optional fields — they're shape-only filler in the test fixtures and aren't asserted on.github.com-microsoft-vscode · f57a83c8 · 2026-06-10
- 1.6ETVagentHost: implement Claude truncateSession (Phase 6.7 — Restore Checkpoint + Start Over) (#323197) * agentHost: implement Claude truncateSession (Phase 6.7 — Restore Checkpoint + Start Over) Implements IAgent.truncateSession for the Claude agent host so the workbench "Restore Checkpoint" and "Start Over" actions work end-to-end: - Point-restore (turnId): truncates the conversation in place on the same SDK session id / protocol URI via the SDK's `resumeSessionAt` option. The protocol turn is resolved to its SDK assistant-envelope uuid and staged as a one-shot anchor the next turn's rebuild applies (lazy — full history is preserved if the user restores then walks away). - Remove-all (no turnId, "Start Over"): tears down the live subprocess, deletes the on-disk transcript, and recreates a fresh provisional under the same id, preserving the model/agent/permissionMode overlay. Teardown awaits the subprocess's actual exit (Query.return() -> the SDK's memoized cleanup -> transport.waitForExit()) before deleting + respawning the same `--session-id`, fixing a "Session ID ... is already in use" race. Also fixes a pre-existing rebind consumer-loop handoff race surfaced by truncation (post-restore turn could hang), and simplifies the pipeline's query handles (single `_query` mirroring the warm subprocess; `_needsRebind` as the sole health signal). Unit + integration green (427 Claude tests); both flows live-E2E verified against real Claude. See node/claude/phase6.7-plan.md for the implementation log and node/claude/roadmap.md (Phase 6.7) marked done. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: address Phase 6.7 PR review - claudeAgentSession: retain the pending truncation anchor until a rebuild succeeds (read it without clearing, clear only after materialize / rebuild installs the pipeline). A throw/cancel after reading no longer silently drops `resumeSessionAt`, so the next send retries the checkpoint restore. + regression test. - claudeSdkPipeline: `_ensureQueryBound` now honors `_needsRebind` (rebuilds via the rematerializer like `send()` does) so pre-flight helpers (reloadPlugins / snapshotResolvedCustomizations) never operate on a dead stream after an abort/crash. - claudeAgent: cold remove-all fails fast when no working directory is available (SDK cwd absent and no live session), mirroring `_resumeSession` and the fork path, instead of recreating a provisional that fails later. - claudeSdkPipeline: pass the error object to the logger in shutdownAndWait instead of stringifying it, preserving stack traces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · cd6804d7 · 2026-06-26
- 1.4ETVClaude agent host: native (BYOK) transport (Phases 18–19) (#323037) * Claude agent host: native (BYO-Anthropic) transport + transport-branched models (Phases 18–19) - Phase 18: single transport-branched model source. Proxy mode fetches Claude models from CAPI; native mode enumerates via the SDK's query().supportedModels(). - Phase 19: direct (non-proxied) Claude access. claudeUseCopilotProxy: false routes the SDK to Anthropic on the user's own credentials, no proxy in path. - Native auth comes from the subprocess env (ANTHROPIC_API_KEY, or CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`). Fixes the SDK >=0.3 env-replace semantics by spreading process.env in native mode so the token + PATH actually reach the claude subprocess. - IClaudeAgentSdkService keeps its 1:1-with-SDK contract: adds a query() passthrough; enumeration orchestration moved to the caller and closes the throwaway query (leak guard, covered by a new test). - Cleanup: removed the speculative native-binary-path feature; moved ClaudeTransport into claudeProxyService.ts; dropped a trivial model-list helper; tightened buildSubprocessEnv env construction. * Address PR review: explicit enumeration teardown + plan accuracy - _fetchNativeModels: abort the enumeration options' abortController in the finally (alongside query.close()) so teardown is explicit. - buildModelEnumerationOptions doc: the caller, not the SDK service, aborts the controller at teardown. - phase19-plan: reflect the 1:1 query() passthrough (enumeration lifecycle lives in ClaudeAgent, not a supportedModels() service method); fix the ClaudeTransport home (claudeProxyService.ts) and helper signatures.github.com-microsoft-vscode · 8a73e455 · 2026-06-26
- 1.1ETVfeat: XAA enterprise-managed MCP authentication (ID-JAG) (#318067) Implements a 3-legged OAuth flow for enterprise-managed MCP servers where VS Code routes per-resource authentication through a tenant-wide IdP via ID-JAG (draft-ietf-oauth-identity-assertion-authz-grant) token exchange: 1. User signs in once to the enterprise IdP (Auth Code + PKCE via the existing DynamicAuthProvider base class). The IdP id_token is stored in OS secret storage and survives window reload. 2. The id_token is exchanged at the IdP for a resource-scoped ID-JAG assertion (RFC 8693 token exchange, subject_token_type=id_token, requested_token_type=id-jag, audience=<resource AS>). 3. The ID-JAG is redeemed at the resource's authorization server (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer) for a resource-scoped access token. 4. The resource access token is used to call the MCP server. Surfaces: - New 'mcp.enterpriseManagedAuth.idp' setting (issuer/clientId/clientSecret), delivered via policy (McpEnterpriseManagedAuthIdp). The setting is hidden from Settings UI (included: false) but readable/writable by hand for local dev. APPLICATION scope so it never syncs. - New 'enterpriseManaged' flag on MCP HTTP server entries triggers the XAA flow instead of per-server Dynamic Client Registration. - New proposed API 'authSessionAudience' adds optional 'audience' to AuthenticationProviderSessionOptions so the XAA provider can receive the resource AS URL through the standard session options shape. - IAuthenticationService grows createOrGetXaaProvider(issuer): registers one XAA provider per IdP issuer (shared across enterprise MCP servers). - Resource-AS client secrets (distinct from IdP client secrets) are stored in OS secret storage keyed by (resource indicator, resource client_id) and resolved through the existing 'Set Client Secret' codelens above oauth.clientId in mcp.json, with a prompt fallback for first run. Silent re-mint on reload: getSessions reads the persisted IdP session from base-class secret storage and silently runs legs 2-4 to produce a resource token without prompting. Only escalates to createSession when the resource AS needs interactive client-secret entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 2d95154a · 2026-05-27
- 1.0ETVFunctional & accurate `languageModelToolInvoked` telemetry for Claude & Codex (#323660) * Centralize languageModelToolInvoked telemetry across agent-host providers The `languageModelToolInvoked` event was emitted only by CopilotAgentSession, so Claude and Codex agent-host sessions emitted ~0 per-tool events — the biggest telemetry parity gap for the agent host (no per-tool volume, error rate, or invocation latency for those providers). Lift the emission into the provider-agnostic AgentSideEffects layer (which already processes ChatToolCallComplete for every provider) and add an optional `provider` field so copilot/claude/codex are distinguishable. - New AgentHostToolCallTracker (mirrors AgentHostTurnTracker): stamps the tool start, emits on ChatToolCallComplete, computes invocationTimeMs via StopWatch, with a dedup guard and per-session leak guards. Result/sourceKind derivation is extracted into pure, unit-testable helpers. - AgentHostTelemetryReporter.toolInvoked emits the event with `provider` and a chat-channel->session normalized chatSessionId (matching the value CopilotAgentSession previously emitted). - Remove CopilotAgentSession's duplicate emission and the now-unused ITelemetryService dependency / startTimeMs tracking. - `provider` is optional on the shared event type so the workbench emitter is unaffected. Tests: pure-function unit tests for the derivation helpers plus AgentSideEffects integration tests (success / userCancelled / mcp / client / error, dedup, and the in-flight leak guard). Also verified live in the Agents window for all three providers (provider = claude / copilotcli / codex, toolSourceKind = agentHost). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review feedback - languageModelToolInvoked classification: use `copilotcli` (the real provider string) in the example, not `copilot`. - toolSourceKindFromContributor: add a default branch so an unrecognized contributor kind from a newer protocol version yields a valid telemetry value instead of `undefined`. - AgentSideEffects: resolve the provider from the signal's agent only (always present for an agent-driven tool call); drop the getAgent(sessionKey) fallback that could be passed an AHP chat-channel URI. - CopilotAgentSession: remove a duplicated doc comment on _activeToolCalls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Combine the two ChatToolCallStart guards into one block The tool-call-id->agent registration and the telemetry start-stamp shared the identical `ChatToolCallStart && agent` guard; merge them. The start-stamp now runs just before dispatchServerAction rather than just after — a negligible shift in the invocationTimeMs baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Use high-resolution timing for tool invocationTimeMs Tool calls often complete in under a millisecond, so StopWatch.create(false) (Date.now, 1ms granularity) reported invocationTimeMs=0 for fast tools (e.g. a Codex `echo`, verified live at ~0.55ms). Switch to StopWatch.create(true) (performance.now), matching the workbench languageModelToolInvoked emitter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Report accurate toolSourceKind and userCancelled for Claude/Codex tool telemetry The centralized `languageModelToolInvoked` event now fires for all agent-host providers, but the Claude/Codex mappers left two fields inaccurate: MCP tools reported `toolSourceKind: agentHost` instead of `mcp`, and a tool the user denied at the approval prompt reported `result: error` instead of `userCancelled`. Both are derived from the tool-call signal, so the fix is in the mappers. MCP source kind — stamp a `ToolCallContributorKind.MCP` contributor on the tool-call start so `toolSourceKindFromContributor` resolves `mcp`: - Codex carries a per-session server -> customizationId map on the map state, populated by the agent whenever it applies the MCP inventory (not only at session-customization time, which raced the async inventory discovery), and stamps it on `mcpToolCall` starts. - Claude enriches `ChatToolCallStart` signals for `mcp__` tools using the session's last customizations, keeping the MCP lookup out of the mapper. Cancellation result — a denied tool now carries `error.code` so `deriveToolInvokedResult` maps it to `userCancelled` rather than `error`: - Codex records tool-call ids the host declined and drains the flag once in a shared completion prologue, so every tool type (command, file change, MCP, dynamic, web search) is classified uniformly. - Claude classifies the `is_error` deny message via the new standalone `claudeToolDenial` helper (also breaks an import cycle and is exhaustively unit-tested); a genuine tool error stays `error`. Adds direct mapper tests for the new branches plus negative guards (no contributor without a customization; a non-deny error keeps no code). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Implement refreshChangesetCatalog in FakeChangesetService for main drift main added `refreshChangesetCatalog` to `IAgentHostChangesetService` after this branch forked. CI compiles the PR merged with main, so the test fake must implement the new member — a no-op, matching the sibling lifecycle stubs. Local `tsgo` typecheck-client passed on the branch alone because its own copy of the interface predates the addition; reproduced by materializing the merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>github.com-microsoft-vscode · f18c6b4f · 2026-06-30
- 1.0ETVClaude agent host: Phase 8.5 — rich tool-call rendering (#317184) * Claude agent host: roadmap status sync + Phase 8.5 (rich tool-call rendering) - Mark Phases 5, 6, 7, 8 as DONE (implementations have shipped; just catching the headings up to reality). - Insert Phase 8.5 — Rich tool-call rendering parity with Copilot. Today the Claude permission card for Bash reads 'Run shell command' with no command shown; Bash/Grep/Glob rows render in the generic renderer instead of the dedicated terminal/search renderers. This phase ports Copilot's getInvocationMessage / getPastTenseMessage / getToolKind / getShellLanguage / getToolInputString shape into claudeToolDisplay.ts and wires them through the permission, mapper, and replay paths. Phase 6.5 (Fork) intentionally stays Deferred. * Claude agent host: Phase 8.5 plan (super-planner + grilling) Adds phase8.5-plan.md alongside roadmap.md's Phase 8.5 section. Synthesized from a 3-model council (GPT-5.5, Claude Opus 4.6, GPT-5.3-Codex) and refined through a grill-with-docs session. Locked decisions: - D1: getClaudeToolKind is a TOOL_ROWS column (single source of truth). - D2: add Agent row to TOOL_ROWS; delete SUBAGENT_TOOL_NAMES. - D3: defensive Record<string, unknown> access; no per-tool exported types. - D4: pastTenseMessage is success-aware (tool_result.is_error). - D5: live mapper mirrors Copilot's stash-on-start/reuse-on-complete pattern, with state encapsulated in a new ClaudeToolCallRegistry class (replaces today's bare maps on ClaudeMapperState). - D6: _meta single-write on Start; reducer carries to Complete; replay emits on its single terminal action (asymmetry by design). - D7: MCP tools get toolKind: undefined. - D8: one big per-tool snapshot table covering all 5 helpers. - D9: getClaudeInvocationMessage('Task', ...) owns the Task description fallback; Phase 12's site reduces to a plain helper call. - D10: _meta stays flat (no per-kind namespacing). Steps cover claudeToolDisplay (helpers + columns), claudeCanUseTool (permission card rich invocation + _meta), sessionPermissions (forward _meta through pending->ready), claudeMapSessionEvents (registry migration + _meta on Start + success-aware past tense), claudeReplayMapper (parity), claudeSubagentSignals (inner tool parity), and the snapshot + behavior tests. * Claude agent host: Phase 8.5 implementation (rich tool-call rendering) Brings tool-call rendering parity with Copilot: - Rich invocation/past-tense messages per tool (Bash 'Running `git status`' → 'Ran `git status`', Read 'Reading [README.md](...)' → 'Read [README.md](...)', Grep/Glob with patterns, file links for path-bearing tools, subagent descriptions for Task/Agent). - `_meta.toolKind` ('terminal'/'search'/'subagent') stamped at the tool-open seam to drive the workbench's specialized renderers; reducer carries it forward through every state transition (D6 in plan). - New `ClaudeToolCallRegistry` encapsulates per-session tool-call attribution + input accumulation + computed start-info, mirroring Copilot's 'stash on start, reuse on complete' pattern. Critical bug fix bundled in: - Emit `SessionToolCallReady` at `content_block_stop` so auto-allowed tools (which the Claude SDK runs without invoking `canUseTool`) transition Streaming → Running and the subsequent `SessionToolCallComplete` is accepted by the reducer instead of dropped. Without this, every auto-allowed tool widget rendered empty after completion. D6 parity fix for inner subagent tools: - `claudeSubagentSignals` now calls `registry.seedParsedInput()` for inner tool_use blocks (which arrive pre-parsed on synthesized assistant messages rather than via input_json_delta), so the live tool_result handler emits rich past-tense text matching the replay path instead of falling back to '{displayName} finished'. Also fixes a pre-existing Phase 10 stub: - `ClaudeAgent.onClientToolCallComplete` is now a benign no-op. The AgentSideEffects autorun fires this hook for EVERY server-dispatched SessionToolCallComplete envelope (including normal SDK tool completions), so the previous `throw new Error('TODO: Phase 10')` corrupted every tool flow. Client (MCP) tool registration via `setClientTools` still throws since Phase 10 hasn't landed. Tests: - New: `claudeToolCallRegistry.test.ts` (lifecycle + seedParsedInput coverage). - Snapshot: `claudeToolDisplay.test.ts` covers every tool row × all helpers. - Mapper: `claudeMapSessionEvents.test.ts` Test 9.5 asserts the new content_block_stop Ready emission. - Subagent: `claudeSubagentSignals.test.ts` extended to assert rich past-tense for inner-tool completion (D6 parity). - Agent: `claudeAgent.test.ts` asserts onClientToolCallComplete is a no-op. Verified live in the Agents window: NEW Claude [Local] chat with 'Run `git status` and then read README.md' produces both tool widgets in the expanded thinking block with command + output visible. * Address PR review feedback - claudeCanUseTool: drop the redundant '?? JSON.stringify(input)' fallback. getClaudeToolInputString already wraps stringify in try/catch and returns undefined on failure; the outer call was re-running the same stringify that just failed (would throw on non-serializable input). - sessionPermissions.createToolReadyAction: drop the state._meta forwarding. The reducer's SessionToolCallReady branch derives _meta via tcBase(tc) from the prior state, so the action-level _meta was never read. - claudeToolCallRegistry.finalize: preserve the raw inputBuffer as toolInput when JSON.parse fails or yields a non-object. Without this, malformed payloads rendered an empty input section in the UI. Test updated to assert raw-buffer preservation. - claudeToolDisplay.formatPathAsMarkdownLink + WebFetch link: escape the link label via escapeMarkdownLinkLabel. File names containing ']' or '\\' would otherwise break out of the [...] label and could cause malformed rendering / injection. - claudeAgent.integrationTest (Phase 7 §5.3 Read tool round-trip): expected signal sequence updated for the Phase 8.5 mapper's new SessionToolCallReady emission at content_block_stop. The mapper-side Ready (auto-allow path) now lands between toolCallDelta and the permission card's pending_confirmation.github.com-microsoft-vscode · 1e4d8bd8 · 2026-05-18
- 0.9ETVPhase 10.5: unify ClaudeAgentSession lifecycle, retire ClaudeMaterializer (#317884) * Phase 10.5: unify ClaudeAgentSession lifecycle, retire ClaudeMaterializer Collapse the dual-map session lifecycle (`_provisionalSessions` + `_sessions`) onto a single `ClaudeAgentSession` identity per `sessionId`. The session now owns its full materialize flow (SDK startup, abort gates, DB ref open, pipeline construction, rematerializer attach, metadata overlay write, bijective state seed). `ClaudeMaterializer` is gone; its pure helpers (`buildOptions`, `buildClientMcpServers`, `buildSubprocessEnv`) live in a new `claudeSdkOptions.ts` module. Why - Phase 10's race regressions (C1, C1-resume, S1) were all compensation for the dual-map split. With one object identity per session, the fixes become structurally trivial and the compensation paths delete. - `_materializeProvisional` and `_resumeSession` were ~80-line orchestrators trying to be methods on the session entity \u2014 now they are: build canUseTool + delegate to `session.materialize(ctx)`, then fire the public materialize event. Behavior preserved - `IAgent` provider surface unchanged. - Phase 10 race regression tests still cover the same races (materialize gap, resume bootstrap gap, rebind failure leaves diff dirty). - Resume path explicitly skips the overlay write (overlay is the SOURCE on resume); new test guards this. `changeModel` simplified - `session.setModel(model)` is now the single mutation entry. It stashes provisional state when no pipeline exists, queues runtime model+effort with the 'max'->'xhigh' clamp when materialized, and writes the metadata overlay in both cases. Agent's `changeModel` collapses to a sequencer + delegation. Tests - 1810/1810 agentHost unit suite passing. - 7/7 real Claude SDK integration tests passing (2 pre-existing pending). - Live workbench E2E: full tool-call round-trip end-to-end (createSession -> setClientTools(13 tools) -> materialize -> openBrowserPage request -> workbench permission UI -> approve -> workbench opens https://example.com -> tool_result returned -> session.result). 0 occurrences of legacy failure patterns. Plan + roadmap - Roadmap Phase 10.5 marked DONE. - Full plan at src/vs/platform/agentHost/node/claude/phase10.5-plan.md. * Address PR review: fix stale setModel link + document buildOptions ambient env readsgithub.com-microsoft-vscode · 336d4b28 · 2026-05-21
- 0.9ETVPhase 10.6: MCP elicitation (real `onElicitation` translation) (#325908) * Phase 10.6: MCP elicitation (real onElicitation translation) Replace the Phase-7 `onElicitation` cancel-stub with a real translation. When an MCP server calls `elicit/create`, the request now surfaces to the user as structured input (a `ChatInputRequested` form/URL prompt, the same channel `AskUserQuestion` uses — not the permission gate) instead of being silently auto-cancelled, and the answer is delivered back to the server as an `ElicitResult`. - New `claudeElicitation.ts`: pure projections (schema -> questions, answers -> content). Each field is runtime-validated with the base-layer `validation.ts`, the field type is derived from that validator, and pinned to the MCP SDK's `PrimitiveSchemaDefinition` by a compile-time guard. - New `claudeElicitationBridge.ts`: `handleElicitation` parks on `requestUserInput`, observes the SDK abort signal, and maps the response back to an `ElicitResult` (decline only for an explicit user Decline; no-session / teardown / abort -> cancel). - Wire `onElicitation` through `IBuildOptionsInput` / `IMaterializeContext` / `ClaudeAgent._makeOnElicitation`, mirroring `canUseTool`; remove the `logElicitation` stub. - form + url modes; Phase-7 Test 18 stub replaced with the real translation; extensive projection + bridge + agent-level + integration coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Phase 10.6: address PR review — correctness fixes vs the workbench consumer Copilot review surfaced real gaps against agentHostSessionHandler.ts: - Schema-aware answer coercion: the consumer renders number/integer/boolean questions as text inputs and returns text answers, so coerce "3"/"false" back to 3/false per the field type (uncoercible values dropped). - Disable free-form input on strict MCP enum/select questions (the consumer defaults allowFreeformInput to true, allowing out-of-schema values that the translator would silently drop). - Cancel question-less requests instead of falsely accepting: a url-mode request missing its URL, or a form with no representable fields, otherwise becomes the consumer's injected required text question and resolves as accept. The bridge now cancels a request with neither a URL nor questions. - Prototype-pollution safety: read answers with Object.hasOwn and build content via Object.fromEntries so an untrusted `__proto__`/`constructor` field name can't read an inherited member (crash) or mutate the prototype. - Docs: correct the drift-guard docstring (catches incompatible reshapes, not additive new variants); drop the out-of-scope SDKElicitationComplete router bullet from the roadmap; reconcile stale test-count notes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · ead88ae1 · 2026-07-15
- 0.9ETVclaude: load Agent SDK from companion extension instead of bundling (#319263) * claude: load Agent SDK from companion extension instead of bundling Extracts SDK loading behind IClaudeAgentSdkLoaderService with two implementations (bundled require-based loader and a VS Code extension loader for ms-vscode.vscode-claude-sdk), selected once at construction by a routing service driven by experiment config. Adds install-on-demand UX that surfaces an install button when the SDK extension is missing, with cancellation-aware retry/fallback in the chat handler. * claude: address PR feedback on SDK loader - Use CLAUDE_SDK_EXTENSION_ID constant in install UX instead of hardcoded id, with placeholder in the localized error message. - Clarify install-timeout setting description: waits for install/detection only, not activation.github.com-microsoft-vscode · ca14fa12 · 2026-06-01
- 0.9ETVClaude agent host: surface user/workspace native plugins in customizations (Phase 17B) (#322766) * Claude agent host: surface user/workspace native plugins in customizations (Phase 17B) Surface Claude-native plugins (`enabledPlugins`) and their bundled components in the Agents-window customization list, mirroring the Phase 16 disk-scan / Part A hooks pattern. Discovery only — the SDK already loads these via `settingSources`, so `Options.plugins` is untouched. - New scan/claudeNativePluginScan.ts: resolves enabledPlugins (user/project/ local, local-wins precedence) to on-disk roots (marketplace cache + @skills-dir), multi-format manifest detection, fail-soft, id-traversal guard - Capture system/init.plugins in claudeSdkPipeline (source-keyed; path fallback) - Project plugins as top-level PluginCustomization; post-materialize filter + suppress SDK-namespaced standalone component duplicates - Exclude @skills-dir plugin dirs from the standalone skill scan - Tests: scan resolver, projection/filter, init-capture, skill-scan dedupe * Claude native plugins: numeric version tie-break for cached plugin roots Address PR review: the equal-mtime tie-break used plain string comparison, which picks 0.0.9 over 0.0.10. Use localeCompare with { numeric: true } so version dirs order naturally; add a regression test. * Claude agent host: mark Phase 17 done; document native-plugin learnings Mark Phase 17 complete in roadmap.md (both parts shipped) and phase17-plan.md, and capture the native-plugin surfacing learnings (surface-only no Options.plugins, source-over-path post-materialize match, PB-10 fallback suppression) in CONTEXT.md.github.com-microsoft-vscode · 77f0f646 · 2026-06-24
- 0.9ETVClaude agent: Phase 13 — session restoration (#316343) * Claude agent: Phase 13 — session restoration Implement IAgent.getSessionMessages for the Claude provider so the workbench can reload an existing Claude session's full transcript across agent-host restarts. Unblocks self-hosting. - claudeAgentSdkService.ts: add getSessionMessages binding - claudeReplayMapper.ts (NEW): SessionMessage[] -> readonly Turn[] per CONTEXT M7. Pure function; no by-products, no persistence. Splits SDK shape detection (parseSessionMessage adapter) from the stateful reducer (ReplayBuilder). - claudeAgent.ts: replace getSessionMessages stub with subagent URI dispatch + provisional check + SDK fetch + replay, all wrapped with the listSessions-style warn-log-and-return-[] resilience. - claudeReplayMapper.test.ts: 10 fixtures covering M7 grouping rules (text/tool_result/system, tail-Turn state, subagent markers, CLI-echo synthetic-message drop). - claudeAgent.test.ts: 4 Phase 13 integration tests (happy path, subagent URI, provisional session, SDK throw resilience). - CONTEXT.md: relax M7 notification gate to admit all priorities pending real-world data on priority: 'low' content. - phase13-plan.md (NEW) + roadmap.md: capture decisions, drift, council-review fixes, and mark Phase 13 done. * Read system subtype/content from envelope, not message Copilot review caught that on-disk JSONL system entries put `subtype` (and `content` for compact_boundary, `text` for notification) at the top level of the envelope alongside `type`, NOT nested inside `message`. The SDK's `SessionMessage` type only declares `{ type, uuid, session_id, message, parent_tool_use_id }` so the extra envelope fields aren't typed — but the production session parser fixtures (extensions/copilot/.../claudeSessionParser.spec.ts) confirm the on-disk shape. Net effect of the bug: real `compact_boundary` and `notification` entries were silently dropped even with `includeSystemMessages: true`, defeating the whole point of asking for them. - claudeReplayMapper.ts: parseSystemMessage now reads from the envelope via a single narrow cast; new readSystemEnvelopeText prefers `text` (notification) then `content` (compact_boundary). - claudeReplayMapper.test.ts: makeSystem helper now builds envelope-shaped fixtures; Fixture 4 asserts the actual text surfaces. * Revert "Read system subtype/content from envelope, not message" This reverts f77f3f0cd47. Listening to the SDK contract over the production parser's raw-JSONL fixtures: the SDK's `SessionMessage` type declares `{ type, uuid, session_id, message: unknown, parent_tool_use_id }` for ALL three discriminants. For user/assistant we already read the discriminant-specific payload (role, content, blocks) from `message.*`; doing the same for system (`message.subtype`, `message.text`) is the consistent pattern. The production session parser fixtures parse raw on-disk JSONL — a lower layer than `getSessionMessages()`, which normalizes those entries into the documented `SessionMessage` shape with payload inside `message`. The original implementation was correct.github.com-microsoft-vscode · 1007508e · 2026-05-14
- 0.9ETVagent host: share real-SDK integration tests across Copilot and Claude (#316532) * agent host: share real-SDK integration tests across Copilot and Claude Refactor the real Copilot SDK integration tests so the cross-provider portion is shared with a new Claude real-SDK suite, with per-provider capability flags for behaviors that differ. - Extract `defineSharedRealSdkTests` + helpers into `realSdkTestHelpers.ts`. - Replace `toolApprovalRealSdk.integrationTest.ts` with `copilotRealSdk.integrationTest.ts` (shared suite + Copilot-only tests: usage cost, cd-prefix strip, git-driven diffs). - Fold the standalone `sessionDiffsRealSdk` test into Copilot's suite. - Add `claudeRealSdk.integrationTest.ts` gated behind `AGENT_HOST_REAL_SDK=1 AGENT_HOST_REAL_SDK_CLAUDE=1`. SDK directory is resolved from the dev dependency at `node_modules/@anthropic-ai/claude-agent-sdk`. Auth requires an OAuth token with Copilot access (a vanilla `gh auth token` does not work); the second env var ensures the suite isn't auto-enabled. - Per-test server isolation: each test gets a fresh agent host so a broken test can't poison subsequent ones (notably Claude's mid-turn dispose path). Real bugs fixed along the way: - Session URIs are now UUIDs. Claude SDK rejects non-UUID session IDs. - Dev `product.ts` stub now carries `tokenEntitlementUrl` / `mcpRegistryDataUrl`, so the out-of-sources Claude path no longer hits `Failed to parse URL from undefined` in `CopilotApiService._mintToken`. - `createRealSession` defaults to `isolation: 'folder'` so the agent runs in the test's working dir instead of silently materializing into `<wd>.worktrees/...`. - macOS `/var` <-> `/private/var` mismatch in the diff test via `realpathSync(mkdtempSync(...))`. - The shell-permission test was Copilot-shaped (assumed a pending `toolCallReady`); Claude's `default` mode auto-approves safe `Bash` at the SDK layer. Test now waits for `toolCallComplete` so it works on both providers. - Tool names parameterized per provider (`bash`/`Bash`, `task`/`Task`, `exit_plan_mode`/`ExitPlanMode`). Add a deterministic unit test for the `skipPermission: true` flag on the shell-helper tools (`read_bash` / `write_bash` / `bash_shutdown` / `list_bash`) since the original model-driven real-SDK regression test for that flag was inherently flaky. * address review: lazily probe Claude SDK path, drop console.error The module-eval-time call to resolveClaudeSdkPath() emitted console.error when the SDK directory was missing, which can fail the test runner even with the suite disabled. Probe filesystem only when the suite is opted in via env vars; return undefined silently otherwise — the suite gate itself surfaces the missing dependency by skipping.github.com-microsoft-vscode · 0d23db45 · 2026-05-15