feat(vscode): auto-refresh read-only agent view (#2832)
This commit is contained in:
+5
-3
@@ -72,9 +72,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
points. The extension now renders those restore points read-only in its Agent
|
||||
View, and thread summaries include read-only workspace and branch metadata so
|
||||
the VS Code Agent View can show when a thread or agent lane is on another
|
||||
branch. This answers the VS Code GUI lane without exposing chat webviews,
|
||||
inline edits, or retry/undo/restore runtime mutation endpoints yet (#461,
|
||||
#462, #480, #1217, #2341, #1584, #2327, #2580, #2808). Thanks @AiurArtanis
|
||||
branch. Agent View and restore-point data now auto-refresh on a configurable
|
||||
read-only interval so branch/workspace changes become visible without a
|
||||
manual refresh. This answers the VS Code GUI lane without exposing chat
|
||||
webviews, inline edits, or retry/undo/restore runtime mutation endpoints yet
|
||||
(#461, #462, #480, #1217, #2341, #1584, #2327, #2580, #2808). Thanks @AiurArtanis
|
||||
for the Agent View prompt, @lbcheng888 for the earlier scaffold, @gaord for
|
||||
the GUI runtime API direction, @douglarek, @caeserchen, and @nightt5879 for
|
||||
the branch visibility trail, and @BigBenLabs, @lzx1545642258, @yangdaowan,
|
||||
|
||||
@@ -72,9 +72,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
points. The extension now renders those restore points read-only in its Agent
|
||||
View, and thread summaries include read-only workspace and branch metadata so
|
||||
the VS Code Agent View can show when a thread or agent lane is on another
|
||||
branch. This answers the VS Code GUI lane without exposing chat webviews,
|
||||
inline edits, or retry/undo/restore runtime mutation endpoints yet (#461,
|
||||
#462, #480, #1217, #2341, #1584, #2327, #2580, #2808). Thanks @AiurArtanis
|
||||
branch. Agent View and restore-point data now auto-refresh on a configurable
|
||||
read-only interval so branch/workspace changes become visible without a
|
||||
manual refresh. This answers the VS Code GUI lane without exposing chat
|
||||
webviews, inline edits, or retry/undo/restore runtime mutation endpoints yet
|
||||
(#461, #462, #480, #1217, #2341, #1584, #2327, #2580, #2808). Thanks @AiurArtanis
|
||||
for the Agent View prompt, @lbcheng888 for the earlier scaffold, @gaord for
|
||||
the GUI runtime API direction, @douglarek, @caeserchen, and @nightt5879 for
|
||||
the branch visibility trail, and @BigBenLabs, @lzx1545642258, @yangdaowan,
|
||||
|
||||
@@ -11,6 +11,8 @@ This first slice is intentionally small:
|
||||
- show a read-only Agent View with recent runtime thread summaries from
|
||||
`/v1/threads/summary`
|
||||
- show recent read-only restore points from `/v1/snapshots`
|
||||
- refresh the read-only Agent View automatically so branch/workspace metadata
|
||||
catches up while agents are working
|
||||
|
||||
It does not expose the full chat webview, VS Code Agent View chat/editor
|
||||
integration, inline edit application, marketplace publish workflow, or
|
||||
@@ -26,7 +28,9 @@ code --install-extension codewhale-vscode-0.8.53.vsix
|
||||
```
|
||||
|
||||
Configure `codewhale.commandPath`, `codewhale.runtimeHost`,
|
||||
`codewhale.runtimePort`, and `codewhale.runtimeToken` from VS Code settings.
|
||||
`codewhale.runtimePort`, `codewhale.runtimeToken`, and
|
||||
`codewhale.agentViewRefreshIntervalSeconds` from VS Code settings.
|
||||
Set the refresh interval to `0` to disable automatic read-only refreshes.
|
||||
|
||||
Keep the runtime on `127.0.0.1` unless you deliberately front it with trusted
|
||||
local networking controls.
|
||||
|
||||
@@ -42,6 +42,8 @@ function activate(context) {
|
||||
const output = vscode.window.createOutputChannel("CodeWhale");
|
||||
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
|
||||
const statusView = new status_1.RuntimeStatusView();
|
||||
let autoRefreshTimer;
|
||||
let autoRefreshInFlight = false;
|
||||
status.command = "codewhale.checkRuntime";
|
||||
context.subscriptions.push(output, status);
|
||||
context.subscriptions.push(vscode.window.registerWebviewViewProvider(status_1.RuntimeStatusView.viewType, statusView));
|
||||
@@ -62,23 +64,11 @@ function activate(context) {
|
||||
status.tooltip = tooltip;
|
||||
status.show();
|
||||
};
|
||||
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.openTerminal", () => {
|
||||
const checkAndRefreshRuntime = async (showSpinner, logResult) => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.openCodeWhaleTerminal)(config);
|
||||
output.appendLine(`Opened CodeWhale terminal using ${config.commandPath}.`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.startRuntime", () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.startRuntimeTerminal)(config);
|
||||
const baseUrl = (0, runtime_1.runtimeBaseUrl)(config);
|
||||
updateStatus("$(sync~spin) CodeWhale", `Runtime terminal started for ${baseUrl}`);
|
||||
output.appendLine(`Started CodeWhale runtime terminal at ${baseUrl}.`);
|
||||
void vscode.window.showInformationMessage(`CodeWhale runtime starting at ${baseUrl}`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.checkRuntime", async () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
if (showSpinner) {
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
}
|
||||
const state = await (0, runtime_1.checkRuntime)(config);
|
||||
statusView.update(state);
|
||||
switch (state.kind) {
|
||||
@@ -107,8 +97,64 @@ function activate(context) {
|
||||
statusView.updateSnapshots([], "Connect to the runtime to load restore points.");
|
||||
break;
|
||||
}
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
if (logResult) {
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
const runAutoRefresh = async () => {
|
||||
if (autoRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
autoRefreshInFlight = true;
|
||||
try {
|
||||
await checkAndRefreshRuntime(false, false);
|
||||
}
|
||||
finally {
|
||||
autoRefreshInFlight = false;
|
||||
}
|
||||
};
|
||||
const scheduleAutoRefresh = () => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = undefined;
|
||||
}
|
||||
const intervalSeconds = (0, runtime_1.readRuntimeConfig)().agentViewRefreshIntervalSeconds;
|
||||
if (intervalSeconds === 0) {
|
||||
output.appendLine("Agent View auto-refresh is disabled.");
|
||||
return;
|
||||
}
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
void runAutoRefresh();
|
||||
}, intervalSeconds * 1000);
|
||||
output.appendLine(`Agent View auto-refresh scheduled every ${intervalSeconds}s.`);
|
||||
};
|
||||
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
|
||||
scheduleAutoRefresh();
|
||||
context.subscriptions.push(new vscode.Disposable(() => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
}
|
||||
}), vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration("codewhale.agentViewRefreshIntervalSeconds")) {
|
||||
scheduleAutoRefresh();
|
||||
}
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.openTerminal", () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.openCodeWhaleTerminal)(config);
|
||||
output.appendLine(`Opened CodeWhale terminal using ${config.commandPath}.`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.startRuntime", () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.startRuntimeTerminal)(config);
|
||||
const baseUrl = (0, runtime_1.runtimeBaseUrl)(config);
|
||||
updateStatus("$(sync~spin) CodeWhale", `Runtime terminal started for ${baseUrl}`);
|
||||
output.appendLine(`Started CodeWhale runtime terminal at ${baseUrl}.`);
|
||||
void vscode.window.showInformationMessage(`CodeWhale runtime starting at ${baseUrl}`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.checkRuntime", async () => {
|
||||
return await checkAndRefreshRuntime(true, true);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.refreshAgentView", async () => {
|
||||
try {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -48,11 +48,13 @@ function readRuntimeConfig() {
|
||||
const host = config.get("runtimeHost", "127.0.0.1").trim() || "127.0.0.1";
|
||||
const port = config.get("runtimePort", 7878);
|
||||
const token = config.get("runtimeToken", "").trim();
|
||||
const interval = config.get("agentViewRefreshIntervalSeconds", 15);
|
||||
return {
|
||||
commandPath,
|
||||
host,
|
||||
port,
|
||||
token: token.length > 0 ? token : undefined,
|
||||
agentViewRefreshIntervalSeconds: clampRefreshInterval(interval),
|
||||
};
|
||||
}
|
||||
function runtimeBaseUrl(config) {
|
||||
@@ -229,6 +231,12 @@ function readString(value) {
|
||||
function readNumber(value) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
function clampRefreshInterval(value) {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 15;
|
||||
}
|
||||
return Math.max(0, Math.min(300, Math.floor(value)));
|
||||
}
|
||||
function shellQuote(value) {
|
||||
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
||||
return value;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -83,6 +83,13 @@
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Optional bearer token for authenticated runtime endpoints."
|
||||
},
|
||||
"codewhale.agentViewRefreshIntervalSeconds": {
|
||||
"type": "number",
|
||||
"default": 15,
|
||||
"minimum": 0,
|
||||
"maximum": 300,
|
||||
"description": "Seconds between read-only Agent View refreshes. Set to 0 to disable automatic refresh."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
readRuntimeConfig,
|
||||
runtimeBaseUrl,
|
||||
startRuntimeTerminal,
|
||||
type RuntimeState,
|
||||
} from "./runtime";
|
||||
import { RuntimeStatusView } from "./status";
|
||||
|
||||
@@ -14,6 +15,8 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
const output = vscode.window.createOutputChannel("CodeWhale");
|
||||
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
|
||||
const statusView = new RuntimeStatusView();
|
||||
let autoRefreshTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let autoRefreshInFlight = false;
|
||||
|
||||
status.command = "codewhale.checkRuntime";
|
||||
context.subscriptions.push(output, status);
|
||||
@@ -41,7 +44,95 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
status.show();
|
||||
};
|
||||
|
||||
const checkAndRefreshRuntime = async (
|
||||
showSpinner: boolean,
|
||||
logResult: boolean,
|
||||
): Promise<RuntimeState> => {
|
||||
const config = readRuntimeConfig();
|
||||
if (showSpinner) {
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
}
|
||||
|
||||
const state = await checkRuntime(config);
|
||||
statusView.update(state);
|
||||
|
||||
switch (state.kind) {
|
||||
case "connected":
|
||||
updateStatus("$(check) CodeWhale", state.detail);
|
||||
try {
|
||||
await refreshAgentView();
|
||||
await refreshSnapshots();
|
||||
} catch (error: unknown) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
statusView.updateThreads([], "Runtime thread summaries unavailable.");
|
||||
statusView.updateSnapshots([], detail);
|
||||
output.appendLine(`Runtime Agent View details unavailable: ${detail}`);
|
||||
}
|
||||
break;
|
||||
case "auth-required":
|
||||
updateStatus("$(lock) CodeWhale", state.detail);
|
||||
statusView.updateThreads([], "Runtime token is required before threads can load.");
|
||||
statusView.updateSnapshots([], "Runtime token is required before restore points can load.");
|
||||
break;
|
||||
case "offline":
|
||||
case "error":
|
||||
updateStatus("$(warning) CodeWhale", state.detail);
|
||||
statusView.updateThreads([], "Connect to the runtime to load recent threads.");
|
||||
statusView.updateSnapshots([], "Connect to the runtime to load restore points.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (logResult) {
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
const runAutoRefresh = async (): Promise<void> => {
|
||||
if (autoRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoRefreshInFlight = true;
|
||||
try {
|
||||
await checkAndRefreshRuntime(false, false);
|
||||
} finally {
|
||||
autoRefreshInFlight = false;
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleAutoRefresh = (): void => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = undefined;
|
||||
}
|
||||
|
||||
const intervalSeconds = readRuntimeConfig().agentViewRefreshIntervalSeconds;
|
||||
if (intervalSeconds === 0) {
|
||||
output.appendLine("Agent View auto-refresh is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
void runAutoRefresh();
|
||||
}, intervalSeconds * 1000);
|
||||
output.appendLine(`Agent View auto-refresh scheduled every ${intervalSeconds}s.`);
|
||||
};
|
||||
|
||||
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
|
||||
scheduleAutoRefresh();
|
||||
context.subscriptions.push(
|
||||
new vscode.Disposable(() => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
}
|
||||
}),
|
||||
vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration("codewhale.agentViewRefreshIntervalSeconds")) {
|
||||
scheduleAutoRefresh();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.openTerminal", () => {
|
||||
@@ -64,39 +155,7 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.checkRuntime", async () => {
|
||||
const config = readRuntimeConfig();
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
const state = await checkRuntime(config);
|
||||
statusView.update(state);
|
||||
|
||||
switch (state.kind) {
|
||||
case "connected":
|
||||
updateStatus("$(check) CodeWhale", state.detail);
|
||||
try {
|
||||
await refreshAgentView();
|
||||
await refreshSnapshots();
|
||||
} catch (error: unknown) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
statusView.updateThreads([], "Runtime thread summaries unavailable.");
|
||||
statusView.updateSnapshots([], detail);
|
||||
output.appendLine(`Runtime Agent View details unavailable: ${detail}`);
|
||||
}
|
||||
break;
|
||||
case "auth-required":
|
||||
updateStatus("$(lock) CodeWhale", state.detail);
|
||||
statusView.updateThreads([], "Runtime token is required before threads can load.");
|
||||
statusView.updateSnapshots([], "Runtime token is required before restore points can load.");
|
||||
break;
|
||||
case "offline":
|
||||
case "error":
|
||||
updateStatus("$(warning) CodeWhale", state.detail);
|
||||
statusView.updateThreads([], "Connect to the runtime to load recent threads.");
|
||||
statusView.updateSnapshots([], "Connect to the runtime to load restore points.");
|
||||
break;
|
||||
}
|
||||
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
return state;
|
||||
return await checkAndRefreshRuntime(true, true);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface RuntimeConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
token?: string;
|
||||
agentViewRefreshIntervalSeconds: number;
|
||||
}
|
||||
|
||||
export function readRuntimeConfig(): RuntimeConfig {
|
||||
@@ -42,11 +43,13 @@ export function readRuntimeConfig(): RuntimeConfig {
|
||||
const host = config.get<string>("runtimeHost", "127.0.0.1").trim() || "127.0.0.1";
|
||||
const port = config.get<number>("runtimePort", 7878);
|
||||
const token = config.get<string>("runtimeToken", "").trim();
|
||||
const interval = config.get<number>("agentViewRefreshIntervalSeconds", 15);
|
||||
return {
|
||||
commandPath,
|
||||
host,
|
||||
port,
|
||||
token: token.length > 0 ? token : undefined,
|
||||
agentViewRefreshIntervalSeconds: clampRefreshInterval(interval),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,6 +265,13 @@ function readNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function clampRefreshInterval(value: number): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 15;
|
||||
}
|
||||
return Math.max(0, Math.min(300, Math.floor(value)));
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
||||
return value;
|
||||
|
||||
Reference in New Issue
Block a user