Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>M1 Copilot ClientNew to Visual Studio Code? Get it now.
M1 Copilot Client

M1 Copilot Client

Meridai

| (0) | Free
M1 Copilot VSCode Extension (UC-1.13.1)
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

m1-copilot-client

UC-1.13.1 — client-side Copilot: a VSCode extension + Node companion that lets a Customer Operator chat with M1's existing agents, approve each proposed command inline, and execute it on their own workstation under their own identity. The legacy m1-execution-runner stays the headless-automation lane (FR-8).

Status

Scaffold. Several slices are stubbed — see M1-project-context/Plans/UC-1.13.1-Plan.md for the full implementation plan and the open W-gates (W1 canonical-JSON sub-spec, W2 audit sink, W4 latency instrumentation, W5 routing discriminator).

Architecture

extension host  ←postMessage→  webview (React, message list + approval cards)
      │
      │  spawn (only here)
      ▼
   companion executor  ──HTTPS──▶  m1-agents /webhook/callbacks/{execution_id}
      ▲
      │  outbound SSE
      └────────────────────  m1-agents /events/stream/session/{session_id}

The companion is the only component that may invoke child_process.spawn. The webview never executes anything. The extension host never proxies command text without going through the companion.

Security model

Before execution, the companion verifies that the ProposedAction it received was issued by the server:

  1. HMAC-SHA256 over canonical JSON of (action_id, tool_name, args) matches signed_digest. Keys are looked up by digest_kid from extension settings, supporting dual-key rotation (NFR-6, BR-6).
  2. expires_at is in the future. Expired actions are rejected.
  3. The corresponding ApprovalDecision was observed on the SSE stream. Without ACTION_APPROVED for the same action_id, the extension host never invokes the executor (FR-5 / AC-5.1).

If any of these checks fail, the companion posts state=rejected_no_approval and refuses to execute.

Client-side redaction (src/companion/redact.ts) runs over both stdout and stderr before egress (NFR-13). The redaction list is best-effort and is backstopped by server-side audit-sink scrubbing.

The bearer token comes from VSCode settings; the service URLs are derived from the selected environment. TLS is required (NFR-4) — the derived URLs are always https://.

Settings

Pick a single environment and the three service URLs (m1-agents, Control Tower, and Keycloak) are mapped automatically — there is no longer a per-URL setting to keep in sync.

Setting Purpose
m1Copilot.environment Target environment: PRD (default), UAT, or DEV. The m1-agents, Control Tower, and Keycloak URLs are derived from this. UAT/DEV only take effect when m1Copilot.devMode is enabled.
m1Copilot.devMode Developer mode. When enabled, unlocks selecting the UAT/DEV environments (with real Keycloak auth). When disabled, the extension always targets PRD.
m1Copilot.sessionId Optional; auto-generated if absent.
m1Copilot.hmacKeys Deprecated / offline-dev only. When non-empty, overrides the bootstrap fetch with a static kid → hex key map. In production the keyset is fetched from m1-agents (see "HMAC key distribution" below).

The Keycloak clientId is hardcoded (m1-copilot-client). The OAuth scopes and an offline useMockAuth flag are no longer surfaced in the settings UI; advanced users can still override m1Copilot.keycloak.scopes and m1Copilot.useMockAuth directly in settings.json.

Keycloak ↔ m1-agents token bridge

The extension authenticates the operator with Keycloak (RS256 realm-signed access tokens). m1-agents validates internal HS256 JWTs. The two are bridged by POST /auth/exchange on m1-agents:

  1. After Keycloak login, the extension wraps the KeycloakTokenProvider in ExchangedTokenProvider.
  2. On the first downstream call, the extension POSTs the Keycloak access token to /auth/exchange with grant_type=urn:ietf:params:oauth:grant-type:token-exchange.
  3. m1-agents validates the token against the realm's JWKS, reads tenant_id from the configured claim (default name: tenant_id), and returns a fresh access_token (5 min) + refresh_token (8 h).
  4. When the access token nears expiry, the extension calls /auth/exchange with grant_type=refresh_token — no Keycloak round-trip. Operators can work 24/7 as long as refreshes succeed; if the refresh JWT also expires, the extension transparently does a full re-exchange.

