fix(agentrun): 添加队列任务重试资源命令

This commit is contained in:
Codex
2026-07-12 02:47:00 +02:00
parent db75577b8f
commit 9276e751f5
6 changed files with 373 additions and 11 deletions
+160
View File
@@ -146,6 +146,125 @@ export function renderResourceResult(command: string, raw: Record<string, unknow
return renderedCliResult(raw.ok !== false, command, renderResourceTable(items, options.output === "wide"));
}
export function renderQueueAttemptList(command: string, raw: Record<string, unknown>, options: AgentRunResourceOptions, requestedTaskId: string, items: Record<string, unknown>[]): RenderedCliResult {
if (options.raw) return renderMachine(command, raw, "json", raw.ok !== false);
const data = record(innerData(raw));
const taskId = stringOrNull(data.taskId) ?? requestedTaskId;
const cursor = typeof data.cursor === "number" ? String(data.cursor) : stringOrNull(data.cursor);
const rawItems = arrayRecords(data.items);
if (options.full) {
return renderMachine(command, {
kind: "AttemptList",
taskId,
count: data.count ?? rawItems.length,
cursor,
items: rawItems,
}, options.output === "yaml" ? "yaml" : "json", raw.ok !== false);
}
const payload = { kind: "AttemptList", taskId, count: items.length, cursor, items };
if (options.output === "json" || options.output === "yaml") return renderMachine(command, payload, options.output, raw.ok !== false);
if (options.output === "name") return renderedCliResult(raw.ok !== false, command, items.map((item) => `attempt/${String(item.name ?? "")}`).join("\n"));
if (items.length === 0) return renderedCliResult(raw.ok !== false, command, `No attempt resources found for task/${taskId}.`);
const lines = [
`Attempts for task/${taskId}`,
renderTable(["NAME", "INDEX", "STATE", "PREVIOUS", "RUN", "CMD", "RJOB", "AGE", "FAILURE", "REASON"], items.map((item) => [
resourceName(item),
displayValue(item.retryIndex),
displayValue(item.state),
shortId(stringOrDash(item.previousAttemptId)),
shortId(stringOrDash(item.runId)),
shortId(stringOrDash(item.commandId)),
shortId(stringOrDash(item.runnerJobId)),
displayValue(item.age),
queueAttemptFailureSummary(item),
truncateOneLine(displayValue(item.reason), 48),
])),
];
if (cursor !== null) lines.push(`Next: ${nextQueueAttemptPageCommand(command, cursor, options.limit)}`);
return renderedCliResult(raw.ok !== false, command, lines.join("\n"));
}
export function renderQueueRetrySummary(command: string, raw: Record<string, unknown>, options: AgentRunResourceOptions, requestedTaskId: string): RenderedCliResult {
if (options.raw) return renderMachine(command, raw, "json", raw.ok !== false);
const data = record(innerData(raw));
const task = record(data.task);
const attempt = record(data.attempt);
const previousAttempt = record(data.previousAttempt);
const allowedTransition = record(data.allowedTransition);
const taskId = stringOrNull(task.id) ?? requestedTaskId;
if (options.full) {
return renderMachine(command, { kind: "TaskRetry", taskId, ...data }, options.output === "yaml" ? "yaml" : "json", raw.ok !== false);
}
const normalizedAttempt = compactQueueRetryAttempt(attempt);
const normalizedPreviousAttempt = compactQueueRetryAttempt(previousAttempt);
const run = record(data.run);
const runId = stringOrNull(run.id) ?? stringOrNull(attempt.runId);
const commandResource = record(data.command);
const commandId = stringOrNull(commandResource.id) ?? stringOrNull(attempt.commandId);
const runnerJob = record(data.runnerJob);
const runnerJobId = stringOrNull(runnerJob.id) ?? stringOrNull(attempt.runnerJobId);
const payload = {
kind: "TaskRetry",
taskId,
dryRun: options.dryRun,
mutation: data.mutation === true,
idempotentReplay: data.idempotentReplay === true,
task: pickCompact(task, ["id", "state", "queue", "lane", "payloadHash", "updatedAt"]),
attempt: normalizedAttempt,
previousAttempt: normalizedPreviousAttempt,
allowedTransition,
run: compactQueueRetryIdentity(run, ["id", "status", "terminalStatus", "createdAt", "updatedAt"]),
command: compactQueueRetryIdentity(commandResource, ["id", "type", "state", "terminalStatus", "createdAt", "updatedAt"]),
runnerJob: compactQueueRetryIdentity(runnerJob, ["id", "state", "phase", "terminalStatus", "attemptId", "createdAt", "updatedAt"]),
};
if (options.output === "json" || options.output === "yaml") return renderMachine(command, payload, options.output, raw.ok !== false);
const retryIndex = attempt.retryIndex ?? allowedTransition.retryIndex;
const attemptId = stringOrNull(attempt.attemptId);
const previousAttemptId = stringOrNull(previousAttempt.attemptId) ?? stringOrNull(allowedTransition.previousAttemptId);
const lines = [
`${options.dryRun ? "Task retry validated" : "Task retry submitted"}: task/${taskId}`,
`OK: ${String(raw.ok !== false)}`,
`DryRun: ${String(options.dryRun)}`,
`Mutation: ${String(data.mutation === true)}`,
`IdempotentReplay: ${String(data.idempotentReplay === true)}`,
`Transition: ${displayValue(allowedTransition.from)} -> ${displayValue(allowedTransition.to)} retryIndex=${displayValue(retryIndex)}`,
`Attempt: ${attemptId === null ? "planned" : `attempt/${shortId(attemptId)}`} state=${displayValue(attempt.state ?? allowedTransition.to)}`,
`PreviousAttempt: ${previousAttemptId === null ? "-" : `attempt/${shortId(previousAttemptId)}`}`,
];
if (runId !== null) lines.push(`Run: run/${shortId(runId)}`);
if (commandId !== null) lines.push(`Command: command/${shortId(commandId)}`);
if (runnerJobId !== null) lines.push(`RunnerJob: runnerjob/${shortId(runnerJobId)}`);
const next = [
`bun scripts/cli.ts agentrun get attempts --task ${taskId}`,
`bun scripts/cli.ts agentrun describe task/${taskId}`,
runId === null ? null : `bun scripts/cli.ts agentrun events run/${runId} --after-seq 0 --limit 100`,
runId === null || commandId === null ? null : `bun scripts/cli.ts agentrun describe command/${commandId} --run ${runId}`,
].filter((value): value is string => value !== null);
lines.push("", "Next:", ...next.map((value) => ` ${value}`));
return renderedCliResult(raw.ok !== false, command, lines.join("\n"));
}
function compactQueueRetryAttempt(value: Record<string, unknown>): Record<string, unknown> | null {
if (Object.keys(value).length === 0) return null;
const attempt = pickCompact(value, ["attemptId", "taskId", "retryIndex", "state", "idempotencyKey", "reason", "payloadHash", "previousAttemptId", "runId", "commandId", "runnerJobId", "sessionId", "sessionPath", "failureKind", "failureMessage", "createdAt", "updatedAt", "activatedAt", "terminalAt"]);
attempt.reason = boundedQueueAttemptText(attempt.reason, 240);
attempt.failureMessage = boundedQueueAttemptText(attempt.failureMessage, 320);
attempt.sessionPath = boundedQueueAttemptText(attempt.sessionPath, 240);
return attempt;
}
function compactQueueRetryIdentity(value: Record<string, unknown>, keys: string[]): Record<string, unknown> | null {
return Object.keys(value).length === 0 ? null : pickCompact(value, keys);
}
function nextQueueAttemptPageCommand(command: string, cursor: string, limit: number): string {
const base = command
.replace(/\s+--cursor(?:=|\s+)\S+/gu, "")
.replace(/\s+--limit(?:=|\s+)\S+/gu, "")
.trim();
return `bun scripts/cli.ts ${base} --cursor ${cursor} --limit ${limit}`;
}
export function renderDescribe(command: string, raw: Record<string, unknown>, options: AgentRunResourceOptions, ref: AgentRunResourceRef, text: string): RenderedCliResult {
if (options.raw) return renderMachine(command, raw, "json", raw.ok !== false);
if (options.output === "json" || options.output === "yaml") {
@@ -355,6 +474,7 @@ export function parseResourceKind(raw: string | undefined): AgentRunResourceKind
if (raw === undefined) return null;
const value = raw.toLowerCase().replace(/s$/u, "");
if (value === "task" || value === "qt" || value === "queue" || value === "queuetask") return "task";
if (value === "attempt") return "attempt";
if (value === "run") return "run";
if (value === "command" || value === "cmd") return "command";
if (value === "runnerjob" || value === "runner-job" || value === "rjob" || value === "job") return "runnerjob";
@@ -432,6 +552,46 @@ export function normalizeTaskItems(data: unknown, options: AgentRunResourceOptio
});
}
export function normalizeQueueAttemptItems(data: unknown): Record<string, unknown>[] {
const items = arrayRecords(record(data).items ?? data);
return items.map((item) => ({
kind: "attempt",
name: item.attemptId,
attemptId: item.attemptId,
taskId: item.taskId,
retryIndex: item.retryIndex,
state: item.state,
idempotencyKey: item.idempotencyKey,
reason: boundedQueueAttemptText(item.reason, 240),
payloadHash: item.payloadHash,
previousAttemptId: item.previousAttemptId,
runId: item.runId,
commandId: item.commandId,
runnerJobId: item.runnerJobId,
sessionId: item.sessionId,
sessionPath: boundedQueueAttemptText(item.sessionPath, 240),
failureKind: item.failureKind,
failureMessage: boundedQueueAttemptText(item.failureMessage, 320),
createdAt: item.createdAt,
updatedAt: item.updatedAt,
activatedAt: item.activatedAt,
terminalAt: item.terminalAt,
age: relativeAge(stringOrNull(item.updatedAt) ?? stringOrNull(item.createdAt)),
}));
}
function boundedQueueAttemptText(value: unknown, maxChars: number): string | null {
const text = stringOrNull(value);
return text === null ? null : truncateOneLine(text, maxChars);
}
function queueAttemptFailureSummary(item: Record<string, unknown>): string {
const kind = stringOrNull(item.failureKind);
const message = stringOrNull(item.failureMessage);
if (kind === null && message === null) return "-";
return truncateOneLine(kind === null ? message ?? "-" : message === null ? kind : `${kind}: ${message}`, 72);
}
export function taskListState(options: AgentRunResourceOptions): string {
const requested = options.state?.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
return requested?.[0] ?? "running";