export const CLAWD_TASK_KIND = "clawd_chat"; export type ClawdTaskStatus = | "queued" | "running" | "completed" | "failed" | "canceled"; export type ClawdTaskProgressPayload = Record; export type ClawdTaskMetadata = { composerMode: "clawd"; source: "openclaw"; taskKind: typeof CLAWD_TASK_KIND; status: ClawdTaskStatus; userMessageId: string; assistantMessageId: string; processingHint: string; progressEvents: ClawdTaskProgressPayload[]; skillTagId?: string | null; skillTagName?: string | null; requestId?: string | null; error?: string | null; createdAt: number; updatedAt: number; }; const CLAWD_TASK_STATUSES = new Set([ "queued", "running", "completed", "failed", "canceled", ]); export function normalizeClawdTaskMetadata( value: unknown ): ClawdTaskMetadata | null { if (!value || typeof value !== "object" || Array.isArray(value)) return null; const raw = value as Record; if (raw.taskKind !== CLAWD_TASK_KIND || raw.composerMode !== "clawd") { return null; } const status = String(raw.status || "") as ClawdTaskStatus; const userMessageId = String(raw.userMessageId || "").trim(); const assistantMessageId = String(raw.assistantMessageId || "").trim(); if ( !CLAWD_TASK_STATUSES.has(status) || !userMessageId || !assistantMessageId ) { return null; } const createdAt = Number(raw.createdAt); const updatedAt = Number(raw.updatedAt); return { composerMode: "clawd", source: "openclaw", taskKind: CLAWD_TASK_KIND, status, userMessageId, assistantMessageId, processingHint: String(raw.processingHint || "").trim(), progressEvents: Array.isArray(raw.progressEvents) ? raw.progressEvents.filter( (item): item is ClawdTaskProgressPayload => Boolean(item) && typeof item === "object" && !Array.isArray(item) ) : [], skillTagId: raw.skillTagId ? String(raw.skillTagId) : null, skillTagName: raw.skillTagName ? String(raw.skillTagName) : null, requestId: raw.requestId ? String(raw.requestId) : null, error: raw.error ? String(raw.error) : null, createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(), updatedAt: Number.isFinite(updatedAt) ? updatedAt : Date.now(), }; } export function isPendingClawdTaskStatus(status: ClawdTaskStatus) { return status === "queued" || status === "running"; } export function appendClawdTaskProgress( current: ClawdTaskProgressPayload[], progress: ClawdTaskProgressPayload, maxItems = 200 ) { const eventId = String(progress.event_id || "").trim(); const sequence = String(progress.sequence || "").trim(); const key = eventId ? `event:${eventId}` : sequence ? `sequence:${sequence}` : [ String(progress.step_code || ""), String(progress.step_state || ""), String(progress.message || progress.display || ""), ].join("|"); const next = current.some((item) => { const itemEventId = String(item.event_id || "").trim(); const itemSequence = String(item.sequence || "").trim(); const itemKey = itemEventId ? `event:${itemEventId}` : itemSequence ? `sequence:${itemSequence}` : [ String(item.step_code || ""), String(item.step_state || ""), String(item.message || item.display || ""), ].join("|"); return itemKey === key; }) ? current : [...current, progress]; return next.slice(-Math.max(1, maxItems)); }