Development

Architecture

How the VS Code extension host, PI SDK, Svelte webview, message bridge, trust gate, and configuration files fit together.

Last reviewed October 25, 2026 · 8 min read

Browse the wiki

PiLot Studio is a VS Code extension with two runtimes: the Node-based extension host and a sandboxed Chromium webview. The host owns PI, the filesystem, credentials, and child processes. The webview owns only the interface. They communicate through VS Code's postMessage bridge.

This page is based on CodeWiki's AST analysis of the PiLot 3.0.0 source (569 components, 84 leaf nodes) plus a review of the host modules.

The two runtimes

Runtime What runs there What it can access
Extension host loader.cjs, the bundled extension, the PI SDK, session files, provider credentials, MCP child processes, voice helpers, Git. VS Code APIs, Node, the filesystem, the network.
Webview Svelte 5 components, the transcript, the composer, dialogs. The DOM and acquireVsCodeApi(); no Node, no direct filesystem.

The host is a single Node process per VS Code window. A PiAgentProvider instance owns one active PI session at a time; switching sessions replaces the session object, not the whole extension.

Activation

  1. VS Code reads the extension's main, which is dist/loader.cjs.
  2. The loader finds a globally installed @earendil-works/pi-coding-agent package. It checks explicit locations first (pi-agent.binaryPath, PI_HOME, PI_AGENT_DIR), then package-manager locations, and picks the newest readable version.
  3. The loader refuses a PI SDK older than 1.4.0 with an update prompt.
  4. The loader hooks Node's module resolution so the extension bundle can require the ESM-only SDK, then loads the extension.
  5. activate() checks that the extension host runs Node 22.19+. VS Code only reaches that floor at 1.105.
  6. It reads the pi-agent.* configuration, bridges agentDir into PI_CODING_AGENT_DIR, creates the provider, registers the piAgentChat webview view, registers commands, starts the update checker, and installs a configuration listener.

The loader does not bundle the PI SDK. That keeps the extension smaller and ensures PiLot Studio uses the same PI installation, packages, and credentials as the CLI.

Host modules

The CodeWiki component index groups the host into these responsibilities:

