fix: 增强 AgentRun control-plane status drill-down

This commit is contained in:
Codex
2026-06-10 18:31:02 +00:00
parent 6c39fab601
commit ef5c1c0680
3 changed files with 188 additions and 16 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ bun scripts/cli.ts agentrun v01 control-plane cleanup-released-pvs --limit 200 -
bun scripts/cli.ts agentrun v01 control-plane cleanup-released-pvs --limit 200 --confirm
```
`status` 只读观察 `G14:/root/agentrun-v01` 当前 commit、对应 PipelineRun、GitOps latest、Argo Application、`agentrun-v01` workload、manager source commit 和 git mirror 摘要,并报告 Argo revision 是否对齐 `v0.1-gitops` latest。默认输出是 compact commander 视图,只保留 `summary`、阶段耗时、对齐状态和 drill-down 命令;需要远端 stdout/stderr tail 时显式加 `--full`,需要原始 git mirror cache 输出时显式加 `--raw``status` 会向 stderr 输出 `agentrun.control-plane.status.progress` 阶段事件,覆盖 `source``runtime``git-mirror`,避免长时间聚合时无可见进展。`trigger-current` 会先把固定 source worktree 快进到 `origin/v0.1`,再以当前 commit 创建 commit-pinned PipelineRun;同名 PipelineRun 正在运行或已经成功时必须拒绝重复触发,只允许在失败态或不存在时创建。该命令只提交 CI/CD 工作,不等待完整 PipelineRun 或 rollout 完成,后续用 `status` 轮询。`refresh` 只对 `argocd/agentrun-g14-v01` 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。
`status` 只读观察 `G14:/root/agentrun-v01` 当前 commit、对应 PipelineRun、GitOps latest、Argo Application、`agentrun-v01` workload、manager source commit 和 git mirror 摘要,并报告 Argo revision 是否对齐 `v0.1-gitops` latest。默认输出是 compact commander 视图,只保留 `summary`、阶段耗时、对齐状态和 drill-down 命令;需要远端 stdout/stderr tail 时显式加 `--full`,需要原始 git mirror cache 输出时显式加 `--raw``status` 额外支持 `--pipeline-run <name>``--source-commit <sha>` 定点查询,返回 `target``targetValidation``next.*` drill-down,便于直接判断某次 run 是成功、历史成功、运行中、缺失还是 source mismatch。`status` 会向 stderr 输出 `agentrun.control-plane.status.progress` 阶段事件,覆盖 `source``runtime``git-mirror`,避免长时间聚合时无可见进展。`trigger-current` 会先把固定 source worktree 快进到 `origin/v0.1`,再以当前 commit 创建 commit-pinned PipelineRun;同名 PipelineRun 正在运行或已经成功时必须拒绝重复触发,只允许在失败态或不存在时创建。该命令只提交 CI/CD 工作,不等待完整 PipelineRun 或 rollout 完成,后续用 `status` 轮询。`refresh` 只对 `argocd/agentrun-g14-v01` 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。
`cleanup-runs` 是 AgentRun `v0.1` 完成态 CI workspace retention 入口,只清理 `agentrun-ci` namespace 中超过 `--min-age-minutes``agentrun-v01-ci-*` PipelineRun,通过 Tekton ownerRef 释放临时 workspace PVC。dry-run 必须披露候选 PipelineRun、owned PVC、active mount 保护、local-path 实际估算 bytes 和 confirm 命令。默认保护最新完成的 PipelineRun,保留当前 CI/CD 状态证据。`cleanup-released-pvs` 是二次回收入口,只处理 `agentrun-ci``local-path``Delete` reclaim policy 的 `Released` PV;它不触碰 `agentrun-v01` runtime namespace、业务 PVC、Secret、registry storage 或 GitOps desired state。磁盘治理和 G14 safe-stop 规则见 `docs/reference/gc.md`
+11 -8
View File
@@ -19,6 +19,13 @@ assertCondition(
agentRunUsage,
);
assertCondition(
agentRunUsage.some((line) => line.includes("control-plane status --pipeline-run agentrun-v01-ci-<short-sha>"))
&& agentRunUsage.some((line) => line.includes("control-plane status --source-commit <full-sha>")),
"AgentRun help must expose targeted control-plane status drill-down options",
agentRunUsage,
);
assertCondition(
agentRunUsage.some((line) => line.includes("queue submit --json-stdin --dry-run"))
&& agentRunUsage.some((line) => line.includes("queue dispatch <taskId> --json-stdin --dry-run"))
@@ -46,7 +53,7 @@ assertCondition(
const globalHelp = JSON.stringify(rootHelp());
assertCondition(
globalHelp.includes("agentrun v01 queue|sessions|control-plane|git-mirror"),
globalHelp.includes("agentrun v01 aipod-specs|queue|sessions|control-plane|git-mirror"),
"global help must index AgentRun v0.1 entrypoints",
rootHelp(),
);
@@ -54,14 +61,9 @@ assertCondition(
const agentRunSource = readFileSync("scripts/src/agentrun.ts", "utf8");
const runtimeJsonFallback = "\"node <<'NODE' || printf '{}\\\\n'\"";
function statusScriptSection(label: string): string {
const start = agentRunSource.indexOf(`printf '${label}='`);
return start >= 0 ? agentRunSource.slice(start, start + 500) : "";
}
assertCondition(
statusScriptSection("pipelineRunCondition").includes(runtimeJsonFallback)
&& statusScriptSection("ciSummary").includes(runtimeJsonFallback),
agentRunSource.includes(runtimeJsonFallback)
&& agentRunSource.includes("const sourceCommit = params.find((entry) => entry?.name === 'revision')?.value || null;"),
"AgentRun control-plane status must degrade empty runtime JSON snippets instead of failing the whole status probe",
);
@@ -69,6 +71,7 @@ console.log(JSON.stringify({
ok: true,
checks: [
"AgentRun command help exposes cleanup-runs and cleanup-released-pvs",
"AgentRun command help exposes targeted control-plane status drill-down options",
"AgentRun command help exposes queue dry-run and compact commander usage",
"AgentRun command help presents heredoc/stdin before reusable file fallbacks",
"global help indexes AgentRun v0.1 entrypoints",
+176 -7
View File
@@ -48,6 +48,8 @@ export function agentRunHelp(): unknown {
"bun scripts/cli.ts agentrun v01 sessions read <sessionId> --reader-id cli",
"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 status --pipeline-run agentrun-v01-ci-<short-sha>",
"bun scripts/cli.ts agentrun v01 control-plane status --source-commit <full-sha>",
"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",
@@ -69,7 +71,7 @@ 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, parseDisclosureOptions(args.slice(3)));
if (action === "status") return await status(config, parseStatusOptions(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 (action === "cleanup-runs") return await cleanupRuns(config, parseCleanupRunsOptions(args.slice(3)));
@@ -124,6 +126,12 @@ interface DisclosureOptions {
raw: boolean;
}
interface StatusOptions extends DisclosureOptions {
sourceCommit: string | null;
pipelineRun: string | null;
targetMode: "latest-source-head" | "source-commit" | "pipeline-run";
}
interface TimedValue<T> {
value: T;
elapsedMs: number;
@@ -137,6 +145,50 @@ function parseDisclosureOptions(args: string[]): DisclosureOptions {
return { full: raw || args.includes("--full"), raw };
}
function parseStatusOptions(args: string[]): StatusOptions {
let sourceCommit: string | null = null;
let pipelineRun: string | null = null;
let full = false;
let raw = false;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--full") {
full = true;
continue;
}
if (arg === "--raw") {
raw = true;
full = true;
continue;
}
if (arg === "--source-commit") {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error("--source-commit requires a value");
if (!isGitSha(value)) throw new Error("--source-commit must be a full 40-character git SHA");
sourceCommit = value;
index += 1;
continue;
}
if (arg === "--pipeline-run") {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error("--pipeline-run requires a value");
if (!isAgentRunPipelineRunName(value)) throw new Error("--pipeline-run must be an agentrun-v01-ci-<12+ hex> PipelineRun name");
pipelineRun = value;
index += 1;
continue;
}
throw new Error(`unsupported status option: ${arg}`);
}
if (sourceCommit !== null && pipelineRun !== null) throw new Error("control-plane status accepts only one of --source-commit or --pipeline-run");
return {
full,
raw,
sourceCommit,
pipelineRun,
targetMode: pipelineRun !== null ? "pipeline-run" : sourceCommit !== null ? "source-commit" : "latest-source-head",
};
}
function parseTriggerOptions(args: string[]): TriggerOptions {
return parseConfirmOptions(args);
}
@@ -208,7 +260,7 @@ function positiveIntegerOption(args: string[], name: string, defaultValue: numbe
return Math.min(value, maxValue);
}
async function status(config: UniDeskConfig, options: DisclosureOptions): Promise<Record<string, unknown>> {
async function status(config: UniDeskConfig, options: StatusOptions): 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",
@@ -223,9 +275,17 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
const source = sourceProbe.value;
const localSourceCommit = matchLine(source.stdout, "sourceCommit=");
const originSourceCommit = matchLine(source.stdout, "originV01=");
const sourceCommit = isGitSha(originSourceCommit ?? "") ? originSourceCommit : localSourceCommit;
const latestSourceCommit = isGitSha(originSourceCommit ?? "") ? originSourceCommit : localSourceCommit;
const gitopsLatest = matchLine(source.stdout, "gitopsLatest=");
const pipelineRun = sourceCommit ? pipelineRunName(sourceCommit) : null;
let sourceCommit = options.sourceCommit ?? (options.pipelineRun !== null ? null : latestSourceCommit);
let sourceCommitSource = options.sourceCommit !== null
? "option"
: options.pipelineRun !== null
? "pipeline-run-param"
: sourceCommit === originSourceCommit
? "origin/v0.1"
: "local-head";
const pipelineRun = options.pipelineRun ?? (sourceCommit ? pipelineRunName(sourceCommit) : null);
const [runtimeProbe, mirrorProbe] = await Promise.all([
timedStatusStage("runtime", () => capture(config, g14K3sRoute, ["script", "--", statusScript(pipelineRun)])),
timedStatusStage("git-mirror", () => readGitMirrorStatus(config)),
@@ -237,6 +297,22 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
const pipelineRunCondition = labeledJson(k3s.stdout, "pipelineRunCondition");
const managerImage = parseManagerImage(k3s.stdout);
const mirrorSummary = mirror.summary;
const pipelineRunSourceCommit = stringOrNull(pipelineRunCondition.sourceCommit);
if (options.pipelineRun !== null && sourceCommit === null && pipelineRunSourceCommit !== null && isGitSha(pipelineRunSourceCommit)) {
sourceCommit = pipelineRunSourceCommit;
sourceCommitSource = "pipeline-run-param";
}
const target = {
mode: options.targetMode,
sourceCommit,
sourceCommitSource,
pipelineRun,
pipelineRunSourceCommit,
latestSourceCommit,
latestPipelineRun: latestSourceCommit ? pipelineRunName(latestSourceCommit) : null,
isLatestSource: Boolean(sourceCommit && latestSourceCommit && sourceCommit === latestSourceCommit),
sourceMatchesPipelineRun: sourceCommit === null || pipelineRunSourceCommit === null ? null : sourceCommit === pipelineRunSourceCommit,
};
const runtimeAlignment = {
localHeadMatchesOrigin: Boolean(localSourceCommit && originSourceCommit && localSourceCommit === originSourceCommit),
argoRevision: argo.revision,
@@ -246,7 +322,15 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
managerSourceCommit: managerImage.sourceCommit,
managerSourceMatchesExpected: Boolean(sourceCommit && managerImage.sourceCommit === sourceCommit),
};
const targetValidation = buildAgentRunTargetValidation({
pipelineRun,
pipelineRunCondition,
sourceCommit,
targetIsLatestSource: target.isLatestSource,
managerSourceMatchesExpected: runtimeAlignment.managerSourceMatchesExpected,
});
const summary = {
target,
sourceCommit,
expectedPipelineRun: pipelineRun,
pipelineRun: {
@@ -254,6 +338,7 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
reason: pipelineRunCondition.reason ?? null,
completionTime: pipelineRunCondition.completionTime ?? null,
},
targetValidation,
argo,
managerImage,
gitMirror: {
@@ -278,8 +363,10 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
command: "agentrun v01 control-plane status",
lane: "v0.1",
summary,
target,
sourceCommit,
sourceCommitSource: sourceCommit === originSourceCommit ? "origin/v0.1" : "local-head",
sourceCommitSource,
latestSourceCommit,
localSourceCommit,
originSourceCommit,
gitopsLatest,
@@ -303,16 +390,21 @@ async function status(config: UniDeskConfig, options: DisclosureOptions): Promis
...(options.raw ? { raw: mirror.raw } : {}),
},
runtimeAlignment,
targetValidation,
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",
expandWith: `bun scripts/cli.ts agentrun v01 control-plane status${statusTargetArg(options, target)} --full`,
rawWith: `bun scripts/cli.ts agentrun v01 control-plane status${statusTargetArg(options, target)} --raw`,
},
next: {
statusByPipelineRun: pipelineRun ? `bun scripts/cli.ts agentrun v01 control-plane status --pipeline-run ${pipelineRun} --full` : null,
statusBySourceCommit: sourceCommit ? `bun scripts/cli.ts agentrun v01 control-plane status --source-commit ${sourceCommit} --full` : null,
taskRuns: pipelineRun ? `trans G14:k3s kubectl get taskrun -n ${ciNamespace} -l tekton.dev/pipelineRun=${pipelineRun} -o wide` : null,
logs: pipelineRun ? `trans G14:k3s logs -n ${ciNamespace} -l tekton.dev/pipelineRun=${pipelineRun} --tail 120` : null,
triggerCurrent: "bun scripts/cli.ts agentrun v01 control-plane trigger-current --confirm",
refresh: "bun scripts/cli.ts agentrun v01 control-plane refresh --confirm",
},
@@ -935,8 +1027,12 @@ function statusScript(pipelineRun: string | null): string {
"const fs = require('node:fs');",
"const pr = JSON.parse(fs.readFileSync('/tmp/agentrun-v01-pipelinerun.json', 'utf8'));",
"const condition = pr?.status?.conditions?.[0] || {};",
"const params = Array.isArray(pr?.spec?.params) ? pr.spec.params : [];",
"const sourceCommit = params.find((entry) => entry?.name === 'revision')?.value || null;",
"console.log(JSON.stringify({",
" name: pr?.metadata?.name || null,",
" sourceCommit,",
" createdAt: pr?.metadata?.creationTimestamp || null,",
" status: condition.status || null,",
" reason: condition.reason || null,",
" message: condition.message || null,",
@@ -1709,6 +1805,79 @@ function pipelineRunName(sourceCommit: string): string {
return `agentrun-v01-ci-${sourceCommit.slice(0, 12)}`;
}
function isAgentRunPipelineRunName(value: string): boolean {
return /^agentrun-v01-ci-[0-9a-f]{12,40}(?:-[a-z0-9-]+)?$/u.test(value);
}
function statusTargetArg(options: StatusOptions, target: Record<string, unknown>): string {
if (options.pipelineRun !== null) return ` --pipeline-run ${options.pipelineRun}`;
if (options.sourceCommit !== null) return ` --source-commit ${options.sourceCommit}`;
const sourceCommit = stringOrNull(target.sourceCommit);
return sourceCommit ? ` --source-commit ${sourceCommit}` : "";
}
function buildAgentRunTargetValidation(input: {
pipelineRun: string | null;
pipelineRunCondition: Record<string, unknown>;
sourceCommit: string | null;
targetIsLatestSource: boolean;
managerSourceMatchesExpected: boolean;
}): Record<string, unknown> {
const pipelineRunExists = input.pipelineRun !== null && stringOrNull(input.pipelineRunCondition.name) !== null;
const pipelineStatus = stringOrNull(input.pipelineRunCondition.status);
const pipelineReason = stringOrNull(input.pipelineRunCondition.reason);
const pipelineRunSourceCommit = stringOrNull(input.pipelineRunCondition.sourceCommit);
const sourceMatchesPipelineRun = input.sourceCommit === null || pipelineRunSourceCommit === null ? null : input.sourceCommit === pipelineRunSourceCommit;
let state = "unknown";
const blockers: string[] = [];
const warnings: string[] = [];
if (input.pipelineRun === null) {
blockers.push("pipeline-run-unresolved");
} else if (!pipelineRunExists) {
state = "missing-pipelinerun";
blockers.push("pipeline-run-not-found");
} else if (sourceMatchesPipelineRun === false) {
state = "source-mismatch";
blockers.push("source-commit-does-not-match-pipelinerun-param");
} else if (pipelineStatus === "True") {
state = input.targetIsLatestSource && input.managerSourceMatchesExpected ? "current-succeeded" : "historical-succeeded";
} else if (pipelineStatus === "False") {
state = "pipeline-failed";
blockers.push(pipelineReason ?? "pipeline-failed");
} else if (pipelineStatus === "Unknown") {
state = "pipeline-running";
} else {
state = "pipeline-status-unknown";
warnings.push("pipeline-condition-missing");
}
if (pipelineRunExists && input.targetIsLatestSource && !input.managerSourceMatchesExpected) {
warnings.push("runtime-manager-source-not-yet-aligned");
}
if (pipelineRunExists && !input.targetIsLatestSource) {
warnings.push("target-is-not-latest-source-head");
}
return {
state,
pipelineRunExists,
pipelineStatus,
pipelineReason,
sourceMatchesPipelineRun,
targetIsLatestSource: input.targetIsLatestSource,
managerSourceMatchesExpected: input.managerSourceMatchesExpected,
blockers,
warnings,
interruptedOrUnknown: state === "pipeline-status-unknown" || state === "missing-pipelinerun",
nextActions: {
inspectPipelineRun: input.pipelineRun ? `bun scripts/cli.ts agentrun v01 control-plane status --pipeline-run ${input.pipelineRun} --full` : null,
inspectSourceCommit: input.sourceCommit ? `bun scripts/cli.ts agentrun v01 control-plane status --source-commit ${input.sourceCommit} --full` : null,
triggerCurrent: "bun scripts/cli.ts agentrun v01 control-plane trigger-current --confirm",
},
};
}
function matchLine(text: string, prefix: string): string | null {
const line = text.split(/\r?\n/u).find((item) => item.startsWith(prefix));
return line ? line.slice(prefix.length).trim() || null : null;