Tyler James Leonhardt
90d · built 2026-07-24
90-day totals
- Commits
- 81
- Grow
- 20.1
- Maintenance
- 24.7
- Fixes
- 3.1
- Total ETV
- 47.8
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 47 %
- By Growth share
- Top 33 %
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).
↓-16.1 %
vs 31 prior
↑+1.5 pp
recent vs prior
↑+6.6 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.
- 2.5ETVClaude agent — Phase 7: tool permission round-trip + interactive tools (#315399) * Claude agent — Phase 7: tool permission round-trip + interactive tools Wires the Claude SDK's canUseTool callback through to the host. ExitPlanMode = permission gate; AskUserQuestion = user-input prompt. Adds cross-message tool_use to tool_result correlation in ClaudeMapperState with defense-in-depth cleanup on result. * Address PR review comments - Localize TOOL_DISPLAY display names + MCP fallback (claudeToolDisplay) - Symmetric id derivation in flattenAskUserAnswers / buildAskUserSessionInputQuestions for empty-header questions - Await Query.setPermissionMode in ClaudeAgentSession.setPermissionMode and at the sendMessage callsite so the SDK acks the mode change before the next prompt yields - Guard JSON.stringify trace serialization behind getLevel() <= LogLevel.Trace - Observe options.signal in _handleCanUseTool: deny on already-aborted, race the parked permission/user-input prompt with the abort listener - Add unit tests covering the empty-header round-trip and both abort pathsgithub.com-microsoft-vscode · 798d93bd · 2026-05-08
- 2.5ETVAdd CopilotAPI service for a future Claude agent (#313553) * Add CopilotAPI service & plan for Claude This service can be used to send requests to CAPI which we will need as we shim requests from agents to CAPI. * Add CopilotAPI service & plan for Claude This service can be used to send requests to CAPI which we will need as we shim requests from agents to CAPI. * Clarify signal propagation behavior in ICopilotApiServiceRequestOptions documentationgithub.com-microsoft-vscode · ddb2b117 · 2026-04-30
- 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
- 2.0ETVagentHost/claude: add CAPI-backed local Anthropic proxy service (#313677) * agentHost/claude: add CAPI-backed local Anthropic proxy service Nothing wires this up yet — this lands the proxy + supporting helpers in preparation for the Claude Agent integration. No callers; no behavior changes for existing agents. Introduces ClaudeProxyService — a refcounted local HTTP proxy that speaks the Anthropic Messages API on the inbound side and CopilotApiService on the outbound side. Lets a Claude Agent SDK subprocess (future work) connect via ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN and see this as a real Anthropic endpoint while we route through CAPI. Surfaces: - GET / health check (unauthenticated) - GET /v1/models filtered to Anthropic-vendor + /v1/messages - POST /v1/messages non-streaming + SSE streaming pass-through - POST /v1/messages/count_tokens 501 (CAPI does not support it) Other modules: - claudeModelId.ts parse/format SDK <-> CAPI model IDs (e.g. claude-opus-4-6 <-> claude-opus-4-6-20250929) - anthropicBetas.ts filter inbound anthropic-beta headers to a CAPI- supported allowlist - anthropicErrors.ts proxy-authored Anthropic error envelopes (uses Anthropic.ErrorType from @anthropic-ai/sdk) - claudeProxyAuth.ts parse Bearer <nonce>.<sessionId> auth header Phase 1.5 contract changes in CopilotApiService: - introduce CopilotApiError carrying Anthropic.ErrorResponse envelope - COPILOT_API_ERROR_STATUS_STREAMING (520) sentinel for mid-stream errors that have no upstream HTTP status; the proxy coerces this to 502 when surfacing the error before SSE headers are sent. Lifecycle: - ClaudeProxyService is a Disposable registered on the agent host main disposable store. Start() returns refcounted handles; the listener binds lazily on first start and tears down when refcount reaches 0 (or dispose() is called). - Concurrent start() calls share an in-flight bind via a _starting promise to avoid orphaned servers; if dispose() runs while binding, the just-bound server is torn down and the awaiting caller's promise rejects. Tests cover model ID round-trips, beta-header filtering, auth parsing, the full proxy request lifecycle (non-streaming, streaming, error mapping, refcounting, concurrent start/dispose, late-binding token update), and the Phase 1.5 CopilotApiService contract additions. Subprocess ownership invariant: callers that hand baseUrl + nonce to a Claude SDK subprocess MUST kill the subprocess before disposing the handle. After dispose() the proxy may rebind on a different port and the subprocess would silently lose its endpoint. * agentHost/claude: defer request handler attach until runtime is built Address PR review: the http.createServer handler closed over `runtime` before it was assigned. There's a narrow microtask window between `server.listen()` resolving and runtime construction completing, in which an incoming request would hit a temporal-dead-zone ReferenceError on `runtime`. Fix: pass no handler to `createServer()`, build runtime fully (it can now be `const`), then `server.on('request', ...)` afterwards. Node's single-threaded event loop guarantees no `request` event is parsed and dispatched between `listen` resolving and the synchronous `server.on('request', ...)` registration, so the handler safely closes over `runtime` with no TDZ window and no `let` indirection.github.com-microsoft-vscode · 0d54c258 · 2026-05-01
- 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.1ETVagentHost/claude: Phase 3 reference grounding + Phase 4 ClaudeAgent skeleton (#313780)github.com-microsoft-vscode · 7211c0f3 · 2026-05-01
- 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