103 lines
3.3 KiB
JavaScript
103 lines
3.3 KiB
JavaScript
function normalizeNumber(value, fallback) {
|
|
const numeric = Number(value);
|
|
return Number.isFinite(numeric) ? numeric : fallback;
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
|
|
function formatElapsedSeconds(seconds) {
|
|
const safeSeconds = Math.max(0, Math.floor(seconds));
|
|
if (safeSeconds < 60) {
|
|
return `${safeSeconds} 秒`;
|
|
}
|
|
const minutes = Math.floor(safeSeconds / 60);
|
|
const remainingSeconds = safeSeconds % 60;
|
|
return remainingSeconds > 0 ? `${minutes} 分 ${remainingSeconds} 秒` : `${minutes} 分钟`;
|
|
}
|
|
|
|
function normalizeStepStatus(value, fallback = "pending") {
|
|
return value === "done" || value === "running" || value === "failed" || value === "pending"
|
|
? value
|
|
: fallback;
|
|
}
|
|
|
|
function normalizeSteps(steps) {
|
|
if (!Array.isArray(steps)) {
|
|
return [];
|
|
}
|
|
return steps
|
|
.map((step, index) => {
|
|
const text = typeof step?.text === "string" ? step.text.trim() : "";
|
|
if (!text) {
|
|
return null;
|
|
}
|
|
return {
|
|
id: typeof step?.id === "string" && step.id.trim() ? step.id.trim() : `step-${index + 1}`,
|
|
text,
|
|
status: normalizeStepStatus(step?.status),
|
|
};
|
|
})
|
|
.filter(Boolean)
|
|
.slice(0, 10);
|
|
}
|
|
|
|
function buildDefaultLongRunningSteps(elapsedSeconds) {
|
|
const elapsedText = formatElapsedSeconds(elapsedSeconds);
|
|
return [
|
|
{ id: "receive-task", text: "接收对话任务", status: "done" },
|
|
{ id: "locate-thread", text: "定位目标 Codex 线程", status: "done" },
|
|
{ id: "write-desktop-thread", text: "写入 Codex 桌面线程记录", status: "done" },
|
|
{ id: "await-thread-reply", text: `等待目标线程回复,已等待 ${elapsedText}`, status: "running" },
|
|
{ id: "write-back-boss", text: "回写 Boss 对话窗口", status: "pending" },
|
|
];
|
|
}
|
|
|
|
export function normalizeLongRunningProgressIntervalMs(value) {
|
|
const numeric = normalizeNumber(value, 20_000);
|
|
if (numeric <= 0) {
|
|
return 0;
|
|
}
|
|
return clamp(Math.floor(numeric), 5_000, 60_000);
|
|
}
|
|
|
|
export function buildLongRunningCodexProgressSnapshot({
|
|
task = {},
|
|
startedAtMs,
|
|
nowMs = Date.now(),
|
|
phase = "awaiting_reply",
|
|
baseProgress,
|
|
heartbeatCount = 0,
|
|
} = {}) {
|
|
const started = normalizeNumber(startedAtMs, nowMs);
|
|
const elapsedSeconds = Math.max(0, Math.round((nowMs - started) / 1000));
|
|
const liveSteps = normalizeSteps(baseProgress?.steps);
|
|
const steps = liveSteps.length > 0 ? liveSteps : buildDefaultLongRunningSteps(elapsedSeconds);
|
|
const warnings = Array.isArray(baseProgress?.warnings)
|
|
? baseProgress.warnings.filter(Boolean).slice(0, 8)
|
|
: [];
|
|
if (!warnings.some((warning) => warning?.id === "codex-turn-long-running")) {
|
|
warnings.unshift({
|
|
id: "codex-turn-long-running",
|
|
severity: "info",
|
|
message: `Codex 桌面线程仍在执行,已等待 ${formatElapsedSeconds(elapsedSeconds)}。`,
|
|
});
|
|
}
|
|
|
|
return {
|
|
...(baseProgress && typeof baseProgress === "object" ? baseProgress : {}),
|
|
phase,
|
|
status: "running",
|
|
steps,
|
|
warnings,
|
|
longRunning: {
|
|
taskId: typeof task?.taskId === "string" ? task.taskId : undefined,
|
|
targetThreadDisplayName:
|
|
typeof task?.targetThreadDisplayName === "string" ? task.targetThreadDisplayName : undefined,
|
|
elapsedSeconds,
|
|
heartbeatCount: Math.max(0, Math.floor(normalizeNumber(heartbeatCount, 0))),
|
|
},
|
|
};
|
|
}
|