Every other m1-agents route (/chat, /events/stream/..., /config/companion-bootstrap, /webhook/callbacks/...) receives the exchanged internal token, not the raw Keycloak token.

HMAC key distribution

The companion verifies every ProposedAction.signed_digest against an HMAC-SHA256 key shared with m1-agents. The key is not bundled in the marketplace artifact (a leaked key lets anyone forge ProposedActions). Instead:

  1. User installs the extension from the marketplace — no manual key configuration needed.
  2. User runs Start M1 Copilot → Keycloak login.
  3. Extension calls GET /config/companion-bootstrap on m1-agents with the access token and receives {active_kid, keys, ttl_seconds}.
  4. The response is cached in ExtensionContext.secrets (OS keychain) and reused for that TTL. On HMAC verification failure (server-side rotation), the cache is invalidated and re-fetched once.

For offline development without a live m1-agents, set m1Copilot.hmacKeys to bypass the bootstrap fetch (the first kid becomes active), and set the hidden m1Copilot.useMockAuth to true to use the mock token provider.

Keycloak setup

The M1 platform already runs two Keycloak realms managed by m1-platform-gitops (apps/keycloak/envs/{uat,prd}):

Env Realm URL Realm name
DEV https://auth-dev.m1ops.com/realms/m1-realm m1-realm
UAT https://keycloak-uat.m1ops.com/realms/m1-uat m1-uat
PRD https://keycloak.m1ops.com/realms/m1-prd m1-prd

A dedicated public client m1-copilot-client is registered in both realms with PKCE S256 required (no client secret). Redirect URIs cover the native VSCode schemes:

  • vscode://Meridai.m1-copilot-client/*
  • vscode-insiders://Meridai.m1-copilot-client/*

Settings examples

DEV (requires developer mode):

{
  "m1Copilot.devMode": true,
  "m1Copilot.environment": "DEV"
}

UAT (requires developer mode):

{
  "m1Copilot.devMode": true,
  "m1Copilot.environment": "UAT"
}

PRD (default — no settings required):

{
  "m1Copilot.environment": "PRD"
}

The extension computes the redirect URI as ${vscode.env.uriScheme}://Meridai.m1-copilot-client/auth-callback and tunnels it via vscode.env.asExternalUri for Remote SSH / Codespaces / web hosts.

Refresh tokens are persisted via ExtensionContext.secrets, which is backed by the macOS Keychain, Windows Credential Vault, or libsecret/KWallet on Linux (NFR-5). The access token is held only in memory. Run M1 Copilot: Sign Out from the command palette to clear stored credentials.

Open gates / known gaps

  • W1 (canonical-JSON sub-spec) — the digest serialization used here (canonicalJson in src/companion/executor.ts) is a sorted-keys, no-whitespace JSON suitable for review, but it MUST be confirmed against m1-agents before this scaffold interops with production.
  • S2 (Keycloak SSO) — auth-code + PKCE flow is implemented; OS-keychain refresh-token storage uses ExtensionContext.secrets. ID-token signature verification (JWKS-based) is a follow-up — today the access token is verified server-side by m1-agents, and the ID token is only used to read the email claim for display.
  • Webview bundle — the React app in src/webview/App.tsx is not yet wired through Vite. The webview HTML is a placeholder proving the postMessage channel works.
  • Persistent buffer — OfflineWatchdog defaults to in-memory storage. For real offline survival across reloads, pass a BufferStorage backed by ExtensionContext.workspaceState from extension.ts.

Scripts

  • npm run typecheck — tsc --noEmit
  • npm run lint — ESLint flat config with TypeScript parser
  • npm test — Vitest unit tests
  • npm run compile — emit out/
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft