@@ -277,8 +277,9 @@ describe("extractDeliveryInfo", () => {
277277 } ) ;
278278
279279 it ( "derives delivery info from stored last route metadata when deliveryContext is missing" , ( ) => {
280- const sessionKey = "agent:main:matrix:channel:!lowercased:example.org" ;
281- storeState . store [ sessionKey ] = {
280+ const sessionKey = "agent:main:matrix:channel:!MixedCase:example.org" ;
281+ const legacyKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
282+ storeState . store [ legacyKey ] = {
282283 sessionId : "session-1" ,
283284 updatedAt : Date . now ( ) ,
284285 origin : {
@@ -361,34 +362,69 @@ describe("extractDeliveryInfo", () => {
361362 } ) ;
362363 } ) ;
363364
364- it ( "prefers an older routable normalized alias over a fresher non-routable alias" , ( ) => {
365- const queriedKey = "agent:main:matrix:channel:! MiXeDCase:Example.Org " ;
366- const routableAlias = "agent:main:matrix:channel:! MixedCase:Example.Org " ;
367- const canonicalKey = "agent:main:matrix:channel:! mixedcase:example.org " ;
365+ it ( "prefers an older routable normalized alias over a fresher non-routable alias for non-opaque keys " , ( ) => {
366+ const queriedKey = "agent:main:telegram:group: MiXeDCase" ;
367+ const routableAlias = "agent:main:telegram:group: MixedCase" ;
368+ const canonicalKey = "agent:main:telegram:group: mixedcase" ;
368369 storeState . store [ canonicalKey ] = {
369370 sessionId : "fresh-normalized-session" ,
370371 updatedAt : Date . now ( ) ,
371372 origin : {
372- provider : "matrix " ,
373+ provider : "telegram " ,
373374 } ,
374375 } ;
375376 storeState . store [ routableAlias ] = {
376377 sessionId : "older-routable-session" ,
377378 updatedAt : Date . now ( ) - 1_000 ,
378379 deliveryContext : {
379- channel : "matrix " ,
380- to : "room:! MixedCase:Example.Org " ,
381- accountId : "matrix -account" ,
380+ channel : "telegram " ,
381+ to : "telegram: MixedCase" ,
382+ accountId : "telegram -account" ,
382383 } ,
383384 } ;
384385
385386 const result = extractDeliveryInfo ( queriedKey ) ;
386387
387388 expect ( result ) . toEqual ( {
388389 deliveryContext : {
389- channel : "matrix" ,
390- to : "room:!MixedCase:Example.Org" ,
391- accountId : "matrix-account" ,
390+ channel : "telegram" ,
391+ to : "telegram:MixedCase" ,
392+ accountId : "telegram-account" ,
393+ } ,
394+ threadId : undefined ,
395+ } ) ;
396+ } ) ;
397+
398+ it ( "keeps freshest routable alias ordering for non-opaque keys" , ( ) => {
399+ const queriedKey = "agent:main:telegram:group:MiXeDCase" ;
400+ const canonicalKey = "agent:main:telegram:group:mixedcase" ;
401+ const routableAlias = "agent:main:telegram:group:MixedCase" ;
402+ storeState . store [ canonicalKey ] = {
403+ sessionId : "older-canonical-session" ,
404+ updatedAt : Date . now ( ) - 1_000 ,
405+ deliveryContext : {
406+ channel : "telegram" ,
407+ to : "telegram:old-route" ,
408+ accountId : "telegram-account" ,
409+ } ,
410+ } ;
411+ storeState . store [ routableAlias ] = {
412+ sessionId : "fresh-routable-session" ,
413+ updatedAt : Date . now ( ) ,
414+ deliveryContext : {
415+ channel : "telegram" ,
416+ to : "telegram:fresh-route" ,
417+ accountId : "telegram-account" ,
418+ } ,
419+ } ;
420+
421+ const result = extractDeliveryInfo ( queriedKey ) ;
422+
423+ expect ( result ) . toEqual ( {
424+ deliveryContext : {
425+ channel : "telegram" ,
426+ to : "telegram:fresh-route" ,
427+ accountId : "telegram-account" ,
392428 } ,
393429 threadId : undefined ,
394430 } ) ;
@@ -416,26 +452,57 @@ describe("extractDeliveryInfo", () => {
416452 } ) ;
417453 } ) ;
418454
419- it ( "prefers the freshest routable alias even when the normalized key is already routable" , ( ) => {
420- const queriedKey = "agent:main:matrix:channel:!MiXeDCase:Example.Org" ;
421- const canonicalKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
422- const fresherAlias = "agent:main:matrix:channel:!MixedCase:Example.Org" ;
423- storeState . store [ canonicalKey ] = {
424- sessionId : "older-canonical-session" ,
455+ it ( "prefers the exact mixed-case Matrix entry over a fresher folded legacy alias" , ( ) => {
456+ // Matrix room IDs are case-sensitive (openclaw#75670): the exact mixed-case
457+ // session is canonical and must win over a stale lowercased legacy alias even
458+ // when the alias is fresher. (Previously these collapsed to one lowercased key
459+ // and freshest won — that collapse was the bug.)
460+ const queriedKey = "agent:main:matrix:channel:!MixedCase:Example.Org" ;
461+ const legacyFoldedKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
462+ storeState . store [ queriedKey ] = {
463+ sessionId : "exact-mixedcase-session" ,
425464 updatedAt : Date . now ( ) - 1_000 ,
465+ deliveryContext : {
466+ channel : "matrix" ,
467+ to : "room:!MixedCase:Example.Org" ,
468+ accountId : "matrix-account" ,
469+ } ,
470+ } ;
471+ storeState . store [ legacyFoldedKey ] = {
472+ sessionId : "fresher-legacy-folded-session" ,
473+ updatedAt : Date . now ( ) ,
426474 deliveryContext : {
427475 channel : "matrix" ,
428476 to : "room:!mixedcase:example.org" ,
429477 accountId : "matrix-account" ,
430478 } ,
431479 } ;
432- storeState . store [ fresherAlias ] = {
433- sessionId : "fresh-alias-session" ,
480+
481+ const result = extractDeliveryInfo ( queriedKey ) ;
482+
483+ expect ( result ) . toEqual ( {
484+ deliveryContext : {
485+ channel : "matrix" ,
486+ to : "room:!MixedCase:Example.Org" ,
487+ accountId : "matrix-account" ,
488+ } ,
489+ threadId : undefined ,
490+ } ) ;
491+ } ) ;
492+
493+ it ( "finds Matrix thread entries with a legacy lowercased room and preserved event id" , ( ) => {
494+ const queriedKey =
495+ "agent:main:matrix:channel:!MixedCase:Example.Org:thread:$RootEvent:Example.Org" ;
496+ const legacyThreadKey =
497+ "agent:main:matrix:channel:!mixedcase:example.org:thread:$RootEvent:Example.Org" ;
498+ storeState . store [ legacyThreadKey ] = {
499+ sessionId : "legacy-thread-session" ,
434500 updatedAt : Date . now ( ) ,
435501 deliveryContext : {
436502 channel : "matrix" ,
437503 to : "room:!MixedCase:Example.Org" ,
438504 accountId : "matrix-account" ,
505+ threadId : "$RootEvent:Example.Org" ,
439506 } ,
440507 } ;
441508
@@ -446,11 +513,123 @@ describe("extractDeliveryInfo", () => {
446513 channel : "matrix" ,
447514 to : "room:!MixedCase:Example.Org" ,
448515 accountId : "matrix-account" ,
516+ threadId : "$RootEvent:Example.Org" ,
449517 } ,
518+ threadId : "$RootEvent:Example.Org" ,
519+ } ) ;
520+ } ) ;
521+
522+ it ( "does not return a case-distinct lowercase Matrix sibling when the mixed-case key has no exact entry" , ( ) => {
523+ const queriedKey = "agent:main:matrix:channel:!MixedCase:Example.Org" ;
524+ const lowercaseSiblingKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
525+ storeState . store [ lowercaseSiblingKey ] = buildEntry ( {
526+ channel : "matrix" ,
527+ to : "room:!mixedcase:example.org" ,
528+ accountId : "matrix-account" ,
529+ } ) ;
530+
531+ const result = extractDeliveryInfo ( queriedKey ) ;
532+
533+ expect ( result ) . toEqual ( {
534+ deliveryContext : undefined ,
535+ threadId : undefined ,
536+ } ) ;
537+ } ) ;
538+
539+ it ( "does not return a mixed-case Matrix sibling for a lowercase room query" , ( ) => {
540+ const queriedKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
541+ const mixedSiblingKey = "agent:main:matrix:channel:!MixedCase:Example.Org" ;
542+ storeState . store [ mixedSiblingKey ] = buildEntry ( {
543+ channel : "matrix" ,
544+ to : "room:!MixedCase:Example.Org" ,
545+ accountId : "matrix-account" ,
546+ } ) ;
547+
548+ const result = extractDeliveryInfo ( queriedKey ) ;
549+
550+ expect ( result ) . toEqual ( {
551+ deliveryContext : undefined ,
450552 threadId : undefined ,
451553 } ) ;
452554 } ) ;
453555
556+ it ( "does not return an exact lowercase Matrix key with mixed-case delivery metadata" , ( ) => {
557+ const queriedKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
558+ storeState . store [ queriedKey ] = buildEntry ( {
559+ channel : "matrix" ,
560+ to : "room:!MixedCase:Example.Org" ,
561+ accountId : "matrix-account" ,
562+ } ) ;
563+
564+ const result = extractDeliveryInfo ( queriedKey ) ;
565+
566+ expect ( result ) . toEqual ( {
567+ deliveryContext : undefined ,
568+ threadId : undefined ,
569+ } ) ;
570+ } ) ;
571+
572+ it ( "returns a confirmed lowercased Matrix legacy artifact for a mixed-case key" , ( ) => {
573+ const queriedKey = "agent:main:matrix:channel:!MixedCase:Example.Org" ;
574+ const legacyArtifactKey = "agent:main:matrix:channel:!mixedcase:example.org" ;
575+ storeState . store [ legacyArtifactKey ] = buildEntry ( {
576+ channel : "matrix" ,
577+ to : "room:!MixedCase:Example.Org" ,
578+ accountId : "matrix-account" ,
579+ } ) ;
580+
581+ const result = extractDeliveryInfo ( queriedKey ) ;
582+
583+ expect ( result ) . toEqual ( {
584+ deliveryContext : {
585+ channel : "matrix" ,
586+ to : "room:!MixedCase:Example.Org" ,
587+ accountId : "matrix-account" ,
588+ } ,
589+ threadId : undefined ,
590+ } ) ;
591+ } ) ;
592+
593+ it ( "returns a confirmed lowercased Matrix room-alias artifact" , ( ) => {
594+ const queriedKey = "agent:main:matrix:channel:#MixedAlias:Example.Org" ;
595+ const legacyArtifactKey = "agent:main:matrix:channel:#mixedalias:example.org" ;
596+ storeState . store [ legacyArtifactKey ] = buildEntry ( {
597+ channel : "matrix" ,
598+ to : "room:#MixedAlias:Example.Org" ,
599+ accountId : "matrix-account" ,
600+ } ) ;
601+
602+ const result = extractDeliveryInfo ( queriedKey ) ;
603+
604+ expect ( result ) . toEqual ( {
605+ deliveryContext : {
606+ channel : "matrix" ,
607+ to : "room:#MixedAlias:Example.Org" ,
608+ accountId : "matrix-account" ,
609+ } ,
610+ threadId : undefined ,
611+ } ) ;
612+ } ) ;
613+
614+ it ( "does not return a folded Matrix thread artifact when the stored thread id differs by case" , ( ) => {
615+ const queriedKey = "agent:main:matrix:channel:!MixedCase:Example.Org:thread:$ThreadRootAbC" ;
616+ const foldedThreadKey =
617+ "agent:main:matrix:channel:!mixedcase:example.org:thread:$threadrootabc" ;
618+ storeState . store [ foldedThreadKey ] = buildEntry ( {
619+ channel : "matrix" ,
620+ to : "room:!MixedCase:Example.Org" ,
621+ accountId : "matrix-account" ,
622+ threadId : "$threadrootabc" ,
623+ } ) ;
624+
625+ const result = extractDeliveryInfo ( queriedKey ) ;
626+
627+ expect ( result ) . toEqual ( {
628+ deliveryContext : undefined ,
629+ threadId : "$ThreadRootAbC" ,
630+ } ) ;
631+ } ) ;
632+
454633 it ( "falls back to the base session when a thread entry only has partial route metadata" , ( ) => {
455634 const baseKey = "agent:main:matrix:channel:!MixedCase:example.org" ;
456635 const threadKey = `${ baseKey } :thread:$thread-event` ;
0 commit comments