From c636aec727e6189fc078a25ae346fc8145cd23cf Mon Sep 17 00:00:00 2001 From: EduSantosBrito Date: Sat, 9 May 2026 18:11:26 +0200 Subject: [PATCH] fix: agents spec alignment --- packages/opencode/src/skill/index.ts | 14 ++++++-- packages/opencode/test/skill/skill.test.ts | 40 ++++++++++++++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 1 + packages/web/src/content/docs/skills.mdx | 4 +++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 696ab887a72f..2c5c195bd4a0 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -28,6 +28,7 @@ const SKILL_PATTERN = "**/SKILL.md" export const Info = Schema.Struct({ name: Schema.String, description: Schema.optional(Schema.String), + disableModelInvocation: Schema.optional(Schema.Boolean), location: Schema.String, content: Schema.String, }).pipe(withStatics((s) => ({ zod: zod(s) }))) @@ -93,7 +94,13 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I if (!md) return - const parsed = z.object({ name: z.string(), description: z.string().optional() }).safeParse(md.data) + const parsed = z + .object({ + name: z.string(), + description: z.string().optional(), + "disable-model-invocation": z.boolean().optional(), + }) + .safeParse(md.data) if (!parsed.success) return if (state.skills[parsed.data.name]) { @@ -108,6 +115,7 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I state.skills[parsed.data.name] = { name: parsed.data.name, description: parsed.data.description, + disableModelInvocation: parsed.data["disable-model-invocation"], location: match, content: md.content, } @@ -251,7 +259,9 @@ export const layer = Layer.effect( const available = Effect.fn("Skill.available")(function* (agent?: Agent.Info) { const s = yield* InstanceState.get(state) - const list = Object.values(s.skills).toSorted((a, b) => a.name.localeCompare(b.name)) + const list = Object.values(s.skills) + .filter((skill) => !skill.disableModelInvocation) + .toSorted((a, b) => a.name.localeCompare(b.name)) if (!agent) return list return list.filter((skill) => Permission.evaluate("skill", skill.name, agent.permission).action !== "deny") }) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index d73750b0831b..f135273f75aa 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -142,6 +142,46 @@ description: Second test skill. ), ) + it.live("hides skills with disabled model invocation from available catalog", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + yield* Effect.promise(() => + Promise.all([ + Bun.write( + path.join(dir, ".opencode", "skill", "visible-skill", "SKILL.md"), + `--- +name: visible-skill +description: Visible to the model. +--- + +# Visible Skill +`, + ), + Bun.write( + path.join(dir, ".opencode", "skill", "manual-skill", "SKILL.md"), + `--- +name: manual-skill +description: Only load when explicitly requested. +disable-model-invocation: true +--- + +# Manual Skill +`, + ), + ]), + ) + + const skill = yield* Skill.Service + expect((yield* skill.all()).map((item) => item.name).toSorted()).toEqual(["manual-skill", "visible-skill"]) + expect((yield* skill.get("manual-skill"))?.name).toBe("manual-skill") + expect((yield* skill.available()).map((item) => item.name)).toEqual(["visible-skill"]) + expect(Skill.fmt(yield* skill.available(), { verbose: true })).not.toContain("manual-skill") + }), + { git: true }, + ), + ) + it.live("skips skills with missing frontmatter", () => provideTmpdirInstance( (dir) => diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 220278b8c28b..ca2a4a16db48 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4244,6 +4244,7 @@ export type AppSkillsResponses = { 200: Array<{ name: string description?: string + disableModelInvocation?: boolean location: string content: string }> diff --git a/packages/web/src/content/docs/skills.mdx b/packages/web/src/content/docs/skills.mdx index 2ce88ea5682f..79b91bb23d4f 100644 --- a/packages/web/src/content/docs/skills.mdx +++ b/packages/web/src/content/docs/skills.mdx @@ -41,9 +41,13 @@ Only these fields are recognized: - `license` (optional) - `compatibility` (optional) - `metadata` (optional, string-to-string map) +- `disable-model-invocation` (optional, boolean) Unknown frontmatter fields are ignored. +Set `disable-model-invocation: true` to hide a skill from the model's available skills catalog. +The skill can still be loaded when explicitly requested by name. + --- ## Validate names