118 lines
3.1 KiB
JavaScript
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(),
|
|
};
|
|
}
|