@@ -48,6 +48,10 @@ function summary(sessionKey: string, overrides: Partial<SessionEntrySummary["ent
4848 > ;
4949}
5050
51+ function hasDanglingSurrogate ( value : string ) : boolean {
52+ return / [ \uD800 - \uDBFF ] (? ! [ \uDC00 - \uDFFF ] ) | (?< ! [ \uD800 - \uDBFF ] ) [ \uDC00 - \uDFFF ] / u. test ( value ) ;
53+ }
54+
5155describe ( "Skill Workshop history scan" , ( ) => {
5256 it ( "keeps interactive sessions and excludes system-owned work" , ( ) => {
5357 expect ( isSkillHistoryScanSessionEligible ( summary ( "agent:main:main" ) ) ) . toBe ( true ) ;
@@ -244,6 +248,33 @@ describe("Skill Workshop history scan", () => {
244248 expect ( transcript ) . toContain ( "…redacted…" ) ;
245249 } ) ;
246250
251+ it . each ( [
252+ {
253+ name : "a prefix-only budget" ,
254+ content : "😀tail" ,
255+ maxChars : 8 ,
256+ includesOmission : false ,
257+ } ,
258+ {
259+ name : "the retained head" ,
260+ content : `😀${ "a" . repeat ( 80 ) } ` ,
261+ maxChars : 51 ,
262+ includesOmission : true ,
263+ } ,
264+ {
265+ name : "the retained tail" ,
266+ content : `${ "a" . repeat ( 60 ) } 😀${ "x" . repeat ( 7 ) } ` ,
267+ maxChars : 51 ,
268+ includesOmission : true ,
269+ } ,
270+ ] ) ( "keeps $name UTF-16 safe" , ( { content, maxChars, includesOmission } ) => {
271+ const transcript = formatSkillHistoryScanTranscript ( [ { role : "user" , content } ] , maxChars ) ;
272+
273+ expect ( transcript . length ) . toBeLessThanOrEqual ( maxChars ) ;
274+ expect ( transcript . includes ( "[older session content omitted]" ) ) . toBe ( includesOmission ) ;
275+ expect ( hasDanglingSurrogate ( transcript ) ) . toBe ( false ) ;
276+ } ) ;
277+
247278 it ( "continues before the oldest cursor and can return to new work" , ( ) => {
248279 const candidates = [
249280 { instanceId : "new" , sessionKey : "main" , updatedAtMs : 300 , entry : summary ( "new" ) . entry } ,
0 commit comments