fix: improve agentrun status visibility
This commit is contained in:
+183
-30
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user