openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
136 lines (135 loc) • 6.13 kB
JavaScript
import { r as asNullableRecord } from "./record-coerce-DItp3I4t.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { a as getNodeSqliteKysely, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { i as openOpenClawStateDatabase, s as runOpenClawStateWriteTransaction } from "./openclaw-state-db-BRTnL-D8.js";
import { a as sha256Hex } from "./crypto-digest-C4hqTb_e.js";
import { a as updateConfigMachineState, r as readConfigMachineState } from "./config-machine-state-BCereLZr.js";
import { n as withOpenClawStateLease } from "./openclaw-state-lease-Ciyr2uVu.js";
import { o as databaseOptions, s as ensureSkillWorkshopSchema } from "./store-sqlite-record-9Z9Ph1pe.js";
import path from "node:path";
import { randomUUID } from "node:crypto";
//#region src/skills/workshop/collection-review-state.ts
const REVIEW_CLAIM_MS = 66e4;
const SKILL_COLLECTION_REVIEW_RETENTION_COUNT = 90;
const SKILL_COLLECTION_REVIEW_HISTORY_LIMIT = 20;
function workspaceKey(workspaceDir) {
return sha256Hex(path.resolve(workspaceDir));
}
async function withSkillCollectionReviewClaim(workspaceDir, run, options = {}) {
return await withOpenClawStateLease({
scope: "skill-collection-review",
key: workspaceKey(workspaceDir),
database: {
scope: "shared",
options
},
leaseMs: REVIEW_CLAIM_MS,
waitMs: 0,
leaseLabel: "skill collection review claim",
operationLabel: "skill-collection.review"
}, async () => await run());
}
function reviewMap(state, field) {
return asNullableRecord(state[field]) ?? {};
}
function readReviewState(options) {
return asNullableRecord(readConfigMachineState("skills.curatorState", options)?.lastResult) ?? {};
}
function readSkillReviewOutcomes(options = {}) {
const state = readReviewState(options);
return {
collectionReviews: reviewMap(state, "collectionReviews"),
experienceReviews: reviewMap(state, "experienceReviews")
};
}
function recordSkillCollectionReviewStatus(workspaceDir, review, options = {}) {
const status = review.error !== void 0 ? {
attemptedAtMs: review.attemptedAtMs,
error: formatErrorMessage(review.error).slice(0, 300)
} : {
attemptedAtMs: review.attemptedAtMs,
...review.succeededAtMs !== void 0 ? { succeededAtMs: review.succeededAtMs } : {}
};
recordWorkspaceReview("collectionReviews", workspaceDir, status, {
lastAttemptAtMs: status.attemptedAtMs,
...status.succeededAtMs !== void 0 ? { lastSuccessAtMs: status.succeededAtMs } : {},
lastError: status.error ?? null
}, options);
}
function recordSkillExperienceReviewOutcome(workspaceDir, review, options = {}) {
recordWorkspaceReview("experienceReviews", workspaceDir, review, {}, options);
}
function recordWorkspaceReview(field, workspaceDir, review, columns, options) {
updateConfigMachineState("skills.curatorState", (current) => {
const state = asNullableRecord(current?.lastResult) ?? {};
return {
lastAttemptAtMs: 0,
lastSuccessAtMs: null,
lastError: null,
...current,
...columns,
lastResult: {
...state,
[field]: {
...asNullableRecord(state[field]),
[workspaceKey(workspaceDir)]: review
}
}
};
}, options);
}
function parseStoredNames(value, field) {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed) || !parsed.every((entry) => typeof entry === "string")) throw new Error(`Invalid ${field} in stored skill collection review.`);
return parsed;
}
function parseStoredDrops(value) {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) throw new Error("Invalid dropped entries in stored skill collection review.");
return parsed.map((entry) => {
const record = asNullableRecord(entry);
if (!record || typeof record.name !== "string" || typeof record.reason !== "string") throw new Error("Invalid dropped entry in stored skill collection review.");
return {
name: record.name,
reason: record.reason
};
});
}
function listSkillCollectionReviewOutcomes(workspaceDir, options = {}) {
ensureSkillWorkshopSchema(options);
const database = openOpenClawStateDatabase(databaseOptions(options));
const kysely = getNodeSqliteKysely(database.db);
return executeSqliteQuerySync(database.db, kysely.selectFrom("skill_workshop_collection_reviews").select([
"backup_id",
"create_time",
"kept_names_json",
"written_names_json",
"dropped_json"
]).where("workspace_dir", "=", path.resolve(workspaceDir)).orderBy("create_time", "desc").orderBy("review_id", "desc").limit(SKILL_COLLECTION_REVIEW_HISTORY_LIMIT)).rows.map((row) => ({
createTime: row.create_time,
backupId: row.backup_id,
kept: parseStoredNames(row.kept_names_json, "kept names"),
written: parseStoredNames(row.written_names_json, "written names"),
dropped: parseStoredDrops(row.dropped_json)
}));
}
function recordSkillCollectionReviewHistory(workspaceDir, nowMs, result, options = {}) {
ensureSkillWorkshopSchema(options);
runOpenClawStateWriteTransaction(({ db }) => {
const kysely = getNodeSqliteKysely(db);
const resolvedWorkspaceDir = path.resolve(workspaceDir);
executeSqliteQuerySync(db, kysely.insertInto("skill_workshop_collection_reviews").values({
review_id: randomUUID(),
workspace_dir: resolvedWorkspaceDir,
backup_id: result.backupId,
create_time: nowMs,
kept_names_json: JSON.stringify(result.kept),
written_names_json: JSON.stringify(result.written),
dropped_json: JSON.stringify(result.dropped)
}));
const retainedReviewIds = kysely.selectFrom("skill_workshop_collection_reviews").select("review_id").where("workspace_dir", "=", resolvedWorkspaceDir).orderBy("create_time", "desc").orderBy("review_id", "desc").limit(SKILL_COLLECTION_REVIEW_RETENTION_COUNT);
executeSqliteQuerySync(db, kysely.deleteFrom("skill_workshop_collection_reviews").where("workspace_dir", "=", resolvedWorkspaceDir).where("review_id", "not in", retainedReviewIds));
}, databaseOptions(options));
}
//#endregion
export { recordSkillExperienceReviewOutcome as a, recordSkillCollectionReviewStatus as i, readSkillReviewOutcomes as n, withSkillCollectionReviewClaim as o, recordSkillCollectionReviewHistory as r, listSkillCollectionReviewOutcomes as t };