feat(awooop): record recurrence handoff proposals

This commit is contained in:
Your Name
2026-05-19 00:13:40 +08:00
parent 0028993851
commit fb9b0b3b7c
6 changed files with 424 additions and 19 deletions

View File

@@ -110,12 +110,16 @@ type RecurrenceWorkItemActionResult = {
incident_id?: string | null;
mode?: string | null;
requested_mode?: string | null;
handoff_kind?: string | null;
handoff_status?: string | null;
handoff_owner?: string | null;
allowed?: boolean | null;
executed?: boolean | null;
safety_level?: string | null;
writes_incident_state?: boolean | null;
writes_auto_repair_result?: boolean | null;
writes_ticket?: boolean | null;
creates_external_ticket?: boolean | null;
verification_result_preview?: string | null;
next_step?: string | null;
checks?: Array<{ name?: string | null; passed?: boolean | null; detail?: string | null }>;
@@ -153,7 +157,7 @@ type RecurrenceWorkItemActionResult = {
};
type RecurrenceWorkItemActionState = {
loading?: "preview" | "dryRun" | null;
loading?: "preview" | "dryRun" | "handoff" | null;
result?: RecurrenceWorkItemActionResult | null;
error?: string | null;
};
@@ -331,6 +335,25 @@ function recurrencePreviewKey(preview?: string | null) {
return "unknown";
}
function recurrenceHandoffStatusKey(status?: string | null) {
if (
status === "ready_to_record" ||
status === "recorded" ||
status === "record_failed" ||
status === "blocked"
) {
return status;
}
return "unknown";
}
function recurrenceHandoffKindKey(kind?: string | null) {
if (kind === "ticket_proposal" || kind === "manual_review") {
return kind;
}
return "unknown";
}
function buildWorkItems(
telemetry: Telemetry,
t: ReturnType<typeof useTranslations>
@@ -653,7 +676,7 @@ function RecurrenceWorkQueuePanel({
const summary = recurrence?.summary;
const runWorkItemAction = useCallback(async (
workItemId: string,
action: "preview" | "dryRun"
action: "preview" | "dryRun" | "handoff"
) => {
setActionState((current) => ({
...current,
@@ -662,21 +685,36 @@ function RecurrenceWorkQueuePanel({
const encodedProjectId = encodeURIComponent(projectId);
const encodedWorkItemId = encodeURIComponent(workItemId);
const result = action === "preview"
? await fetchJson<RecurrenceWorkItemActionResult>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/preview?project_id=${encodedProjectId}&work_item_id=${encodedWorkItemId}`,
12000
)
: await postJson<RecurrenceWorkItemActionResult>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/dry-run`,
{
project_id: projectId,
work_item_id: workItemId,
mode: "auto",
limit: 300,
},
15000
);
let result: RecurrenceWorkItemActionResult | null = null;
if (action === "preview") {
result = await fetchJson<RecurrenceWorkItemActionResult>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/preview?project_id=${encodedProjectId}&work_item_id=${encodedWorkItemId}`,
12000
);
} else if (action === "dryRun") {
result = await postJson<RecurrenceWorkItemActionResult>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/dry-run`,
{
project_id: projectId,
work_item_id: workItemId,
mode: "auto",
limit: 300,
},
15000
);
} else {
result = await postJson<RecurrenceWorkItemActionResult>(
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/handoff`,
{
project_id: projectId,
work_item_id: workItemId,
mode: "auto",
handoff_kind: "ticket_proposal",
limit: 300,
},
15000
);
}
setActionState((current) => ({
...current,
@@ -736,6 +774,8 @@ function RecurrenceWorkQueuePanel({
const actionAllowed = actionResult?.allowed === true;
const actionModeKey = recurrenceActionModeKey(actionResult?.mode);
const previewKey = recurrencePreviewKey(actionResult?.verification_result_preview);
const handoffStatusKey = recurrenceHandoffStatusKey(actionResult?.handoff_status);
const handoffKindKey = recurrenceHandoffKindKey(actionResult?.handoff_kind);
return (
<article
@@ -802,6 +842,17 @@ function RecurrenceWorkQueuePanel({
? t("actions.dryRunning")
: t("actions.dryRun")}
</button>
<button
type="button"
onClick={() => runWorkItemAction(workItemId, "handoff")}
disabled={currentAction?.loading === "handoff"}
className="inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-white px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#9bc7a4] hover:bg-[#f0faf2] hover:text-[#17602a] disabled:cursor-not-allowed disabled:opacity-60"
>
<GitBranch className="h-3.5 w-3.5" aria-hidden="true" />
{currentAction?.loading === "handoff"
? t("actions.handoffing")
: t("actions.handoff")}
</button>
</>
) : null}
{runHref ? (
@@ -863,6 +914,21 @@ function RecurrenceWorkQueuePanel({
recorded: String(actionResult.history?.recorded ?? false),
})}
</p>
{actionResult.handoff_status ? (
<p>
{t("actions.handoffStatus", {
status: t(`actions.handoffStatuses.${handoffStatusKey}` as never),
kind: t(`actions.handoffKinds.${handoffKindKey}` as never),
})}
</p>
) : null}
{actionResult.creates_external_ticket !== undefined ? (
<p>
{t("actions.externalTicket", {
created: String(actionResult.creates_external_ticket ?? false),
})}
</p>
) : null}
{actionResult.ticket_preview?.title ? (
<p className="truncate">
{t("actions.ticket", {