Module Main files Responsibility
Extension host and agent lifecycle extension.ts, pi-agent-provider.ts, message-handler.ts, pi-settings.ts, session-manager.ts, session-resources.ts, project-trust.ts, footer-manager.ts, message-serializer.ts, protocol/types.ts Activation, the active PI session, webview messages, config, trust, session list, context and mentions, status line.
Model and provider registry model-registry-handler.ts, provider-migration.ts Model catalog, provider auth state, offline/online refresh, provider renames.
MCP and codemode mcp-manager.ts Parse and validate mcp.json, delegate add/remove/login/logout to the CLI, list servers and tools, codemode settings.
Packages, skills, and resources package-manager.ts, session-resources.ts pi list/install/remove/update, package resource enrichment, skill and prompt discovery, mention resolution.
Voice dictation voice-manager.ts Model download/cache, native helper lifecycle, transcription messages, platform errors.
Git and source control git-commit-message.ts, git-extension.ts Staged-diff collection, commit-message drafting, writing to the SCM input box.
Runtime, updates, diagnostics pi-binary.ts, binary-service.ts, loader.cjs, loader-version.cjs, runtime-guard.ts, update-checker.ts, utils/native-addons.ts, utils/shell.ts, commands/diagnostics.ts, commands/index.ts Binary resolution, version floors, shell execution, update checks, native-addon rebuild, diagnostics, command registration.
Webview bridge webview/mcp-display.ts, webview/messages.ts, webview/types/index.ts, webview/app.d.ts Message envelope validation, shared types, MCP display normalization.
Build and release package.json, tsconfig*.json, .github/workflows/* esbuild bundle, Vite webview build, type checks, tests, CI.

pi-agent-provider.ts is the largest module (171 components). It owns the session, webview, model registry, MCP manager, package manager, voice manager, update state, and most command handlers. The other host modules are deliberately narrower and receive dependencies through constructors, which is what makes them testable without a full VS Code window.

Prompt flow

ChatPanel
  → postMessage({ type: "prompt", data })
  → MessageHandler validates the envelope
  → PiAgentProvider.prompt()
  → SessionResources.resolveMentions() + getProjectContext()
  → PI AgentSession.prompt()
  → provider endpoint
  → PI events
  → notifyWebview()
  → App.svelte updates state
  → transcript components render

Mentions are resolved before the SDK call. The host reads each mentioned file, enforces the 10 MB and 50 KB limits, and builds a file-context block. Built-in slash commands are intercepted before the SDK call; skill and template commands are passed through so PI can expand them.

Auto Context adds a short Project Root / Project Name / Project Version preamble. It is not a general file-context system; @ mentions are the explicit file mechanism.

Session lifecycle

  1. PiAgentProvider.createSession() resolves the workspace root, trust decision, agent directory, and session directory.
  2. It constructs a PI ResourceLoader with settings, additional skill/extension/template paths, and discovery flags.
  3. It creates an AgentSession through the PI SDK and opens or creates the session file.
  4. It binds an ExtensionUIContext so extension setStatus() calls and session_start handlers reach the webview.
  5. It sends the serialized transcript, resource counts, model state, and footer data to the webview.
  6. Streaming events, tool calls, usage updates, and errors are forwarded as typed messages.

Forking creates a new session file, copies the path up to the selected entry, records the parent session, and swaps the provider to the fork. The original file is untouched.

The webview

The webview is a Svelte 5 application built by Vite. It uses runes for state and is served from dist/webview with a per-load CSP nonce.

  • CSP: default-src 'self'; script-src limited to self plus a per-load nonce; connect-src limited to self and the npm registry; img-src limited to self and data:; object-src 'none'; base-uri 'self'; form-action 'self'.
  • No Node: the webview cannot read files, spawn processes, or access credentials.
  • Message validation: parseHostMessage() and shared type guards reject malformed or hostile payloads. The host also validates incoming webview messages.
  • Rendering: markdown is rendered with escaping; code blocks, Mermaid, tables, and tool results have dedicated components. The message renderer avoids inline event handlers so the CSP does not need unsafe-inline.
  • State: App.svelte holds the active tab, transcript, model, providers, streaming state, resources, and update badges. Child panels request data from the host and listen for typed replies.

The same webview bundle is used in the sidebar and in editor tabs (pi-agent.openCurrentSessionInEditor, pi-agent.newChatInEditor).

Trust and security boundaries

PI 1.4 gates project-local resources behind trust. PiLot Studio resolves that decision before creating a session:

  • The probe only runs when the workspace contains project-local .pi settings, MCP servers, packages, or extensions.
  • A stored decision in PI's trust.json is reused.
  • A prompt can persist or apply only to the current window.
  • Cancellation and probe errors fail closed (untrusted).
  • The decision is shared with the CLI, so the two do not disagree.

Other boundaries:

  • Binary path: pi-agent.binaryPath can come from workspace settings, so values containing shell metacharacters are refused. Bare names are resolved with a PATH scan, not a shell.
  • Shell arguments: package and Git commands use argv arrays or quoted arguments; repository content goes over stdin where a shell cannot reinterpret it.
  • Mentions: paths are resolved inside the workspace; binary extensions are skipped.
  • Credentials: keys are written to auth.json atomically with owner-only permissions; an unparseable file is left untouched.
  • Webview: CSP plus message validation, with no direct host access.

Storage map

Path Owner Contents
<agentDir>/auth.json PI Provider keys and OAuth credentials.
<agentDir>/models.json PI Custom providers and models.
<agentDir>/settings.json PI Runtime settings, including codemode.
<agentDir>/mcp.json PI Global MCP servers and autoEnableCodemode.
<agentDir>/trust.json PI Project trust decisions.
<agentDir>/sessions/ PI Session files.
<agentDir>/voice-models/ PiLot Cached whisper models.
<workspace>/.pi/settings.json Project Trusted project codemode and runtime overrides.
<workspace>/.pi/mcp.json Project Trusted project MCP servers.

agentDir defaults to ~/.pi/agent. sessionDir adds an optional subdirectory under it.

Network use

PiLot Studio itself contacts:

  • pi.dev/api/latest-version for PI release checks (skipped when offline or PI_SKIP_VERSION_CHECK is set);
  • the npm registry for the Packages marketplace and package updates;
  • Hugging Face for the first voice-model download;
  • MCP servers you configure.

Model requests go directly to the provider endpoint selected in PI. PiLot Studio has no proxy or telemetry service. Diagnostics are local and off by default.

Design constraints

  • PI owns the agent. PiLot Studio does not fork PI's prompt loop or session format; it mirrors PI state and uses PI's own extension factories.
  • The CLI is the compatibility target. Packages, MCP, trust, auth, models, and sessions must remain usable from pi.
  • The webview is untrusted. Every message is validated; every file operation happens in the host.
  • Version floors are explicit. Node 22.19, VS Code 1.105, PI 1.4.0. Failing early with a readable message is preferred to a module-load crash.

Documentation provenance

The module map and component counts on this page come from CodeWiki's AST analysis of the PiLot repository at version 3.0.0 (569 components, 84 leaf nodes). The behavior descriptions were then checked against the source files and the 3.0.0 changelog. If the source and this page disagree, the source is authoritative; please open an issue with the page title.

Something inaccurate or missing? Open an issue with the page title.