@@ -784,6 +784,7 @@ const {
784784 removeRetiredPackagedSkills: removeRetiredPackagedSkillsCh,
785785 refreshBuiltInSkillRecord: refreshBuiltInSkillRecordCh,
786786 readSkillImportText: readSkillImportTextCh,
787+ stripSkillToolBlocks: stripSkillToolBlocksCh,
787788 buildCustomSkillsPrompt: buildCustomSkillsPromptCh,
788789 getEligibleSkillCatalog: getEligibleSkillCatalogCh,
789790 buildSkillLoaderDefinition: buildSkillLoaderDefinitionCh,
@@ -808,6 +809,7 @@ const {
808809 removeRetiredPackagedSkills: removeRetiredPackagedSkillsFx,
809810 refreshBuiltInSkillRecord: refreshBuiltInSkillRecordFx,
810811 readSkillImportText: readSkillImportTextFx,
812+ stripSkillToolBlocks: stripSkillToolBlocksFx,
811813 buildCustomSkillsPrompt: buildCustomSkillsPromptFx,
812814 getEligibleSkillCatalog: getEligibleSkillCatalogFx,
813815 buildSkillLoaderDefinition: buildSkillLoaderDefinitionFx,
@@ -12821,6 +12823,70 @@ Keep this text unchanged.`;
1282112823 }
1282212824});
1282312825
12826+ test('Agent Skills parsing rejects unsupported YAML without data loss and normalizes raw input', () => {
12827+ const wrappedPlain = `---
12828+ name: pdf-processing
12829+ description: Extract PDF text and fill forms.
12830+ Use when working with PDF documents.
12831+ ---
12832+ # Safe fallback
12833+
12834+ Keep the complete source.`;
12835+ const explicitIndent = `---
12836+ name: pdf-processing
12837+ description: >2-
12838+ Extract PDF text and fill forms.
12839+ ---
12840+ # Safe fallback
12841+
12842+ Keep the explicit block source.`;
12843+ const supportedRaw = '\uFEFF\r\n\r\n---\r\nname: pdf-processing\r\ndescription: Extract PDF text.\r\n---\r\n# PDF workflow\r\n\r\nFollow it.';
12844+ const oversizedFrontmatter = `---
12845+ name: pdf-processing
12846+ description: Extract PDF text.
12847+ metadata: ${'x'.repeat(8200)}
12848+ ---
12849+ # Safe fallback
12850+
12851+ Keep the oversized source.`;
12852+
12853+ for (const [label, normalizeSkills, buildPrompt, stripBlocks] of [
12854+ ['chrome', normalizeCustomSkillsCh, buildCustomSkillsPromptCh, stripSkillToolBlocksCh],
12855+ ['firefox', normalizeCustomSkillsFx, buildCustomSkillsPromptFx, stripSkillToolBlocksFx],
12856+ ]) {
12857+ for (const [caseName, content, preservedText] of [
12858+ ['wrapped plain scalar', wrappedPlain, 'Use when working with PDF documents.'],
12859+ ['explicit indentation indicator', explicitIndent, 'description: >2-'],
12860+ ]) {
12861+ const id = `unsupported-${caseName.replace(/\s+/g, '-')}`;
12862+ const [skill] = normalizeSkills([{ id, content }]);
12863+ assert.equal(skill.name, 'Safe fallback', `${label}: ${caseName} was partially accepted`);
12864+ const prompt = buildPrompt([skill], {
12865+ mode: 'act',
12866+ tier: 'full',
12867+ activeSkillIds: new Set([id]),
12868+ });
12869+ assert.match(prompt, new RegExp(preservedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${label}: ${caseName} source was lost`);
12870+ assert.match(prompt, /Keep the .* source\./, `${label}: ${caseName} body was lost`);
12871+ }
12872+
12873+ const stripped = stripBlocks(supportedRaw);
12874+ assert.equal(
12875+ stripped,
12876+ '# PDF workflow\n\nFollow it.',
12877+ `${label}: raw BOM/CRLF/leading whitespace changed frontmatter handling`,
12878+ );
12879+
12880+ const [oversized] = normalizeSkills([{ id: 'oversized-frontmatter', content: oversizedFrontmatter }]);
12881+ assert.equal(oversized.name, 'Safe fallback', `${label}: oversized frontmatter was partially accepted`);
12882+ assert.match(
12883+ stripBlocks(oversizedFrontmatter),
12884+ /metadata: x{100}/,
12885+ `${label}: oversized frontmatter was destructively stripped`,
12886+ );
12887+ }
12888+ });
12889+
1282412890test('skill semantic intents are bounded, explicit, and shared by loader catalogs', () => {
1282512891 for (const [label, normalizeSkills, getCatalog, buildLoader, maxIntents, maxIntentChars] of [
1282612892 ['chrome', normalizeCustomSkillsCh, getEligibleSkillCatalogCh, buildSkillLoaderDefinitionCh, MAX_CUSTOM_SKILL_INTENTS_CH, MAX_CUSTOM_SKILL_INTENT_CHARS_CH],
0 commit comments