Merge pull request #2569 from pikasTech/fix/workbench-native-state-file

fix: 由 YAML 绑定 Workbench L0 状态文件
This commit is contained in:
Lyon
2026-07-20 00:34:50 +08:00
committed by GitHub
4 changed files with 47 additions and 2 deletions
+1
View File
@@ -140,6 +140,7 @@ lanes:
upstream: http://127.0.0.1:5174
healthPath: /health/live
stateDir: .state/workbench-native/services
stateFile: .state/workbench-native/state.json
mode: agentrun-native
temporal:
address: 10.43.145.243:7233
@@ -9,6 +9,7 @@ test("L1 Workbench exposure uses YAML HTTPS origin and separate bind/probe addre
expect(native).toBeDefined();
const env = hwlabNativeDevelopmentEnvironment(native!, spec, "0123456789abcdef0123456789abcdef01234567");
expect(env.WORKBENCH_NATIVE_SERVICE_STATE_DIR).toBe(native!.stateDir);
expect(env.WORKBENCH_NATIVE_STATE_FILE).toBe(`${spec.workspace}/${native!.stateFile}`);
expect(env.WORKBENCH_MODE).toBe("agentrun-native");
expect(env.WORKBENCH_CLOUD_API_URL).toBeUndefined();
expect(env.WORKBENCH_CLOUD_API_AUTHORIZATION).toBeUndefined();
+43 -2
View File
@@ -1,3 +1,5 @@
import { resolve } from "node:path";
import { runCommand } from "./command";
import { readEnvSourceFile, requiredEnvValue } from "./secrets";
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeNativeDevelopmentSpec } from "./hwlab-node-lanes";
@@ -12,6 +14,7 @@ export function hwlabNativeDevelopmentHelp(): Record<string, unknown> {
command: "hwlab nodes native-development workbench",
usage: [
"bun scripts/cli.ts hwlab nodes native-development workbench api|worker|web start|stop|restart|status|logs --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development workbench cli <health|session|turn|events ...> --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development caserun api|web start|stop|restart|status|logs --node <node> --lane <lane>",
],
contract: "L1 API/Web 用户入口只输出 owning YAML 解析出的固定 HTTPS 域名;IP、port 和 bind/probe 地址不作为用户入口。",
@@ -23,8 +26,6 @@ export function runHwlabNativeDevelopmentCommand(args: string[]): Record<string,
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return hwlabNativeDevelopmentHelp();
const [applicationRaw, serviceRaw, actionRaw] = args;
const application = parseApplication(applicationRaw);
const service = parseService(serviceRaw, application);
const action = parseAction(actionRaw);
const node = requiredOption(args, "--node");
const lane = requiredOption(args, "--lane");
if (!isHwlabRuntimeLane(lane)) throw new Error(`--lane must be declared in ${hwlabRuntimeLaneConfigPath()}`);
@@ -32,6 +33,27 @@ export function runHwlabNativeDevelopmentCommand(args: string[]): Record<string,
const native = spec.nativeDevelopment?.[application];
if (native === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.${application} is required`);
const env = application === "workbench" ? workbenchEnvironment(spec, native as HwlabRuntimeNativeDevelopmentSpec["workbench"]) : hwlabCaseRunNativeDevelopmentEnvironment(native as NonNullable<HwlabRuntimeNativeDevelopmentSpec["caserun"]>);
if (application === "workbench" && serviceRaw === "cli") {
const commandArgs = withoutOptions(args.slice(2), ["--node", "--lane"]);
if (commandArgs.length === 0) throw new Error("native-development workbench cli requires a Workbench command");
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "workbench", ...commandArgs];
const result = runCommand(command, spec.workspace, {
timeoutMs: cliTimeoutMs(commandArgs),
env: { ...process.env, ...env, WORKBENCH_API_URL: env.WORKBENCH_NATIVE_API_URL },
});
return {
ok: result.exitCode === 0,
command: "hwlab nodes native-development workbench cli",
node,
lane,
transport: commandArgs.includes("--over-api") ? "native-api" : "local-dispatcher",
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.workbench`,
workbenchResult: parsePayload(result.stdout),
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
const service = parseService(serviceRaw, application);
const action = parseAction(actionRaw);
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", application, "service", service, action];
const result = runCommand(command, spec.workspace, { timeoutMs: 15_000, env: { ...process.env, ...env } });
const payload = parsePayload(result.stdout);
@@ -119,6 +141,7 @@ export function hwlabNativeDevelopmentEnvironment(native: HwlabRuntimeNativeDeve
HWLAB_KAFKA_CLIENT_ID: native.kafka.clientId,
HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID: native.kafka.hwlabEventConsumerGroupId,
WORKBENCH_NATIVE_SERVICE_STATE_DIR: native.stateDir,
WORKBENCH_NATIVE_STATE_FILE: resolve(spec.workspace, native.stateFile),
WORKBENCH_API_BIND_HOST: native.api.bindHost,
WORKBENCH_API_PROBE_HOST: native.api.probeHost,
WORKBENCH_PUBLIC_BASE_URL: native.publicExposure.publicBaseUrl,
@@ -170,6 +193,24 @@ function requiredOption(args: string[], option: string): string {
return value;
}
function withoutOptions(args: string[], options: string[]): string[] {
const result: string[] = [];
for (let index = 0; index < args.length; index += 1) {
if (options.includes(args[index])) {
index += 1;
continue;
}
result.push(args[index]);
}
return result;
}
function cliTimeoutMs(args: string[]): number {
const index = args.indexOf("--timeout-ms");
const requested = index >= 0 ? Number.parseInt(args[index + 1] ?? "", 10) : 0;
return Number.isSafeInteger(requested) && requested > 0 ? requested + 5_000 : 30_000;
}
function hwlabCaseRunNativeDevelopmentEnvironment(native: NonNullable<HwlabRuntimeNativeDevelopmentSpec["caserun"]>): NodeJS.ProcessEnv {
return {
CASERUN_NATIVE_SERVICE_STATE_DIR: native.stateDir,
+2
View File
@@ -676,6 +676,7 @@ export interface HwlabRuntimeNativeDevelopmentSpec {
readonly workbench: {
readonly publicExposure: HwlabRuntimeNativePublicExposureSpec;
readonly stateDir: string;
readonly stateFile: string;
readonly mode: "agentrun-native";
readonly temporal: {
readonly address: string;
@@ -1499,6 +1500,7 @@ function nativeDevelopmentConfig(value: unknown, path: string): HwlabRuntimeNati
workbench: {
publicExposure,
stateDir: relativeWorkspacePathField(stringField(workbench, "stateDir", `${path}.workbench`), `${path}.workbench.stateDir`),
stateFile: relativeWorkspacePathField(stringField(workbench, "stateFile", `${path}.workbench`), `${path}.workbench.stateFile`),
mode,
temporal: {
address: stringField(temporal, "address", `${path}.workbench.temporal`),