11import { afterEach , beforeEach , describe , expect , it } from "vitest" ;
2+ import { PLUGIN_APPROVAL_DESCRIPTION_MAX_LENGTH } from "../../infra/plugin-approvals.js" ;
23import {
34 createOpenClawTestState ,
45 type OpenClawTestState ,
@@ -69,14 +70,29 @@ describe("resolveSkillWorkshopToolApproval", () => {
6970 ) ;
7071 } ) ;
7172
72- it ( "bounds approval metadata without dropping required proposal facts " , async ( ) => {
73+ it ( "bounds approval metadata without splitting UTF-16 surrogates " , async ( ) => {
7374 const workspaceDir = await tempDirs . make ( "openclaw-skill-workshop-policy-long-name-" ) ;
7475 const description = "d" . repeat ( 160 ) ;
76+ const content = "# Long name\n" ;
77+ const proposalIdLength = 60 + 1 + 8 + 1 + 10 ;
78+ const fixedLines = [
79+ `Proposal ID: ${ "p" . repeat ( proposalIdLength ) } ` ,
80+ `Description: ${ description } ` ,
81+ "Support files: 0" ,
82+ `Body size: ${ ( Buffer . byteLength ( content , "utf8" ) / 1024 ) . toFixed ( 1 ) } KB` ,
83+ ] ;
84+ const skillPrefix = "Target skill: " ;
85+ const fixedLength = fixedLines . join ( "\n" ) . length + skillPrefix . length + fixedLines . length ;
86+ const availableSkillNameLength = Math . max (
87+ 1 ,
88+ PLUGIN_APPROVAL_DESCRIPTION_MAX_LENGTH - fixedLength ,
89+ ) ;
90+ const prefix = "n" . repeat ( availableSkillNameLength - 2 ) ;
7591 const proposal = await proposeCreateSkill ( {
7692 workspaceDir,
77- name : "n" . repeat ( 240 ) ,
93+ name : ` ${ prefix } \u{1F600}tail` ,
7894 description,
79- content : "# Long name\n" ,
95+ content,
8096 } ) ;
8197
8298 const result = await resolveSkillWorkshopToolApproval ( {
@@ -86,14 +102,21 @@ describe("resolveSkillWorkshopToolApproval", () => {
86102 } ) ;
87103 const approvalDescription = result ?. requireApproval ?. description ?? "" ;
88104
89- expect ( approvalDescription . length ) . toBeLessThanOrEqual ( 512 ) ;
105+ expect ( approvalDescription . length ) . toBeLessThanOrEqual (
106+ PLUGIN_APPROVAL_DESCRIPTION_MAX_LENGTH ,
107+ ) ;
90108 expect ( approvalDescription ) . toContain ( `Proposal ID: ${ proposal . record . id } ` ) ;
91109 expect ( approvalDescription ) . toContain ( `Description: ${ description } ` ) ;
92110 expect ( approvalDescription ) . toContain ( "Support files: 0" ) ;
93111 expect ( approvalDescription ) . toContain (
94112 `Body size: ${ ( Buffer . byteLength ( proposal . content , "utf8" ) / 1024 ) . toFixed ( 1 ) } KB` ,
95113 ) ;
96- expect ( approvalDescription ) . toContain ( "Target skill: nnn" ) ;
114+ const targetLine = result ?. requireApproval ?. description . split ( "\n" ) [ 1 ] ?? "" ;
115+
116+ expect ( targetLine ) . toBe ( `Target skill: ${ prefix } …` ) ;
117+ expect ( approvalDescription ) . not . toMatch (
118+ / [ \uD800 - \uDBFF ] (? ! [ \uDC00 - \uDFFF ] ) | (?< ! [ \uD800 - \uDBFF ] ) [ \uDC00 - \uDFFF ] / ,
119+ ) ;
97120 } ) ;
98121
99122 it ( "renders proposal-controlled fields without approval-line injection" , async ( ) => {
0 commit comments