openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
96 lines (95 loc) • 5.15 kB
JavaScript
import { a as asOptionalRecord } from "../../record-coerce-DItp3I4t.js";
import "../../string-coerce-runtime-GQa0ehRA.js";
//#region extensions/crabbox/doctor-contract-api.ts
const imageFields = /* @__PURE__ */ new Set([
"checkpointId",
"kind",
"state",
"createdAtMs",
"lastUsedAtMs",
"baseCommit",
"operation"
]);
const captureFields = /* @__PURE__ */ new Set([
"type",
"id",
"startedAtMs",
"leaseId",
"provider",
"phase"
]);
const retirementFields = /* @__PURE__ */ new Set(["type", "checkpointId"]);
const nonempty = (value) => typeof value === "string" && value.trim().length > 0;
const timestamp = (value) => typeof value === "number" && Number.isFinite(value);
function isLegacyOperation(value) {
if (value === void 0) return true;
const operation = asOptionalRecord(value);
if (!operation) return false;
if (operation.type === "retire") return Object.keys(operation).every((key) => retirementFields.has(key)) && nonempty(operation.checkpointId);
return operation.type === "capture" && Object.keys(operation).every((key) => captureFields.has(key)) && nonempty(operation.id) && timestamp(operation.startedAtMs) && (operation.leaseId === void 0 || nonempty(operation.leaseId)) && (operation.provider === void 0 || nonempty(operation.provider)) && (operation.phase === "scrubbing" || operation.phase === "creating" || operation.phase === "uncertain");
}
function isLegacyImage(value) {
const image = asOptionalRecord(value);
return Boolean(image && Object.keys(image).every((key) => imageFields.has(key)) && typeof image.checkpointId === "string" && typeof image.kind === "string" && (image.state === "pending" || image.state === "available") && timestamp(image.createdAtMs) && timestamp(image.lastUsedAtMs) && (image.baseCommit === void 0 || nonempty(image.baseCommit)) && (image.checkpointId ? nonempty(image.checkpointId) && nonempty(image.kind) : image.kind === "" && image.state === "pending") && isLegacyOperation(image.operation));
}
const stateMigrations = [{
id: "crabbox-warm-profile-v2",
label: "Crabbox warm profiles",
doctorOnly: true,
async detectLegacyState({ context, env }) {
const { WARM_IMAGE_MAX_ENTRIES, listCrabboxLegacyWarmLeases } = await import("../../crabbox-worker-warm-image-store-B5GxQliq.js");
const pending = (await context.openPluginStateKeyedStore({
namespace: "warm-images",
maxEntries: WARM_IMAGE_MAX_ENTRIES,
overflowPolicy: "reject-new"
}).entries()).filter(({ value }) => asOptionalRecord(value)?.version !== 2).length;
const leases = listCrabboxLegacyWarmLeases(env);
return pending || leases.length ? { preview: [...pending ? [`- ${pending} legacy Crabbox warm-image row(s) require profile-envelope migration.`] : [], ...leases.map((lease) => `- Legacy Crabbox lease ${lease.leaseId} requires provider cleanup acknowledgement: openclaw crabbox warm-images --recover ${lease.selector} --acknowledge-provider-cleanup`)] } : null;
},
async migrateLegacyState({ context, env }) {
const { WARM_IMAGE_MAX_ENTRIES, crabboxLegacyWarmImageCaptureSelector, listCrabboxLegacyWarmLeases } = await import("../../crabbox-worker-warm-image-store-B5GxQliq.js");
const changes = [];
const warnings = [];
const store = context.openPluginStateKeyedStore({
namespace: "warm-images",
maxEntries: WARM_IMAGE_MAX_ENTRIES,
overflowPolicy: "reject-new"
});
for (const { key, value } of await store.entries()) {
if (asOptionalRecord(value)?.version === 2) continue;
if (!isLegacyImage(value)) {
warnings.push(`Crabbox warm profile ${key} has an unsupported record; left it unchanged for manual repair.`);
continue;
}
const { operation, ...image } = value;
const migrated = {
version: 2,
...image.checkpointId ? { image } : {},
allocations: {},
...operation ? { operation } : !image.checkpointId ? { operation: {
type: "capture",
id: crabboxLegacyWarmImageCaptureSelector(key, value),
startedAtMs: image.createdAtMs,
phase: "uncertain"
} } : {}
};
try {
if (await store.update?.(key, (current) => JSON.stringify(current) === JSON.stringify(value) ? migrated : void 0)) changes.push(`Migrated Crabbox warm profile ${key} without inventing an allocation choice.`);
else warnings.push(`Crabbox warm profile ${key} changed or atomic update is unavailable; left it unchanged.`);
} catch (error) {
warnings.push(`Failed migrating Crabbox warm profile ${key}; left it unchanged: ${String(error)}`);
}
}
const leases = listCrabboxLegacyWarmLeases(env);
if (leases.length) {
warnings.push(`${leases.length} legacy Crabbox lease row(s) still block warm-profile admission; their original cold/checkpoint choices are unknown.`);
for (const lease of leases) warnings.push(`Resolve lease ${lease.leaseId} through its original Gateway or provider, stop the original Gateway/capture processes, and confirm provider cleanup before running: openclaw crabbox warm-images --recover ${lease.selector} --acknowledge-provider-cleanup. Then rerun openclaw doctor --fix. The row was not changed.`);
}
return {
changes,
warnings
};
}
}];
//#endregion
export { stateMigrations };