feat: route code agent m3 io through hwlab api skill cli
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { DEV_ENDPOINT, loadMvpGateSummary } from "../../../internal/mvp-gate/summary.mjs";
|
||||
import { runM3IoSkillCommand } from "../../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs";
|
||||
|
||||
function formatJson(value) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
@@ -169,6 +170,9 @@ function writeHelp(stdout) {
|
||||
commands: [
|
||||
"health",
|
||||
"project list",
|
||||
"m3 status --api-base-url URL",
|
||||
"m3 io --action do.write --value true --approved --api-base-url URL",
|
||||
"m3 io --action di.read --api-base-url URL",
|
||||
"test e2e --env dev --mvp",
|
||||
"test e2e --env dev --mvp --dry-run",
|
||||
"test e2e --env dev --mvp --live --confirm-dev --confirmed-non-production"
|
||||
@@ -185,8 +189,25 @@ export async function runCli(argv, io) {
|
||||
const stdout = io.stdout ?? process.stdout;
|
||||
const stderr = io.stderr ?? process.stderr;
|
||||
const repoRoot = io.cwd ?? process.cwd();
|
||||
let summary;
|
||||
const env = io.env ?? process.env;
|
||||
|
||||
const [command = "help", subcommand, ...rest] = argv;
|
||||
const { flags, options } = parseArgs(rest);
|
||||
|
||||
if (command === "m3" && ["io", "status"].includes(subcommand)) {
|
||||
const result = await runM3IoSkillCommand(["m3", subcommand, ...rest], {
|
||||
env,
|
||||
now: io.now,
|
||||
requestJson: io.requestJson
|
||||
});
|
||||
writeJson(result.ok ? stdout : stderr, {
|
||||
...result,
|
||||
command: `hwlab-cli ${["m3", subcommand, ...redactM3CliArgs(rest)].join(" ")}`
|
||||
});
|
||||
return result.ok ? 0 : 2;
|
||||
}
|
||||
|
||||
let summary;
|
||||
try {
|
||||
summary = loadMvpGateSummary(repoRoot);
|
||||
} catch (error) {
|
||||
@@ -194,9 +215,6 @@ export async function runCli(argv, io) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const [command = "help", subcommand, ...rest] = argv;
|
||||
const { flags, options } = parseArgs(rest);
|
||||
|
||||
if (command === "health") {
|
||||
writeJson(stdout, mvpGateOverview(summary));
|
||||
return 0;
|
||||
@@ -264,6 +282,18 @@ export async function runCli(argv, io) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function redactM3CliArgs(args = []) {
|
||||
const redacted = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
redacted.push(args[index]);
|
||||
if (args[index] === "--api-base-url" && index + 1 < args.length) {
|
||||
redacted.push("<hwlab-api-base-url>");
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return redacted;
|
||||
}
|
||||
|
||||
export function formatRuntime(runtime) {
|
||||
return formatJson(runtime);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
HWLAB_M3_IO_API_ROUTE,
|
||||
HWLAB_M3_STATUS_API_ROUTE
|
||||
} from "../../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs";
|
||||
import { runCli } from "./cli.mjs";
|
||||
|
||||
async function captureCli(args, options = {}) {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
const exitCode = await runCli(args, {
|
||||
cwd: options.cwd ?? process.cwd(),
|
||||
env: options.env ?? {
|
||||
PATH: process.env.PATH
|
||||
},
|
||||
requestJson: options.requestJson,
|
||||
stdout: {
|
||||
write(chunk) {
|
||||
stdout += chunk;
|
||||
}
|
||||
},
|
||||
stderr: {
|
||||
write(chunk) {
|
||||
stderr += chunk;
|
||||
}
|
||||
}
|
||||
});
|
||||
return { exitCode, stdout, stderr };
|
||||
}
|
||||
|
||||
test("repo hwlab-cli m3 status uses Skill CLI and calls only HWLAB API /v1/m3/status", async () => {
|
||||
const calls = [];
|
||||
const result = await captureCli(
|
||||
[
|
||||
"m3",
|
||||
"status",
|
||||
"--api-base-url",
|
||||
"http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
|
||||
"--trace-id",
|
||||
"trc_hwlab_cli_status"
|
||||
],
|
||||
{
|
||||
requestJson: async (url, request) => {
|
||||
calls.push({ url, request });
|
||||
assert.equal(url, `http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667${HWLAB_M3_STATUS_API_ROUTE}`);
|
||||
assert.equal(request.method, "GET");
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
status: "live",
|
||||
sourceKind: "DEV-LIVE",
|
||||
traceId: "trc_hwlab_cli_status",
|
||||
boxes: [{
|
||||
id: "boxsimu_2",
|
||||
resourceId: "res_boxsimu_2",
|
||||
online: true,
|
||||
ports: {
|
||||
DI1: {
|
||||
value: false
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
const body = JSON.parse(result.stdout);
|
||||
assert.equal(body.route, HWLAB_M3_STATUS_API_ROUTE);
|
||||
assert.equal(body.method, "GET");
|
||||
assert.equal(body.readback.value, false);
|
||||
assert.equal(body.command.includes("hwlab-cloud-api.hwlab-dev"), false);
|
||||
assert.equal(calls.length, 1);
|
||||
});
|
||||
|
||||
test("repo hwlab-cli m3 io blocks direct hardware targets before request", async () => {
|
||||
const result = await captureCli(
|
||||
[
|
||||
"m3",
|
||||
"io",
|
||||
"--action",
|
||||
"di.read",
|
||||
"--api-base-url",
|
||||
"http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301",
|
||||
"--trace-id",
|
||||
"trc_hwlab_cli_direct_blocked"
|
||||
],
|
||||
{
|
||||
requestJson: async () => {
|
||||
throw new Error("direct hardware target must not be called");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(result.exitCode, 2);
|
||||
const body = JSON.parse(result.stderr);
|
||||
assert.equal(body.route, HWLAB_M3_IO_API_ROUTE);
|
||||
assert.equal(body.blocker.code, "direct_hardware_target_blocked");
|
||||
assert.equal(body.hwlabApi.redactedUrl, null);
|
||||
assert.equal(JSON.stringify(body).includes("patch-panel.hwlab-dev"), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user