Merge pull request #2814 from Hmbown/codex/v090-vscode-agent-view-preview

feat(vscode): add read-only Agent View preview
This commit is contained in:
Hunter Bown
2026-06-05 19:35:55 -07:00
committed by GitHub
13 changed files with 273 additions and 23 deletions
+7 -7
View File
@@ -40,13 +40,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking
direction.
- Added an official VS Code extension Phase 0 scaffold with terminal launch,
local runtime attach checks, status bar state, and a CodeWhale runtime status
view. This answers the VS Code GUI lane without exposing chat webviews,
Agent View, inline edits, or retry/undo runtime endpoints yet (#461, #462,
#480, #1584, #2580). Thanks @AiurArtanis for the Agent View prompt,
@lbcheng888 for the earlier scaffold, and @BigBenLabs, @lzx1545642258,
@yangdaowan, @mangdehuang, @VerrPower, @hejia-v, @nasus9527, and @ygzhang-cn
for the GUI/VS Code demand and validation trail.
local runtime attach checks, status bar state, and a read-only Agent View
preview backed by recent runtime thread summaries. This answers the VS Code
GUI lane without exposing chat webviews, inline edits, or retry/undo runtime
endpoints yet (#461, #462, #480, #1584, #2580). Thanks @AiurArtanis for the
Agent View prompt, @lbcheng888 for the earlier scaffold, and @BigBenLabs,
@lzx1545642258, @yangdaowan, @mangdehuang, @VerrPower, @hejia-v,
@nasus9527, and @ygzhang-cn for the GUI/VS Code demand and validation trail.
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
managed session. The endpoint preserves thread title/model/mode/workspace
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
+7 -7
View File
@@ -40,13 +40,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking
direction.
- Added an official VS Code extension Phase 0 scaffold with terminal launch,
local runtime attach checks, status bar state, and a CodeWhale runtime status
view. This answers the VS Code GUI lane without exposing chat webviews,
Agent View, inline edits, or retry/undo runtime endpoints yet (#461, #462,
#480, #1584, #2580). Thanks @AiurArtanis for the Agent View prompt,
@lbcheng888 for the earlier scaffold, and @BigBenLabs, @lzx1545642258,
@yangdaowan, @mangdehuang, @VerrPower, @hejia-v, @nasus9527, and @ygzhang-cn
for the GUI/VS Code demand and validation trail.
local runtime attach checks, status bar state, and a read-only Agent View
preview backed by recent runtime thread summaries. This answers the VS Code
GUI lane without exposing chat webviews, inline edits, or retry/undo runtime
endpoints yet (#461, #462, #480, #1584, #2580). Thanks @AiurArtanis for the
Agent View prompt, @lbcheng888 for the earlier scaffold, and @BigBenLabs,
@lzx1545642258, @yangdaowan, @mangdehuang, @VerrPower, @hejia-v,
@nasus9527, and @ygzhang-cn for the GUI/VS Code demand and validation trail.
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
managed session. The endpoint preserves thread title/model/mode/workspace
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
+6 -4
View File
@@ -7,11 +7,13 @@ This first slice is intentionally small:
- open CodeWhale in an integrated terminal
- start `codewhale serve --http` in a visible terminal
- check a local runtime through `/health` and `/v1/runtime/info`
- show connection state in the status bar and CodeWhale activity view
- show connection state in the status bar
- show a read-only Agent View with recent runtime thread summaries from
`/v1/threads/summary`
It does not expose the full chat webview, VS Code Agent View integration,
inline edit application, marketplace publish workflow, or retry/undo/snapshot
GUI endpoints yet.
It does not expose the full chat webview, VS Code Agent View chat/editor
integration, inline edit application, marketplace publish workflow, or
retry/undo/snapshot GUI endpoints yet.
## Local Use
+27
View File
@@ -45,6 +45,12 @@ function activate(context) {
status.command = "codewhale.checkRuntime";
context.subscriptions.push(output, status);
context.subscriptions.push(vscode.window.registerWebviewViewProvider(status_1.RuntimeStatusView.viewType, statusView));
const refreshAgentView = async () => {
const config = (0, runtime_1.readRuntimeConfig)();
const threads = await (0, runtime_1.listThreadSummaries)(config);
statusView.updateThreads(threads, "Showing recent runtime threads.");
output.appendLine(`Loaded ${threads.length} runtime thread summaries.`);
};
const updateStatus = (text, tooltip) => {
status.text = text;
status.tooltip = tooltip;
@@ -72,18 +78,39 @@ function activate(context) {
switch (state.kind) {
case "connected":
updateStatus("$(check) CodeWhale", state.detail);
try {
await refreshAgentView();
}
catch (error) {
const detail = error instanceof Error ? error.message : String(error);
statusView.updateThreads([], detail);
output.appendLine(`Runtime thread summaries unavailable: ${detail}`);
}
break;
case "auth-required":
updateStatus("$(lock) CodeWhale", state.detail);
statusView.updateThreads([], "Runtime token is required before threads can load.");
break;
case "offline":
case "error":
updateStatus("$(warning) CodeWhale", state.detail);
statusView.updateThreads([], "Connect to the runtime to load recent threads.");
break;
}
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
return state;
}));
context.subscriptions.push(vscode.commands.registerCommand("codewhale.refreshAgentView", async () => {
try {
await refreshAgentView();
}
catch (error) {
const detail = error instanceof Error ? error.message : String(error);
statusView.updateThreads([], detail);
output.appendLine(`Runtime thread summaries unavailable: ${detail}`);
void vscode.window.showWarningMessage(detail);
}
}));
context.subscriptions.push(vscode.commands.registerCommand("codewhale.openRuntimeDocs", () => {
void vscode.env.openExternal(vscode.Uri.parse("https://github.com/Hmbown/CodeWhale/blob/main/docs/RUNTIME_API.md"));
}));
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
+42
View File
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.readRuntimeConfig = readRuntimeConfig;
exports.runtimeBaseUrl = runtimeBaseUrl;
exports.checkRuntime = checkRuntime;
exports.listThreadSummaries = listThreadSummaries;
exports.startRuntimeTerminal = startRuntimeTerminal;
exports.openCodeWhaleTerminal = openCodeWhaleTerminal;
const http = __importStar(require("node:http"));
@@ -84,6 +85,17 @@ async function checkRuntime(config) {
version,
};
}
async function listThreadSummaries(config, limit = 8) {
const baseUrl = runtimeBaseUrl(config);
const response = await requestJson(`${baseUrl}/v1/threads/summary?limit=${encodeURIComponent(String(limit))}`, config.token);
if (response.statusCode === 401) {
throw new Error("Thread summaries require the runtime bearer token.");
}
if (response.statusCode !== 200) {
throw new Error(`Thread summary returned HTTP ${response.statusCode}.`);
}
return readThreadSummaries(response.body);
}
function startRuntimeTerminal(config) {
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
const args = [
@@ -152,6 +164,36 @@ function readVersion(value) {
const version = value.version;
return typeof version === "string" ? version : undefined;
}
function readThreadSummaries(value) {
if (!Array.isArray(value)) {
return [];
}
return value.flatMap((item) => {
if (!item || typeof item !== "object") {
return [];
}
const record = item;
const id = readString(record.id);
if (!id) {
return [];
}
return [
{
id,
title: readString(record.title) ?? "New Thread",
preview: readString(record.preview) ?? "",
model: readString(record.model) ?? "unknown",
mode: readString(record.mode) ?? "agent",
archived: record.archived === true,
updatedAt: readString(record.updated_at) ?? "",
latestTurnStatus: readString(record.latest_turn_status),
},
];
});
}
function readString(value) {
return typeof value === "string" ? value : undefined;
}
function shellQuote(value) {
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
return value;
File diff suppressed because one or more lines are too long
+38
View File
@@ -43,6 +43,8 @@ class RuntimeStatusView {
baseUrl: "http://127.0.0.1:7878",
detail: "Runtime has not been checked yet.",
};
threads = [];
threadsDetail = "Connect to the runtime to load recent threads.";
resolveWebviewView(view) {
this.view = view;
view.webview.options = { enableScripts: true };
@@ -56,6 +58,9 @@ class RuntimeStatusView {
else if (message.command === "terminal") {
void vscode.commands.executeCommand("codewhale.openTerminal");
}
else if (message.command === "threads") {
void vscode.commands.executeCommand("codewhale.refreshAgentView");
}
});
this.render();
}
@@ -63,12 +68,20 @@ class RuntimeStatusView {
this.state = state;
this.render();
}
updateThreads(threads, detail) {
this.threads = threads;
this.threadsDetail = detail;
this.render();
}
render() {
if (!this.view) {
return;
}
const badge = labelFor(this.state.kind);
const nonce = makeNonce();
const threadsHtml = this.threads.length > 0
? this.threads.map((thread) => renderThread(thread)).join("")
: `<p class="detail">${escapeHtml(this.threadsDetail)}</p>`;
this.view.webview.html = `<!doctype html>
<html lang="en">
<head>
@@ -79,6 +92,11 @@ class RuntimeStatusView {
body { padding: 14px; color: var(--vscode-foreground); font-family: var(--vscode-font-family); }
.status { margin-bottom: 12px; font-weight: 600; }
.detail { margin: 0 0 14px; color: var(--vscode-descriptionForeground); line-height: 1.45; }
.section-title { margin: 18px 0 8px; font-size: 11px; font-weight: 700; letter-spacing: 0; text-transform: uppercase; color: var(--vscode-descriptionForeground); }
.thread { padding: 8px 0; border-top: 1px solid var(--vscode-sideBarSectionHeader-border, var(--vscode-panel-border)); }
.thread-title { margin-bottom: 4px; font-weight: 600; overflow-wrap: anywhere; }
.thread-preview { margin-bottom: 5px; color: var(--vscode-descriptionForeground); line-height: 1.35; overflow-wrap: anywhere; }
.thread-meta { color: var(--vscode-descriptionForeground); font-size: 11px; overflow-wrap: anywhere; }
code { color: var(--vscode-textLink-foreground); }
button { width: 100%; margin: 4px 0; }
</style>
@@ -88,8 +106,11 @@ class RuntimeStatusView {
<p class="detail">${escapeHtml(this.state.detail)}</p>
<p class="detail"><code>${escapeHtml(this.state.baseUrl)}</code></p>
<button data-command="check">Check Runtime</button>
<button data-command="threads">Refresh Threads</button>
<button data-command="start">Start Local Runtime</button>
<button data-command="terminal">Open CodeWhale Terminal</button>
<div class="section-title">Agent View</div>
${threadsHtml}
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
for (const button of document.querySelectorAll("button[data-command]")) {
@@ -101,6 +122,16 @@ class RuntimeStatusView {
}
}
exports.RuntimeStatusView = RuntimeStatusView;
function renderThread(thread) {
const status = thread.latestTurnStatus ? ` · ${thread.latestTurnStatus}` : "";
const archived = thread.archived ? " · archived" : "";
const updated = thread.updatedAt ? ` · ${formatTimestamp(thread.updatedAt)}` : "";
return `<div class="thread">
<div class="thread-title">${escapeHtml(thread.title)}</div>
<div class="thread-preview">${escapeHtml(thread.preview || "No recent message.")}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${archived}${updated}`)}</div>
</div>`;
}
function labelFor(kind) {
switch (kind) {
case "connected":
@@ -113,6 +144,13 @@ function labelFor(kind) {
return "Offline";
}
}
function formatTimestamp(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
return date.toLocaleString();
}
function escapeHtml(value) {
return value
.replace(/&/g, "&amp;")
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
+6 -1
View File
@@ -20,6 +20,7 @@
"onCommand:codewhale.openTerminal",
"onCommand:codewhale.startRuntime",
"onCommand:codewhale.checkRuntime",
"onCommand:codewhale.refreshAgentView",
"onCommand:codewhale.openRuntimeDocs",
"onView:codewhale.runtimeStatus"
],
@@ -44,6 +45,10 @@
"command": "codewhale.checkRuntime",
"title": "CodeWhale: Check Runtime"
},
{
"command": "codewhale.refreshAgentView",
"title": "CodeWhale: Refresh Agent View"
},
{
"command": "codewhale.openRuntimeDocs",
"title": "CodeWhale: Open Runtime API Docs"
@@ -90,7 +95,7 @@
{
"type": "webview",
"id": "codewhale.runtimeStatus",
"name": "Runtime"
"name": "Agent View"
}
]
}
+30
View File
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import {
checkRuntime,
listThreadSummaries,
openCodeWhaleTerminal,
readRuntimeConfig,
runtimeBaseUrl,
@@ -19,6 +20,13 @@ export function activate(context: vscode.ExtensionContext): void {
vscode.window.registerWebviewViewProvider(RuntimeStatusView.viewType, statusView),
);
const refreshAgentView = async (): Promise<void> => {
const config = readRuntimeConfig();
const threads = await listThreadSummaries(config);
statusView.updateThreads(threads, "Showing recent runtime threads.");
output.appendLine(`Loaded ${threads.length} runtime thread summaries.`);
};
const updateStatus = (text: string, tooltip: string): void => {
status.text = text;
status.tooltip = tooltip;
@@ -56,13 +64,22 @@ export function activate(context: vscode.ExtensionContext): void {
switch (state.kind) {
case "connected":
updateStatus("$(check) CodeWhale", state.detail);
try {
await refreshAgentView();
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
statusView.updateThreads([], detail);
output.appendLine(`Runtime thread summaries unavailable: ${detail}`);
}
break;
case "auth-required":
updateStatus("$(lock) CodeWhale", state.detail);
statusView.updateThreads([], "Runtime token is required before threads can load.");
break;
case "offline":
case "error":
updateStatus("$(warning) CodeWhale", state.detail);
statusView.updateThreads([], "Connect to the runtime to load recent threads.");
break;
}
@@ -71,6 +88,19 @@ export function activate(context: vscode.ExtensionContext): void {
}),
);
context.subscriptions.push(
vscode.commands.registerCommand("codewhale.refreshAgentView", async () => {
try {
await refreshAgentView();
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
statusView.updateThreads([], detail);
output.appendLine(`Runtime thread summaries unavailable: ${detail}`);
void vscode.window.showWarningMessage(detail);
}
}),
);
context.subscriptions.push(
vscode.commands.registerCommand("codewhale.openRuntimeDocs", () => {
void vscode.env.openExternal(
+65
View File
@@ -10,6 +10,17 @@ export interface RuntimeState {
version?: string;
}
export interface ThreadSummary {
id: string;
title: string;
preview: string;
model: string;
mode: string;
archived: boolean;
updatedAt: string;
latestTurnStatus?: string;
}
export interface RuntimeConfig {
commandPath: string;
host: string;
@@ -66,6 +77,26 @@ export async function checkRuntime(config: RuntimeConfig): Promise<RuntimeState>
};
}
export async function listThreadSummaries(
config: RuntimeConfig,
limit = 8,
): Promise<ThreadSummary[]> {
const baseUrl = runtimeBaseUrl(config);
const response = await requestJson(
`${baseUrl}/v1/threads/summary?limit=${encodeURIComponent(String(limit))}`,
config.token,
);
if (response.statusCode === 401) {
throw new Error("Thread summaries require the runtime bearer token.");
}
if (response.statusCode !== 200) {
throw new Error(`Thread summary returned HTTP ${response.statusCode}.`);
}
return readThreadSummaries(response.body);
}
export function startRuntimeTerminal(config: RuntimeConfig): vscode.Terminal {
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
const args = [
@@ -145,6 +176,40 @@ function readVersion(value: unknown): string | undefined {
return typeof version === "string" ? version : undefined;
}
function readThreadSummaries(value: unknown): ThreadSummary[] {
if (!Array.isArray(value)) {
return [];
}
return value.flatMap((item) => {
if (!item || typeof item !== "object") {
return [];
}
const record = item as Record<string, unknown>;
const id = readString(record.id);
if (!id) {
return [];
}
return [
{
id,
title: readString(record.title) ?? "New Thread",
preview: readString(record.preview) ?? "",
model: readString(record.model) ?? "unknown",
mode: readString(record.mode) ?? "agent",
archived: record.archived === true,
updatedAt: readString(record.updated_at) ?? "",
latestTurnStatus: readString(record.latest_turn_status),
},
];
});
}
function readString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}
function shellQuote(value: string): string {
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
return value;
+42 -1
View File
@@ -1,5 +1,5 @@
import * as vscode from "vscode";
import type { RuntimeState } from "./runtime";
import type { RuntimeState, ThreadSummary } from "./runtime";
export class RuntimeStatusView implements vscode.WebviewViewProvider {
public static readonly viewType = "codewhale.runtimeStatus";
@@ -10,6 +10,8 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
baseUrl: "http://127.0.0.1:7878",
detail: "Runtime has not been checked yet.",
};
private threads: ThreadSummary[] = [];
private threadsDetail = "Connect to the runtime to load recent threads.";
resolveWebviewView(view: vscode.WebviewView): void {
this.view = view;
@@ -21,6 +23,8 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
void vscode.commands.executeCommand("codewhale.startRuntime");
} else if (message.command === "terminal") {
void vscode.commands.executeCommand("codewhale.openTerminal");
} else if (message.command === "threads") {
void vscode.commands.executeCommand("codewhale.refreshAgentView");
}
});
this.render();
@@ -31,6 +35,12 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
this.render();
}
updateThreads(threads: ThreadSummary[], detail: string): void {
this.threads = threads;
this.threadsDetail = detail;
this.render();
}
private render(): void {
if (!this.view) {
return;
@@ -38,6 +48,10 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
const badge = labelFor(this.state.kind);
const nonce = makeNonce();
const threadsHtml =
this.threads.length > 0
? this.threads.map((thread) => renderThread(thread)).join("")
: `<p class="detail">${escapeHtml(this.threadsDetail)}</p>`;
this.view.webview.html = `<!doctype html>
<html lang="en">
<head>
@@ -48,6 +62,11 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
body { padding: 14px; color: var(--vscode-foreground); font-family: var(--vscode-font-family); }
.status { margin-bottom: 12px; font-weight: 600; }
.detail { margin: 0 0 14px; color: var(--vscode-descriptionForeground); line-height: 1.45; }
.section-title { margin: 18px 0 8px; font-size: 11px; font-weight: 700; letter-spacing: 0; text-transform: uppercase; color: var(--vscode-descriptionForeground); }
.thread { padding: 8px 0; border-top: 1px solid var(--vscode-sideBarSectionHeader-border, var(--vscode-panel-border)); }
.thread-title { margin-bottom: 4px; font-weight: 600; overflow-wrap: anywhere; }
.thread-preview { margin-bottom: 5px; color: var(--vscode-descriptionForeground); line-height: 1.35; overflow-wrap: anywhere; }
.thread-meta { color: var(--vscode-descriptionForeground); font-size: 11px; overflow-wrap: anywhere; }
code { color: var(--vscode-textLink-foreground); }
button { width: 100%; margin: 4px 0; }
</style>
@@ -57,8 +76,11 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
<p class="detail">${escapeHtml(this.state.detail)}</p>
<p class="detail"><code>${escapeHtml(this.state.baseUrl)}</code></p>
<button data-command="check">Check Runtime</button>
<button data-command="threads">Refresh Threads</button>
<button data-command="start">Start Local Runtime</button>
<button data-command="terminal">Open CodeWhale Terminal</button>
<div class="section-title">Agent View</div>
${threadsHtml}
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
for (const button of document.querySelectorAll("button[data-command]")) {
@@ -70,6 +92,17 @@ export class RuntimeStatusView implements vscode.WebviewViewProvider {
}
}
function renderThread(thread: ThreadSummary): string {
const status = thread.latestTurnStatus ? ` · ${thread.latestTurnStatus}` : "";
const archived = thread.archived ? " · archived" : "";
const updated = thread.updatedAt ? ` · ${formatTimestamp(thread.updatedAt)}` : "";
return `<div class="thread">
<div class="thread-title">${escapeHtml(thread.title)}</div>
<div class="thread-preview">${escapeHtml(thread.preview || "No recent message.")}</div>
<div class="thread-meta">${escapeHtml(`${thread.mode} · ${thread.model}${status}${archived}${updated}`)}</div>
</div>`;
}
function labelFor(kind: RuntimeState["kind"]): string {
switch (kind) {
case "connected":
@@ -83,6 +116,14 @@ function labelFor(kind: RuntimeState["kind"]): string {
}
}
function formatTimestamp(value: string): string {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
return date.toLocaleString();
}
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&amp;")