From 7f6c329206b2e1af27c213adbe91229b09d57641 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2026 02:29:19 +0200 Subject: [PATCH] feat: aggregate Workbench native status --- tools/src/workbench-cli.ts | 4 +++- tools/src/workbench-native-service.test.ts | 28 ++++++++++++++++++++++ tools/src/workbench-native-service.ts | 24 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tools/src/workbench-native-service.test.ts diff --git a/tools/src/workbench-cli.ts b/tools/src/workbench-cli.ts index 49aa34c0..41ff8107 100644 --- a/tools/src/workbench-cli.ts +++ b/tools/src/workbench-cli.ts @@ -1,10 +1,11 @@ import type { WorkbenchCommand } from "../../internal/workbench/contracts.ts"; import { workbenchRuntime } from "../../internal/workbench/runtime.ts"; -import { workbenchNativeServiceCommand } from "./workbench-native-service.ts"; +import { workbenchNativeServiceCommand, workbenchNativeServicesStatus } from "./workbench-native-service.ts"; export async function runWorkbenchCli(argv: string[], env: Record = process.env) { const parsed = parse(argv); if (parsed.help || parsed.positionals.length === 0) return help(); + if (parsed.positionals[0] === "service" && parsed.positionals[1] === "status") return workbenchNativeServicesStatus({ cwd: process.cwd(), env }); if (parsed.positionals[0] === "service") return workbenchNativeServiceCommand({ service: parsed.positionals[1], action: parsed.positionals[2] ?? "status", cwd: process.cwd(), env }); if (parsed.positionals[0] === "events" && parsed.positionals[1] === "inspect") return inspectEvents(parsed, env); const command = commandFrom(parsed, env); @@ -29,6 +30,7 @@ function help() { "hwlab-cli workbench turn submit --actor-id ID --session-id ID --message TEXT [--trace-id ID] [--over-api]", "hwlab-cli workbench events inspect --session-id ID [--trace-id ID] --over-api [--timeout-ms MS] [--wait-for user,backend,assistant,terminal,final] [--min-events N] [--timing-detail summary|full]", "hwlab-cli workbench turn cancel --actor-id ID --trace-id ID [--over-api]", + "hwlab-cli workbench service status", "hwlab-cli workbench service api|worker|web start|stop|restart|status|logs" ], transportContract: "--over-api only changes transport; --overapi is unsupported", diff --git a/tools/src/workbench-native-service.test.ts b/tools/src/workbench-native-service.test.ts new file mode 100644 index 00000000..389b6db2 --- /dev/null +++ b/tools/src/workbench-native-service.test.ts @@ -0,0 +1,28 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { expect, test } from "bun:test"; + +import { workbenchNativeServicesStatus } from "./workbench-native-service.ts"; + +test("aggregates all Workbench native service states in one query", async () => { + const cwd = mkdtempSync(join(tmpdir(), "hwlab-workbench-status-")); + try { + const result = await workbenchNativeServicesStatus({ + cwd, + env: { WORKBENCH_NATIVE_SERVICE_STATE_DIR: ".state/services" } + }); + + expect(result).toMatchObject({ + ok: false, + operation: "workbench.service.status", + status: "stopped", + summary: { total: 3, running: 0, unavailable: 3 }, + valuesPrinted: false + }); + expect(Object.keys(result.services).sort()).toEqual(["api", "web", "worker"]); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } +}); diff --git a/tools/src/workbench-native-service.ts b/tools/src/workbench-native-service.ts index 462c70da..244020fc 100644 --- a/tools/src/workbench-native-service.ts +++ b/tools/src/workbench-native-service.ts @@ -4,6 +4,30 @@ import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; type ServiceName = "api" | "worker" | "web"; +const SERVICE_NAMES: ServiceName[] = ["api", "worker", "web"]; + +export async function workbenchNativeServicesStatus(input: { cwd: string; env: Record }) { + const results = await Promise.all(SERVICE_NAMES.map((service) => workbenchNativeServiceCommand({ + service, + action: "status", + cwd: input.cwd, + env: input.env + }))); + const services = Object.fromEntries(results.map((result) => [result.service, result])); + const runningCount = results.filter((result) => result.ok).length; + return { + ok: runningCount === SERVICE_NAMES.length, + operation: "workbench.service.status", + status: runningCount === SERVICE_NAMES.length ? "running" : runningCount === 0 ? "stopped" : "degraded", + summary: { + total: SERVICE_NAMES.length, + running: runningCount, + unavailable: SERVICE_NAMES.length - runningCount + }, + services, + valuesPrinted: false + }; +} export async function workbenchNativeServiceCommand(input: { service: string; action: string; cwd: string; env: Record }) { const service = validService(input.service);