From 05717ed7c6b6eb28f18540fcebad47d9c36a5d01 Mon Sep 17 00:00:00 2001 From: Tatsuro Shibamura Date: Mon, 24 Aug 2026 16:48:54 +0900 Subject: [PATCH] Guide models to pass positional script arguments as a string array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skills system prompt only showed the JSON-object form for script arguments, so models leaned toward it even for file-based scripts that document CLI-style positional arguments — a shape the args schema and the runner have accepted all along. The guidance now scopes the object form to named arguments (inline scripts included) and adds the string-array form for positional ones, matching the wording the Python implementation adopted. Co-Authored-By: Claude Fable 5 --- packages/core/src/skills/provider.test.ts | 16 ++++++++++++++++ packages/core/src/skills/provider.ts | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/core/src/skills/provider.test.ts b/packages/core/src/skills/provider.test.ts index 8915dc2..b4db751 100644 --- a/packages/core/src/skills/provider.test.ts +++ b/packages/core/src/skills/provider.test.ts @@ -85,6 +85,22 @@ describe('skillsProvider advertising', () => { expect(prompt).not.toContain('Read the table, then run convert.'); }); + it('distinguishes the two script argument shapes in the default prompt', async () => { + const contribution = await runProvider(skillsProvider([unitConverter])); + + const prompt = contribution.instructions.join('\n'); + const scriptGuidance = prompt.split('\n').filter((line) => line.includes('script')); + // Named arguments go as a JSON object — inline scripts included — while file-based scripts + // documenting CLI-style positional arguments take a string array. + expect( + scriptGuidance.some((line) => line.includes('JSON object') && line.includes('inline scripts')), + ).toBe(true); + expect( + scriptGuidance.some((line) => line.includes('array of strings') && line.includes('file-based scripts')), + ).toBe(true); + expect(prompt).toContain('not as top-level tool parameters'); + }); + it('escapes skill metadata so a crafted description cannot close the element', async () => { const injected = inlineSkill({ name: 'crafted', diff --git a/packages/core/src/skills/provider.ts b/packages/core/src/skills/provider.ts index 944d12a..f435031 100644 --- a/packages/core/src/skills/provider.ts +++ b/packages/core/src/skills/provider.ts @@ -65,8 +65,10 @@ const RESOURCE_INSTRUCTIONS = const SCRIPT_INSTRUCTIONS = '- Use `run_skill_script` to run referenced scripts, using the name exactly as listed.\n' + - '- Pass script arguments inside `args` as a JSON object' + - ' (e.g. `args: {"length": 24}`), not as top-level tool parameters.\n'; + '- Pass named script arguments inside `args` as a JSON object, including for inline scripts' + + ' (e.g. `args: {"length": 24}`), not as top-level tool parameters.\n' + + '- For file-based scripts that document CLI-style positional arguments, pass `args` as an array of strings' + + ' (e.g. `args: ["input.docx", "--output", "result.idx"]`).\n'; /** * Advertises skills to the model and gives it the tools to pull them in.