Merge pull request #2811 from Hmbown/codex/v090-vscode-scaffold

feat(vscode): add local runtime extension scaffold
This commit is contained in:
Hunter Bown
2026-06-05 19:14:20 -07:00
committed by GitHub
19 changed files with 4785 additions and 0 deletions
+8
View File
@@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`workflow_run` tool until cancellation, replay, and worktree semantics are
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, #2580). Thanks @AiurArtanis for the Agent View prompt, @lbcheng888 for
the earlier scaffold, and @BigBenLabs, @lzx1545642258, @yangdaowan,
@mangdehuang, @VerrPower, @hejia-v, 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
+4
View File
@@ -644,6 +644,10 @@ Current v0.9 track credits:
- **[aboimpinto](https://github.com/aboimpinto)** — sidebar command polish and
pausable custom-command lifecycle direction harvested into the v0.9 track
(#2788, #2732)
- **[lbcheng888](https://github.com/lbcheng888)** and
**[AiurArtanis](https://github.com/AiurArtanis)** — VS Code extension
scaffold direction and Agent View request that shaped the official Phase 0
extension (#1022, #2580)
- **[HUQIANTAO](https://github.com/HUQIANTAO)** — `web_run` cache-state
lock-splitting, turn-metadata prefix-cache stability, and project-context
cache work (#2502, #2517, #2636)
+8
View File
@@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`workflow_run` tool until cancellation, replay, and worktree semantics are
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, #2580). Thanks @AiurArtanis for the Agent View prompt, @lbcheng888 for
the earlier scaffold, and @BigBenLabs, @lzx1545642258, @yangdaowan,
@mangdehuang, @VerrPower, @hejia-v, 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
+2
View File
@@ -0,0 +1,2 @@
node_modules/
*.vsix
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-2025 DeepSeek CLI Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+29
View File
@@ -0,0 +1,29 @@
# CodeWhale for VS Code
Official CodeWhale extension scaffold for local development.
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
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.
## Local Use
```bash
npm install
npm run compile
npm run package
code --install-extension codewhale-vscode-0.8.53.vsix
```
Configure `codewhale.commandPath`, `codewhale.runtimeHost`,
`codewhale.runtimePort`, and `codewhale.runtimeToken` from VS Code settings.
Keep the runtime on `127.0.0.1` unless you deliberately front it with trusted
local networking controls.
+6
View File
@@ -0,0 +1,6 @@
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="CodeWhale">
<rect width="128" height="128" rx="20" fill="#0f172a"/>
<path d="M26 72c0-21 17-38 38-38h21c10 0 19 6 23 15 2 4 6 7 10 8-5 4-11 6-18 6h-2c-1 23-20 41-43 41H34c-7 0-12-5-12-12V80c0-4 3-8 8-8h-4z" fill="#38bdf8"/>
<circle cx="82" cy="56" r="5" fill="#0f172a"/>
<path d="M42 85h36M45 96h25" stroke="#0f172a" stroke-width="6" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 484 B

+95
View File
@@ -0,0 +1,95 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = activate;
exports.deactivate = deactivate;
const vscode = __importStar(require("vscode"));
const runtime_1 = require("./runtime");
const status_1 = require("./status");
function activate(context) {
const output = vscode.window.createOutputChannel("CodeWhale");
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
const statusView = new status_1.RuntimeStatusView();
status.command = "codewhale.checkRuntime";
context.subscriptions.push(output, status);
context.subscriptions.push(vscode.window.registerWebviewViewProvider(status_1.RuntimeStatusView.viewType, statusView));
const updateStatus = (text, tooltip) => {
status.text = text;
status.tooltip = tooltip;
status.show();
};
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
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 () => {
const config = (0, runtime_1.readRuntimeConfig)();
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
const state = await (0, runtime_1.checkRuntime)(config);
statusView.update(state);
switch (state.kind) {
case "connected":
updateStatus("$(check) CodeWhale", state.detail);
break;
case "auth-required":
updateStatus("$(lock) CodeWhale", state.detail);
break;
case "offline":
case "error":
updateStatus("$(warning) CodeWhale", state.detail);
break;
}
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
return state;
}));
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"));
}));
void vscode.commands.executeCommand("codewhale.checkRuntime");
}
function deactivate() {
// No background process is owned by the extension; runtime starts in a user-visible terminal.
}
//# sourceMappingURL=extension.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
+161
View File
@@ -0,0 +1,161 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.readRuntimeConfig = readRuntimeConfig;
exports.runtimeBaseUrl = runtimeBaseUrl;
exports.checkRuntime = checkRuntime;
exports.startRuntimeTerminal = startRuntimeTerminal;
exports.openCodeWhaleTerminal = openCodeWhaleTerminal;
const http = __importStar(require("node:http"));
const vscode = __importStar(require("vscode"));
function readRuntimeConfig() {
const config = vscode.workspace.getConfiguration("codewhale");
const commandPath = config.get("commandPath", "codewhale").trim() || "codewhale";
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();
return {
commandPath,
host,
port,
token: token.length > 0 ? token : undefined,
};
}
function runtimeBaseUrl(config) {
return `http://${config.host}:${config.port}`;
}
async function checkRuntime(config) {
const baseUrl = runtimeBaseUrl(config);
const health = await requestJson(`${baseUrl}/health`, config.token);
if (health.statusCode === 0) {
return { kind: "offline", baseUrl, detail: "Runtime is not reachable." };
}
if (health.statusCode === 401) {
return { kind: "auth-required", baseUrl, detail: "Runtime requires a token." };
}
if (health.statusCode !== 200) {
return {
kind: "error",
baseUrl,
detail: `Health check returned HTTP ${health.statusCode}.`,
};
}
const info = await requestJson(`${baseUrl}/v1/runtime/info`, config.token);
if (info.statusCode === 401) {
return { kind: "auth-required", baseUrl, detail: "Runtime info requires a token." };
}
const version = readVersion(info.body);
return {
kind: "connected",
baseUrl,
detail: version ? `Connected to CodeWhale ${version}.` : "Connected to CodeWhale runtime.",
version,
};
}
function startRuntimeTerminal(config) {
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
const args = [
"serve",
"--http",
"--host",
shellQuote(config.host),
"--port",
String(config.port),
];
if (config.token) {
args.push("--auth-token", shellQuote(config.token));
}
terminal.sendText(`${shellQuote(config.commandPath)} ${args.join(" ")}`);
terminal.show();
return terminal;
}
function openCodeWhaleTerminal(config) {
const terminal = vscode.window.createTerminal("CodeWhale");
terminal.sendText(shellQuote(config.commandPath));
terminal.show();
return terminal;
}
async function requestJson(url, token) {
try {
return await new Promise((resolve, reject) => {
const request = http.get(url, {
timeout: 2500,
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
}, (response) => {
let body = "";
response.setEncoding("utf8");
response.on("data", (chunk) => {
body += chunk;
});
response.on("end", () => {
resolve({
statusCode: response.statusCode ?? 0,
body: parseJson(body),
});
});
});
request.on("timeout", () => {
request.destroy(new Error("Runtime check timed out."));
});
request.on("error", reject);
});
}
catch (error) {
const detail = error instanceof Error ? error.message : String(error);
return { statusCode: 0, body: { error: detail } };
}
}
function parseJson(raw) {
try {
return JSON.parse(raw);
}
catch {
return undefined;
}
}
function readVersion(value) {
if (!value || typeof value !== "object") {
return undefined;
}
const version = value.version;
return typeof version === "string" ? version : undefined;
}
function shellQuote(value) {
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
return value;
}
return `'${value.replace(/'/g, "'\\''")}'`;
}
//# sourceMappingURL=runtime.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
+131
View File
@@ -0,0 +1,131 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.RuntimeStatusView = void 0;
const vscode = __importStar(require("vscode"));
class RuntimeStatusView {
static viewType = "codewhale.runtimeStatus";
view;
state = {
kind: "offline",
baseUrl: "http://127.0.0.1:7878",
detail: "Runtime has not been checked yet.",
};
resolveWebviewView(view) {
this.view = view;
view.webview.options = { enableScripts: true };
view.webview.onDidReceiveMessage((message) => {
if (message.command === "check") {
void vscode.commands.executeCommand("codewhale.checkRuntime");
}
else if (message.command === "start") {
void vscode.commands.executeCommand("codewhale.startRuntime");
}
else if (message.command === "terminal") {
void vscode.commands.executeCommand("codewhale.openTerminal");
}
});
this.render();
}
update(state) {
this.state = state;
this.render();
}
render() {
if (!this.view) {
return;
}
const badge = labelFor(this.state.kind);
const nonce = makeNonce();
this.view.webview.html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
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; }
code { color: var(--vscode-textLink-foreground); }
button { width: 100%; margin: 4px 0; }
</style>
</head>
<body>
<div class="status">${escapeHtml(badge)}</div>
<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="start">Start Local Runtime</button>
<button data-command="terminal">Open CodeWhale Terminal</button>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
for (const button of document.querySelectorAll("button[data-command]")) {
button.addEventListener("click", () => vscode.postMessage({ command: button.dataset.command }));
}
</script>
</body>
</html>`;
}
}
exports.RuntimeStatusView = RuntimeStatusView;
function labelFor(kind) {
switch (kind) {
case "connected":
return "Connected";
case "auth-required":
return "Token Required";
case "error":
return "Runtime Error";
case "offline":
return "Offline";
}
}
function escapeHtml(value) {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function makeNonce() {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let nonce = "";
for (let index = 0; index < 32; index += 1) {
nonce += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}
return nonce;
}
//# sourceMappingURL=status.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
+3851
View File
File diff suppressed because it is too large Load Diff
+109
View File
@@ -0,0 +1,109 @@
{
"name": "codewhale-vscode",
"displayName": "CodeWhale",
"description": "Official CodeWhale VS Code integration scaffold for local runtime attach and terminal launch.",
"version": "0.8.53",
"publisher": "codewhale",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Hmbown/CodeWhale.git",
"directory": "extensions/vscode"
},
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:codewhale.openTerminal",
"onCommand:codewhale.startRuntime",
"onCommand:codewhale.checkRuntime",
"onCommand:codewhale.openRuntimeDocs",
"onView:codewhale.runtimeStatus"
],
"main": "./out/extension.js",
"files": [
"out",
"media",
"README.md",
"LICENSE"
],
"contributes": {
"commands": [
{
"command": "codewhale.openTerminal",
"title": "CodeWhale: Open Terminal"
},
{
"command": "codewhale.startRuntime",
"title": "CodeWhale: Start Local Runtime"
},
{
"command": "codewhale.checkRuntime",
"title": "CodeWhale: Check Runtime"
},
{
"command": "codewhale.openRuntimeDocs",
"title": "CodeWhale: Open Runtime API Docs"
}
],
"configuration": {
"title": "CodeWhale",
"properties": {
"codewhale.commandPath": {
"type": "string",
"default": "codewhale",
"description": "Command or absolute path used to launch CodeWhale."
},
"codewhale.runtimeHost": {
"type": "string",
"default": "127.0.0.1",
"description": "Local host used for CodeWhale runtime attach checks."
},
"codewhale.runtimePort": {
"type": "number",
"default": 7878,
"minimum": 1,
"maximum": 65535,
"description": "Local port used for CodeWhale runtime attach checks."
},
"codewhale.runtimeToken": {
"type": "string",
"default": "",
"description": "Optional bearer token for authenticated runtime endpoints."
}
}
},
"viewsContainers": {
"activitybar": [
{
"id": "codewhale",
"title": "CodeWhale",
"icon": "media/codewhale.svg"
}
]
},
"views": {
"codewhale": [
{
"type": "webview",
"id": "codewhale.runtimeStatus",
"name": "Runtime"
}
]
}
},
"scripts": {
"compile": "tsc -p ./",
"check": "npm run compile",
"package": "vsce package --no-dependencies"
},
"devDependencies": {
"@types/node": "^20.19.27",
"@types/vscode": "^1.90.0",
"@vscode/vsce": "^3.7.0",
"typescript": "^5.9.3"
}
}
+89
View File
@@ -0,0 +1,89 @@
import * as vscode from "vscode";
import {
checkRuntime,
openCodeWhaleTerminal,
readRuntimeConfig,
runtimeBaseUrl,
startRuntimeTerminal,
} from "./runtime";
import { RuntimeStatusView } from "./status";
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();
status.command = "codewhale.checkRuntime";
context.subscriptions.push(output, status);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(RuntimeStatusView.viewType, statusView),
);
const updateStatus = (text: string, tooltip: string): void => {
status.text = text;
status.tooltip = tooltip;
status.show();
};
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
context.subscriptions.push(
vscode.commands.registerCommand("codewhale.openTerminal", () => {
const config = readRuntimeConfig();
openCodeWhaleTerminal(config);
output.appendLine(`Opened CodeWhale terminal using ${config.commandPath}.`);
}),
);
context.subscriptions.push(
vscode.commands.registerCommand("codewhale.startRuntime", () => {
const config = readRuntimeConfig();
startRuntimeTerminal(config);
const baseUrl = 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 = 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);
break;
case "auth-required":
updateStatus("$(lock) CodeWhale", state.detail);
break;
case "offline":
case "error":
updateStatus("$(warning) CodeWhale", state.detail);
break;
}
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
return state;
}),
);
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",
),
);
}),
);
void vscode.commands.executeCommand("codewhale.checkRuntime");
}
export function deactivate(): void {
// No background process is owned by the extension; runtime starts in a user-visible terminal.
}
+153
View File
@@ -0,0 +1,153 @@
import * as http from "node:http";
import * as vscode from "vscode";
export type RuntimeStateKind = "connected" | "offline" | "auth-required" | "error";
export interface RuntimeState {
kind: RuntimeStateKind;
baseUrl: string;
detail: string;
version?: string;
}
export interface RuntimeConfig {
commandPath: string;
host: string;
port: number;
token?: string;
}
export function readRuntimeConfig(): RuntimeConfig {
const config = vscode.workspace.getConfiguration("codewhale");
const commandPath = config.get<string>("commandPath", "codewhale").trim() || "codewhale";
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();
return {
commandPath,
host,
port,
token: token.length > 0 ? token : undefined,
};
}
export function runtimeBaseUrl(config: RuntimeConfig): string {
return `http://${config.host}:${config.port}`;
}
export async function checkRuntime(config: RuntimeConfig): Promise<RuntimeState> {
const baseUrl = runtimeBaseUrl(config);
const health = await requestJson(`${baseUrl}/health`, config.token);
if (health.statusCode === 0) {
return { kind: "offline", baseUrl, detail: "Runtime is not reachable." };
}
if (health.statusCode === 401) {
return { kind: "auth-required", baseUrl, detail: "Runtime requires a token." };
}
if (health.statusCode !== 200) {
return {
kind: "error",
baseUrl,
detail: `Health check returned HTTP ${health.statusCode}.`,
};
}
const info = await requestJson(`${baseUrl}/v1/runtime/info`, config.token);
if (info.statusCode === 401) {
return { kind: "auth-required", baseUrl, detail: "Runtime info requires a token." };
}
const version = readVersion(info.body);
return {
kind: "connected",
baseUrl,
detail: version ? `Connected to CodeWhale ${version}.` : "Connected to CodeWhale runtime.",
version,
};
}
export function startRuntimeTerminal(config: RuntimeConfig): vscode.Terminal {
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
const args = [
"serve",
"--http",
"--host",
shellQuote(config.host),
"--port",
String(config.port),
];
if (config.token) {
args.push("--auth-token", shellQuote(config.token));
}
terminal.sendText(`${shellQuote(config.commandPath)} ${args.join(" ")}`);
terminal.show();
return terminal;
}
export function openCodeWhaleTerminal(config: RuntimeConfig): vscode.Terminal {
const terminal = vscode.window.createTerminal("CodeWhale");
terminal.sendText(shellQuote(config.commandPath));
terminal.show();
return terminal;
}
async function requestJson(
url: string,
token: string | undefined,
): Promise<{ statusCode: number; body: unknown }> {
try {
return await new Promise<{ statusCode: number; body: unknown }>((resolve, reject) => {
const request = http.get(
url,
{
timeout: 2500,
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
},
(response) => {
let body = "";
response.setEncoding("utf8");
response.on("data", (chunk: string) => {
body += chunk;
});
response.on("end", () => {
resolve({
statusCode: response.statusCode ?? 0,
body: parseJson(body),
});
});
},
);
request.on("timeout", () => {
request.destroy(new Error("Runtime check timed out."));
});
request.on("error", reject);
});
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
return { statusCode: 0, body: { error: detail } };
}
}
function parseJson(raw: string): unknown {
try {
return JSON.parse(raw);
} catch {
return undefined;
}
}
function readVersion(value: unknown): string | undefined {
if (!value || typeof value !== "object") {
return undefined;
}
const version = (value as { version?: unknown }).version;
return typeof version === "string" ? version : undefined;
}
function shellQuote(value: string): string {
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
return value;
}
return `'${value.replace(/'/g, "'\\''")}'`;
}
+101
View File
@@ -0,0 +1,101 @@
import * as vscode from "vscode";
import type { RuntimeState } from "./runtime";
export class RuntimeStatusView implements vscode.WebviewViewProvider {
public static readonly viewType = "codewhale.runtimeStatus";
private view?: vscode.WebviewView;
private state: RuntimeState = {
kind: "offline",
baseUrl: "http://127.0.0.1:7878",
detail: "Runtime has not been checked yet.",
};
resolveWebviewView(view: vscode.WebviewView): void {
this.view = view;
view.webview.options = { enableScripts: true };
view.webview.onDidReceiveMessage((message: { command?: string }) => {
if (message.command === "check") {
void vscode.commands.executeCommand("codewhale.checkRuntime");
} else if (message.command === "start") {
void vscode.commands.executeCommand("codewhale.startRuntime");
} else if (message.command === "terminal") {
void vscode.commands.executeCommand("codewhale.openTerminal");
}
});
this.render();
}
update(state: RuntimeState): void {
this.state = state;
this.render();
}
private render(): void {
if (!this.view) {
return;
}
const badge = labelFor(this.state.kind);
const nonce = makeNonce();
this.view.webview.html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
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; }
code { color: var(--vscode-textLink-foreground); }
button { width: 100%; margin: 4px 0; }
</style>
</head>
<body>
<div class="status">${escapeHtml(badge)}</div>
<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="start">Start Local Runtime</button>
<button data-command="terminal">Open CodeWhale Terminal</button>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
for (const button of document.querySelectorAll("button[data-command]")) {
button.addEventListener("click", () => vscode.postMessage({ command: button.dataset.command }));
}
</script>
</body>
</html>`;
}
}
function labelFor(kind: RuntimeState["kind"]): string {
switch (kind) {
case "connected":
return "Connected";
case "auth-required":
return "Token Required";
case "error":
return "Runtime Error";
case "offline":
return "Offline";
}
}
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function makeNonce(): string {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let nonce = "";
for (let index = 0; index < 32; index += 1) {
nonce += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}
return nonce;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2022",
"lib": ["ES2022"],
"outDir": "out",
"rootDir": "src",
"strict": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}