Files
Josh Spicer a4ce08f735 Refactor 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>
2026-06-22 17:42:10 -07:00
..

Mock Copilot policy endpoints

A standalone dev tool that mocks the Copilot policy endpoints that DefaultAccountService (src/vs/workbench/services/accounts/browser/defaultAccount.ts) calls, so you can exercise the entitlement / token / MCP-registry / managed-settings (policy) pipeline locally without the real GitHub backend.

It is not part of the shipped product — it is a local Node server + web GUI.

What it mocks

Endpoint Path product.json key Response
Entitlements /copilot_internal/user entitlementUrl IEntitlementsData (chat_enabled, copilot_plan, cloud_session_storage_enabled, …)
Token /copilot_internal/v2/token tokenEntitlementUrl { token: "agent_mode=1;editor_preview_features=1;mcp=1;…:sig" }
MCP registry /copilot/mcp_registry mcpRegistryDataUrl { mcp_registries: [{ url, registry_access }] }
Managed settings /copilot_internal/managed_settings managedSettingsUrl IManagedSettingsResponse (enterprise settings.json)

The flow is gated: the token and managed settings are only fetched when entitlements report chat_enabled: true, and the MCP registry only when the token enables mcp.

Usage

npm run mock-policy-server          # starts on http://127.0.0.1:3000
npm run mock-policy-server -- --port 4000
npm run mock-policy-server -- --schema ./copilot-agent-runtime/schema/managed-settings-schema.json
  1. Open the printed GUI URL.
  2. Pick an endpoint tab, choose a preset or edit the JSON, and Save.
  3. Click Wire all endpoints to point product.overrides.json at this server.
  4. Reload Code OSS (running from sources, so VSCODE_DEV is set).
  5. Sign in with your GitHub/Copilot account.
  6. Run Developer: Sync Account Policy (forces a refresh).
  7. Run Developer: Policy Diagnostics to inspect the applied values.

Click Unwire to restore the original URLs.

Managed-settings schema

The GUI loads the managed-settings JSON schema and, on the Managed Settings tab, warns about top-level keys that are not declared in it (mirroring how projectManagedSettings drops undeclared keys). The schema source is resolved in this order:

  1. --schema <url | file-uri | path> CLI flag
  2. MANAGED_SETTINGS_SCHEMA environment variable
  3. Default: ./copilot-agent-runtime/schema/managed-settings-schema.json, resolved against the app's current working directory (normally the vscode repo root, where the schema repo sits side-by-side).

http(s):// URLs and file:// URIs are both accepted; relative paths are resolved from the cwd. The schema is re-read on every Refresh, so you can edit it without restarting the server. A missing schema is non-fatal — the GUI just shows the resolved path and skips schema validation.

How wiring works

src/bootstrap-meta.ts merges product.overrides.json over product.json with a shallow, top-level Object.assign, only when VSCODE_DEV is set, and the file is git-ignored. To override nested keys the tool writes back the entire defaultChatAgent object (seeded from product.json) with only the four endpoint URLs flipped, preserving every other key. Unwiring restores those URLs to their product.json values and removes the file if nothing else remains.

Caveats

  • Works for the default (github.com) provider path, which reads these URLs directly from config. The enterprise provider derives some URLs from the enterprise host instead.
  • You must be signed in; the fetch only fires for an authenticated account.
  • Overrides require a reload and only apply when running from sources.
  • The server ignores the Authorization header — any token is accepted.

Files

  • server.js — zero-dependency Node http server (endpoints + control API + schema loader + static).
  • endpoints.js — shared endpoint definitions and presets (used by server and GUI).
  • public/ — the web GUI (index.html, app.js, style.css).