feat: 支持 Queue 终态幂等重试
This commit is contained in:
@@ -115,6 +115,8 @@ async function dispatch(args: ParsedArgs): Promise<CliResult> {
|
||||
if (group === "queue" && command === "read" && id) return readQueueTask(args, id);
|
||||
if (group === "queue" && command === "cancel" && id) return cancelQueueTask(args, id);
|
||||
if (group === "queue" && command === "dispatch" && id) return dispatchQueueTask(args, id);
|
||||
if (group === "queue" && command === "retry" && id) return retryQueueTaskCli(args, id);
|
||||
if (group === "queue" && command === "attempts" && id) return listQueueAttemptsCli(args, id);
|
||||
if (group === "queue" && command === "refresh" && id) return refreshQueueTask(args, id);
|
||||
if (group === "runs" && command === "create") return client(args).post("/api/v1/runs", await jsonFile(args));
|
||||
if (group === "runs" && command === "show" && id) return showRun(args, id);
|
||||
@@ -573,6 +575,61 @@ export function summarizeQueueDispatchResult(result: JsonValue, taskId: string):
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeQueueRetryResult(result: JsonValue, taskId: string): JsonRecord {
|
||||
const record = jsonRecordValue(result);
|
||||
if (!record) throw new AgentRunError("schema-invalid", "queue retry response must be an object", { httpStatus: 2 });
|
||||
const task = jsonRecordValue(record.task);
|
||||
const attempt = jsonRecordValue(record.attempt);
|
||||
const run = jsonRecordValue(record.run);
|
||||
const command = jsonRecordValue(record.command);
|
||||
const runnerJob = jsonRecordValue(record.runnerJob);
|
||||
const runId = stringValue(run?.id) ?? stringValue(attempt?.runId);
|
||||
const commandId = stringValue(command?.id) ?? stringValue(attempt?.commandId);
|
||||
const sessionId = stringValue(attempt?.sessionId) ?? stringValue(jsonRecordValue(task?.sessionRef)?.sessionId);
|
||||
return {
|
||||
action: "queue-retry-summary",
|
||||
taskId: stringValue(task?.id) ?? taskId,
|
||||
mutation: record.mutation === true,
|
||||
idempotentReplay: record.idempotentReplay === true,
|
||||
task: summarizeQueueTaskRecord(task, taskId),
|
||||
attempt: summarizeAttemptRecord(attempt),
|
||||
previousAttempt: summarizeAttemptRecord(jsonRecordValue(record.previousAttempt)),
|
||||
allowedTransition: jsonRecordValue(record.allowedTransition),
|
||||
run: summarizeRunRecord(run),
|
||||
command: summarizeCommandRecord(command),
|
||||
runnerJob: summarizeRunnerJobRecord(runnerJob),
|
||||
fullResponseBytes: jsonByteLength(result),
|
||||
valuesPrinted: false,
|
||||
pollCommands: {
|
||||
attempts: `./scripts/agentrun queue attempts ${taskId}`,
|
||||
queue: `describe task/${taskId}`,
|
||||
...(runId ? { run: `describe run/${runId}`, result: `result run/${runId}${commandId ? ` --command ${commandId}` : ""}`, events: `events run/${runId} --after-seq 0 --limit 100 --tail-summary` } : {}),
|
||||
...(runId && commandId ? { command: `describe command/${commandId} --run ${runId}` } : {}),
|
||||
...(sessionId ? { logs: `logs session/${sessionId} --tail 100` } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeQueueAttemptListResult(result: JsonValue, taskId: string, limit: number): JsonRecord {
|
||||
const record = jsonRecordValue(result);
|
||||
if (!record) throw new AgentRunError("schema-invalid", "queue attempts response must be an object", { httpStatus: 2 });
|
||||
const items = queueItems(record.items);
|
||||
return {
|
||||
action: "queue-attempt-list-summary",
|
||||
taskId: stringValue(record.taskId) ?? taskId,
|
||||
count: numberValue(record.count) ?? items.length,
|
||||
cursor: stringValue(record.cursor),
|
||||
limit,
|
||||
items: items.slice(0, limit).map((attempt) => withoutFullRecordBytes(compactRecord(attempt, { keys: ["attemptId", "taskId", "retryIndex", "state", "idempotencyKey", "reason", "payloadHash", "previousAttemptId", "runId", "commandId", "runnerJobId", "sessionId", "sessionPath", "failureKind", "failureMessage", "createdAt", "updatedAt", "activatedAt", "terminalAt"] }))),
|
||||
fullResponseBytes: jsonByteLength(result),
|
||||
valuesPrinted: false,
|
||||
drillDownCommands: {
|
||||
full: `./scripts/agentrun queue attempts ${taskId} --full`,
|
||||
next: stringValue(record.cursor) ? `./scripts/agentrun queue attempts ${taskId} --cursor ${String(record.cursor)} --limit ${limit}` : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeRunEvent(event: JsonRecord, summaryChars: number): JsonRecord {
|
||||
const payload = jsonRecordValue(event.payload) ?? {};
|
||||
const type = stringValue(event.type) ?? "unknown";
|
||||
@@ -1604,6 +1661,28 @@ async function dispatchQueueTask(args: ParsedArgs, taskId: string): Promise<Json
|
||||
return summarizeQueueDispatchResult(result, taskId);
|
||||
}
|
||||
|
||||
async function retryQueueTaskCli(args: ParsedArgs, taskId: string): Promise<JsonValue> {
|
||||
const idempotencyKey = optionalFlag(args, "idempotency-key");
|
||||
const reason = optionalFlag(args, "reason");
|
||||
if (!idempotencyKey) throw new AgentRunError("schema-invalid", "queue retry requires --idempotency-key", { httpStatus: 2 });
|
||||
if (!reason) throw new AgentRunError("schema-invalid", "queue retry requires --reason", { httpStatus: 2 });
|
||||
const body: JsonRecord = { idempotencyKey, reason, dryRun: args.flags.get("dry-run") === true };
|
||||
const result = await client(args).post(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/retry`, body);
|
||||
if (wantsExpandedOutput(args)) return result;
|
||||
return summarizeQueueRetryResult(result, taskId);
|
||||
}
|
||||
|
||||
async function listQueueAttemptsCli(args: ParsedArgs, taskId: string): Promise<JsonValue> {
|
||||
const params = new URLSearchParams();
|
||||
const cursor = optionalFlag(args, "cursor");
|
||||
const limit = integerFlag(args, "limit", 20, { min: 1, max: 100 });
|
||||
if (cursor) params.set("cursor", cursor);
|
||||
params.set("limit", String(limit));
|
||||
const result = await client(args).get(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/attempts?${params.toString()}`);
|
||||
if (wantsExpandedOutput(args)) return result;
|
||||
return summarizeQueueAttemptListResult(result, taskId, limit);
|
||||
}
|
||||
|
||||
async function queueDispatchBody(args: ParsedArgs): Promise<JsonRecord> {
|
||||
const body = await optionalJsonFile(args);
|
||||
const copy = (flagName: string, key = flagName.replace(/-([a-z])/gu, (_, letter: string) => letter.toUpperCase())): void => {
|
||||
@@ -2247,6 +2326,8 @@ function help(args: ParsedArgs, group?: string): JsonRecord {
|
||||
"queue read <taskId> [--reader-id <reader>] [--dry-run] [--full|--raw]",
|
||||
"queue cancel <taskId> [--reason <text>] [--dry-run] [--full|--raw]",
|
||||
"queue dispatch <taskId> [--json-stdin|--json-file <dispatch.json>] [--idempotency-key <key>] [--image <image>] [--namespace <namespace>] [--dry-run] [--full|--raw]",
|
||||
"queue retry <taskId> --idempotency-key <key> --reason <text> [--dry-run] [--full|--raw]",
|
||||
"queue attempts <taskId> [--cursor <retry-index>] [--limit <n>] [--full|--raw]",
|
||||
"queue refresh <taskId> [--dry-run] [--full|--raw]",
|
||||
"aipod-specs list",
|
||||
"aipod-specs show <name>",
|
||||
|
||||
Reference in New Issue
Block a user