Josh Spicer
90d · built 2026-07-24
90-day totals
- Commits
- 45
- Grow
- 8.9
- Maintenance
- 5.6
- Fixes
- 1.2
- Total ETV
- 15.7
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 44 %
- By Growth share
- Top 4 %
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).
↓-46.2 %
vs 13 prior
↓-18.2 pp
recent vs prior
↑+11.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.
- 2.0ETVpolicy: dev mock server for copilot_internal policy endpoints (#321871) * policy: add dev mock server for copilot_internal policy endpoints Adds scripts/mock-policy-server, a standalone dev tool (npm run mock-policy-server) that mocks the Copilot policy endpoints DefaultAccountService calls: entitlements (/copilot_internal/user), token (/copilot_internal/v2/token), MCP registry (/copilot/mcp_registry) and managed settings (/copilot_internal/managed_settings). A small web GUI lets devs pick presets or edit each JSON response, and Wire/Unwire buttons point product.overrides.json at the local server (preserving the rest of defaultChatAgent, since bootstrap-meta merges overrides shallowly). The managed-settings JSON schema is loaded from --schema/MANAGED_SETTINGS_SCHEMA, defaulting to ./copilot-agent-runtime/schema/managed-settings-schema.json relative to the app cwd; web URLs and file URIs are accepted, and the GUI warns about keys not declared in the schema. The three browser/shared .js files are added to .eslint-allowed-javascript-files since the GUI loads them directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: address mock-policy-server review feedback - Scope permissive CORS to the mocked GET endpoints only; keep /api/* same-origin so a website can't drive /api/wire and rewrite product.overrides.json (CSRF). - Coerce an empty editor body to {} instead of "" so mocked responses stay JSON objects. - Build the endpoint meta line with textContent/DOM nodes instead of innerHTML. - Drop the misused tablist/tab ARIA roles; the nav now has an aria-label and the active item uses aria-current. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: document mock policy server in add-policy skill Add local-testing.md to the add-policy skill with basic steps for using the mock policy server (scripts/mock-policy-server) to exercise the account/managed-settings flow locally, and link it from SKILL.md and github-managed-settings.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: polish mock server GUI — schema validation, wiring backup, localStorage persistence * policy: auto-save, rename wiring to product.overrides.json, copy path button * mock-policy-server: convert server.js to TypeScript; add raw response diagnostics - Convert server.js → server.ts (runs via --experimental-strip-types) - Add endpoints.d.ts type declarations for the UMD endpoints module - Add managedSettingsRawResponse to IDefaultAccountProvider/IDefaultAccountService - Show raw response in Developer: Sync Account Policy output - Remove server.js from eslint allowed-javascript-files * mock-policy-server: convert all JS to TypeScript - endpoints.js → endpoints.ts with proper interfaces (replaces .d.ts) - public/app.js → public/app.ts with full type annotations - Server uses module.stripTypeScriptTypes() to serve .ts as plain JS to the browser — no build step needed - Remove all mock-policy-server entries from .eslint-allowed-javascript-files --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 593c7f23 · 2026-06-18
- 2.0ETVpolicy: enterprise managed_settings for Copilot clients (#318623) * chat plugins: add policy-backed enabledPlugins / marketplaces / strictMarketplaces settings Adds three new chat.plugins.* settings, each policy-backed: - chat.plugins.enabledPlugins (policy: objectChatEnabledPlugins) mapping plugin IDs (`<plugin>@<marketplace>`) to enable/disable. - chat.plugins.marketplaces (policy: array ofChatPluginMarketplaces) marketplace references (GitHub shorthand or Git URI). User entries survive alongside policy entries. - chat.plugins.strictMarketplaces (policy: ChatStrictMarketplaces) boolean restricting trust to listed marketplaces only. All three are gated on `tags: ['experimental']`. Consumers (plugin discovery, install, URL handler, marketplace service, quick-pick action) now read via `inspect()` so default + user + policy layers all flow through. A shared `readConfiguredMarketplaces` helper in marketplaceReference.ts dedups the inspect pattern across 5 sites. Adds three matching fields to IPolicyData so the policy framework has slots to fill in once the wiring lands; until then they're undefined and behave like an empty policy (no-op). Plugin discovery now distinguishes filesystem-path entries (removable from UI) from enterprise plugin IDs (non-removable) via a single shared loop; `IAgentPlugin.remove` is optional accordingly. build/lib/policies/policyData.jsonc regenerated for the new policy keys. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: implement ADR-002 enterprise managed_settings fetch & policy wiring Wires the previously-added chat.plugins.* policy slots to the new `/copilot_internal/managed_settings` endpoint on the authenticated Copilot host. Core behavior in DefaultAccountProvider: - Fetches managed_settings alongside entitlements; shares the 1-hour cache used by other account-policy fetches. - Silent fallback to local-only policy on any non-2xx, network error, parse error, or missing managedSettingsUrl. - Rate-limit-aware: backs off all /copilot_internal/* calls when the endpoint signals 429, 403 + X-RateLimit-Remaining: 0, or any non-2xx with Retry-After. - adaptManagedSettings flattens the API's structured extraKnownMarketplaces map into the existing string-array shape that chat.plugins.marketplaces consumes; tolerates malformed entries and unknown response keys (forward-compatible). - Telemetry: emits `defaultaccount:managedSettings:fetch` (owner: joshspicer) with an `outcome` bucket (ok / no-response / parse-error / status:NNN) and a `rateLimitBackoffActive` flag. Surface area: - IDefaultAccountProvider/Service expose managedSettingsFetchStatus and managedSettingsFetchedAt; ManagedSettingsFetchStatus is a named union. - Developer: Policy Diagnostics shows a Managed Settings section with the URL status, last-fetched timestamp, and a JSON dump of the applied managed-settings policy slice. - product.json adds a managedSettingsUrl key (populated via distro). Refactor: `readHeader` and `retryAfterFromHeaders` are moved to `platform/request/common/request.ts` so githubRepoFetcher.ts and this new code share one implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * bump distro to 36d906669669f12466c6912bd65d9eeb47c6522d Pulls in managedSettingsUrl from microsoft/vscode-distro#1422. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * update policyData * policy: address PR review feedback - Restore historical default for chat.plugins.marketplaces (['github/copilot-plugins', 'github/awesome-copilot#marketplace']) so existing users don't lose the two built-in marketplaces on update. Regenerate policyData.jsonc accordingly. - Seed _managedSettingsFetchStatus = 'ok' on cache-hit so Policy Diagnostics reports the applied state after a process restart that warm-starts from cached policyData (instead of stuck at 'not yet fetched'). - Scope the <plugin>@<marketplace> ID-resolution rule to the enterprise ChatEnabledPlugins setting only. User-typed entries in chat.pluginLocations that happen to contain '@' are now treated as filesystem paths, as a user would expect, not silently rewritten to ~/.copilot/installed-plugins/<x>/<y>/. Split _resolvePluginPath into a path-only resolver and a dedicated _resolveEnterprisePluginId. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: revert unnecessary _pluginLocationsConfig refactor chat.pluginLocations has no policy slot, so observableConfigValue (which uses getValue() under the hood) is functionally equivalent to the hand-rolled inspect() version. Reverting reduces diff thechurn inspect-based observable is now used only for _enterpriseEnabledPluginsConfig where the default+user+policy merge actually matters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: split managed marketplaces into dedicated policy-only setting Adds chat.plugins.extraMarketplaces (ChatExtraMarketplaces policy, included: false so it's hidden from the Settings UI). This receives the 'extraKnownMarketplaces' payload from the managed_settings API. Restores chat.plugins.marketplaces to its pre-PR shape: no policy slot, no inspect()-juggling required in consumers, no risk of accidentally clobbering user data. Users write to chat.plugins.marketplaces; the enterprise writes to chat.plugins.extraMarketplaces; the effective set is the union. Consumer simplifications: - readConfiguredMarketplaces returns { userValues, extraValues, two getValue() reads, no inspect() needed.effectiveValues } - Write-back is now just [...userValues, refValue] in all three sites. - 'Manage Plugin Marketplaces' still surfaces the 'managed by enterprise policy' badge by checking ref membership in extraValues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: tidy managed_settings code paths - fetchMarketplacePlugins: drop the over-engineered pre-dedup-by-string; parseMarketplaceReferences already dedups by canonical id. - agentPluginServiceImpl: pass source.remove directly to _toPlugin instead of wrapping in a null-asserted closure. - adaptManagedSettings: use a Set for flatten-and-dedup (insertion order is preserved). - getDefaultAccountFromAuthenticatedSessions: spread merge instead of three explicit field assignments. - developerActions: collapse the 'ok' branch into the catch-all backtick wrap; same behavior, less code. - marketplaceReference.ts: tighter JSDoc on IConfiguredMarketplaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: enforce ChatEnabledPlugins and strict-marketplace gates at discovery Previously the enterprise-managed policy values were delivered into the policy framework but not a plugin already installed locallyenforced (e.g. via the marketplace discovery path) would remain active even when the policy excluded it or strict-marketplace mode rejected its source. Adds policy enforcement on AgentPluginService.plugins, applied after discovery dedup/sort and gated by two observables: - ChatEnabledPlugins policy: when set, filters the surfaced plugin set to only those whose '<name>@<marketplace>' ID appears in the policy map with value true. Plugins without a marketplace provenance (filesystem entries from chat.pluginLocations) are unaffected. - ChatStrictMarketplaces: when on, filters out plugins whose source marketplace is not trusted. Trust is sourced ONLY from chat.plugins.extraMarketplaces (the policy-only user-setslot) entries in chat.plugins.marketplaces do NOT grant trust under strict mode. This matches the ADR-002 semantics: strict mode hands full marketplace control to the enterprise. Also updates the chat.plugins.strictMarketplaces description text to match the new behavior (was still pointing at the user setting). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: extract managed_settings adapter to dedicated helper Moves IManagedSettingsResponse and adaptManagedSettings out of defaultAccount.ts and into a new managedSettings.ts in the same folder. Adapter is a pure transformation function with no service dependencies, so it belongs in its own file alongside the HTTP/wiring code. Renames the test file to managedSettings.test.ts to match what it actually tests and tightens the suite name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: tidy enforcement filter and sync strict-marketplace policy description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: show policy-blocked plugins as disabled instead of hiding them Blocked plugins (ChatEnabledPlugins / strict marketplaces) now stay visible but are forced disabled via their enablement observable, and the enable affordance notifies the user instead of re-enabling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: enforce enabledPlugins/strictMarketplaces for Copilot-CLI plugins CLI-installed plugins under `~/.copilot/installed-plugins/<marketplace>/<plugin>/` have no `fromMarketplace` metadata, so they previously bypassed enterprise policy. Derive their identity from the install-path bucket (matching the convention used by `_resolveEnterprisePluginId`) so enabledPlugins gating applies, and add a bucket-name heuristic for strict marketplaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * log raw managed_settings response at trace level Helps debug schema drift / unknown server fields that get dropped by adaptManagedSettings(). Trace-only so it's off by default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * improve managed_settings warning for missing repo/url When a github source is missing 'repo' or a git source is missing 'url', emit a specific warning naming the missing field instead of the misleading 'unknown source type' message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * preserve marketplace name through managed_settings policy delivery The managed_settings adapter previously flattened extraKnownMarketplaces entries to bare "<owner>/<repo>" or "<url>" strings, losing the marketplace name. That broke enabledPlugins matching because plugin IDs are keyed as "<plugin>@<marketplace-name>" but our parsed reference's displayLabel was derived from the URL/repo instead. Changes: - adapter now emits { name, source } objects preserving the full shape - IPolicyData.extraKnownMarketplaces accepts string | object entries - parseMarketplaceReferences gains object-handling, using name as displayLabel - workspacePluginSettingsService shares the object parser - policy schema relaxed to allow object items Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: clarify chat.plugins.enabledPlugins description The previous 'Merged with entries from chat.pluginLocations' was misleading: the two settings use different key namespaces (plugin IDs vs filesystem paths) and the enabledPlugins policy also acts as an allowlist that gates marketplace-discovered not a symmetric merge.plugins Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: add description for chat.plugins.extraMarketplaces The setting was missing a markdownDescription, so the Settings UI card rendered empty when shown under 'Managed by organization'. Also updated the policy localization to mention the new { name, source } object form. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: shorten chat.plugins.extraMarketplaces description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: drop policy name from extraMarketplaces description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: re-fetch plugin marketplaces when ExtraMarketplaces policy changes pluginMarketplaceService.onDidChangeMarketplaces only listened for PluginsEnabled and PluginMarketplaces config changes, so the ExtraMarketplaces values delivered by the ChatExtraMarketplaces policy never triggered a the union was stale until the next user editrefetch to chat.plugins.marketplaces or a workspace-trust change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: extract IExtraKnownMarketplaceEntry to base/common/managedSettings Move the enterprise-managed marketplace entry type out of defaultAccount.ts into a dedicated managedSettings.ts so the type lives alongside other managed-settings-specific code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: cleanup pass - Sync policyData.jsonc ChatExtraMarketplaces description with the source declaration in chat.shared.contribution.ts (object-form entries were missing from the policy artifact). - Reorder Event import in agentPluginServiceImpl.ts to keep base/common imports alphabetical. - Fix stale doc reference (COPILOT_CLI_INSTALLED_PLUGINS_DIR -> the function it actually mirrors). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: accept host-only git URLs in extraKnownMarketplaces ADR-002 describes the `git` source `url` as a free-form `(string)` the example happens to be a full clone URL, but the schema doesn't require a repo path. Our marketplace-URI parser was rejecting host-only HTTPS endpoints (e.g. `https://plugins.internal.example.com`), so enterprise policy entries with marketplace-registry-style URLs were silently dropped before they ever reached the UI. Relax `parseUriMarketplaceReference` to accept host-only URLs and treat them as a marketplace endpoint identified by host alone. The canonical id becomes `git:<host>/` so distinct hosts still dedupe correctly. Existing path-aware behavior is preserved unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: fix string entry guard in extraKnownMarketplaces policy.value; fix test cloneUrl expectation - Handle string-typed entries in extraKnownMarketplaces (IPolicyData allows string | IExtraKnownMarketplaceEntry) - Fix test expectation: URI.parse normalizes host-only URLs to include trailing slash Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: read extraMarketplaces dict and convert to nested entry shape The setting schema is now `{ [name]: url-or-shorthand }` (object), so readConfiguredMarketplaces must convert each entry to the nested IExtraMarketplaceObjectEntry shape that parseMarketplaceReferences expects. Uses a regex to detect GitHub shorthand (owner/repo[#ref]) vs URI. TypeError in CI: 'extraValues is not iterable' on [...userValues, ...extraValues]. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: extract extraKnownMarketplacesToConfigDict helper + add regression tests for Settings Editor display Extract the policy.value conversion for ChatExtraMarketplaces out of chat.shared.contribution.ts into a reusable, unit-testable helper. The helper converts the IExtraKnownMarketplaceEntry[] policy payload into the { [name]: url-or-shorthand } dict that: - the Settings Editor's ComplexObject renderer can display inline as key/value rows (instead of just 'Edit in settings.json'), and - readConfiguredMarketplaces reverses back into IExtraMarketplaceObjectEntry[] so parseMarketplaceReferences preserves displayLabel = name. Tests added: undefined owner/repo owner/repo#ref raw URL (+ optional #ref) parseMarketplaceReferences flow (the regression test that catches the 'extraValues is not iterable' bug we just hit in CI) - schema-shape: chat.plugins.extraMarketplaces is registered with type=object + additionalProperties.type=['string'], the exact shape the Settings Editor requires to render as ComplexObject Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: stop spurious 'invalid marketplace entry' warnings for object-form entries url dict, policy entries always reach the marketplace fetcher as IExtraMarketplaceObjectEntry objects (not strings). The validation loop was only accepting strings, producing a 'Ignoring invalid marketplace entry: [object Object]' debug log for every valid policy entry. Validate using parseMarketplaceObjectEntry for object values so the warning fires only for genuinely-unparseable entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: drop schema-shape test that double-registered chat contribution commands The schema-shape test for chat.plugins.extraMarketplaces imported the full chat.shared.contribution module to populate the configuration registry. This re-registered commands (already registered by the workbench under test), producing 'Cannot register two commands with the same id: workbench.action.chat.markHelpful' and cascading disposable leaks in unrelated suites (EditorService, WorkingCopyBackupTracker). The other 5 tests (extraKnownMarketplacesToConfigDict + end-to-end round trip) cover the actual behavior that broke; the schema shape is exercised implicitly by the round-trip test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: normalize github.com URI/SSH refs to the GitHub shorthand canonical id Plugin marketplace trust under strict mode compares canonicalId. A plugin discovered from 'https://github.com/microsoft/vscode-team-kit.git' was being blocked even though 'microsoft/vscode-team-kit' was in the trusted list, because the URI parser produced 'git:github.com/microsoft/vscode-team-kit.git' while the shorthand parser produced 'github:microsoft/vscode-team-kit'. When parseUriMarketplaceReference / parseScpMarketplaceReference detect a github.com authority, emit the same canonical id form the shorthand parser uses so all three forms (shorthand, https URI, SCP) collapse to a single trusted reference. Existing dedup test now expects 1 entry instead of 2; ref-distinction test collapses the https+#ref entry with its shorthand sibling. Added a focused regression test asserting all four forms produce identical canonical ids. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * update policy * fix dupe policy export --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · b24c5e38 · 2026-05-29
- 1.2ETVAHP/Customizations: BrowserPluginGitCommandService for adding plugins in web (#313575) * chat: add real BrowserPluginGitCommandService for adding plugins in web Replaces the throwing stub at src/vs/workbench/contrib/chat/browser/pluginGitCommandService.ts with a real implementation that lets browser/web clients install agent plugins from public (and authenticated) GitHub repositories without a local git binary or AHP server. How it works - Resolve the requested ref to a commit SHA via GitHub's /repos/{owner}/{repo}/commits/{ref} endpoint. - Download the tarball at that SHA, decompress with the platform DecompressionStream, and stream-extract the USTAR archive directly into the workbench virtual file system at the caller's targetDir. The standard GitHub-archive wrapper directory ({repo}-{shortSha}/) is stripped so consumers see a clean tree, and any prior contents are wiped first so files removed upstream don't linger. - Persist {owner, repo, ref, sha, fetchedAt} per-target via IStorageService (chat.plugins.browserCache.v1). This lets revParse() answer locally and lets pull()/checkout() short-circuit when the upstream SHA matches the cached one -- which also feeds CustomizationRef.nonce so the AHP server's plugin manager dedupes. - Best-effort silent IAuthenticationService lookup attaches a GitHub token when one is already available, enabling private-repo installs; public repos still work with no session. 401/403 surfaces a typed GitHubAuthRequiredError so future UI can drive sign-in. - checkout() handles SHA-pinned plugin sources (the AbstractGitPluginSource path): no-op when the SHA matches, otherwise fetches the tarball at the requested SHA. Branches/tags/short SHAs resolve through the commits API. - Non-GitHub clone URLs throw an actionable localized error directing users to the desktop client or a remote agent host. - TAR extraction validates entry paths (rejects '.', '..', empty, NUL, leading-slash segments and double-checks isEqualOrParent) so a malicious archive cannot escape targetDir. The DI singleton registration in chat.contribution.ts already wires IPluginGitService -> BrowserPluginGitCommandService; the new constructor parameters are injected automatically. Tests - New src/vs/workbench/contrib/chat/test/browser/pluginGitCommandService.test.ts covers URL parsing (canonical / trailing-slash / extra-segment / malformed), tarball fetch+extract, no-op pull on SHA match, re-download on SHA change, stale-file cleanup, path-traversal entry rejection, checkout no-op / re-extract / no-metadata, and the auth-required error path. Test fixtures build a minimal valid USTAR + gzip via CompressionStream so bytes round-trip through the production DecompressionStream. Reuse notes - Uses isSuccess / isClientError / asJson from platform/request rather than rolling status-code checks. - Uses dirname / isEqualOrParent / joinPath from base/common/resources for path arithmetic and traversal defence. - GitHubApiClient (sessions/contrib/github) was considered but is layering-isolated, JSON-only, and forces sign-in -- wrong semantics for best-effort silent auth and binary tarball download. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chat: address council review on browser plugin git service Council review surfaced ~12 follow-ups across correctness, security, and parser robustness. This commit addresses the actionable ones. Correctness - Stage extraction in a sibling `.staging-{uuid}` directory and swap into place via `IFileService.move(..., overwrite=true)` only on success. If anything throws (network, gunzip, malformed tar, cancellation, FS write error), the staging dir is cleaned up and the existing `targetDir` is left untouched. Previously the target was wiped *before* extraction began, so a mid-flight failure left the persisted SHA cache pointing at an empty directory. (consensus C1) - `pull()` now wraps its catch block with `_maybeLogTransientError` for parity with `cloneRepository` / `checkout`. (S6) - `revParse(repoDir, ref)` no longer silently ignores `ref`: when asked for a 40-hex SHA that does not match the cached one, it throws instead of lying. (S3) Rate-limit detection - New `GitHubRateLimitError`, distinct from `GitHubAuthRequiredError`, thrown when GitHub returns 403 with `X-RateLimit-Remaining: 0` or a `Retry-After` header. Higher-layer UI can present "wait" rather than "sign in". `_maybeLogTransientError` logs the retry-after window. (C2) Auth + redirects - Drop the dead `followRedirects: 5` option (browser fetch ignores it per IRequestService impl). Add a comment on `fetchAndExtractGitHubTarball` explaining the codeload signed-URL flow: GitHub's tarball endpoint 302s to a URL whose authorization is encoded in the URL itself, so private-repo downloads survive the cross-origin Authorization-header strip. (C3 cleanup) - Document the multi-account auth-session selection limitation in `_lookupGitHubToken` rather than try to solve it here -- account selection is the auth provider's responsibility. (C6) Parser robustness - `readOctal` -> `readNumericField`. Now handles: - leading whitespace (legal POSIX padding) -- previously truncated to 0 - GNU base-256 binary encoding (high bit of byte 0 set) -- previously silently mis-aligned subsequent block offsets - Invalid fields throw rather than silently returning 0, so corrupt tarballs surface as errors instead of producing empty entries. - USTAR `prefix` field now joined unconditionally per spec (`${prefix}/${name}`); previous heuristic skipped the prefix when `name.startsWith(prefix)` which is non-standard. The GNU LongLink path correctly bypasses prefix join via a `fromLongLink` flag. (C5) - `stripArchiveRoot` rejects absolute paths instead of silently rebasing them under `targetDir`. (S4) - `safeJoinUnderTarget` also rejects backslash-bearing segments to defend against Windows-style separators on tar entries that would escape `targetDir` when materialised through a Windows AHP server's `agent-client:` provider. (S1) URL parsing - Reorder normalisation in `parseGitHubCloneUrl` so trailing slashes are stripped before the `.git` suffix; URLs like `https://github.com/o/r.git/` now parse correctly. (S2) Cache hygiene - Cache-key on `getComparisonKey(targetDir, ignoreFragment=true)` instead of `URI.toString()`, so callers passing equivalent URIs with different trailing-slash / percent-encoding don't silently miss the cache. (S5) - On first cache load, kick off a fire-and-forget sweep that drops entries whose `targetDir` no longer exists on disk. Bounds the storage map size when external code (e.g. `cleanupPluginSource`) deletes a plugin directory without notifying us. (C4) Tests - Rate-limit (`GitHubRateLimitError`) on `403 + X-RateLimit-Remaining: 0`. - Failed extraction leaves the previous `targetDir` intact and the cache reporting the previous SHA. - Backslash-traversal entry rejected. - USTAR prefix split + GNU LongLink paths via a new `makeGzippedTarWithSpecial` test fixture. - `parseGitHubCloneUrl` accepts `https://github.com/o/r.git/`. - `revParse` throws on unrelated full SHA, accepts cached one. Council items not addressed in this commit - Multi-account session selection (C6): documented as a VS Code-wide auth UX concern, not a plugin-git issue. - Cross-origin redirect end-to-end test (C3): the unit-test stub doesn't simulate redirects; the fix is a real-world smoke test against vscode.dev which is out of scope for this commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chat: keep default plugin toolbar actions * chat: refine plugin add actions * chat: reuse signed in github session for plugin clone * chat: fallback to anonymous plugin clone * chat: fetch plugin repos via tree+raw to bypass CORS GitHub's /tarball/ endpoint 302-redirects to codeload.github.com, which returns no CORS headers. Browser fetch() therefore fails the preflight check with TypeError: Failed to fetch before any of the existing auth-retry logic in BrowserPluginGitCommandService can run, so even public repos with a signed-in session cannot be installed. Replace the tarball flow with two CORS-friendly endpoints: - GET /repos/{owner}/{repo}/git/trees/{sha}?recursive=1 returns the full file listing. - GET https://raw.githubusercontent.com/{owner}/{repo}/{sha}/{path} returns each blob's bytes (and accepts the same Authorization header for private repos). Drop the now-unused gunzip + tar parser. Update tests to stub the new tree + raw responses instead of building synthetic gzipped tar archives. * chat: fetch plugin blobs via api.github.com to bypass CORS raw.githubusercontent.com refuses the OPTIONS preflight that an Authorization: Bearer header forces, so the previous tree+raw flow still failed the CORS check (TypeError: Failed to fetch). Switch the per-blob download to api.github.com's /git/blobs/{sha} endpoint, which is properly CORS-enabled and accepts auth headers. The blob SHA already comes back from the tree response, so no extra round-trips are needed; content is base64-encoded JSON which we decode via decodeBase64. Also add a loggedRequest wrapper that re-throws transport-level errors with the URL we were trying to reach. Without it, browser fetch CORS / DNS failures bubble up as a bare 'TypeError: Failed to fetch' that hides which request actually failed. Surface the same context through _maybeLogTransientError in the install path. * chat: drop unused bytesResponse test helper * chat: rename githubTarballFetcher to githubRepoFetcher The file no longer contains tarball logic — it fetches the repo tree and individual blobs via api.github.com. Rename to match. * chat: trim verbose comments in plugin git service * chat: tidy plugin git service auth ladder and comments - Flatten the nested try/catch in cloneRepository into a linear loop over the auth-ladder rungs (signed-in token → anonymous → fresh repo session). - Trim further verbose comments in the fetcher and cache helpers. * chat: surface locally-installed plugin items in remote-harness view When the active harness has both an itemProvider and a syncProvider (remote agent host), fetchItems blends remote items with local items. The local pass only included PromptsStorage.local / .user files, so files contributed by locally-installed agent plugins (e.g. one just cloned into vscode-userdata:/User/agent-plugins/...) never reached the customizations UI. Widen the local pass to also include PromptsStorage.plugin files. Plugin files are not individually syncable — the plugin is the unit of sync — so they're returned without the syncable marker. They still get the right grouping via the normalizer's plugin-URI check. * chat: refresh customizations debug output - Stage 6: render fromMarketplace as name@version (marketplace, type) instead of [object Object]. - Stage 3: surface syncable count and per-item syncable / pluginUri flags so the local syncable vs locally-installed plugin split (added in fetchLocalSyncableItems) is visible. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · e6b9ae7f · 2026-05-01
- 1.2ETVAdd discovery of copilot MDM policy (#320991) * Add Copilot managed settings policy source * Refactor Copilot managed settings policy evaluation * Clarify raw managed settings bridge policy * Use dedicated Copilot managed settings service * Avoid Copilot managed settings IPC update loopgithub.com-microsoft-vscode · d72b81a9 · 2026-06-12
- 1.1ETVLoad managed-settings.json from well-known disk path (#321870) Add a file-based managed-settings delivery channel that reads managed-settings.json from a well-known per-OS disk path in the main process and exposes it to renderer windows over IPC. Mirrors the existing Copilot managed-settings (server / native MDM) channels. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 865f5a0b · 2026-06-24
- 0.9ETVLift customization items into a single observable model (#312463) * Lift customization items into a single observable model Removes debt where the sidebar 'Customizations' widget computed per-section counts via a parallel discovery code path (customizationCounts.ts) that diverged from the actual Customizations editor's data (e.g. when a ChatSessionCustomizationProvider or AHP customization was active). Introduces a singleton owning the per-active-IAICustomizationItemsModel harness ProviderCustomizationItemSource cache and exposing per-section IObservable<readonly IAICustomizationListItem[]>. Both the editor list widget and sidebar surfaces (per-link badges + header total) now read from the same observables, so counts cannot diverge from what the editor shows. - New: aiCustomizationItemsModel.ts (+ unit tests) - Editor list widget + management editor: consume the model via autoruns - Sidebar CustomizationLinkViewItem: single autorun over the model / IMcpService.servers / IAgentPluginService.plugins - AICustomizationShortcutsWidget header total: derived sum - Deletes customizationCounts.ts and its test - Updates fixture and AI_CUSTOMIZATIONS.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address council review + CI feedback - Make items model lazy: sections only fetch on first read, avoiding the 5x provider enumeration on construction. Source.onDidChange / harness switch / workspace change refetch only sections that have already been observed. - Cache sources by descriptor identity (not id) and prune entries whose descriptor is no longer in availableHarnesses. Fixes stale binding when an external harness re-registers under the same id. - Cache per-section count derived in the constructor (no allocation per call). - Add IAICustomizationItemsModel.whenSectionLoaded(section) so editor's setSection (now async again) can await the first keeps thefetch screenshot fixture deterministic. - Sidebar header total now sums over CUSTOMIZATION_ITEMS (the visible links) instead of every prompts-based section, so it cannot exceed the sum of per-link badges. Excludes Prompts which the sidebar does not surface. - Wrap items-model unit tests in a sub-suite so per-test teardown disposes before the leak-check teardown runs. Add coverage for lazy fetching and descriptor re-registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Wire IAICustomizationItemsModel into the list widget fixture The list widget now consumes IAICustomizationItemsModel; the fixture was still injecting only the underlying services and so failed to instantiate the widget. Register the real AICustomizationItemsModel (it transparently fetches through the existing prompts-service mock) and add the missing mock methods (findAgentSkills, getPromptSlashCommands, getHooks, getSkillUIIntegrations) that the model's discovery path exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · fc158466 · 2026-04-25
- 0.8ETVFix AI customization grouping for provider-supplied built-in items (#313568) * sessions: count remote plugin customizations Remote agent-host customization providers can contribute plugin rows and plugin-sourced items without a local plugin URI. Preserve provider-declared storage while normalizing items and include remote provider plugin rows in sidebar plugin counts, excluding locally synced remote-client rows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: dedupe locally installed plugins from harness rows When the AHP harness reports plugins that are already known to IAgentPluginService (e.g. the local Copilot CLI surfaces its installed plugins as remote-host customizations), do not double-count or double-display them. Match by display name and fold harness-provided rows into the locally installed plugin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: add data-source mirror tests for customizations counts Adds a new 'data sources' suite to AICustomizationItemsModel tests that: - Validates getCount() for each prompts-based section (Agents, Skills, Instructions, Prompts, Hooks) reflects provider items filtered by the section's prompt type. - Validates getCount() refetches and updates when the provider fires onDidChange. - Validates getPluginCount() in three scenarios: only local plugins, only harness plugin rows (with type='plugin' / 'plugins' and remote-client filtered out), and a mix that exercises name-based dedup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve groupKey/isBuiltin in URI-inference fallback The Agents app customization provider declares its built-in items only via groupKey: BUILTIN_STORAGE — without an explicit storage, extensionId, pluginUri, or workspace-anchored URI. The final fallback in inferStorageAndGroup dropped groupKey/isBuiltin and returned PromptsStorage.user, so those items rendered under 'User' instead of 'Built-in'. Carry groupKey and isBuiltin through the fallback so the list widget preserves the provider's intent. Adds a regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align getPluginCount dedup with PluginListWidget basename fallback When a locally installed plugin has a falsy IAgentPlugin.label, the editor PluginListWidget renders it under basename(plugin.uri) (see installedPluginToItem), but the items-model getPluginCount derived its dedup key from (label ?? '').toLowerCase(). Result: a remote provider row whose name matched the URI basename was hidden by the editor list but still added to the sidebar plugin count, recreating the very count drift this PR is trying to eliminate. Use the same (label || basename(uri)) fallback as the widget. Adds a regression test that fails without the fix. Council-review: addresses 3/3 consensus finding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address council review: align dedup, widen storage type Two fixes from a multi-model code review of this PR: 1. PluginListWidget.installedPluginToItem used `plugin.label ?? basename`, but the items model's getPluginCount uses `plugin.label || basename`. For plugins with an empty-string label, the dedup keys diverged, so a remote provider row matching the URI basename was hidden by the editor list yet still added to the sidebar plugin count — recreating the very count drift earlier commits in this PR aim to eliminate. Switch the widget to `||` so empty labels also fall back to the URI basename. 2. ICustomizationItem.storage was typed as PromptsStorage, but inferStorageAndGroup compared `item.storage === (BUILTIN_STORAGE as unknown as PromptsStorage)`, which is type-laundering. Widen the field to AICustomizationPromptsStorage so providers can declare `storage: BUILTIN_STORAGE` without a cast, and drop the cast in inferStorageAndGroup. Coerce back to PromptsStorage at the IChatPromptSlashCommand boundary in getPromptSlashCommands. * Recognize User/globalStorage extension paths as extension-owned Extensions like Copilot Chat materialize prompt files under their own `globalStorageUri` and register them via the prompt-file provider API (e.g. ~/<userdata>/User/globalStorage/github.copilot-chat/ask-agent/Ask.agent.md). When such items reach inferStorageAndGroup with no extensionId/pluginUri/storage and the URI doesn't fall under a workspace or plugin folder, they would previously land in "User" instead of the chat extension's "Built-in" group. Extend extractExtensionIdFromPath to also recognize User/globalStorage/<extensionId>/... paths. Use a strict publisher.name regex to avoid matching unrelated entries like state.vscdb. Add three regression tests. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ubuntu <josh@ahp.4mywozgnka0etnlo23z031udwc.xx.internal.cloudapp.net>github.com-microsoft-vscode · 1eb3e7e8 · 2026-05-02
- 0.7ETVchat: replace embedded MCP/plugin editors with compact detail widgets (#312465) * Replace embedded MCP/plugin editors with compact detail widgets The Chat Customizations management editor previously embedded the full-page McpServerEditor and AgentPluginEditor inside its split-pane detail host. Those editors are designed for a wide standalone editor area and rendered visually broken in the narrow split pane regardless of CSS overrides. This change introduces two small dedicated detail components, EmbeddedMcpServerDetail and EmbeddedAgentPluginDetail, which render compactly in the split pane (icon, name, scope/source, description) and offer an 'Open in editor' link to launch the full standalone editor for advanced flows. The previous '.extension-editor' host overrides are removed in favor of focused '.ai-customization-embedded-detail' styles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add standalone fixtures for embedded MCP/plugin detail widgets Adds six new component-explorer fixtures that render EmbeddedMcpServerDetail and EmbeddedAgentPluginDetail in isolation (workspace/user/empty for MCP, installed/marketplace/empty for plugin). These complement the existing host- editor fixtures and catch regressions in the widgets themselves without requiring the full management editor stack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove 'Open in editor' link from embedded MCP/plugin detail widgets The full-page McpServerEditor and AgentPluginEditor are visually broken in several flows, so linking out to them from the compact detail widget did more harm than and the link looked out of place in an otherwise minimalgood header. Drop the link, its handlers, and the now-unused services (IHoverService, IEditorService, IInstantiationService) along with the related CSS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: fix codicon class & doc inaccuracy - EmbeddedMcpServerDetail: server.codicon already contains the full codicon class name (e.g. "codicon-foo"), matching the pattern in mcpServerWidgets.ts. The previous code prefixed it again and validated against the Codicon registry, so custom server icons never rendered. Mirror the existing pattern: `codicon ${server.codicon}` when set, otherwise the themed mcpServerIcon. - AI_CUSTOMIZATIONS.md: drop stale mention of the "Open in editor" link, which was removed earlier in this branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fixtures: add findHarnessById to mock ICustomizationHarnessService The AICustomizationManagementEditor constructor calls harnessService.findHarnessById, which the fixture mock did not implement. This caused all renderEditor-based fixtures (including McpServerDetailNarrow and PluginDetailNarrow) to crash during construction and produce blank screenshots in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 37eaefac · 2026-04-25
- 0.7ETVRefactor Copilot managed-settings for maintainability (#322439) * Refactor Copilot managed-settings for maintainability Centralize structured (object/array) managed-setting handling behind a single descriptor table so adding a key touches one place, consolidate the duplicated equality helpers onto `equals`, and add shared `hasManagedSettingsDefinitions` and `managedSettingValue` helpers. Strictly behavior-preserving. Incorporates a 3-model maintainability review: - `adaptManagedSettings` builds the scalar remainder via `{ ...response }` plus delete (CopyDataProperties) instead of for..in + assignment, so a server-sent own `__proto__` key cannot trigger the inherited setter. This matches the original `...rest` semantics; adds a regression test. - `managedSettingValue` is memoized per key so its policy-definition reference identity is real rather than incidental to the call site. - Corrected JSDoc and skill docs that overstated `responseField` as compiler-checked; it is a hand-maintained union backstopped by tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify why structured managed-settings keys must declare type: 'string' The bag-carrying `type` is load-bearing, not cosmetic: `projectManagedSettings` gates each value with `typeof value === type` and drops mismatches, and the native MDM watcher reads the registry/plist value as that type. Spell out that omitting it (or declaring the object/array type) makes a structured key fail projection and silently never apply. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: allocation-free empty check, __proto__ test, doc accuracy - hasManagedSettingsDefinitions: reuse the allocation-free isEmptyObject helper instead of Object.keys(...).length (the bot's only valid nit). - Add a primitive `__proto__` regression test proving a server-sent `{"__proto__": true}` scalar is dropped, never pollutes the result (disproves the reviewer's prototype-pollution concern). - Fix github-managed-settings.md: omitting `type` or declaring `'object'`/`'array'` is a compile error (the field is required and constrained to `'string' | 'number' | 'boolean'`), not a runtime drop; only `'number'`/`'boolean'` compile-but-drop-at-runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Surface managed-settings source in Policy Diagnostics Centralize the server-over-MDM precedence into a shared selectManagedSettings helper (plus a ManagedSettingsSource union) and reuse it in both AccountPolicyService and the Policy Diagnostics report, so the report can never drift from the source that policy evaluation actually applies. Rewrite the diagnostics "Managed Settings" section to: - show the Active source (GitHub Server API / Native MDM / None) - break down each channel (server fetch status + raw response, native MDM bag) - label the raw response as the last *successful* fetch, so a later failed fetch (e.g. a 404) no longer looks like it contradicts an empty effective bag - compute the true effective bag via the shared projectManagedSettings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mock policy server "Generate example" not persisting The "Generate example" button filled the editor and the localStorage draft but never called debouncedSave(), so the generated body was never POSTed to /api/state and the endpoint kept serving the empty preset. Add the missing debouncedSave() to match applyPreset() and the editor input handler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Strip prose from Policy Diagnostics and collect managed-settings parse errors The Developer: Policy Diagnostics "Managed Settings" section now renders data only (tables and JSON blocks, no explanatory paragraphs). It also collects non-fatal parsing/normalization warnings from every stage of the managed-settings pipeline, jsonc-style (accumulate, never throw), and surfaces them in a new "Parse Errors" section: - adapt: re-runs adaptManagedSettings on the raw server response - project: re-runs projectManagedSettings against the declared policy keys - parse: re-parses JSON-payload string values with the jsonc parser This explains why a key is silently dropped. For example a server extraKnownMarketplaces entry with source "github" but no "repo" now shows the "requires \"repo\"" warning instead of just vanishing from the bag. Adds a focused test for that github-without-repo normalization case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tighten Policy Diagnostics managed-settings rendering (review follow-up) Code-quality pass on the managed-settings diagnostics section: - Extract a jsonBlock() helper for the repeated fenced-JSON rendering (4 call sites collapsed). - Parse only the known JSON-payload keys (enabledPlugins, strictKnownMarketplaces, extraKnownMarketplaces) instead of a leading-brace heuristic. This mirrors what PolicyConfiguration actually parses, avoids mis-sniffing scalar values, and catches malformed payloads that don't start with a brace. - Unify the raw-response guard on isObject() so the printed raw response and the adapt-stage warning harvest use one predicate. - Drop the defensive object copy in projectManagedSettings(); it is read-only, so normalize undefined with `?? {}` instead of spreading. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix native MDM availability in Policy Diagnostics; tidy table headers The diagnostics report showed "Native MDM | Available | no (desktop only)" even on desktop. ICopilotManagedSettingsService was registered only in the electron-main process and hand-plumbed into AccountPolicyService, but never placed in the renderer service collection, so the report's accessor.get(ICopilotManagedSettingsService) always threw and mislabeled the channel as unavailable. Register the CopilotManagedSettingsChannelClient (the renderer's handle to the main-process service) in the service collection in both desktop.main.ts and sessions.main.ts. The diagnostics now resolves it on desktop and Agents windows and reports real native MDM availability and values; web still has no native channel and correctly reports unavailable. Also tidy the report builder: extract a PROPERTY_VALUE_TABLE_HEADER constant for the five repeated two-column table headers, and drop the now-misleading "(desktop only)" annotation on the availability row. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · a4ce08f7 · 2026-06-23
- 0.6ETVAllow one policy to apply to many settings (#321515) * config: allow one policy to govern multiple settings via policyReference Previously each enterprise policy mapped to exactly one configuration setting: `ConfigurationRegistry` enforced a strict 1:1 policy-name guard and rejected any second setting reusing a policy name. This made it impossible for a single admin policy to lock more than one setting (for example, gating the same agent in both the editor window and the Agents window). This introduces an explicit, subordinate attachment mechanism while keeping the strict owner guard: - Add `IPolicyReference` (name + optional runtime-only `value` / `managedSettings`). A setting may declare `policyReference` to be governed by a policy that is *owned* (fully declared via `policy`) by another setting. References deliberately carry no catalog metadata (category / minimumVersion / localization) so exactly one owner per policy name provides those, keeping the exported catalog and generated ADMX/plist unambiguous. - `ConfigurationRegistry` keeps `getPolicyConfigurations()` 1:1 (owners) and adds `getPolicyReferenceConfigurations()` (name -> set of subordinate settings). The duplicate-owner guard is retained; a setting declaring both `policy` and `policyReference` is rejected. - `PolicyConfiguration` resolves references: owners win when present, and a reference synthesizes a per-process definition from its own schema type so the policy still resolves in processes where the owner is not loaded. Policy changes now fan out to the owner and all references. - Developer "policy-controlled settings" diagnostics list references (tagged) alongside owners, and trace logging reports owner/reference registration and fan-out for debugging. Adds unit tests covering registration, the both-declared rejection, owner+reference value application, change propagation, and orphan references (owner registered elsewhere). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: gate Claude/Codex 3P agents across editor and Agents windows Uses the new policyReference mechanism to extend enterprise policy control over the agent-host third-party agents. - Reuse the existing `Claude3PIntegration` policy (owned by the Copilot extension setting `github.copilot.chat.claudeAgent.enabled`) by adding `policyReference`s on the agent-host setting `chat.agentHost.claudeAgent.enabled` and the Agents window setting `sessions.chat.claudeAgent.enabled`. Disabling Claude via policy now applies across the editor window and the Agents window. - Add a new `Codex3PIntegration` policy, owned by `chat.agentHost.codexAgent.enabled` — the single runtime through which Codex is surfaced in both windows. - Both references and the Codex owner respect `chat_preview_features_enabled === false` so organizations that disable preview features automatically disable these agents. - Regenerate the policy catalog entry for Codex3PIntegration; Claude3PIntegration stays owned by the existing extension setting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy export: link referenced settings in the exported catalog The exported policy catalog (build/lib/policies/policyData.jsonc) drives enterprise docs. Now that one policy can govern multiple settings via policyReference, each catalog entry should list every setting the policy controls so docs can surface all of them. - Add `referencedSettings?: string[]` to PolicyDto. - During export, populate it for each policy: references registered in the workbench process are discovered from the configuration registry, and references that live in app surfaces not loaded during export (the Agents window, a separate layer the workbench cannot import) are supplemented from a small documented CROSS_SURFACE_POLICY_REFERENCES map. The union is sorted for stable output. - Regenerate the catalog: Claude3PIntegration now links both `chat.agentHost.claudeAgent.enabled` and `sessions.chat.claudeAgent.enabled`. Codex3PIntegration governs only its owning setting, so it has no referencedSettings. - Add trace diagnostics reporting how many referenced settings were linked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * config: make policy owners win over references regardless of registration order Council review found that a late-registering policy owner could not replace an earlier subordinate `policyReference`. `AbstractPolicyService.updatePolicyDefinitions` merged existing-wins and only re-ran when the definition *count* grew, while `PolicyConfiguration` only gave owners precedence within a single batch. So when a reference registered before its owner across separate update cycles — exactly the editor-window case, where the agent-host Claude reference loads eagerly via chat.shared.contribution while the Copilot extension owner loads later — the owner's `value`/`managedSettings`/`restrictedValue` were silently dropped. Fix: - `AbstractPolicyService.updatePolicyDefinitions` now replaces an existing definition for a policy name when a different definition object is submitted, and re-runs `_updatePolicyDefinitions` whenever anything changed (not only on count growth). - `PolicyConfiguration` resolves each policy name owner-first against the registry (not just the current batch) and caches the source schema object per name, so it submits a stable definition object and only re-submits on a real change (e.g. a reference being upgraded to its owner). This keeps owners authoritative regardless of registration order while avoiding redundant policy-service / watcher churn. Adds regression tests: owner definition wins when both are present, and a late-registering owner supersedes an earlier reference definition. Verified: node config/policy suites and browser AccountPolicyService / MultiplexPolicyService tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: drop non-cloneable value callback from serialized policiesData CI's `policyExport.integrationTest` hung (60s timeout) on all Electron jobs. Root cause: `AbstractPolicyService.serialize()` included the full `PolicyDefinition` — including its `value` callback (a function) — in the `policiesData` that the main process sends to the renderer as part of the window configuration. Electron structure- clones that payload over IPC, and a function cannot be cloned ("An object could not be cloned"), so window-configuration resolution never completed and the export window never exited. This only surfaced now because the agent host settings contribution (`agentHostStarter.config.contribution.ts`) is imported in the main process, making it the first policy with a `value` callback registered in the main process policy service. Renderer-only policies reach the main service through the IPC channel, which already drops functions, so their callbacks never entered `policiesData`. Fix: `serialize()` now emits a structured-clone-safe definition (type, managedSettings, restrictedValue) via `toSerializablePolicyDefinition`, dropping the `value` callback. The callback is only evaluated by account-based policy services in the owning process and is never read by `PolicyChannelClient` consumers, so nothing depends on it being transported. Adds a regression test asserting `serialize()` output is structured-clone-safe. Verified: the local `--export-policy-data` run now completes and its output matches the checked-in build/lib/policies/policyData.jsonc under the integration test's normalization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * config: simplify policyReference implementation Trim the policyReference change in response to review feedback that it was larger than necessary: - Drop the defensive policy type-consistency guard (`findPolicyTypeMismatch`) and its tests. The owner is authoritative for the resolved definition's type, so a mismatched reference type is harmless in practice; the strict owner guard and the both-declared guard remain. - Revert the developer "policy-controlled settings" diagnostics changes — that was a debug-only nicety, not needed for the feature. - Condense verbose comments and remove redundant trace logging in PolicyConfiguration and the IPolicyReference doc. No behavior change to policy resolution; the regression tests (owner-wins, late-owner, reference resolution, serialize clone-safety) are unchanged and still pass, and the policy export still matches the checked-in catalog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * config: merge reference runtime bits so a policy callback is never dropped Second council review found that owner-first resolution dropped a reference's account-policy `value` callback. Claude3PIntegration's owner is the Copilot extension setting (contributed via product.extensionConfigurationPolicy as JSON), which cannot carry a `value` function; the `chat_preview_features_enabled` callback lives only on the agent-host and sessions `policyReference`s. Because the owner won unconditionally, AccountPolicyService (which only evaluates gating when `policy.value` exists) never applied preview-feature gating to Claude in any process where the Copilot extension is loaded. Fix: `resolvePolicyDefinition` now merges — the owner provides the authoritative type plus any runtime bits it declares, and references fill `value` / `managedSettings` the owner does not provide (and still supply the whole definition when no owner is loaded). This keeps owners authoritative while letting a reference contribute the callback an extension/distro owner cannot declare. Also fixes a related gap (flagged in review): deregistering a policy owner now re-resolves the policy name so the definition falls back to a surviving reference, tracked via a key→policy-name map. Definition change detection now compares the resolved definition's fields (callbacks come from stable registry objects) instead of the source object identity. Adds regression tests: a value-less owner uses the reference's callback, and owner deregistration falls back to the reference. Export still matches the checked-in catalog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * config: make policyReference a pure pointer (drop value/managedSettings) A `policyReference` should not redefine policy semantics — the owner is the single source of truth. Allowing references to carry their own `value` callback recreated the owner-vs-reference divergence that required merge/precedence rules and produced the bug the previous review round fixed. `IPolicyReference` is now just `{ name }`. A reference contributes only the policy name so the setting is gated and the OS policy watcher observes the name in processes where the owner is not loaded; the owner provides the type, value callback and all other runtime behaviour. `resolvePolicyDefinition` is correspondingly simplified back to owner-authoritative (no value/managedSettings merge). Behavioural effect: the Claude agent-host and Agents-window settings are no longer auto-disabled by the `chat_preview_features_enabled` GitHub account policy — they are gated by the `Claude3PIntegration` policy itself (admin OS/MDM + account policy by name), exactly as the editor-window extension setting already behaves on main. That preview-features auto-disable was an artifact of the reference-value workaround, not a requirement; the extension-owned `Claude3PIntegration` cannot express a callback anyway. Codex keeps its preview-features auto-disable because its owner is core code. Tests updated: owner definition is authoritative, reference only contributes the name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: enforce policyReference type match; diagnostics + cleanups - export throws if a policyReference setting's type differs from the owning policy's type - drop hardcoded CROSS_SURFACE_POLICY_REFERENCES (referencedSettings now registry-driven) - Developer: Policy Diagnostics lists policyReference settings - rename updateToPolicyDefinitionType -> toPolicyDefinitionType, fix 'proprety' typo, tighten comment - regenerate policyData.jsonc * policy: address PR review nits - make Codex3PIntegration policy description surface-agnostic (drop 'directly in the editor'); sync policyData.jsonc - fix 'acutal' -> 'actual' typo in the policyReference tests added by this PR * policy: trim verbose policyReference comments to one-liners * policy: trim verbose comments across the PR * policy: trim comments in policy.ts --------- Co-authored-by: Ubuntu <josh@ahp.4mywozgnka0etnlo23z031udwc.xx.internal.cloudapp.net> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 2fcb6694 · 2026-06-17
- 0.5ETVplugins: use manifest `name` for direct-install plugin labels (#315888) * plugins: use manifest `name` for direct-install plugin labels Direct-installed plugins (no marketplace metadata) were displaying their filesystem basename (e.g. `sukumarp2022--slide-creator-plugin`) instead of the human-readable `name` declared in their `plugin.json` manifest. Pre-read the manifest at plugin construction time and extend the label fallback chain to `fromMarketplace?.name ?? manifestName ?? basename(uri)`, matching the pattern already used by `readSinglePluginManifest`. Also introduces a tighter `IPluginManifest` interface to replace the loose `Record<string, unknown>` type. Fixes #315855 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * plugins: address PR review - Guard `manifest.name` with `typeof === 'string'` to defend against untrusted JSON (e.g. `name: 123`), which TypeScript can't catch because the parsed manifest is cast from `unknown`. - Add tests covering the manifest-name label fallback: one for the happy path (direct install with manifest `name`) and one for the fallback to basename when `name` is missing, blank, or non-string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · e04aa9d8 · 2026-05-12
- 0.4ETVAlign managed settings MDM and account-based (#321218) * Route Copilot plugin/marketplace policies through the single managed-settings flow Make `IPolicyData.managedSettings` the single channel for enterprise-managed Copilot settings so server-delivered and native MDM-delivered settings resolve identically — the only difference is the source. - `adaptManagedSettings` now emits only the canonical `managedSettings` bag: scalars flatten to dot-paths; `enabledPlugins` / `extraKnownMarketplaces` are carried as canonical JSON strings (the same shape an admin authors via native MDM). `PolicyConfiguration` parses them back into the object-typed settings. - `ChatEnabledPlugins` / `ChatExtraMarketplaces` / `ChatStrictMarketplaces` now read from `policyData.managedSettings` and declare their managed-setting keys; removed the typed `enabledPlugins` / `extraKnownMarketplaces` / `strictKnownMarketplaces` fields from `IPolicyData`. - Relocate `extraKnownMarketplacesToConfigDict` to `base/common/managedSettings` for layering; re-export from the chat contrib. - Tests: rewrite `adaptManagedSettings` assertions to the canonical bag; add an end-to-end equivalence test proving a server JSON string and a native MDM JSON string resolve to the identical typed object. * Address review feedback - Reword `adaptManagedSettings` doc to not imply it enforces the declared schema; clarify that declaration-driven filtering happens later in `projectManagedSettings`. - Short-circuit `hasManagedSettingsPolicyDefinitions` instead of building the full aggregated definitions object just to answer a boolean.github.com-microsoft-vscode · 976d9981 · 2026-06-12
- 0.4ETVsessions: add customizations overview header action (#312868) * sessions: add home button to customizations header to open welcome page Add a home icon button to the 'Customizations' collapsible header in the Agents sidebar. The button: - Appears on hover of the header row (opacity transition) - Opens the AI Customizations management editor and navigates to the welcome/overview page via showWelcomePage() - Supports keyboard (Enter/Space), click, and touch tap (iOS) - Shows a tooltip via IHoverService: 'Open Customizations Overview' - Does not interfere with the existing collapse/expand toggle Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: move customizations home button to leading position Move the home button to the start of the header (before the toggle pill) and make it always visible. This makes the entrypoint more discoverable for new users since it's no longer hidden behind a hover state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: align home icon with section icons below Use matching padding (4px 8px) so the home icon's horizontal position matches the agent/skill/instruction icons listed below the header. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: refine home icon alignment to match section rows Remove font-size and explicit height overrides; instead mirror the sidebar-action-button layout exactly (padding 4px 8px + gap 10px) so the home icon naturally sizes and aligns with the section icons. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: align home icon and tighten color hierarchy in customizations header - Drop the header button's left padding and set the home button's right padding to 10px so 'Customizations' sits exactly 10px after the home icon, matching the icon->label gap of the section rows below. - Remove the home icon's opacity dim; use the strong agentsPanel foreground so the home icon and 'Customizations' label render at the same color weight. - Apply --vscode-descriptionForeground to the section rows below the header so they read as secondary content under the prominent header. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: bump home icon size to match bold header weight Codicons are a single-weight font, so font-weight has no effect on icons. Increase the home icon's font-size to 18px so its visual stroke weight matches the bold 'Customizations' label. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: revert home icon size bump Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: replace home button with 'Manage customizations' footer link Move the entrypoint to the customizations welcome page from a header icon to a footer link below the section list. The footer: - Sits inside the collapsible content (collapses with the section) - Uses subtle descriptionForeground styling with a top border separator - Includes a right-arrow that nudges on hover for affordance - Supports click, touch tap (iOS), and keyboard (Enter/Space) - Restores the original header layout (no padding/color hacks) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: add customizations overview header action Replace the footer-style Customizations entrypoint with a dedicated header home action so the overview remains accessible in both expanded and collapsed states. Add focused browser coverage for opening the welcome page without toggling collapse. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: align customizations overview action with counts Nudge the Customizations header overview action left so it visually aligns with the per-section count column. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: simplify customizations overview editor check Use the concrete AI Customizations management editor type instead of a structural showWelcomePage guard when opening the overview from the sidebar header action. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 974736ed · 2026-04-29
- 0.3ETVsessions: add `sessions.customizations.sidebarMode` enum setting (#314862) * agents app: add chat.customizations.sidebarOpensWelcome enum setting Adds a three-value enum setting chat.customizations.sidebarOpensWelcome that controls the presentation and behavior of the Customizations section in the Agents sidebar: - 'welcome' (default): one item per category, clicking opens the Customizations welcome page - 'section': one item per category, clicking deep-links to that category's section in the Customizations editor (prior behavior) - 'single': replaces the per-category list with a single 'Customizations' entry (with total count badge) that opens the welcome page Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: fix customizations toolbar hygiene Use tab indentation in the single-entry CSS block so the hygiene check passes.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: stop single-entry customization button from stretching The shared .customization-link-button-container has flex: 1 so it can\nshare the toolbar height with sibling rows. In single-entry mode there\nis only one container in a flex-column toolbar, which made the\n'Customizations' button stretch to fill the section's full height. Set\nflex: none on the single-entry container so it sizes to its content,\nmatching the per-section row height.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: rename customizations sidebar setting and scope to Agents app - Renames `chat.customizations.sidebarOpensWelcome` -> `sessions.customizations.sidebarMode` (more generic; no longer baked into the 'opens welcome' framing).\n- Moves the setting registration and `SessionsCustomizationsSidebarMode` enum out of `vs/workbench/contrib/chat` (where they affected all consumers) into `vs/sessions/contrib/sessions/browser/customizationsToolbar.contribution.ts`, so the setting is only registered when the Agents app loads.\n- Updates the widget, action handler, fixture, and AI_CUSTOMIZATIONS.md to use the new key/enum.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: keep customizations widget at stable DOM position on re-render On a sidebar mode setting change, the widget removed its rendered\nroot from the parent and appended a new one. `DOM.append` puts the\nnew element at the END of the past the agent-host-toolbar\nsibling registered after the customizations which stacked\nthe two toolbars' `border-top` rules right next to each other and\nproduced a stray double divider above 'Customizations'.\n\nFix by appending a stable wrapper element once in the constructor and\nrendering into it; re-renders now clear and refill the wrapper without\ndisturbing its position in the parent.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address PR review: dedupe total count, skip re-render when welcome<->section - Extract _totalCount() helper used by both _render and _renderSingleEntry - Track _renderedSingle bool instead of exact mode; only re-render when crossing the single<->non-single boundary (welcome and section produce identical DOM, click behavior already resolved at click-time) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 22bff377 · 2026-05-07
- 0.2ETVhide unsupported customizations categories in Agents app (harness-based) (#313278) * hide unsupported customizations categories in Agents app (harness-based) * Fix mock harness descriptor icons Agent-Logs-Url: https://github.com/microsoft/vscode/sessions/ce93aa65-5ce5-43c9-92bf-a0fc1b9d0150 Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 60cabe81 · 2026-04-29
- 0.2ETVAdd telemetry for applied enterprise policies (#326686) * Add telemetry for applied managed settings and device policies Emit a consolidated policy.applied event reporting which managed settings and OS/MDM device policies are applied, value buckets (e.g. default model forced to auto, telemetry level), and per-policy delivery source attribution (osPolicy/nativeMdm/server/file/accountData/ accountGate). A browser-layer workbench contribution consumes IPolicyService and fires once at startup plus on relevant policy, managed-settings, account-data, and gate changes (debounced, deduped). No raw policy values are collected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify applied policy telemetry * Coalesce policy telemetry during startup * Bucket invalid telemetry policy values --------- Co-authored-by: digitarald <hkirschner@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · d3afb120 · 2026-07-20
- 0.2ETVchat: gate aiCustomization sync event subscriptions when itemProvider is present (#314548) Follow-up to #314413. When an itemProvider is set on a customization harness, ProviderCustomizationItemSource.fetchItems treats the provider as the single source of truth and skips local syncable enumeration. However, the constructor still subscribed to syncProvider and prompts service change events, so providers that already forward those underlying events via their own onDidChange (e.g. LocalAgentHostCustomizationItemProvider) caused duplicate refreshes. Gate the syncProvider/promptsService event subscriptions on the same condition that gates the data path, plus add regression tests for both the no-double-counting and event-gating behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 1622b23e · 2026-05-05
- 0.2ETVpolicy: resolve managed settings per-key across delivery channels (#323780) * policy: resolve managed settings per-key across delivery channels Managed settings previously used a single authoritative source: the first non-empty delivery channel (native MDM > server > file) won wholesale and the others were ignored entirely. Switch to per-key precedence: the same order is honored, but resolved key-by-key. A key locked by a higher-precedence channel still cannot be overwritten, while keys a higher channel leaves unset are now filled in by a lower channel. Centralize the resolution in a new pickManagedSettings() (replacing selectManagedSettings) that returns the merged bag, per-key provenance, and the active sources, so policy evaluation and the Policy Diagnostics report share one implementation. Build the merged bag with Object.fromEntries so an untrusted __proto__ key cannot corrupt its prototype chain. Rework the Policy Diagnostics report to show every contributing source, a per-key Resolution table (effective value, winning source, and struck-through overrides), and per-key policy attribution. Add unit coverage for the per-key merge (provenance, fill-down, falsy values, ordering, prototype-pollution guard) plus an end-to-end test proving two keys can win from two different channels and both reach policy evaluation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: iterate managed-settings bags with Object.keys over own props Address PR feedback: switch pickManagedSettings from `for...in` to a guarded `Object.keys` loop so only own enumerable properties are visited (managed-settings bags are untrusted input) and absent channels are skipped explicitly. Note: `for...in` over an undefined bag was already a no-op (never threw), so this is a robustness/clarity improvement rather than a crash fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * policy: tidy pickManagedSettings unit suite Drop two redundant cases (distinct-keys and standalone activeSources ordering, both already covered by the headline and empty/absent tests) and fold the non-contributing-middle-channel ordering check into the empty/absent snapshot. Verified against the live node unit runner (all green). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 5a270e6d · 2026-07-01
- 0.2ETVGate Claude3PIntegration behind editor preview policy (#322851) * Gate Claude3PIntegration behind editor preview policy Flip ownership of the `Claude3PIntegration` enterprise policy so it can honor the account-side editor preview-features flag. Previously the policy was owned by the copilot-chat extension setting `github.copilot.chat.claudeAgent.enabled` via the distro `product.json` `extensionConfigurationPolicy` block. Because `product.json` is pure JSON it cannot carry a `value(policyData)` callback, so the policy only responded to OS/MDM admin policy and never honored `chat_preview_features_enabled` (sourced from the Copilot token's `editor_preview_features`). Codex works because its core setting owns `Codex3PIntegration` in code with a value callback. Now the in-code core setting `chat.agentHost.claudeAgent.enabled` owns `Claude3PIntegration` with the preview-features value callback, and the extension setting attaches via a `policyReference` declared from `product.json`. To express that, an `extensionConfigurationPolicy` entry can now be either form: - the current owner/"parent" syntax (full `IPolicy`: name, category, minimumVersion, description), or - a reference: `{ "policyReference": { "name": "<owner>" } }`, mirroring the in-code `policyReference` configuration field. `configurationExtensionPoint` and the policy exporter detect the `policyReference` key to route entries to `.policy` vs `.policyReference`; the exporter links reference entries into the owner's `referencedSettings` and skips type validation for settings not registered in the headless export process. Regenerates `policyData.jsonc` and updates the export test fixture. Requires the companion change in microsoft/vscode-distro that turns the claude entry into a `policyReference`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump distro to pick up Claude3PIntegration policy reference Update the distro pin to 7abf39b86c07d094722a4b3ec9f37e78fe3d5db3, which includes the merged change turning the `github.copilot.chat.claudeAgent.enabled` `extensionConfigurationPolicy` entry into a `policyReference` to the in-code `Claude3PIntegration` owner (microsoft/vscode-distro#1434). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ubuntu <josh@ahp.4mywozgnka0etnlo23z031udwc.xx.internal.cloudapp.net> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · bf6124c4 · 2026-06-25
- 0.2ETVchat: gate structured customization preview behind a setting (default off) (#314416) * chat: gate structured customization preview behind a setting (default off) Adds chat.customizations.structuredPreview.enabled (boolean, default false, tag 'preview') to gate the structured markdown preview introduced in #312545. When disabled (the default), the AI Customizations editor falls back to the previous raw-only embedded code editor and hides the Preview/Raw toggle button. When enabled, the editor defaults to the structured preview view. Runtime toggling is handled via an onDidChangeConfiguration listener that snaps the display mode back to raw and clears the preview when the setting is turned off, or triggers a preview re-render when it is turned on with an item already open. Existing component fixtures and unit tests are updated to opt-in to the setting so the preview-first screenshot fixtures remain stable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add runtime test for structured preview setting toggle Adds a unit test that verifies disabling chat.customizations.structuredPreview.enabled at runtime forces the editor back to raw mode and hides the toggle button, addressing review feedback from the Copilot reviewer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix test assertion: use 'Edit' label for editable file in preview mode The initial sanity assertion in the runtime-toggle test was checking for 'View Raw' but the test editor has currentEditingReadOnly=false, so getEditorModeButtonLabel() returns 'Edit' when in preview mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix test stub: add clear() to editorPreviewDisposables clearEditorPreview() calls this.editorPreviewDisposables.clear() but the test stub only provided add() and dispose(). Add clear() to the stub so the runtime-toggle test does not throw. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 15ba359b · 2026-05-05