ai-chatbot/lib/openclaw-chat/task-state.ts

134 lines
4.1 KiB
TypeScript

export const CLAWD_TASK_KIND = "clawd_chat";
export type ClawdTaskStatus =
| "queued"
| "running"
| "completed"
| "failed"
| "canceled";
export type ClawdTaskProgressPayload = Record<string, unknown>;
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;
upstreamTaskId?: string | null;
upstreamTaskKey?: string | null;
upstreamEventsEndpoint?: string | null;
upstreamLastEventId?: number | null;
createdAt: number;
updatedAt: number;
};
const CLAWD_TASK_STATUSES = new Set<ClawdTaskStatus>([
"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<string, unknown>;
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);
const upstreamLastEventId = Number(raw.upstreamLastEventId);
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,
upstreamTaskId: raw.upstreamTaskId ? String(raw.upstreamTaskId) : null,
upstreamTaskKey: raw.upstreamTaskKey ? String(raw.upstreamTaskKey) : null,
upstreamEventsEndpoint: raw.upstreamEventsEndpoint
? String(raw.upstreamEventsEndpoint)
: null,
upstreamLastEventId: Number.isFinite(upstreamLastEventId)
? Math.max(0, Math.trunc(upstreamLastEventId))
: 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));
}