mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 17:14:06 +00:00
fix: show skill proposal support files on inspect
This commit is contained in:
@@ -104,6 +104,12 @@ describe("SkillsProposalInspectResultSchema", () => {
|
||||
],
|
||||
},
|
||||
content: "# Weather Helper\n",
|
||||
supportFiles: [
|
||||
{
|
||||
path: "references/weather.md",
|
||||
content: "Use current weather before recommendations.\n",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(Value.Check(SkillsProposalInspectResultSchema, result)).toBe(true);
|
||||
|
||||
@@ -628,6 +628,7 @@ export const SkillsProposalInspectResultSchema = Type.Object(
|
||||
{
|
||||
record: SkillProposalRecordSchema,
|
||||
content: Type.String(),
|
||||
supportFiles: Type.Optional(Type.Array(SkillProposalSupportFileInputSchema, { maxItems: 64 })),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
@@ -158,6 +158,21 @@ describe("skill_research tool", () => {
|
||||
expect((inspected.details as { proposalContent: string }).proposalContent).toContain(
|
||||
"Check weather, alerts, and timing.",
|
||||
);
|
||||
expect((inspected.content[0] as { text: string }).text).toContain(
|
||||
"--- references/weather.md ---",
|
||||
);
|
||||
expect(
|
||||
(
|
||||
inspected.details as {
|
||||
supportFiles: Array<{ path: string; content: string }>;
|
||||
}
|
||||
).supportFiles,
|
||||
).toEqual([
|
||||
{
|
||||
path: "references/weather.md",
|
||||
content: "Use weather API details and current alerts.\n",
|
||||
},
|
||||
]);
|
||||
|
||||
const revisedByName = await tool.execute("call-5", {
|
||||
action: "revise",
|
||||
|
||||
@@ -287,6 +287,9 @@ function proposalResult(
|
||||
scanState: proposal.record.scan.state,
|
||||
proposedVersion: proposal.record.proposedVersion,
|
||||
...(options.includeContent ? { proposalContent: proposal.content } : {}),
|
||||
...(options.includeContent && proposal.supportFiles
|
||||
? { supportFiles: proposal.supportFiles }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -310,10 +313,15 @@ async function readProposalForInspect(
|
||||
}
|
||||
return proposal;
|
||||
}
|
||||
return await resolvePendingSkillProposal({
|
||||
const resolved = await resolvePendingSkillProposal({
|
||||
name: readStringParam(params, "name", { required: true }),
|
||||
workspaceDir,
|
||||
});
|
||||
const proposal = await inspectSkillProposal(resolved.record.id, { workspaceDir });
|
||||
if (!proposal) {
|
||||
throw new ToolInputError(`Skill proposal not found: ${resolved.record.id}`);
|
||||
}
|
||||
return proposal;
|
||||
}
|
||||
|
||||
function readProposalStatusParam(params: Record<string, unknown>): SkillProposalStatus | undefined {
|
||||
@@ -398,6 +406,14 @@ function formatProposalList(proposals: readonly SkillProposalManifestEntry[]): s
|
||||
}
|
||||
|
||||
function formatProposalInspect(proposal: SkillProposalReadResult): string {
|
||||
const supportFiles =
|
||||
proposal.supportFiles && proposal.supportFiles.length > 0
|
||||
? [
|
||||
"",
|
||||
"Support files:",
|
||||
...proposal.supportFiles.flatMap((file) => ["", `--- ${file.path} ---`, file.content]),
|
||||
]
|
||||
: [];
|
||||
return [
|
||||
`Proposal: ${proposal.record.id}`,
|
||||
`Status: ${proposal.record.status}`,
|
||||
@@ -407,6 +423,7 @@ function formatProposalInspect(proposal: SkillProposalReadResult): string {
|
||||
`Scan: ${proposal.record.scan.state}`,
|
||||
"",
|
||||
proposal.content,
|
||||
...supportFiles,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,14 @@ function formatSkillProposalList(manifest: SkillProposalManifest): string {
|
||||
|
||||
function formatSkillProposalInspect(read: SkillProposalReadResult): string {
|
||||
const { record } = read;
|
||||
const supportFiles =
|
||||
read.supportFiles && read.supportFiles.length > 0
|
||||
? [
|
||||
"",
|
||||
"Support files:",
|
||||
...read.supportFiles.flatMap((file) => ["", `--- ${file.path} ---`, file.content]),
|
||||
]
|
||||
: [];
|
||||
return [
|
||||
`ID: ${record.id}`,
|
||||
`Status: ${record.status}`,
|
||||
@@ -202,6 +210,7 @@ function formatSkillProposalInspect(read: SkillProposalReadResult): string {
|
||||
record.statusReason ? `Reason: ${record.statusReason}` : undefined,
|
||||
"",
|
||||
read.content,
|
||||
...supportFiles,
|
||||
]
|
||||
.filter((line) => line !== undefined)
|
||||
.join("\n");
|
||||
|
||||
@@ -139,6 +139,8 @@ describe("skills workshop cli", () => {
|
||||
|
||||
await runCommand(["skills", "workshop", "inspect", proposalId!]);
|
||||
expect(mocks.runtimeStdout.at(-1)).toContain("status: proposal");
|
||||
expect(mocks.runtimeStdout.at(-1)).toContain("--- references/weather.md ---");
|
||||
expect(mocks.runtimeStdout.at(-1)).toContain("Use current conditions before recommendations.");
|
||||
|
||||
const revisedPath = path.join(mocks.workspaceDir, "revised-proposal.md");
|
||||
await fs.writeFile(
|
||||
|
||||
@@ -118,6 +118,18 @@ describe("skills proposal gateway handlers", () => {
|
||||
});
|
||||
expect(inspect.ok).toBe(true);
|
||||
expect((inspect.response as { content: string }).content).toContain("status: proposal");
|
||||
expect(
|
||||
(
|
||||
inspect.response as {
|
||||
supportFiles?: Array<{ path: string; content: string }>;
|
||||
}
|
||||
).supportFiles,
|
||||
).toEqual([
|
||||
{
|
||||
path: "references/weather.md",
|
||||
content: "Use current weather before recommendations.\n",
|
||||
},
|
||||
]);
|
||||
|
||||
const revise = await callHandler("skills.proposals.revise", {
|
||||
proposalId: created.record.id,
|
||||
|
||||
@@ -71,6 +71,18 @@ describe("skill workshop proposals", () => {
|
||||
"references/weather-api.md",
|
||||
"scripts/check-weather.js",
|
||||
]);
|
||||
await expect(inspectSkillProposal(proposal.record.id)).resolves.toMatchObject({
|
||||
supportFiles: [
|
||||
{
|
||||
path: "references/weather-api.md",
|
||||
content: "# Weather API\n\nUse the current weather endpoint.\n",
|
||||
},
|
||||
{
|
||||
path: "scripts/check-weather.js",
|
||||
content: "export function parseWeather(value) { return value; }\n",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(proposal.record.target.skillFile).toBe(
|
||||
path.join(workspaceDir, "skills", "weather-helper", "SKILL.md"),
|
||||
);
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function inspectSkillProposal(
|
||||
if (options.workspaceDir && !isProposalInWorkspace(read.record, options.workspaceDir)) {
|
||||
return null;
|
||||
}
|
||||
return read;
|
||||
return await hydrateProposalSupportFiles(read);
|
||||
}
|
||||
|
||||
export async function resolvePendingSkillProposal(input: {
|
||||
@@ -804,6 +804,22 @@ async function readRequiredProposal(
|
||||
return read;
|
||||
}
|
||||
|
||||
async function hydrateProposalSupportFiles(
|
||||
read: SkillProposalReadResult,
|
||||
): Promise<SkillProposalReadResult> {
|
||||
const supportFiles = await readProposalSupportFiles(read.record);
|
||||
if (supportFiles.length === 0) {
|
||||
return read;
|
||||
}
|
||||
return {
|
||||
...read,
|
||||
supportFiles: supportFiles.map((file) => ({
|
||||
path: file.path,
|
||||
content: file.content,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function isProposalInWorkspace(record: SkillProposalRecord, workspaceDir: string): boolean {
|
||||
try {
|
||||
assertInsideWorkspace(workspaceDir, record.target.skillFile, "skill file");
|
||||
|
||||
@@ -141,6 +141,7 @@ export type SkillProposalActionInput = {
|
||||
export type SkillProposalReadResult = {
|
||||
record: SkillProposalRecord;
|
||||
content: string;
|
||||
supportFiles?: SkillProposalSupportFileInput[];
|
||||
};
|
||||
|
||||
export type SkillProposalApplyResult = {
|
||||
|
||||
Reference in New Issue
Block a user