mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
Share documentation translation response cleanup, simplify sorted translation-memory JSONL writing, use canonical Node inspector types, and remove redundant benchmark and preview-fixture state. Preserve CLI flags, exit codes, output formats, generated bytes, fixture responses, and benchmark workloads. Focused tests, exact before/after JSONL and mock-response checks, and both import-cycle gates passed. The optional cron-reaper smoke reproduced the same existing worker timeout before and after the refactor. Release notes: internal tooling cleanup only. Hosted CI exception: run 36661925129 failed before tests because the trusted CI harness omitted ci-build-manifest.mjs. Current unrelated run 36663040449 failed identically. Security/dependency review and security-fast passed; skipped CI lanes remain unrun. Merged under the maintainer-authorized pre-existing-failure exception.
276 lines
9.4 KiB
TypeScript
276 lines
9.4 KiB
TypeScript
import type {
|
|
SkillsProposalEvaluateResult,
|
|
SkillsProposalInspectResult,
|
|
SkillsProposalRecordResult,
|
|
SkillsProposalsListResult,
|
|
} from "../packages/gateway-protocol/src/schema/agents-models-skills.js";
|
|
import type { ControlUiMockGateway } from "../ui/src/test-helpers/control-ui-e2e.ts";
|
|
|
|
export function buildSkillWorkshopMocks(baseTime: number) {
|
|
const hour = 60 * 60 * 1000;
|
|
const day = 24 * hour;
|
|
const proposals = [
|
|
{
|
|
id: "prop-release-tweets",
|
|
kind: "update",
|
|
status: "pending",
|
|
title: "Tighten release tweet drafting",
|
|
description: "Capture the changelog-to-tweet flow the agent keeps re-deriving.",
|
|
skillName: "release-tweets",
|
|
skillKey: "release-tweets",
|
|
createdAt: new Date(baseTime - 2 * hour).toISOString(),
|
|
updatedAt: new Date(baseTime - hour).toISOString(),
|
|
scanState: "clean",
|
|
},
|
|
{
|
|
id: "prop-crawler-etiquette",
|
|
kind: "create",
|
|
status: "pending",
|
|
title: "Add crawler etiquette skill",
|
|
description: "Rate limits and robots.txt handling learned during the docs sweep.",
|
|
skillName: "crawler-etiquette",
|
|
skillKey: "crawler-etiquette",
|
|
createdAt: new Date(baseTime - 3 * day).toISOString(),
|
|
updatedAt: new Date(baseTime - 2 * day).toISOString(),
|
|
scanState: "clean",
|
|
},
|
|
{
|
|
id: "prop-changelog-style",
|
|
kind: "update",
|
|
status: "applied",
|
|
title: "Changelog bullet style",
|
|
description: "One bullet per entry, no hard wraps.",
|
|
skillName: "changelog-style",
|
|
skillKey: "changelog-style",
|
|
createdAt: new Date(baseTime - 6 * day).toISOString(),
|
|
updatedAt: new Date(baseTime - 5 * day).toISOString(),
|
|
scanState: "clean",
|
|
},
|
|
] satisfies SkillsProposalsListResult["proposals"];
|
|
const revisionHash = "b".repeat(64);
|
|
const recordFor = (proposal: (typeof proposals)[number]): SkillsProposalRecordResult => ({
|
|
schema: "openclaw.skill-workshop.proposal.v1",
|
|
id: proposal.id,
|
|
kind: proposal.kind,
|
|
status: proposal.status,
|
|
title: proposal.title,
|
|
description: proposal.description,
|
|
createdAt: proposal.createdAt,
|
|
updatedAt: proposal.updatedAt,
|
|
createdBy: "skill-workshop",
|
|
proposedVersion: "2",
|
|
draftFile: "PROPOSAL.md",
|
|
draftHash: "a".repeat(64),
|
|
target: {
|
|
source: "openclaw-workshop",
|
|
skillName: proposal.skillName,
|
|
skillKey: proposal.skillKey,
|
|
skillDir: `.agents/skills/${proposal.skillKey}`,
|
|
skillFile: `.agents/skills/${proposal.skillKey}/SKILL.md`,
|
|
},
|
|
scan: {
|
|
state: proposal.scanState,
|
|
scannedAt: new Date(baseTime - hour).toISOString(),
|
|
critical: 0,
|
|
warn: 0,
|
|
info: 0,
|
|
findings: [],
|
|
},
|
|
});
|
|
const evaluation: SkillsProposalEvaluateResult["evaluation"] = {
|
|
id: "evaluation-control-ui-mock",
|
|
proposedVersion: "2",
|
|
revisionHash,
|
|
trigger: "manual",
|
|
startedAt: new Date(baseTime - 20_000).toISOString(),
|
|
completedAt: new Date(baseTime - 18_000).toISOString(),
|
|
outcomes: [
|
|
{
|
|
pluginId: "fixture-quality",
|
|
pluginVersion: "1.0.0",
|
|
evaluatorId: "readability",
|
|
status: "completed",
|
|
result: {
|
|
summary: "The workflow is bounded and includes a recovery step.",
|
|
decision: "pass",
|
|
decisionReason: "No blocking findings in the sanitized fixture.",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
return {
|
|
list: {
|
|
schema: "openclaw.skill-workshop.proposals-manifest.v1",
|
|
updatedAt: new Date(baseTime - hour).toISOString(),
|
|
proposals,
|
|
installedSkills: proposals
|
|
.filter((proposal) => proposal.status === "applied")
|
|
.map((proposal) => ({
|
|
name: proposal.skillName,
|
|
skillKey: proposal.skillKey,
|
|
description: proposal.description,
|
|
})),
|
|
} satisfies SkillsProposalsListResult,
|
|
inspect: {
|
|
cases: proposals.map((proposal) => ({
|
|
match: { proposalId: proposal.id },
|
|
response: {
|
|
record: {
|
|
...recordFor(proposal),
|
|
...(proposal.id === "prop-release-tweets" ? { evaluation } : {}),
|
|
},
|
|
revisionHash,
|
|
content: [
|
|
`# ${proposal.title}`,
|
|
"",
|
|
proposal.description,
|
|
"",
|
|
"## Steps",
|
|
"1. Gather the source material.",
|
|
"2. Apply the documented workflow.",
|
|
].join("\n"),
|
|
supportFiles: [],
|
|
},
|
|
})),
|
|
},
|
|
evaluation,
|
|
requestRevision: { runId: "skill-workshop-revision-mock", status: "started" },
|
|
};
|
|
}
|
|
|
|
/** Each agent's proposal records own both mutation replies and subsequent reads. */
|
|
function installSkillWorkshopMock(seed: ReturnType<typeof buildSkillWorkshopMocks>): void {
|
|
const gateway = (window as Window & { openclawControlUiE2eGateway?: ControlUiMockGateway })
|
|
.openclawControlUiE2eGateway;
|
|
if (!gateway) {
|
|
return;
|
|
}
|
|
const scopes = new Map<
|
|
string,
|
|
{
|
|
list: SkillsProposalsListResult;
|
|
details: Map<string, SkillsProposalInspectResult>;
|
|
}
|
|
>();
|
|
for (const method of [
|
|
"list",
|
|
"inspect",
|
|
"evaluate",
|
|
"apply",
|
|
"reject",
|
|
"historyStatus",
|
|
"historyScan",
|
|
"read",
|
|
]) {
|
|
gateway.setRequestHandler(
|
|
method === "read" ? "skills.workshop.read" : `skills.proposals.${method}`,
|
|
({ params: input, respond }) => {
|
|
const reject = (message: string) =>
|
|
respond({ __mockError: { code: "INVALID_REQUEST", message } });
|
|
if (method === "historyStatus" || method === "historyScan") {
|
|
return reject(
|
|
"Historical batch scans are retired. Start a learning session from Workshop to review past conversations.",
|
|
);
|
|
}
|
|
const params = (input ?? {}) as {
|
|
agentId?: string;
|
|
proposalId?: string;
|
|
expectedRevisionHash?: string;
|
|
name?: string;
|
|
};
|
|
const agentId = params.agentId ?? "main";
|
|
let scope = scopes.get(agentId);
|
|
if (!scope) {
|
|
scope = {
|
|
list: structuredClone(seed.list),
|
|
details: new Map(
|
|
seed.inspect.cases.map((entry) => [
|
|
entry.match.proposalId,
|
|
structuredClone(entry.response),
|
|
]),
|
|
),
|
|
};
|
|
scopes.set(agentId, scope);
|
|
}
|
|
if (method === "list") {
|
|
respond(scope.list);
|
|
return;
|
|
}
|
|
if (method === "read") {
|
|
const installed = scope.list.installedSkills.find((skill) => skill.name === params.name);
|
|
const detail =
|
|
installed &&
|
|
Array.from(scope.details.values()).find(
|
|
(candidate) =>
|
|
candidate.record.status === "applied" &&
|
|
candidate.record.target.skillKey === installed.skillKey,
|
|
);
|
|
if (!installed || !detail) {
|
|
return reject("Mock Workshop skill not found.");
|
|
}
|
|
respond({ ...installed, content: detail.content });
|
|
return;
|
|
}
|
|
const detail = params.proposalId ? scope.details.get(params.proposalId) : undefined;
|
|
if (!detail) {
|
|
return reject("Unknown mock proposal; refresh the Workshop.");
|
|
}
|
|
if (method === "inspect") {
|
|
respond(detail);
|
|
return;
|
|
}
|
|
if (detail.record.status !== "pending") {
|
|
return reject("Only pending proposals can be evaluated, applied, or rejected.");
|
|
}
|
|
if (params.expectedRevisionHash && params.expectedRevisionHash !== detail.revisionHash) {
|
|
return reject("The proposal revision changed; refresh the proposal before retrying.");
|
|
}
|
|
const now = new Date().toISOString();
|
|
detail.record.updatedAt = now;
|
|
if (method === "evaluate") {
|
|
const evaluation = structuredClone(seed.evaluation);
|
|
evaluation.startedAt = now;
|
|
evaluation.completedAt = now;
|
|
detail.record.evaluation = evaluation;
|
|
} else if (method === "apply") {
|
|
detail.record.status = "applied";
|
|
detail.record.appliedAt = now;
|
|
const skillKey = detail.record.target.skillKey;
|
|
const installed = {
|
|
name: detail.record.kind === "create" ? skillKey : detail.record.target.skillName,
|
|
skillKey,
|
|
description: detail.record.description,
|
|
};
|
|
scope.list.installedSkills = [
|
|
...scope.list.installedSkills.filter((skill) => skill.skillKey !== skillKey),
|
|
installed,
|
|
];
|
|
} else {
|
|
detail.record.status = "rejected";
|
|
detail.record.rejectedAt = now;
|
|
}
|
|
const entry = scope.list.proposals.find((proposal) => proposal.id === detail.record.id);
|
|
if (entry) {
|
|
entry.status = detail.record.status;
|
|
entry.updatedAt = now;
|
|
entry.revisionHash = detail.revisionHash;
|
|
}
|
|
scope.list.updatedAt = now;
|
|
if (method === "evaluate") {
|
|
respond({ record: detail.record, evaluation: detail.record.evaluation });
|
|
} else if (method === "apply") {
|
|
respond({
|
|
record: detail.record,
|
|
targetSkillFile: `.agents/skills/${detail.record.target.skillKey}/SKILL.md`,
|
|
});
|
|
} else {
|
|
respond(detail.record);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
export function skillWorkshopMockInitScript(baseTime: number): string {
|
|
return `(() => { const __name = (target) => target; (${installSkillWorkshopMock.toString()})(${JSON.stringify(buildSkillWorkshopMocks(baseTime))}); })();`;
|
|
}
|