fix: improve agentrun status visibility

This commit is contained in:
Codex
2026-06-02 06:40:27 +00:00
parent e3f1e726b9
commit 2a4f6d7791
3 changed files with 187 additions and 34 deletions
+183 -30
View File
@@ -26,11 +26,13 @@ export function agentRunHelp(): unknown {
output: "json",
usage: [
"bun scripts/cli.ts agentrun v01 control-plane status",
"bun scripts/cli.ts agentrun v01 control-plane status --full",
"bun scripts/cli.ts agentrun v01 control-plane trigger-current --dry-run",
"bun scripts/cli.ts agentrun v01 control-plane trigger-current --confirm",
"bun scripts/cli.ts agentrun v01 control-plane refresh --dry-run",
"bun scripts/cli.ts agentrun v01 control-plane refresh --confirm",
"bun scripts/cli.ts agentrun v01 git-mirror status",
"bun scripts/cli.ts agentrun v01 git-mirror status --full",
"bun scripts/cli.ts agentrun v01 git-mirror sync --confirm",
"bun scripts/cli.ts agentrun v01 git-mirror flush --confirm",
],
@@ -42,12 +44,12 @@ export async function runAgentRunCommand(config: UniDeskConfig, args: string[]):
const [lane, group, action] = args;
if (lane !== "v01") return unsupported(args);
if (group === "control-plane") {
if (action === "status") return await status(config);
if (action === "status") return await status(config, parseDisclosureOptions(args.slice(3)));
if (action === "trigger-current") return await triggerCurrent(config, parseTriggerOptions(args.slice(3)));
if (action === "refresh") return await refresh(config, parseConfirmOptions(args.slice(3)));
}
if (group === "git-mirror") {
if (action === "status") return await gitMirrorStatus(config);
if (action === "status") return await gitMirrorStatus(config, parseDisclosureOptions(args.slice(3)));
if (action === "sync" || action === "flush") {
const options = parseGitMirrorOptions(args.slice(3));
if (options.confirm && !options.wait) return startAsyncAgentRunJob(`agentrun_v01_git_mirror_${action}`, ["bun", "scripts/cli.ts", "agentrun", "v01", "git-mirror", action, "--confirm", "--wait", "--timeout-seconds", String(options.timeoutSeconds)], `Run AgentRun v0.1 git mirror ${action} on G14`);
@@ -72,6 +74,24 @@ interface GitMirrorOptions extends ConfirmOptions {
wait: boolean;
}
interface DisclosureOptions {
full: boolean;
raw: boolean;
}
interface TimedValue<T> {
value: T;
elapsedMs: number;
}
function parseDisclosureOptions(args: string[]): DisclosureOptions {
for (const arg of args) {
if (arg !== "--full" && arg !== "--raw") throw new Error(`unsupported status option: ${arg}`);
}
const raw = args.includes("--raw");
return { full: raw || args.includes("--full"), raw };
}
function parseTriggerOptions(args: string[]): TriggerOptions {
return parseConfirmOptions(args);
}
@@ -92,8 +112,8 @@ function parseGitMirrorOptions(args: string[]): GitMirrorOptions {
return { ...base, timeoutSeconds, wait: args.includes("--wait") };
}
async function status(config: UniDeskConfig): Promise<Record<string, unknown>> {
const source = await capture(config, g14SourceRoute, ["script", "--", [
async function status(config: UniDeskConfig, options: DisclosureOptions): Promise<Record<string, unknown>> {
const sourceProbe = await timedStatusStage("source", () => capture(config, g14SourceRoute, ["script", "--", [
"cd /root/agentrun-v01",
"git fetch origin v0.1 >/dev/null 2>&1 || true",
"printf 'sourceCommit='",
@@ -103,29 +123,79 @@ async function status(config: UniDeskConfig): Promise<Record<string, unknown>> {
"printf 'gitopsLatest='",
`git ls-remote origin ${gitopsBranch} 2>/dev/null | awk '{print $1}' || true`,
"git status --short --branch",
].join("\n")]);
].join("\n")]));
const source = sourceProbe.value;
const localSourceCommit = matchLine(source.stdout, "sourceCommit=");
const originSourceCommit = matchLine(source.stdout, "originV01=");
const sourceCommit = isGitSha(originSourceCommit ?? "") ? originSourceCommit : localSourceCommit;
const gitopsLatest = matchLine(source.stdout, "gitopsLatest=");
const pipelineRun = sourceCommit ? pipelineRunName(sourceCommit) : null;
const k3s = await capture(config, g14K3sRoute, ["script", "--", statusScript(pipelineRun)]);
const [runtimeProbe, mirrorProbe] = await Promise.all([
timedStatusStage("runtime", () => capture(config, g14K3sRoute, ["script", "--", statusScript(pipelineRun)])),
timedStatusStage("git-mirror", () => readGitMirrorStatus(config)),
]);
const k3s = runtimeProbe.value;
const mirror = mirrorProbe.value;
const argo = parseArgoStatus(k3s.stdout);
const mirror = await gitMirrorStatus(config);
const ciSummary = labeledJson(k3s.stdout, "ciSummary");
const pipelineRunCondition = labeledJson(k3s.stdout, "pipelineRunCondition");
const managerImage = parseManagerImage(k3s.stdout);
const mirrorSummary = mirror.summary;
const runtimeAlignment = {
localHeadMatchesOrigin: Boolean(localSourceCommit && originSourceCommit && localSourceCommit === originSourceCommit),
argoRevision: argo.revision,
argoSyncStatus: argo.syncStatus,
argoHealthStatus: argo.healthStatus,
syncedToGitopsLatest: Boolean(gitopsLatest && argo.revision === gitopsLatest),
managerSourceCommit: managerImage.sourceCommit,
managerSourceMatchesExpected: Boolean(sourceCommit && managerImage.sourceCommit === sourceCommit),
};
const summary = {
sourceCommit,
expectedPipelineRun: pipelineRun,
pipelineRun: {
status: pipelineRunCondition.status ?? null,
reason: pipelineRunCondition.reason ?? null,
completionTime: pipelineRunCondition.completionTime ?? null,
},
argo,
managerImage,
gitMirror: {
localV01: mirrorSummary.localV01 ?? null,
githubV01: mirrorSummary.githubV01 ?? null,
localGitops: mirrorSummary.localGitops ?? null,
githubGitops: mirrorSummary.githubGitops ?? null,
pendingFlush: mirrorSummary.pendingFlush ?? null,
sourceInSync: mirrorSummary.sourceInSync ?? null,
gitopsInSync: mirrorSummary.gitopsInSync ?? null,
githubInSync: mirrorSummary.githubInSync ?? null,
},
aligned: pipelineRunCondition.status === "True" &&
runtimeAlignment.localHeadMatchesOrigin === true &&
runtimeAlignment.syncedToGitopsLatest === true &&
runtimeAlignment.managerSourceMatchesExpected === true &&
mirrorSummary.githubInSync === true &&
mirrorSummary.pendingFlush === false,
};
return {
ok: source.exitCode === 0 && k3s.exitCode === 0 && mirror.ok === true,
command: "agentrun v01 control-plane status",
lane: "v0.1",
summary,
sourceCommit,
sourceCommitSource: sourceCommit === originSourceCommit ? "origin/v0.1" : "local-head",
localSourceCommit,
originSourceCommit,
gitopsLatest,
expectedPipelineRun: pipelineRun,
source: compactCapture(source),
runtime: compactCapture(k3s),
timings: {
sourceMs: sourceProbe.elapsedMs,
runtimeMs: runtimeProbe.elapsedMs,
gitMirrorMs: mirrorProbe.elapsedMs,
totalMs: sourceProbe.elapsedMs + Math.max(runtimeProbe.elapsedMs, mirrorProbe.elapsedMs),
},
source: compactCapture(source, { full: options.full || options.raw, stdoutTailChars: 3000, stderrTailChars: 2000 }),
runtime: compactCapture(k3s, { full: options.full || options.raw, stdoutTailChars: 8000, stderrTailChars: 4000 }),
pipelineRunCondition,
ciSummary,
gitMirror: {
@@ -133,13 +203,18 @@ async function status(config: UniDeskConfig): Promise<Record<string, unknown>> {
readUrl: mirror.readUrl,
writeUrl: mirror.writeUrl,
summary: mirror.summary,
probe: compactCapture(mirror.result, { full: options.full || options.raw, stdoutTailChars: 6000, stderrTailChars: 3000 }),
...(options.raw ? { raw: mirror.raw } : {}),
},
runtimeAlignment: {
localHeadMatchesOrigin: Boolean(localSourceCommit && originSourceCommit && localSourceCommit === originSourceCommit),
argoRevision: argo.revision,
argoSyncStatus: argo.syncStatus,
argoHealthStatus: argo.healthStatus,
syncedToGitopsLatest: Boolean(gitopsLatest && argo.revision === gitopsLatest),
runtimeAlignment,
disclosure: {
defaultView: "compact-low-noise",
full: options.full,
raw: options.raw,
stdoutTailOmitted: !(options.full || options.raw),
rawGitMirrorOmitted: !options.raw,
expandWith: "bun scripts/cli.ts agentrun v01 control-plane status --full",
rawWith: "bun scripts/cli.ts agentrun v01 control-plane status --raw",
},
next: {
triggerCurrent: "bun scripts/cli.ts agentrun v01 control-plane trigger-current --confirm",
@@ -198,8 +273,8 @@ async function triggerCurrent(config: UniDeskConfig, options: TriggerOptions): P
writeUrl: gitMirrorWriteUrl,
},
};
const mirrorBefore = await gitMirrorStatus(config);
const mirrorRequirement = gitMirrorSyncRequirement(sourceCommit, String(mirrorBefore.raw ?? ""));
const mirrorBefore = await readGitMirrorStatus(config);
const mirrorRequirement = gitMirrorSyncRequirement(sourceCommit, mirrorBefore.raw);
if (options.dryRun || !options.confirm) {
return {
ok: true,
@@ -221,8 +296,8 @@ async function triggerCurrent(config: UniDeskConfig, options: TriggerOptions): P
};
if (mirrorRequirement.required) {
const synced = await runGitMirrorJob(config, "sync", { confirm: true, dryRun: false, timeoutSeconds: 300, wait: true });
const after = await gitMirrorStatus(config);
const afterRequirement = gitMirrorSyncRequirement(sourceCommit, String(after.raw ?? ""));
const after = await readGitMirrorStatus(config);
const afterRequirement = gitMirrorSyncRequirement(sourceCommit, after.raw);
gitMirrorPreSync = { ...gitMirrorPreSync, sync: synced, after: after.summary, ok: synced.ok === true && afterRequirement.required === false };
if (synced.ok !== true || afterRequirement.required !== false) {
return {
@@ -432,7 +507,35 @@ function triggerScript(sourceCommit: string, pipelineRun: string): string {
].join("\n");
}
async function gitMirrorStatus(config: UniDeskConfig): Promise<Record<string, unknown>> {
async function gitMirrorStatus(config: UniDeskConfig, options: DisclosureOptions = { full: false, raw: false }): Promise<Record<string, unknown>> {
const observation = await readGitMirrorStatus(config);
const summary = observation.summary;
return {
ok: observation.ok,
command: "agentrun v01 git-mirror status",
namespace: gitMirrorNamespace,
readUrl: gitMirrorReadUrl,
writeUrl: gitMirrorWriteUrl,
summary,
...(options.raw ? { raw: observation.raw } : {}),
probe: compactCapture(observation.result, { full: options.full || options.raw, stdoutTailChars: 6000, stderrTailChars: 3000 }),
disclosure: {
defaultView: "compact-low-noise",
full: options.full,
raw: options.raw,
rawOmitted: !options.raw,
probeTailOmitted: !(options.full || options.raw),
expandWith: "bun scripts/cli.ts agentrun v01 git-mirror status --full",
rawWith: "bun scripts/cli.ts agentrun v01 git-mirror status --raw",
},
next: {
sync: "bun scripts/cli.ts agentrun v01 git-mirror sync --confirm",
flush: summary.pendingFlush === true ? "bun scripts/cli.ts agentrun v01 git-mirror flush --confirm" : null,
},
};
}
async function readGitMirrorStatus(config: UniDeskConfig): Promise<Record<string, unknown> & { result: SshCaptureResult; raw: string; summary: Record<string, unknown> }> {
const script = [
"set +e",
"printf 'resources\\n'",
@@ -447,17 +550,12 @@ async function gitMirrorStatus(config: UniDeskConfig): Promise<Record<string, un
const summary = gitMirrorStatusSummary(raw);
return {
ok: result.exitCode === 0 && summary.localV01 !== null,
command: "agentrun v01 git-mirror status",
namespace: gitMirrorNamespace,
readUrl: gitMirrorReadUrl,
writeUrl: gitMirrorWriteUrl,
raw,
summary,
probe: compactCapture(result),
next: {
sync: "bun scripts/cli.ts agentrun v01 git-mirror sync --confirm",
flush: summary.pendingFlush === true ? "bun scripts/cli.ts agentrun v01 git-mirror flush --confirm" : null,
},
result,
};
}
@@ -795,12 +893,56 @@ async function capture(config: UniDeskConfig, target: string, args: string[]): P
return await runSshCommandCapture(config, target, args);
}
function compactCapture(result: SshCaptureResult): Record<string, unknown> {
return {
function compactCapture(result: SshCaptureResult, options: { full?: boolean; stdoutTailChars?: number; stderrTailChars?: number } = {}): Record<string, unknown> {
const stdoutTailChars = options.stdoutTailChars ?? 8000;
const stderrTailChars = options.stderrTailChars ?? 4000;
const full = options.full ?? true;
const payload: Record<string, unknown> = {
exitCode: result.exitCode,
stdoutTail: tail(result.stdout, 8000),
stderrTail: tail(result.stderr, 4000),
stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
stdoutTailOmitted: !full && result.exitCode === 0,
stderrTailOmitted: !full && result.exitCode === 0,
};
if (full || result.exitCode !== 0) {
payload.stdoutTail = tail(result.stdout, stdoutTailChars);
payload.stderrTail = tail(result.stderr, stderrTailChars);
payload.stdoutTruncated = result.stdout.length > stdoutTailChars;
payload.stderrTruncated = result.stderr.length > stderrTailChars;
}
return payload;
}
async function timedStatusStage<T>(stage: string, action: () => Promise<T>): Promise<TimedValue<T>> {
const startedAtMs = Date.now();
progressEvent("agentrun.control-plane.status.progress", { stage, status: "started" });
try {
const value = await action();
const exitCode = isCaptureResult(value) ? value.exitCode : typeof record(value).ok === "boolean" && record(value).ok !== true ? 1 : 0;
progressEvent("agentrun.control-plane.status.progress", {
stage,
status: exitCode === 0 ? "succeeded" : "failed",
exitCode,
elapsedMs: Date.now() - startedAtMs,
});
return { value, elapsedMs: Date.now() - startedAtMs };
} catch (error) {
progressEvent("agentrun.control-plane.status.progress", {
stage,
status: "failed",
elapsedMs: Date.now() - startedAtMs,
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
}
function progressEvent(event: string, payload: Record<string, unknown>): void {
process.stderr.write(`${JSON.stringify({ event, at: new Date().toISOString(), ...payload })}\n`);
}
function isCaptureResult(value: unknown): value is SshCaptureResult {
return typeof value === "object" && value !== null && "exitCode" in value && typeof (value as { exitCode?: unknown }).exitCode === "number";
}
function pipelineRunName(sourceCommit: string): string {
@@ -847,6 +989,17 @@ function parseArgoStatus(text: string): { revision: string | null; syncStatus: s
};
}
function parseManagerImage(text: string): { image: string | null; sourceCommit: string | null } {
const lines = text.split(/\r?\n/u);
const index = lines.findIndex((line) => line.trim() === "managerImage");
if (index < 0) return { image: null, sourceCommit: null };
const [image, sourceCommit] = (lines[index + 1] ?? "").split("\t");
return {
image: image?.trim() || null,
sourceCommit: sourceCommit?.trim() || null,
};
}
function isGitSha(value: string): boolean {
return /^[0-9a-f]{40}$/u.test(value);
}