Files
boss/local-agent/heartbeat-project-snapshot.mjs
2026-06-08 12:22:50 +08:00

118 lines
3.1 KiB
JavaScript

function asArray(value) {
return Array.isArray(value) ? value : [];
}
function normalizeProjectCandidate(candidate) {
if (!candidate || typeof candidate !== "object") {
return null;
}
return { ...candidate };
}
function normalizeProjects(value) {
return asArray(value)
.map((item) => String(item ?? "").trim())
.filter(Boolean);
}
function normalizeProjectCandidates(value) {
return asArray(value)
.map(normalizeProjectCandidate)
.filter(Boolean);
}
function normalizeTimeoutMs(value, fallback = 3_500) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric <= 0) {
return fallback;
}
return Math.max(500, Math.min(30_000, Math.round(numeric)));
}
function normalizeHeartbeatProjects(value = {}) {
return {
projects: normalizeProjects(value.projects),
projectCandidates: normalizeProjectCandidates(value.projectCandidates),
guiConnected: value.guiConnected === true,
};
}
function shouldUseSnapshot(config = {}, runtime = {}) {
if (config.codexSessionDiscoveryWhileMasterTaskBusy === true) {
return false;
}
return runtime.masterTaskBusy === true || runtime.activeMasterTask?.status === "running";
}
export function resolveHeartbeatProjectsFromSnapshot({ config = {}, runtime = {} } = {}) {
if (!shouldUseSnapshot(config, runtime)) {
return { shouldUseSnapshot: false };
}
const snapshot = runtime.lastHeartbeatProjectsSnapshot;
if (snapshot && typeof snapshot === "object") {
return {
shouldUseSnapshot: true,
projects: normalizeProjects(snapshot.projects),
projectCandidates: normalizeProjectCandidates(snapshot.projectCandidates),
guiConnected: snapshot.guiConnected === true,
};
}
return {
shouldUseSnapshot: true,
projects: normalizeProjects(config.projects),
projectCandidates: normalizeProjectCandidates(config.projectCandidates),
guiConnected: false,
};
}
export async function runHeartbeatProjectDiscoveryWithTimeout({
timeoutMs,
fallback = {},
discover,
} = {}) {
if (typeof discover !== "function") {
throw new TypeError("discover must be a function");
}
const effectiveTimeoutMs = normalizeTimeoutMs(timeoutMs);
let timeout;
let timedOut = false;
try {
const value = await Promise.race([
Promise.resolve().then(discover),
new Promise((_, reject) => {
timeout = setTimeout(() => {
timedOut = true;
reject(new Error("CODEX_SESSION_DISCOVERY_TIMEOUT"));
}, effectiveTimeoutMs);
}),
]);
return {
timedOut: false,
value: normalizeHeartbeatProjects(value),
};
} catch (error) {
return {
timedOut,
error,
value: normalizeHeartbeatProjects(fallback),
};
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
}
export function storeHeartbeatProjectsSnapshot(runtime, heartbeatProjects = {}) {
if (!runtime || typeof runtime !== "object") {
return;
}
const snapshot = normalizeHeartbeatProjects(heartbeatProjects);
runtime.lastHeartbeatProjectsSnapshot = {
...snapshot,
capturedAt: new Date().toISOString(),
};
}