diff --git a/packages/extension/package.json b/packages/extension/package.json index 5600ef4..2c799d1 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -66,6 +66,11 @@ "id": "amicode.catalog", "name": "Catalog", "type": "tree" + }, + { + "id": "amicode.runInspector", + "name": "Run Inspector", + "type": "webview" } ] }, diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts index b2f0fe7..e3b0215 100644 --- a/packages/extension/src/extension.ts +++ b/packages/extension/src/extension.ts @@ -8,6 +8,7 @@ import { ChatPanel } from "./chat_panel"; import { DeckPanel } from "./deck_panel"; import { registerCatalogCard } from "./catalog_card_shell"; import { registerTrees } from "./trees"; +import { RunInspectorViewProvider } from "./run_inspector_view"; import { StatusBarManager } from "./status_bar"; import { prepareOpencodeProject, @@ -348,6 +349,12 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { // 1. UI surfaces const trees = registerTrees(ctx); registerCatalogCard(ctx); // #47 dev scaffold — card opens via the save-to-catalog flow + // Sidebar Run Inspector under the context tree (amicode → Armonia / Catalog / Run Inspector) + const runInspector = new RunInspectorViewProvider(ctx); + ctx.subscriptions.push( + vscode.window.registerWebviewViewProvider(RunInspectorViewProvider.viewType, runInspector), + runInspector, + ); ctx.subscriptions.push( // #47 session catalog: record the save (workspaceState + tree), then open // the card. Both prompts (demo replay, live promote) route through here. diff --git a/packages/extension/src/run_inspector_view.ts b/packages/extension/src/run_inspector_view.ts new file mode 100644 index 0000000..6a2bfd6 --- /dev/null +++ b/packages/extension/src/run_inspector_view.ts @@ -0,0 +1,162 @@ +import * as vscode from "vscode"; +import { registerInspectorPoster, type InspectorBridgeMessage } from "./inspector_bridge"; + +// ============================================================================ +// Sidebar Run Inspector — the Work Column inspector relocated under the +// context tree (amicode# — user request). A VS Code WebviewView inside the +// `amicode` container, so it stacks vertically under Armonia + Catalog with +// no extra activity-bar entry. It reuses the existing inspector_bridge fan-out: +// RunsManager already broadcasts run:iteration / run:pulse / run:completion +// via registerInspectorPoster; this view registers its own poster and renders +// a lightweight pulse + fidelity view at sidebar width (320px). The Work Column +// tab can remain for detailed plots; this is the always-visible summary. +// ============================================================================ + +export class RunInspectorViewProvider implements vscode.WebviewViewProvider { + public static readonly viewType = "amicode.runInspector"; + private view?: vscode.WebviewView; + private disposables: vscode.Disposable[] = []; + private lastMsg?: InspectorBridgeMessage; + + constructor(private readonly ctx: vscode.ExtensionContext) {} + + resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ): void { + this.view = webviewView; + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this.ctx.extensionUri], + }; + webviewView.webview.html = this.html(webviewView.webview); + // Register as an inspector poster so run: events land here too. + // The Work Column app also listens; both surfaces stay in sync. + const poster = registerInspectorPoster((msg) => { + const envelope = msg as InspectorBridgeMessage & { source?: string }; + // Only forward our typed inspector messages + if (envelope && typeof (envelope as { type?: string }).type === "string" && String((envelope as { type: string }).type).startsWith("run:")) { + this.lastMsg = envelope as InspectorBridgeMessage; + void webviewView.webview.postMessage(envelope); + } + }); + this.disposables.push(poster); + webviewView.onDidDispose(() => { + poster.dispose(); + this.disposables.forEach((d) => d.dispose()); + this.disposables = []; + if (this.view === webviewView) this.view = undefined; + }); + // Handle messages from the webview (e.g., selectRun) + webviewView.webview.onDidReceiveMessage((msg) => { + if (msg?.type === "selectRun" && typeof msg.runId === "string") { + void vscode.commands.executeCommand("amicode.selectRun"); + } + }); + } + + private html(webview: vscode.Webview): string { + const nonce = Math.random().toString(36).slice(2); + const csp = [ + "default-src 'none'", + "style-src 'unsafe-inline'", + `script-src 'nonce-${nonce}'`, + "img-src data: https:", + "connect-src 'self'", + ].join("; "); + return /* html */ ` + + + + + + + +
+
Run Inspector — sidebar
+
No live run — start a solve to see iterations here.
Live pulse streams to both the sidebar and Work Column.
+ +
+ + +`; + } + + dispose(): void { + this.disposables.forEach((d) => d.dispose()); + } +}