2026 03 22 Chat Plugin Core And Native Vscode Chat Design
Documentation for Plans
Chat Plugin Core & Native VSCode Chat — Architecture Spec
Session: S258 Date: 2026-03-22 Status: Spec Complete — Ready for Implementation Owner: libs/libs-ai/chat-plugin-core + apps/vitto-vscode-extension Predecessor: S133 (Plugin Architecture), S149 (Advanced Plugins), S168 (Canonical Presets), S240 (Native Agent Mode Spec) Calibrated Confidence: 0.84
Executive Summary
The Vitto chat plugin system (17 plugins, built since S133) is locked to Angular. This spec extracts a framework-agnostic plugin core (@libs-ai/chat-plugin-core) that enables the same plugins to work across Angular web, Lit VSCode extension, React Native mobile, Flutter, and any future surface.
Two deliverables:
@libs-ai/chat-plugin-core— New NX library. Zero framework imports. Contains all plugin interfaces, types, factories, categories, platform capability negotiation, and the standard preset factory.- Native "Chat" tab in the VSCode extension — Third top-level tab (alongside MCP and Vitto) rendering a Lit-based agent chat that consumes plugins from
chat-plugin-core.
Table of Contents
- Architecture Overview
- chat-plugin-core Library
- Core Interfaces & Types
- Plugin Categories
- Platform Capability System
- Plugin Resolution
- Platform-Aware Preset Factory
- Migration: vitto-chat → chat-plugin-core
- VSCode Extension: Chat Tab
- IDE Context Adapter
- Extension Host Services
- Message Protocol Extensions
- Infrastructure Details
- Risks & Mitigations
- Phasing
- Key Files Reference
1. Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ @libs-ai/chat-plugin-core │
│ (Pure TypeScript — Zero Framework Deps) │
│ │
│ Interfaces: ChatPlugin, PluginDropPayload, ReactiveValue<T> │
│ Types: PluginCategory, PlatformCapability, PluginPlatform│
│ Factories: createRichContextPlugin, createModelPlugin, ... │
│ Composition: createStandardPluginSet(config) │
│ Resolution: isPluginCompatible(), resolvePluginsForPlatform() │
│ Providers: AwarenessStateProvider, ContextSuggestionProvider │
│ │
│ Imports from: @libs-foundational/core-models (pure TS types) │
│ NO imports from Angular barrels (embeddable-bridge, api-config) │
└─────────┬──────────────────────┬──────────────────┬─────────────┘
│ │ │
┌─────▼──────┐ ┌────────▼───────┐ ┌─────▼──────────┐
│ Angular │ │ Lit (VSCode) │ │ Mobile (RN) │
│ Adapter │ │ Adapter │ │ Adapter │
│ │ │ │ │ │
│ Signal → ✓ │ │ () => prop → ✓ │ │ () => ref → ✓ │
│ Type<> → ✓ │ │ tagName → ✓ │ │ Component → ✓ │
│ │ │ │ │ │
│ vitto-chat │ │ vscode-ext │ │ (future) │
└────────────┘ └────────────────┘ └────────────────┘
Key design decisions:
ReactiveValue<T> = () => T— Angular Signal, Lit getter, React ref — all are callable. Zero wrapper needed for Angular (Signal IS a ReactiveValue).ComponentRef = unknown— Opaque token. Each framework resolves it (Angular:Type<unknown>, Lit: tag string, RN: component class).- Plugin factories are pure functions — They return plain objects with closures. No DI, no decorators, no framework.
- Capability negotiation over platform lists — Plugins declare what they NEED, platforms declare what they PROVIDE. Adding a platform never touches plugin code.
2. chat-plugin-core Library
Directory Structure
libs/libs-ai/chat-plugin-core/
├── src/
│ ├── index.ts # Public API barrel
│ └── lib/
│ ├── interfaces/
│ │ ├── chat-plugin.interface.ts # Core ChatPlugin interface
│ │ ├── plugin-providers.interface.ts # Provider interfaces (AwarenessStateProvider, etc.)
│ │ └── plugin-resolution.ts # Platform capability matching
│ ├── types/
│ │ ├── plugin-categories.ts # PluginCategory enum + metadata
│ │ ├── plugin-capabilities.ts # PlatformCapability + platform presets
│ │ ├── reactive.types.ts # ReactiveValue<T>, ComponentRef
│ │ └── chat-core.types.ts # ChatMessage, QuickAction, SlashCommand, etc.
│ ├── plugins/
│ │ ├── index.ts # All plugin factory exports
│ │ ├── context/ # Category: context
│ │ │ ├── rich-context.plugin.ts
│ │ │ ├── capabilities.plugin.ts
│ │ │ └── suggestions.plugin.ts
│ │ ├── intelligence/ # Category: intelligence
│ │ │ ├── model.plugin.ts
│ │ │ ├── agent.plugin.ts
│ │ │ ├── mastery.plugin.ts
│ │ │ └── learning.plugin.ts
│ │ ├── generation/ # Category: generation
│ │ │ ├── generation.plugin.ts
│ │ │ ├── module-builder.plugin.ts
│ │ │ ├── teach.plugin.ts
│ │ │ └── prompt-library.plugin.ts
│ │ ├── bridge/ # Category: bridge
│ │ │ ├── computer-use-bridge.plugin.ts
│ │ │ ├── observatory.plugin.ts
│ │ │ └── awareness.plugin.ts
│ │ └── input/ # Category: input
│ │ ├── voice.plugin.ts
│ │ ├── action.plugin.ts
│ │ └── context-legacy.plugin.ts
│ ├── composition/
│ │ ├── plugin-preset.factory.ts # createStandardPluginSet() — platform-aware
│ │ └── plugin-resolver.ts # Capability matching + filtering
│ └── utils/
│ └── plugin-guards.ts # isChatPlugin(), hasCapability(), etc.
├── project.json
├── tsconfig.json
├── tsconfig.lib.json
└── jest.config.ts
NX Configuration
// project.json
{
"name": "chat-plugin-core",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/libs-ai/chat-plugin-core/src",
"prefix": "lib",
"projectType": "library",
"tags": ["scope:ai", "type:core"],
"targets": {
"lint": { "executor": "@nx/eslint:lint" }
}
}
Tag type:core (not type:feature) — this is a foundational library that other feature libs depend on.
tsconfig.json (NO angularCompilerOptions)
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"moduleResolution": "bundler",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"module": "preserve"
},
"files": [],
"include": [],
"references": [{ "path": "./tsconfig.lib.json" }]
}
No angularCompilerOptions — this library has zero Angular dependency.
Dependencies
| Dependency | Import | Status |
|---|---|---|
@libs-foundational/core-models |
PlatformRole, RouteContext, CapabilityContext, DerivedFeatures |
Safe — pure TS types, no Angular |
@libs-ux/embeddable-bridge |
DragPayload |
DO NOT import — barrel re-exports Angular services/directives. Define PluginDropPayload locally in chat-plugin-core instead (see below) |
@libs-foundational/api-config |
AI_MODELS_CONFIG |
DO NOT import — barrel re-exports InjectionToken from @angular/core. Extract model constants into chat-plugin-core/types/model-constants.ts |
@libs-ai/vitto-core |
VittoPageContextRegistry |
DO NOT import — Angular service. Replace with ReactiveValue<string[]> via activeSuggestions config option |
Critical rule: chat-plugin-core must NEVER import from any barrel that re-exports Angular code. Even if the specific type you need is pure TS, the barrel import pulls the entire module graph. Always verify the barrel file before adding a dependency.
PluginDropPayload (replaces DragPayload)
// types/chat-core.types.ts
/**
* Lightweight drop payload for plugin onDrop handlers.
* Framework-agnostic subset of @libs-ux/embeddable-bridge DragPayload.
* Angular adapter maps DragPayload → PluginDropPayload.
*/
export interface PluginDropPayload {
id: string;
type: string;
title: string;
data: unknown;
sourceApp?: string;
}
AI Model Constants (replaces AI_MODELS_CONFIG import)
// types/model-constants.ts
/**
* LLM model identifiers used by the model plugin.
* Extracted from @libs-foundational/api-config to avoid Angular barrel.
* Keep in sync with api-config/ai-models.config.ts.
*/
export const CHAT_MODEL_IDS = {
fast: 'claude-sonnet-4-6',
reasoning: 'claude-opus-4-6',
haiku: 'claude-haiku-4-5-20251001',
} as const;
3. Core Interfaces & Types
ReactiveValue — The Framework Bridge
// types/reactive.types.ts
/**
* Framework-agnostic reactive value getter.
*
* Call it to read the current value — identical call signature to:
* - Angular Signal: signal() → T
* - Lit reactive: () => this.property
* - React: () => stateRef.current
* - Plain JS: () => someVariable
*
* AI AGENT GUIDE:
* When creating a plugin config, wrap your framework's reactive primitive:
* Angular: { contextSignal: mySignal } // Signal IS a ReactiveValue
* Lit: { contextSignal: () => this.context } // Arrow wrapping @state
* React: { contextSignal: () => contextRef.current }
* Plain: { contextSignal: () => currentContext } // Any getter function
*/
export type ReactiveValue<T> = () => T;
/**
* Framework-agnostic component reference.
*
* Each framework resolves this to its own component type:
* - Angular: Type<unknown> (class reference)
* - Lit: string (custom element tag name)
* - React Native: React.ComponentType<unknown>
* - Flutter: Type (Dart widget class)
*
* Plugins pass these opaquely — only the framework adapter interprets them.
* The plugin system NEVER inspects or instantiates ComponentRef directly.
*/
export type ComponentRef = unknown;
Provider Interfaces
// interfaces/plugin-providers.interface.ts
/**
* Abstraction over AwarenessDataService.
* Angular service implements this. Other platforms provide their own.
*/
export interface AwarenessStateProvider {
cuBridgeState(): CUBridgeState;
routineSuggestions(): RoutineSuggestion[];
connect(userId: string): void;
disconnect(): void;
}
export interface CUBridgeState {
sessions: Array<{ sessionId: string; status: string }>;
blockers: Array<{
id: string;
sessionId: string;
type: '2fa' | 'captcha' | 'confirmation' | string;
}>;
}
export interface RoutineSuggestion {
id: string;
title: string;
dismissed: boolean;
actions: Array<{ prompt?: string }>;
}
/**
* Abstraction over VittoChatContextService.
* Provides capability and suggestion data for a given context.
*/
export interface ContextSuggestionProvider {
getCapabilitiesForContext(contextType: string): CapabilityEntry[];
getSuggestionsForContext(contextType: string): VittoSuggestion[];
}
export interface CapabilityEntry {
id: string;
name: string;
description: string;
icon: string;
handler: string;
enabled: boolean;
}
ChatPlugin Interface
// interfaces/chat-plugin.interface.ts
/**
* Core ChatPlugin interface — framework-agnostic.
*
* Plugins are plain configuration objects returned by factory functions.
* NOT framework services. NOT classes. NOT singletons.
*
* ═══════════════════════════════════════════════════════════════
* AI AGENT GUIDE — Creating a New Plugin
* ═══════════════════════════════════════════════════════════════
*
* 1. Choose a category (context | intelligence | generation | bridge | input)
* See PluginCategory docs for definitions.
*
* 2. Create a factory: createMyPlugin(config) => ChatPlugin
* File location: plugins/{category}/my-plugin.plugin.ts
*
* 3. Set `requires` to minimum capabilities your plugin needs:
* - Empty array [] = works everywhere (prefer this)
* - Only add capabilities that would BREAK the plugin if missing
*
* 4. Set `platforms` ONLY for inherently platform-specific plugins:
* - Example: 'ide-diagnostics' only makes sense in VSCode
* - Omit platforms to let capability matching decide (preferred)
* - When both `platforms` and `requires` are set, `platforms`
* takes precedence (explicit override)
*
* 5. Export from plugins/index.ts
*
* 6. Add to composition/plugin-preset.factory.ts if standard
*
* Resolution order:
* platforms (if set) → capability match → role gating → included
*
* quickActions may be a getter that reads ReactiveValue —
* always access fresh, never cache the returned array.
* ═══════════════════════════════════════════════════════════════
*/
export interface ChatPlugin {
/** Unique plugin identifier (kebab-case) */
id: string;
/** Display label */
label?: string;
/** Icon identifier (codicon name or emoji) */
icon?: string;
// ─── Classification ─────────────────────────────────────────
/** Plugin category. See PluginCategory docs. */
category: PluginCategory;
/**
* Capabilities this plugin REQUIRES from the host platform.
* Empty [] = universal (works everywhere).
* Only list what would BREAK the plugin if missing.
*/
requires: PlatformCapability[];
/**
* Explicit platform override — OPTIONAL.
* When set, takes precedence over capability matching.
* Plugin ONLY included on listed platforms.
* Use sparingly — prefer `requires` for future-proofing.
*/
platforms?: PluginPlatform[];
// ─── UI Composition ─────────────────────────────────────────
tabs?: ChatPluginTab[];
slashCommands?: SlashCommandDefinition[];
quickActions?: QuickAction[];
outputComponents?: ComponentRef[];
inputAddons?: ComponentRef[];
headerAddons?: ComponentRef[];
// ─── Message Processing ─────────────────────────────────────
enrichMessage?: (msg: ChatMessage) => ChatMessage;
// ─── Lifecycle ──────────────────────────────────────────────
onActivate?: () => void;
onDeactivate?: () => void;
onContextChange?: (context: VittoRichContext) => void;
onDrop?: (data: PluginDropPayload) => ChatMessage | null;
onDestroy?: () => void;
}
4. Plugin Categories
// types/plugin-categories.ts
/**
* AI AGENT GUIDE — Choose the category matching PRIMARY responsibility:
*
* context — Observe/enrich messages with user awareness.
* No UI rendering. Examples: rich-context, capabilities, suggestions.
*
* intelligence — Control AI behavior, orchestration, learning.
* Affects HOW Vitto responds. Examples: model, agent, mastery, learning.
*
* generation — Content creation UIs via tabs/panels.
* Dedicated interfaces for artifacts. Examples: generation, module-builder.
*
* bridge — Connect external systems into chat.
* Proxy state/events from outside. Examples: computer-use-bridge, observatory.
*
* input — Enhance input modalities and interactions.
* Add ways to interact. Examples: voice, action (drag-drop).
*/
export type PluginCategory = 'context' | 'intelligence' | 'generation' | 'bridge' | 'input';
export interface PluginCategoryMetadata {
id: PluginCategory;
label: string;
description: string;
/** Composition priority — lower = earlier in plugin array */
priority: number;
}
export const PLUGIN_CATEGORIES: Record<PluginCategory, PluginCategoryMetadata> = {
context: { id: 'context', label: 'Context', description: 'Observe and enrich with user awareness', priority: 0 },
intelligence: { id: 'intelligence', label: 'Intelligence', description: 'Control AI behavior, agents, learning', priority: 1 },
generation: { id: 'generation', label: 'Generation', description: 'Content creation UIs and artifact builders', priority: 2 },
bridge: { id: 'bridge', label: 'Bridge', description: 'Connect external systems into chat', priority: 3 },
input: { id: 'input', label: 'Input', description: 'Enhance input modalities and interactions', priority: 4 },
};
5. Platform Capability System
// types/plugin-capabilities.ts
/**
* Capabilities a platform provides to plugins.
*
* AI AGENT GUIDE — Creating a new plugin:
* 1. List capabilities your plugin REQUIRES to function
* 2. Only list truly required — not "nice to have"
* 3. requires: [] = works on ALL platforms
* 4. Preset factory auto-excludes from incompatible platforms
*
* Adding a new platform:
* 1. Add to PluginPlatform type
* 2. Add capability list to PLATFORM_CAPABILITIES
* 3. All compatible plugins automatically available
*/
export type PlatformCapability =
// Rendering
| 'component-rendering' // Render plugin tab components
// Input
| 'drag-drop' // Drag-drop interactions
| 'speech-api' // Web Speech API or equivalent STT/TTS
| 'file-upload' // Upload files/images from device
// Context sources
| 'route-context' // URL-based routing with params
| 'ide-context' // IDE file/editor/terminal context
| 'git-context' // Git status, branch, modified files
// System access
| 'file-system' // Read/write local files
| 'terminal' // Execute shell commands
| 'clipboard' // Read/write clipboard
| 'diagnostics' // Language diagnostics (errors, warnings)
// Mobile/native
| 'camera' // Camera access
| 'haptics' // Haptic feedback
| 'push-notifications' // Push notifications
| 'biometrics' // Biometric auth
// Network
| 'sse-streaming' // Consume SSE event streams
| 'websocket' // Maintain WebSocket connections
;
/**
* Known platform identifiers.
* Used for explicit plugin platform overrides.
*/
export type PluginPlatform =
| 'web' // Angular web apps
| 'vscode' // VSCode/Cursor extension (Lit)
| 'chrome-extension' // Chrome extension sidepanel
| 'mobile' // React Native / Flutter
| 'desktop' // Electron / Tauri
;
/**
* Platform capability presets.
*
* AI AGENT GUIDE — Adding a new platform:
* 1. Add platform to PluginPlatform
* 2. Add capabilities here
* 3. Run preset factory tests to verify resolution
*/
export const PLATFORM_CAPABILITIES: Record<PluginPlatform, PlatformCapability[]> = {
web: [
'component-rendering', 'drag-drop', 'speech-api', 'file-upload',
'route-context', 'clipboard', 'sse-streaming', 'websocket',
],
vscode: [
'component-rendering', 'file-system', 'terminal', 'diagnostics',
'git-context', 'ide-context', 'clipboard', 'sse-streaming', 'websocket',
],
'chrome-extension': [
'component-rendering', 'clipboard', 'sse-streaming', 'websocket',
],
mobile: [
'component-rendering', 'speech-api', 'file-upload', 'camera',
'haptics', 'push-notifications', 'biometrics', 'clipboard',
'sse-streaming', 'websocket',
],
desktop: [
'component-rendering', 'file-system', 'terminal', 'git-context',
'clipboard', 'drag-drop', 'sse-streaming', 'websocket',
],
};
6. Plugin Resolution
// interfaces/plugin-resolution.ts
/**
* Resolution order:
* 1. plugin.platforms set → only those platforms (explicit override)
* 2. plugin.requires empty → compatible with ALL platforms
* 3. Otherwise → platform must provide ALL required capabilities
*/
export function isPluginCompatible(
plugin: ChatPlugin,
platform: PluginPlatform,
): boolean {
if (plugin.platforms && plugin.platforms.length > 0) {
return plugin.platforms.includes(platform);
}
if (plugin.requires.length === 0) {
return true;
}
const available = PLATFORM_CAPABILITIES[platform] ?? [];
return plugin.requires.every(cap => available.includes(cap));
}
export function resolvePluginsForPlatform(
plugins: ChatPlugin[],
platform: PluginPlatform,
): ChatPlugin[] {
return plugins.filter(p => isPluginCompatible(p, platform));
}
7. Platform-Aware Preset Factory
// composition/plugin-preset.factory.ts
export interface StandardPluginSetConfig {
/** Target platform */
platform: PluginPlatform;
/** User role — controls feature gating */
platformRole: PlatformRole;
/** Reactive rich context */
contextSignal: ReactiveValue<VittoRichContext | null>;
// Callbacks (universal)
onModelChange?: (id: string) => void;
onAgentStatusRequested?: () => void;
// Optional providers (interface-based)
contextProvider?: ContextSuggestionProvider;
awarenessProvider?: AwarenessStateProvider;
// Component refs (platform-specific)
generationComponents?: { image?: ComponentRef; module?: ComponentRef; video?: ComponentRef; widget?: ComponentRef };
teachComponent?: ComponentRef;
promptComponent?: ComponentRef;
observatoryComponent?: ComponentRef;
awarenessComponents?: { cuProgressIndicator?: ComponentRef };
// Data
userId?: string;
spaceId?: string;
roleLevel?: number;
includeVoice?: boolean;
// Module builder (S248)
moduleContextSignal?: ReactiveValue<ModuleContextState | null>;
onEntityExtract?: (entities: string[]) => void;
onModuleGenerate?: (description: string) => void;
onModuleValidate?: () => void;
onModuleExport?: () => void;
// Suggestions
activeSuggestions?: ReactiveValue<string[]>;
}
/**
* Canonical plugin composition — platform-aware.
*
* Creates ALL plugins in standard order (category priority),
* then filters by:
* 1. Platform capability matching (automatic)
* 2. Role gating (consumer vs T0+)
* 3. Config availability (no component ref = no tab plugin)
*/
export function createStandardPluginSet(config: StandardPluginSetConfig): ChatPlugin[] {
// ... builds all plugins in canonical order, then:
return resolvePluginsForPlatform(allPlugins, config.platform);
}
Canonical order: context → intelligence → generation → bridge → input → learning (silent).
Same order as current createStandardPluginSet in S168 plugin-presets.ts.
8. Migration: vitto-chat → chat-plugin-core
What Moves
From @libs-ai/vitto-chat |
To @libs-ai/chat-plugin-core |
|---|---|
plugins/chat-plugin.interface.ts |
interfaces/chat-plugin.interface.ts |
plugins/*.plugin.ts (all 17) |
plugins/{category}/*.plugin.ts |
plugins/plugin-presets.ts |
composition/plugin-preset.factory.ts |
types/chat.types.ts (types below) |
types/chat-core.types.ts |
Types that must migrate to chat-core.types.ts:
ChatMessage, ChatMessageRole, ChatMessageMetadata, ToolCallInfo, VittoMood, TaskProgressState, TaskDelegation, TaskCheckpoint, VittoPhase, SlashCommandDefinition, SlashCommand, ActionableOutput, ActionableOutputType, ChatPluginTab, QuickAction, VittoRichContext, VittoAppType, VittoCapability, CapabilityActionType, VittoSuggestion, SuggestionType, VittoChatFeatures, chatFeaturesForPlatformRole
Types that stay in vitto-chat (Angular-specific or service-coupled):
VittoChatConfig (uses Angular service patterns), component input types, SpaceContext re-export (stays as local alias, consumers import RouteContext from core-models)
What Stays in vitto-chat
- All Angular components (chat-shell, input bar, message renderer, etc.)
- All Angular services (engine, streaming, persistence, context, etc.)
ChatPluginHostDirective(Angular dynamic component loader)- Angular-specific types (component inputs, service injection tokens)
Zero-Breaking-Change Re-exports
// @libs-ai/vitto-chat/src/lib/plugins/index.ts
// Re-exports for backward compatibility. Import from @libs-ai/chat-plugin-core directly.
export {
type ChatPlugin,
isChatPlugin,
createRichContextPlugin,
createCapabilitiesPlugin,
// ... all 17 plugin factories (see plugins/index.ts for full list)
createStandardPluginSet,
type StandardPluginSetConfig,
} from '@libs-ai/chat-plugin-core';
Angular Adapter
// @libs-ai/vitto-chat/src/lib/adapters/angular-plugin.adapter.ts
import { createStandardPluginSet, StandardPluginSetConfig } from '@libs-ai/chat-plugin-core';
/**
* Angular wrapper. Angular Signals ARE ReactiveValues (same call
* signature). Angular Type<T> IS ComponentRef (unknown). This exists
* for type-narrowing convenience and backward compatibility.
*
* PluginPresetConfig is the EXISTING config type from S168.
* This adapter maps it to StandardPluginSetConfig, injecting platform: 'web'.
*/
export function createAngularPluginSet(config: PluginPresetConfig): ChatPlugin[] {
return createStandardPluginSet({
platform: 'web',
platformRole: config.platformRole,
contextSignal: config.contextSignal,
onModelChange: config.onModelChange,
onAgentStatusRequested: config.onAgentStatusRequested,
generationComponents: config.generationComponents,
teachComponent: config.teachComponent,
promptComponent: config.promptComponent,
observatoryComponent: config.observatoryComponent,
userId: config.userId,
spaceId: config.spaceId,
roleLevel: config.roleLevel,
includeVoice: config.includeVoice,
moduleContextSignal: config.moduleContextSignal,
onEntityExtract: config.onEntityExtract,
onModuleGenerate: config.onModuleGenerate,
onModuleValidate: config.onModuleValidate,
onModuleExport: config.onModuleExport,
// Map Angular services to provider interfaces
contextProvider: config.contextService,
awarenessProvider: config.awarenessService,
awarenessComponents: config.awarenessComponents,
});
}
9. VSCode Extension: Chat Tab
Shell Tab Addition
Current shell in main-panel.ts:1752-1775 has two tabs (MCP, Vitto). Add a third:
<nav class="shell-tabs">
<button class="shell-tab active" data-pane="mcp">
<span class="tab-icon">⚙</span><span>MCP</span>
</button>
<button class="shell-tab" data-pane="chat">
<span class="tab-icon">💬</span><span>Vitto</span>
</button>
<button class="shell-tab" data-pane="agent-chat">
<span class="tab-icon">⚡</span><span>Chat</span>
</button>
</nav>
<div class="shell-content">
<div id="pane-mcp" class="shell-pane lit-pane active"><!-- Lit MCP dashboard --></div>
<div id="pane-chat" class="shell-pane"><!-- Angular iframe --></div>
<div id="pane-agent-chat" class="shell-pane lit-pane"><!-- Native agent chat --></div>
</div>
Agent Chat Lit Component
New Lit component: agent-chat-view.ts mounted inside #pane-agent-chat.
Consumes plugins from chat-plugin-core:
import { createStandardPluginSet } from '@libs-ai/chat-plugin-core';
const plugins = createStandardPluginSet({
platform: 'vscode',
platformRole: 'developer',
contextSignal: () => this.ideContext,
onModelChange: (id) => send({ type: 'setModel', modelId: id }),
onAgentStatusRequested: () => send({ type: 'getAgentStatus' }),
userId: this.userId,
});
Auto-excluded on VSCode (via capability matching):
voice— requiresspeech-apiaction(drag-drop) — requiresdrag-drop
Auto-excluded because no component ref provided:
generation,teach,prompt-library,observatory— no Angular components
Works out of the box:
rich-context→ IDE context (file, selection, diagnostics)capabilities→ IDE-specific capabilitiessuggestions→ IDE-specific suggestionsmodel,agent,mastery,learning→ universal (no requirements)module-builder→ slash commands + enrichment (no tab needed)
Chat UI Features
- Streaming markdown messages
- Tool use cards (file read, command run, diff preview)
- Thinking blocks (collapsible)
- Input bar with slash command palette (from plugins)
- Quick action chips (from plugins)
- Context bar (active file, branch, error count)
- Cancel button during streaming
10. IDE Context Adapter
RouteContext in @libs-foundational/core-models must be extended with IDE contexts.
Addition to context.types.ts:
export type RouteContext =
// Web contexts (existing)
| 'page-builder' | 'modular-universe' | 'universe-explorer'
| 'theme-designer' | 'flow-builder' | 'widget-editor'
| 'dashboard' | 'settings' | 'analytics' | 'user-management'
| 'vitto' | 'vitto-studio' | 'computer-use'
// IDE contexts (new)
| 'ide-editor' | 'ide-terminal' | 'ide-debug' | 'ide-diff'
// Mobile contexts (future)
| 'mobile-home' | 'mobile-notifications'
| 'unknown';
// MUST also add to CapabilityContext:
export type CapabilityContext =
// ... existing values ...
| 'ide-assistant' // New: maps from all ide-* routes
| 'mobile-assistant' // New: maps from all mobile-* routes
;
// MUST also add to ROUTE_TO_CAPABILITY (Record<RouteContext, CapabilityContext>
// requires ALL union members mapped):
export const ROUTE_TO_CAPABILITY: Record<RouteContext, CapabilityContext> = {
// ... existing mappings ...
'ide-editor': 'ide-assistant',
'ide-terminal': 'ide-assistant',
'ide-debug': 'ide-assistant',
'ide-diff': 'ide-assistant',
'mobile-home': 'mobile-assistant',
'mobile-notifications': 'mobile-assistant',
};
In the extension, ContextProviderService maps IDE state to VittoRichContext:
function mapIdeContextToRichContext(ide: IdeContext): VittoRichContext {
return {
contextType: 'ide-editor',
filePath: ide.activeFile?.path,
fileLanguage: ide.activeFile?.language,
fileContent: ide.activeFile?.content,
metadata: {
openFiles: ide.openFiles,
diagnosticCount: ide.diagnostics.reduce((sum, d) => sum + d.errors + d.warnings, 0),
gitBranch: ide.gitStatus?.branch,
gitModified: ide.gitStatus?.modified,
projectType: ide.projectType,
},
};
}
The capabilities.plugin.ts needs IDE-specific capability data added to CAPABILITIES_BY_CONTEXT for ide-editor, ide-terminal, etc.
11. Extension Host Services
Three new services in the extension host (Node.js side), as specified in the S240 Native Agent Mode spec:
| Service | Purpose | Key Methods |
|---|---|---|
IdeToolService |
VSCode API wrappers | readFile, writeFile, runCommand, searchCode, listFiles, getDiagnostics, showDiff |
AgentSessionService |
SSE client + tool execution loop | chat(options), cancel(), handleStreamEvent(), executeTool() |
ContextProviderService |
Gathers IDE context | gatherContext(), getGitStatus(), detectProjectType() |
These follow the S240 spec exactly. See docs/superpowers/specs/2026-03-22-vitto-native-agent-mode-design.md sections 2, 3, 6.
12. Message Protocol Extensions
New message types added to the webview ↔ extension host bridge:
// MessageToExtension additions:
| { type: 'agentMessage'; text: string; sessionId?: string }
| { type: 'agentCancel' }
| { type: 'agentApproveEdit'; path: string }
| { type: 'agentRejectEdit'; path: string }
| { type: 'getIdeContext' }
// MessageFromExtension additions:
| { type: 'agentText'; content: string }
| { type: 'agentThinking'; content: string }
| { type: 'agentToolStart'; tool: string; input: Record<string, unknown> }
| { type: 'agentToolResult'; tool: string; result: IdeToolResult }
| { type: 'agentDone'; summary: string }
| { type: 'agentError'; message: string }
| { type: 'agentCancelled' }
| { type: 'ideContext'; context: IdeContext }
13. Infrastructure Details
Path Mapping
Add to tsconfig.base.json (not generated.paths.json — that's for @module/*):
"@libs-ai/chat-plugin-core": ["libs/libs-ai/chat-plugin-core/src"],
"@libs-ai/chat-plugin-core/*": ["libs/libs-ai/chat-plugin-core/src/*"]
esbuild Resolution — REQUIRES SETUP
The extension does NOT currently import from any @libs-* library. The webview build pipeline (tsconfig.webview.json → tsc → esbuild) lacks path alias resolution for @libs-*. This must be fixed in Phase 3:
Option A (recommended): Add an esbuild alias plugin to esbuild.mjs that reads tsconfig.base.json paths and maps them for bundling. This is a ~15-line plugin.
Option B: Make tsconfig.webview.json extend tsconfig.base.json. This gives it path mappings but may introduce unwanted compiler options — test carefully.
Option C: Use relative imports from the webview source to chat-plugin-core source files. Fragile but works without config changes.
The Node extension build (tsconfig.json extends tsconfig.base.json) can already resolve @libs-* paths — only the webview build needs setup.
CSS Scoping Convention
Per convention vscode-extension:global-css-scoping: global CSS in the Lit webview must be scoped to #pane-mcp to avoid clobbering shell tabs. The Chat tab's Lit components must scope their styles to #pane-agent-chat or use Lit's shadow DOM (preferred).
CSP Policy
Per convention vscode-extension:csp-nonce: webview uses nonce-based script-src. No inline onclick. Use addEventListener in nonce'd script blocks.
14. Risks & Mitigations
| Risk | Severity | Mitigation |
|---|---|---|
AI_MODELS_CONFIG from @libs-foundational/api-config may have Angular deps |
Medium | Verify. If Angular-dependent, extract model constants to core-models or chat-plugin-core |
esbuild/webview cannot resolve @libs-* paths |
High | Webview tsconfig lacks path aliases. Must add esbuild alias plugin or extend tsconfig.base.json. See Infrastructure section. Test in Phase 3 before building Chat UI. |
Backend /engine/agent-session SSE endpoint doesn't exist |
High | Must be built on vitto-api. Extension can be tested with mocks. Backend is separate work. |
Plugin getter pattern (get quickActions()) caching |
Low | Documented in interface: "always access fresh, never cache" |
| Migration blast radius (16 plugins moving) | Medium | Re-export shim in vitto-chat keeps all existing imports working |
RouteContext extension with IDE types affects all consumers |
Low | Additive change — new union members don't break existing pattern matches (TypeScript is structural) |
15. Phasing
Phase 1: chat-plugin-core Library + Plugin Migration (1-2 sessions)
Interfaces and factories MUST move together — the new ChatPlugin interface requires category and requires fields that only exist when factories are updated.
- Create NX library with project.json, tsconfig (no angularCompilerOptions), path mappings
- Move interfaces: ChatPlugin (with
category,requires,platforms), ReactiveValue, ComponentRef - Move types: all chat-core types (see migration list above), categories, capabilities, PluginDropPayload, model constants
- Define provider interfaces (AwarenessStateProvider, ContextSuggestionProvider)
- Move all 17 plugin factories organized by category subdirectories
- Replace
Signal→ReactiveValue,Type<unknown>→ComponentRef - Replace
DragPayload→PluginDropPayload,AI_MODELS_CONFIG→CHAT_MODEL_IDS - Replace
VittoPageContextRegistry→activeSuggestions: ReactiveValue<string[]> - Replace
AwarenessDataService→AwarenessStateProviderinterface - Replace
VittoChatContextService→ContextSuggestionProviderinterface - Add
categoryandrequiresto every plugin factory return value - Move plugin resolver + createStandardPluginSet with platform awareness
- Add re-export shim to vitto-chat
- Verify Angular web app still works (zero breaking changes)
Phase 3: VSCode Chat Tab — Foundation (1 session)
- Add "Chat" tab to shell HTML
- Create ContextProviderService (IDE context gathering)
- Create IdeToolService (VSCode API wrappers)
- Add message protocol extensions (9 new types)
- Wire main-panel.ts message handlers
Phase 4: VSCode Chat Tab — Agent Loop (1 session)
- Create AgentSessionService (SSE streaming + tool execution)
- Integrate with plugins from chat-plugin-core
- Build agent-chat-view Lit component (streaming, tool cards, input)
- Add IDE-specific capabilities and suggestions to plugins
Phase 5: VSCode Chat Tab — Polish (1 session)
- Keybindings and commands (Cmd+Shift+V, context menu)
- DiffContentProvider for write previews
- Destructive command detection
- Rate limiting enforcement
- Terminal output capture
Phase 6: Backend Agent Session Endpoint (1 session)
/engine/agent-sessionSSE endpoint on vitto-api/engine/agent-session/:id/tool-resultPOST endpoint- Reuses existing AgentLoopService with tool filter
- Extension ↔ backend integration testing
16. Key Files Reference
Files to Create
libs/libs-ai/chat-plugin-core/
├── project.json
├── tsconfig.json (NO angularCompilerOptions)
├── tsconfig.lib.json
├── jest.config.ts
└── src/
├── index.ts
└── lib/
├── interfaces/
│ ├── chat-plugin.interface.ts
│ ├── plugin-providers.interface.ts
│ └── plugin-resolution.ts
├── types/
│ ├── plugin-categories.ts
│ ├── plugin-capabilities.ts
│ ├── reactive.types.ts
│ └── chat-core.types.ts
├── plugins/
│ ├── index.ts
│ ├── context/{rich-context,capabilities,suggestions}.plugin.ts
│ ├── intelligence/{model,agent,mastery,learning}.plugin.ts
│ ├── generation/{generation,module-builder,teach,prompt-library}.plugin.ts
│ ├── bridge/{computer-use-bridge,observatory,awareness}.plugin.ts
│ └── input/{voice,action,context-legacy}.plugin.ts
├── composition/
│ ├── plugin-preset.factory.ts
│ └── plugin-resolver.ts
└── utils/
└── plugin-guards.ts
apps/vitto-vscode-extension/src/
├── services/
│ ├── ide-tool.service.ts
│ ├── agent-session.service.ts
│ ├── context-provider.service.ts
│ └── diff-content-provider.ts
└── panels/webview/components/agent/
└── agent-chat-view.ts
Files to Modify
libs/libs-ai/vitto-chat/src/
├── lib/plugins/index.ts # Re-export shim
├── lib/plugins/plugin-presets.ts # Angular adapter wrapper
libs/libs-foundational/core-models/src/
├── lib/types/context.types.ts # Add ide-editor, ide-terminal, etc.
apps/vitto-vscode-extension/src/
├── panels/main-panel.ts # Add Chat tab + agent message handlers
├── panels/webview/types.ts # Add 9 new message types
├── panels/webview/app.ts # (Lit app stays in MCP pane — no change)
tsconfig.base.json # Add @libs-ai/chat-plugin-core path
Plugin Compatibility Matrix
| Plugin | Category | requires | platforms | Web | VSCode | Mobile | Desktop | Chrome Ext |
|---|---|---|---|---|---|---|---|---|
| rich-context | context | [] | — | Y | Y | Y | Y | Y |
| capabilities | context | [] | — | Y | Y | Y | Y | Y |
| suggestions | context | [] | — | Y | Y | Y | Y | Y |
| model | intelligence | [] | — | Y | Y | Y | Y | Y |
| agent | intelligence | [] | — | Y | Y | Y | Y | Y |
| mastery | intelligence | [] | — | Y | Y | Y | Y | Y |
| learning | intelligence | [] | — | Y | Y | Y | Y | Y |
| generation | generation | [component-rendering] | — | Y | Y | Y | Y | Y |
| module-builder | generation | [] | — | Y | Y | Y | Y | Y |
| teach | generation | [component-rendering] | — | Y | Y | Y | Y | Y |
| prompt-library | generation | [component-rendering] | — | Y | Y | Y | Y | Y |
| observatory | bridge | [component-rendering] | — | Y | Y | Y | Y | Y |
| computer-use-bridge | bridge | [] | — | Y | Y | Y | Y | Y |
| awareness | bridge | [] | — | Y | Y | Y | Y | Y |
| voice | input | [speech-api] | — | Y | N | Y | N | N |
| action | input | [drag-drop] | — | Y | N | N | Y | N |
Notes:
component-renderingis provided by all platforms, so generation/teach/prompt-library/observatory are universally compatible. However, they require aComponentRefto be provided in config — if no component is passed, no tab is created (graceful degradation).awarenessplugin exists in codebase but is NOT currently used bycreateStandardPluginSet. It should be migrated tochat-plugin-corefor completeness but remains opt-in — only included when explicitly composed or when a future preset version activates it.
End of spec. This extracts a framework-agnostic plugin core from 16 existing plugins, adds platform capability negotiation for cross-surface reuse, and enables a native Chat tab in the VSCode extension — all without breaking existing Angular consumers.