feat: move web-probe to top-level cli (#933)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-26 09:57:14 +08:00
committed by GitHub
parent 0328967e58
commit 241a2d511f
18 changed files with 249 additions and 274 deletions
+5 -5
View File
@@ -39,8 +39,9 @@ import { nodeRuntimeUnsupportedAction } from "./runtime-common";
import { runNodeEndpointBridge } from "./secret-scripts";
import { nodeRuntimeGitMirrorRun, nodeRuntimeGitMirrorStatus, nodeScopedFullOutput, withNodeRuntimeGitMirrorRendered } from "./status";
import { assertNodeId, positiveIntegerOption, requiredOption, stripOption } from "./utils";
import { parseNodeWebProbeOptions, rewriteDelegatedNodeResult, startNodeDelegatedJob } from "./web-probe";
import { assertKnownOptions, runNodeWebProbe } from "./web-probe-observe";
import { legacyHwlabNodeWebProbeUnsupported } from "../web-probe";
import { rewriteDelegatedNodeResult, startNodeDelegatedJob } from "./web-probe";
import { assertKnownOptions } from "./web-probe-observe";
export { hwlabNodeHelp, hwlabNodeWebProbeHelp, hwlabNodeObservabilityHelp } from "../hwlab-node-help";
@@ -419,8 +420,7 @@ export async function runHwlabNodeCommand(_config: Config, args: string[]): Prom
return runHwlabTestAccountsCommand(args.slice(1));
}
if (domain === "web-probe") {
if (args.length === 1 || args.includes("--help") || args.includes("-h") || args[1] === "help") return hwlabNodeWebProbeHelp();
return runNodeWebProbe(parseNodeWebProbeOptions(args.slice(1)));
return legacyHwlabNodeWebProbeUnsupported(args.slice(1));
}
if (domain === "observability") {
if (args.length === 1 || args.includes("--help") || args.includes("-h") || args[1] === "help") return hwlabNodeObservabilityHelp();
@@ -431,7 +431,7 @@ export async function runHwlabNodeCommand(_config: Config, args: string[]): Prom
return runNodeDelegatedDomain(_config, domain, args.slice(1));
}
if (domain !== "secret") {
return { ok: false, command: `hwlab nodes ${domain ?? ""}`.trim(), message: "supported commands: hwlab nodes control-plane, hwlab nodes git-mirror, hwlab nodes observability, hwlab nodes secret, hwlab nodes test-accounts, hwlab nodes web-probe" };
return { ok: false, command: `hwlab nodes ${domain ?? ""}`.trim(), message: "supported commands: hwlab nodes control-plane, hwlab nodes git-mirror, hwlab nodes observability, hwlab nodes secret, hwlab nodes test-accounts. web-probe moved to top-level: bun scripts/cli.ts web-probe --help" };
}
const options = parseSecretOptions(args.slice(1));
return runNodeSecret(options);
+2 -2
View File
@@ -248,7 +248,7 @@ export function nodeObservabilityPerformanceSummary(options: NodeObservabilityOp
degradedReason: material.error ?? "web-login-secret-unavailable",
next: {
secretStatus: `bun scripts/cli.ts hwlab nodes secret status --node ${options.node} --lane ${options.lane} --name bootstrap-admin`,
webProbe: `bun scripts/cli.ts hwlab nodes web-probe run --node ${options.node} --lane ${options.lane}`,
webProbe: `bun scripts/cli.ts web-probe run --node ${options.node} --lane ${options.lane}`,
},
};
}
@@ -287,7 +287,7 @@ export function nodeObservabilityPerformanceSummary(options: NodeObservabilityOp
? report.error
: result.exitCode === 0 ? "web-performance-summary-not-ready" : "web-performance-summary-probe-failed",
next: {
webProbe: `bun scripts/cli.ts hwlab nodes web-probe run --node ${options.node} --lane ${options.lane}`,
webProbe: `bun scripts/cli.ts web-probe run --node ${options.node} --lane ${options.lane}`,
workbenchSummary: `bun scripts/cli.ts hwlab nodes observability workbench-summary --node ${options.node} --lane ${options.lane}`,
apiMetrics: `bun scripts/cli.ts hwlab nodes observability status --node ${options.node} --lane ${options.lane} --full`,
},
+3 -3
View File
@@ -385,7 +385,7 @@ export function nodeRuntimePublicProbeDiagnostic(publicReady: boolean, targetHos
affectsUserEntry: true,
targetHostReady: targetReady,
message: "control-plane public probe failed; treat this as a public endpoint readiness failure before using web-probe closeout evidence.",
nextAction: "run hwlab nodes web-probe run for the same node/lane after checking publicProbe.web and publicProbe.apiHealth",
nextAction: "run web-probe run for the same node/lane after checking publicProbe.web and publicProbe.apiHealth",
};
}
if (targetHost.probeAvailable !== true) {
@@ -542,7 +542,7 @@ export function summarizeNodeRuntimeControlPlaneStatus(status: Record<string, un
plan: `bun scripts/cli.ts hwlab nodes control-plane plan --node ${scoped.node} --lane ${scoped.lane}`,
triggerCurrent: `bun scripts/cli.ts hwlab nodes control-plane trigger-current --node ${scoped.node} --lane ${scoped.lane} --confirm`,
gitMirrorFlush: `bun scripts/cli.ts hwlab nodes git-mirror flush --node ${scoped.node} --lane ${scoped.lane} --confirm --wait`,
webProbe: `bun scripts/cli.ts hwlab nodes web-probe run --node ${scoped.node} --lane ${scoped.lane}`,
webProbe: `bun scripts/cli.ts web-probe run --node ${scoped.node} --lane ${scoped.lane}`,
},
};
}
@@ -572,7 +572,7 @@ export function nodeRuntimeStatusNextAction(status: Record<string, unknown>, sco
return `bun scripts/cli.ts hwlab nodes control-plane cleanup-runs --node ${scoped.node} --lane ${scoped.lane} --min-age-minutes 60 --limit 20 --dry-run`;
}
if (reason === "public-probe-not-ready") {
return `bun scripts/cli.ts hwlab nodes web-probe run --node ${scoped.node} --lane ${scoped.lane}`;
return `bun scripts/cli.ts web-probe run --node ${scoped.node} --lane ${scoped.lane}`;
}
if (reason === "git-mirror-pending-flush") {
return `bun scripts/cli.ts hwlab nodes git-mirror flush --node ${scoped.node} --lane ${scoped.lane} --confirm --wait`;
+22 -22
View File
@@ -84,7 +84,7 @@ export function recoverWebObserveAnalyzeTurnDetails(options: NodeWebProbeObserve
export function withWebObserveAnalyzeRendered(value: Record<string, unknown>): RenderedCliResult {
return {
ok: value.ok !== false,
command: typeof value.command === "string" ? value.command : "hwlab nodes web-probe observe analyze",
command: typeof value.command === "string" ? value.command : "web-probe observe analyze",
contentType: "text/plain",
renderedText: renderWebObserveAnalyzeTable(value),
};
@@ -321,7 +321,7 @@ export function renderWebObserveAnalyzeTable(value: Record<string, unknown>): st
webObserveShort(webObserveArray(item.slowSamples).map((sample) => webObserveText(record(sample)?.otelTraceId)).filter((text) => text !== "-").join(",") || "-", 36),
]);
const lines = [
`hwlab nodes web-probe observe analyze (${webObserveText(value.status)})`,
`web-probe observe analyze (${webObserveText(value.status)})`,
"",
...(value.ok === false ? [
"Blocked detail:",
@@ -916,7 +916,7 @@ export function upsertWebObserveIndexEntry(entry: WebObserveIndexEntry): Record<
ok: true,
id: entry.id,
path,
statusCommand: `bun scripts/cli.ts hwlab nodes web-probe observe status ${entry.id}`,
statusCommand: `bun scripts/cli.ts web-probe observe status ${entry.id}`,
valuesRedacted: true,
};
} catch (error) {
@@ -925,7 +925,7 @@ export function upsertWebObserveIndexEntry(entry: WebObserveIndexEntry): Record<
id: entry.id,
path,
error: error instanceof Error ? error.message : String(error),
fallback: `bun scripts/cli.ts hwlab nodes web-probe observe status --node ${entry.node} --lane ${entry.lane} --state-dir ${entry.stateDir}`,
fallback: `bun scripts/cli.ts web-probe observe status --node ${entry.node} --lane ${entry.lane} --state-dir ${entry.stateDir}`,
valuesRedacted: true,
};
}
@@ -964,16 +964,16 @@ export function webObserveIndexEntryFromOptions(options: NodeWebProbeObserveOpti
export function webObserveCommandLabel(action: NodeWebProbeObserveAction, options: Pick<NodeWebProbeObserveOptions, "id" | "jobId" | "node" | "lane">): string {
const id = webObserveIdFromOptions(options);
return id === null
? `hwlab nodes web-probe observe ${action} --node ${options.node} --lane ${options.lane}`
: `hwlab nodes web-probe observe ${action} ${id}`;
? `web-probe observe ${action} --node ${options.node} --lane ${options.lane}`
: `web-probe observe ${action} ${id}`;
}
export function webObserveNextCommands(id: string): Record<string, string> {
return {
status: `bun scripts/cli.ts hwlab nodes web-probe observe status ${id}`,
command: `bun scripts/cli.ts hwlab nodes web-probe observe command ${id} --type mark --label checkpoint`,
stop: `bun scripts/cli.ts hwlab nodes web-probe observe stop ${id}`,
analyze: `bun scripts/cli.ts hwlab nodes web-probe observe analyze ${id}`,
status: `bun scripts/cli.ts web-probe observe status ${id}`,
command: `bun scripts/cli.ts web-probe observe command ${id} --type mark --label checkpoint`,
stop: `bun scripts/cli.ts web-probe observe stop ${id}`,
analyze: `bun scripts/cli.ts web-probe observe analyze ${id}`,
};
}
@@ -1058,7 +1058,7 @@ export function renderWebProbeRunResult(result: Record<string, unknown>): Record
"",
"NEXT",
` report: ${reportPath}`,
` rerun: ${result.command ?? `hwlab nodes web-probe run --node ${result.node ?? "-"} --lane ${result.lane ?? "-"}`}`,
` rerun: ${result.command ?? `web-probe run --node ${result.node ?? "-"} --lane ${result.lane ?? "-"}`}`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -1116,10 +1116,10 @@ export function renderWebObserveStartResult(result: Record<string, unknown>): Re
...blockedRows,
"",
"NEXT",
` status: bun scripts/cli.ts hwlab nodes web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts hwlab nodes web-probe observe analyze ${id}`,
` command: bun scripts/cli.ts hwlab nodes web-probe observe command ${id} --type mark --label checkpoint`,
` stop: bun scripts/cli.ts hwlab nodes web-probe observe stop ${id}`,
` status: bun scripts/cli.ts web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts web-probe observe analyze ${id}`,
` command: bun scripts/cli.ts web-probe observe command ${id} --type mark --label checkpoint`,
` stop: bun scripts/cli.ts web-probe observe stop ${id}`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -1174,9 +1174,9 @@ export function renderWebObserveStatusResult(result: Record<string, unknown>): R
...resultSection,
"",
"NEXT",
` status: bun scripts/cli.ts hwlab nodes web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts hwlab nodes web-probe observe analyze ${id}`,
` command: bun scripts/cli.ts hwlab nodes web-probe observe command ${id} --type mark --label checkpoint`,
` status: bun scripts/cli.ts web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts web-probe observe analyze ${id}`,
` command: bun scripts/cli.ts web-probe observe command ${id} --type mark --label checkpoint`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -1203,8 +1203,8 @@ export function renderWebObserveCommandResult(result: Record<string, unknown>):
),
"",
"NEXT",
` status: bun scripts/cli.ts hwlab nodes web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts hwlab nodes web-probe observe analyze ${id}`,
` status: bun scripts/cli.ts web-probe observe status ${id}`,
` analyze: bun scripts/cli.ts web-probe observe analyze ${id}`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -1396,7 +1396,7 @@ export function renderWebObserveAnalyzeResult(result: Record<string, unknown>):
` md: ${analysis.reportMdPath ?? "-"}`,
"",
"NEXT",
` status: bun scripts/cli.ts hwlab nodes web-probe observe status ${id}`,
` status: bun scripts/cli.ts web-probe observe status ${id}`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -1464,7 +1464,7 @@ export function renderWebProbeScriptResult(result: Record<string, unknown>): Rec
"",
"NEXT",
` report: ${reportLoad.path ?? probe.reportPath ?? "-"}`,
` rerun: ${result.command ?? `hwlab nodes web-probe script --node ${result.node ?? "-"} --lane ${result.lane ?? "-"}`}`,
` rerun: ${result.command ?? `web-probe script --node ${result.node ?? "-"} --lane ${result.lane ?? "-"}`}`,
].join("\n");
return withWebObserveRendered(result, renderedText);
}
@@ -79,7 +79,7 @@ export function nodeWebObserveStatusNodeScript(tailLines: number, node: string,
const commandsFailed=commandSummary('failed');
const diagnostics={heartbeatAgeSeconds,heartbeatStale,heartbeatStaleAfterSeconds:Math.round(staleAfterMs/1000),heartbeatUpdatedAt:updatedRaw,terminal,processAlive:alive,effectiveLiveness:heartbeatStale?'stale':alive?'alive':'not-running',commandBacklog:commandsPending.count+commandsProcessing.count,oldestPendingAgeSeconds:commandsPending.oldestAgeSeconds,oldestProcessingAgeSeconds:commandsProcessing.oldestAgeSeconds,valuesRedacted:true};
const commands={pendingCount:commandsPending.count,processingCount:commandsProcessing.count,abandonedCount:commandsAbandoned.count,failedCount:commandsFailed.count,pending:commandsPending.items,processing:commandsProcessing.items,abandoned:commandsAbandoned.items.slice(0,6),failed:commandsFailed.items.slice(0,6),valuesRedacted:true};
console.log(JSON.stringify({ok:true,command:'web-probe-observe status',stateDir:dir,pid:pidText?Number(pidText):null,processAlive:alive,manifest:compactManifest(manifest),heartbeat:compactHeartbeat(heartbeat,diagnostics),diagnostics,commands,tails:{control:tailJsonl('control.jsonl').map(compactControl),samples:tailJsonl('samples.jsonl').map(compactSample),network:tailJsonl('network.jsonl').map(compactNetwork)},next:{command:'bun scripts/cli.ts hwlab nodes web-probe observe command --node '+node+' --lane '+lane+' --state-dir '+dir+' --type mark --label checkpoint',stop:'bun scripts/cli.ts hwlab nodes web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir,forceStop:'bun scripts/cli.ts hwlab nodes web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir+' --force',analyze:'bun scripts/cli.ts hwlab nodes web-probe observe analyze --node '+node+' --lane '+lane+' --state-dir '+dir},valuesRedacted:true}));
console.log(JSON.stringify({ok:true,command:'web-probe-observe status',stateDir:dir,pid:pidText?Number(pidText):null,processAlive:alive,manifest:compactManifest(manifest),heartbeat:compactHeartbeat(heartbeat,diagnostics),diagnostics,commands,tails:{control:tailJsonl('control.jsonl').map(compactControl),samples:tailJsonl('samples.jsonl').map(compactSample),network:tailJsonl('network.jsonl').map(compactNetwork)},next:{command:'bun scripts/cli.ts web-probe observe command --node '+node+' --lane '+lane+' --state-dir '+dir+' --type mark --label checkpoint',stop:'bun scripts/cli.ts web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir,forceStop:'bun scripts/cli.ts web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir+' --force',analyze:'bun scripts/cli.ts web-probe observe analyze --node '+node+' --lane '+lane+' --state-dir '+dir},valuesRedacted:true}));
`)} "$state_dir" ${shellQuote(node)} ${shellQuote(lane)}`;
}
@@ -539,7 +539,7 @@ export function runNodeWebProbeScript(
return renderWebProbeScriptResult({
ok: passed,
status: passed ? "pass" : "blocked",
command: `hwlab nodes web-probe script --node ${options.node} --lane ${options.lane}`,
command: `web-probe script --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
+9 -9
View File
@@ -434,8 +434,8 @@ export function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, un
ok: false,
status: "blocked",
command: options.action === "observe"
? `hwlab nodes web-probe observe ${options.observeAction} --node ${options.node} --lane ${options.lane}`
: `hwlab nodes web-probe ${options.action} --node ${options.node} --lane ${options.lane}`,
? `web-probe observe ${options.observeAction} --node ${options.node} --lane ${options.lane}`
: `web-probe ${options.action} --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
@@ -466,7 +466,7 @@ export function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, un
return renderWebProbeRunResult({
ok: passed,
status: passed ? "pass" : "blocked",
command: `hwlab nodes web-probe run --node ${options.node} --lane ${options.lane}`,
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
@@ -527,7 +527,7 @@ export function runNodeWebProbeAsync(
return renderWebProbeRunResult({
ok: false,
status: "blocked",
command: `hwlab nodes web-probe run --node ${options.node} --lane ${options.lane}`,
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
@@ -565,7 +565,7 @@ export function runNodeWebProbeAsync(
return renderWebProbeRunResult({
ok: passed,
status: passed ? "pass" : "blocked",
command: `hwlab nodes web-probe run --node ${options.node} --lane ${options.lane}`,
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
@@ -925,7 +925,7 @@ export function runNodeWebProbeObserveStart(
"pid=$!",
"printf '%s\\n' \"$pid\" >\"$state_dir/pid\"",
"sleep 1",
`node -e ${shellQuote("const fs=require('fs'); const dir=process.argv[1]; const read=(n)=>{try{return JSON.parse(fs.readFileSync(dir+'/'+n,'utf8'))}catch{return null}}; const pid=fs.existsSync(dir+'/pid')?fs.readFileSync(dir+'/pid','utf8').trim():null; console.log(JSON.stringify({ok:true,command:'web-probe-observe start',jobId:process.argv[2],stateDir:dir,pid:Number(pid)||null,manifestPath:dir+'/manifest.json',heartbeat:read('heartbeat.json'),manifest:read('manifest.json'),statusCommand:'bun scripts/cli.ts hwlab nodes web-probe observe status --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,stopCommand:'bun scripts/cli.ts hwlab nodes web-probe observe stop --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,valuesRedacted:true},null,2))")} "$state_dir" ${shellQuote(jobId)} ${shellQuote(options.node)} ${shellQuote(options.lane)}`,
`node -e ${shellQuote("const fs=require('fs'); const dir=process.argv[1]; const read=(n)=>{try{return JSON.parse(fs.readFileSync(dir+'/'+n,'utf8'))}catch{return null}}; const pid=fs.existsSync(dir+'/pid')?fs.readFileSync(dir+'/pid','utf8').trim():null; console.log(JSON.stringify({ok:true,command:'web-probe-observe start',jobId:process.argv[2],stateDir:dir,pid:Number(pid)||null,manifestPath:dir+'/manifest.json',heartbeat:read('heartbeat.json'),manifest:read('manifest.json'),statusCommand:'bun scripts/cli.ts web-probe observe status --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,stopCommand:'bun scripts/cli.ts web-probe observe stop --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,valuesRedacted:true},null,2))")} "$state_dir" ${shellQuote(jobId)} ${shellQuote(options.node)} ${shellQuote(options.lane)}`,
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const started = parseJsonObject(result.stdout);
@@ -948,7 +948,7 @@ export function runNodeWebProbeObserveStart(
return renderWebObserveStartResult({
ok: result.exitCode === 0 && started?.ok === true,
status: result.exitCode === 0 && started?.ok === true ? "started" : "blocked",
command: `hwlab nodes web-probe observe start --node ${options.node} --lane ${options.lane}`,
command: `web-probe observe start --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
@@ -1653,8 +1653,8 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
: "observe analyze failed before producing a compact report; inspect analyzer stdout/stderr artifacts under the observer analysis directory",
}],
next: {
collectAnalyzerStdout: options.stateDir ? `bun scripts/cli.ts hwlab nodes web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stdout.json` : null,
collectAnalyzerStderr: options.stateDir ? `bun scripts/cli.ts hwlab nodes web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stderr.log` : null,
collectAnalyzerStdout: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stdout.json` : null,
collectAnalyzerStderr: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stderr.log` : null,
valuesRedacted: true,
},
valuesRedacted: true,