From 3ea82adf97fc710f40fb420fc0998f5c7eec55a2 Mon Sep 17 00:00:00 2001 From: Shakker Date: Sat, 30 May 2026 13:52:51 +0100 Subject: [PATCH] fix: show skill proposal support files on inspect --- .../src/schema/agents-models-skills.test.ts | 6 ++++++ .../src/schema/agents-models-skills.ts | 1 + src/agents/tools/skill-research-tool.test.ts | 15 +++++++++++++++ src/agents/tools/skill-research-tool.ts | 19 ++++++++++++++++++- src/cli/skills-cli.ts | 9 +++++++++ src/cli/skills-cli.workshop.test.ts | 2 ++ .../server-methods/skills.proposals.test.ts | 12 ++++++++++++ src/skills/workshop/service.test.ts | 12 ++++++++++++ src/skills/workshop/service.ts | 18 +++++++++++++++++- src/skills/workshop/types.ts | 1 + 10 files changed, 93 insertions(+), 2 deletions(-) diff --git a/packages/gateway-protocol/src/schema/agents-models-skills.test.ts b/packages/gateway-protocol/src/schema/agents-models-skills.test.ts index a547da4cb3b..e59c353b085 100644 --- a/packages/gateway-protocol/src/schema/agents-models-skills.test.ts +++ b/packages/gateway-protocol/src/schema/agents-models-skills.test.ts @@ -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); diff --git a/packages/gateway-protocol/src/schema/agents-models-skills.ts b/packages/gateway-protocol/src/schema/agents-models-skills.ts index a848c97c833..5344e595be4 100644 --- a/packages/gateway-protocol/src/schema/agents-models-skills.ts +++ b/packages/gateway-protocol/src/schema/agents-models-skills.ts @@ -628,6 +628,7 @@ export const SkillsProposalInspectResultSchema = Type.Object( { record: SkillProposalRecordSchema, content: Type.String(), + supportFiles: Type.Optional(Type.Array(SkillProposalSupportFileInputSchema, { maxItems: 64 })), }, { additionalProperties: false }, ); diff --git a/src/agents/tools/skill-research-tool.test.ts b/src/agents/tools/skill-research-tool.test.ts index 53242c8ac42..8586059a115 100644 --- a/src/agents/tools/skill-research-tool.test.ts +++ b/src/agents/tools/skill-research-tool.test.ts @@ -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", diff --git a/src/agents/tools/skill-research-tool.ts b/src/agents/tools/skill-research-tool.ts index 61c57e65090..f39e5cc269c 100644 --- a/src/agents/tools/skill-research-tool.ts +++ b/src/agents/tools/skill-research-tool.ts @@ -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): 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"); } diff --git a/src/cli/skills-cli.ts b/src/cli/skills-cli.ts index 1183af9c843..e91e65c3288 100644 --- a/src/cli/skills-cli.ts +++ b/src/cli/skills-cli.ts @@ -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"); diff --git a/src/cli/skills-cli.workshop.test.ts b/src/cli/skills-cli.workshop.test.ts index 8445e250b77..ead86aa94a7 100644 --- a/src/cli/skills-cli.workshop.test.ts +++ b/src/cli/skills-cli.workshop.test.ts @@ -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( diff --git a/src/gateway/server-methods/skills.proposals.test.ts b/src/gateway/server-methods/skills.proposals.test.ts index f9500629099..8c45e5eb306 100644 --- a/src/gateway/server-methods/skills.proposals.test.ts +++ b/src/gateway/server-methods/skills.proposals.test.ts @@ -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, diff --git a/src/skills/workshop/service.test.ts b/src/skills/workshop/service.test.ts index b4f1456d359..a817a0d9d40 100644 --- a/src/skills/workshop/service.test.ts +++ b/src/skills/workshop/service.test.ts @@ -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"), ); diff --git a/src/skills/workshop/service.ts b/src/skills/workshop/service.ts index 0246fbbf3ca..43de4c66dc0 100644 --- a/src/skills/workshop/service.ts +++ b/src/skills/workshop/service.ts @@ -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 { + 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"); diff --git a/src/skills/workshop/types.ts b/src/skills/workshop/types.ts index 80f67a02483..16292f310a3 100644 --- a/src/skills/workshop/types.ts +++ b/src/skills/workshop/types.ts @@ -141,6 +141,7 @@ export type SkillProposalActionInput = { export type SkillProposalReadResult = { record: SkillProposalRecord; content: string; + supportFiles?: SkillProposalSupportFileInput[]; }; export type SkillProposalApplyResult = {