mirror of https://github.com/openclaw/openclaw.git
155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import type { RunRecord, RunState, TerminationReason } from "./types.js";
|
|
|
|
function nowMs() {
|
|
return Date.now();
|
|
}
|
|
|
|
const DEFAULT_MAX_EXITED_RECORDS = 2_000;
|
|
|
|
function resolveMaxExitedRecords(value?: number): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
return DEFAULT_MAX_EXITED_RECORDS;
|
|
}
|
|
return Math.max(1, Math.floor(value));
|
|
}
|
|
|
|
export type RunRegistry = {
|
|
add: (record: RunRecord) => void;
|
|
get: (runId: string) => RunRecord | undefined;
|
|
list: () => RunRecord[];
|
|
listByScope: (scopeKey: string) => RunRecord[];
|
|
updateState: (
|
|
runId: string,
|
|
state: RunState,
|
|
patch?: Partial<Pick<RunRecord, "pid" | "terminationReason" | "exitCode" | "exitSignal">>,
|
|
) => RunRecord | undefined;
|
|
touchOutput: (runId: string) => void;
|
|
finalize: (
|
|
runId: string,
|
|
exit: {
|
|
reason: TerminationReason;
|
|
exitCode: number | null;
|
|
exitSignal: NodeJS.Signals | number | null;
|
|
},
|
|
) => { record: RunRecord; firstFinalize: boolean } | null;
|
|
delete: (runId: string) => void;
|
|
};
|
|
|
|
export function createRunRegistry(options?: { maxExitedRecords?: number }): RunRegistry {
|
|
const records = new Map<string, RunRecord>();
|
|
const maxExitedRecords = resolveMaxExitedRecords(options?.maxExitedRecords);
|
|
|
|
const pruneExitedRecords = () => {
|
|
if (!records.size) {
|
|
return;
|
|
}
|
|
let exited = 0;
|
|
for (const record of records.values()) {
|
|
if (record.state === "exited") {
|
|
exited += 1;
|
|
}
|
|
}
|
|
if (exited <= maxExitedRecords) {
|
|
return;
|
|
}
|
|
let remove = exited - maxExitedRecords;
|
|
for (const [runId, record] of records.entries()) {
|
|
if (remove <= 0) {
|
|
break;
|
|
}
|
|
if (record.state !== "exited") {
|
|
continue;
|
|
}
|
|
records.delete(runId);
|
|
remove -= 1;
|
|
}
|
|
};
|
|
|
|
const add: RunRegistry["add"] = (record) => {
|
|
records.set(record.runId, { ...record });
|
|
};
|
|
|
|
const get: RunRegistry["get"] = (runId) => {
|
|
const record = records.get(runId);
|
|
return record ? { ...record } : undefined;
|
|
};
|
|
|
|
const list: RunRegistry["list"] = () => {
|
|
return Array.from(records.values()).map((record) => ({ ...record }));
|
|
};
|
|
|
|
const listByScope: RunRegistry["listByScope"] = (scopeKey) => {
|
|
if (!scopeKey.trim()) {
|
|
return [];
|
|
}
|
|
return Array.from(records.values())
|
|
.filter((record) => record.scopeKey === scopeKey)
|
|
.map((record) => ({ ...record }));
|
|
};
|
|
|
|
const updateState: RunRegistry["updateState"] = (runId, state, patch) => {
|
|
const current = records.get(runId);
|
|
if (!current) {
|
|
return undefined;
|
|
}
|
|
const updatedAtMs = nowMs();
|
|
const next: RunRecord = {
|
|
...current,
|
|
...patch,
|
|
state,
|
|
updatedAtMs,
|
|
lastOutputAtMs: current.lastOutputAtMs,
|
|
};
|
|
records.set(runId, next);
|
|
return { ...next };
|
|
};
|
|
|
|
const touchOutput: RunRegistry["touchOutput"] = (runId) => {
|
|
const current = records.get(runId);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
const ts = nowMs();
|
|
records.set(runId, {
|
|
...current,
|
|
lastOutputAtMs: ts,
|
|
updatedAtMs: ts,
|
|
});
|
|
};
|
|
|
|
const finalize: RunRegistry["finalize"] = (runId, exit) => {
|
|
const current = records.get(runId);
|
|
if (!current) {
|
|
return null;
|
|
}
|
|
const firstFinalize = current.state !== "exited";
|
|
const ts = nowMs();
|
|
const next: RunRecord = {
|
|
...current,
|
|
state: "exited",
|
|
terminationReason: current.terminationReason ?? exit.reason,
|
|
exitCode: current.exitCode !== undefined ? current.exitCode : exit.exitCode,
|
|
exitSignal: current.exitSignal !== undefined ? current.exitSignal : exit.exitSignal,
|
|
updatedAtMs: ts,
|
|
};
|
|
records.set(runId, next);
|
|
pruneExitedRecords();
|
|
return { record: { ...next }, firstFinalize };
|
|
};
|
|
|
|
const del: RunRegistry["delete"] = (runId) => {
|
|
records.delete(runId);
|
|
};
|
|
|
|
return {
|
|
add,
|
|
get,
|
|
list,
|
|
listByScope,
|
|
updateState,
|
|
touchOutput,
|
|
finalize,
|
|
delete: del,
|
|
};
|
|
}
|