@@ -43,6 +43,16 @@ describe("doctor session state provider routes", () => {
4343 } ,
4444 } ) ,
4545 ) . toBe ( true ) ;
46+
47+ expect (
48+ storeMayContainPluginSessionRouteState ( {
49+ "agent:main:telegram:direct:2" : {
50+ sessionId : "session-claude-cli" ,
51+ updatedAt : 1 ,
52+ agentRuntimeOverride : "claude-cli" ,
53+ } ,
54+ } ) ,
55+ ) . toBe ( true ) ;
4656 } ) ;
4757
4858 it ( "preserves configured provider CLI runtimes before harness policy normalization" , ( ) => {
@@ -135,10 +145,11 @@ describe("doctor session state provider routes", () => {
135145 ownerId : "codex" ,
136146 ownerLabel : "Codex" ,
137147 cliSessionKeys : [ "codex-cli" ] ,
148+ pinnedRuntimeKeys : [ "agentHarnessId" ] ,
138149 reasons : [
139150 "auto model override" ,
140- "runtime model state" ,
141151 "pinned runtime" ,
152+ "runtime model state" ,
142153 "CLI session binding" ,
143154 "auto auth profile override" ,
144155 ] ,
@@ -206,7 +217,7 @@ describe("doctor session state provider routes", () => {
206217 ] ) ;
207218 } ) ;
208219
209- it ( "keeps owner state when owner remains in the configured route " , ( ) => {
220+ it ( "clears stale runtime pins while preserving configured owner model state " , ( ) => {
210221 const sessionKey = "agent:main:telegram:direct:3" ;
211222 const entry : Record < string , unknown > = {
212223 sessionId : "sess-configured-codex" ,
@@ -234,7 +245,28 @@ describe("doctor session state provider routes", () => {
234245 } ,
235246 } ) ;
236247
237- expect ( scan ) . toEqual ( { repairs : [ ] , manualReview : [ ] } ) ;
248+ expect ( scan . manualReview ) . toStrictEqual ( [ ] ) ;
249+ expect ( scan . repairs ) . toEqual ( [
250+ {
251+ key : sessionKey ,
252+ ownerId : "codex" ,
253+ ownerLabel : "Codex" ,
254+ cliSessionKeys : [ "codex-cli" ] ,
255+ pinnedRuntimeKeys : [ "agentHarnessId" ] ,
256+ reasons : [ "pinned runtime" ] ,
257+ } ,
258+ ] ) ;
259+
260+ expect ( applySessionRouteStateRepair ( { entry, repair : scan . repairs [ 0 ] , now : 123 } ) ) . toBe ( true ) ;
261+ expect ( entry . updatedAt ) . toBe ( 123 ) ;
262+ expect ( entry . providerOverride ) . toBe ( "openai-codex" ) ;
263+ expect ( entry . modelOverride ) . toBe ( "gpt-5.4" ) ;
264+ expect ( entry . modelProvider ) . toBe ( "openai-codex" ) ;
265+ expect ( entry . model ) . toBe ( "gpt-5.4" ) ;
266+ expect ( entry . agentHarnessId ) . toBeUndefined ( ) ;
267+ expect ( entry . cliSessionBindings ) . toStrictEqual ( {
268+ "codex-cli" : { sessionId : "codex-session-3" } ,
269+ } ) ;
238270 } ) ;
239271
240272 it ( "keeps owner CLI state when owner runtime is still configured" , ( ) => {
@@ -263,4 +295,168 @@ describe("doctor session state provider routes", () => {
263295
264296 expect ( scan ) . toEqual ( { repairs : [ ] , manualReview : [ ] } ) ;
265297 } ) ;
298+
299+ it ( "clears stale agentRuntimeOverride-only pins when current route no longer uses the owner" , ( ) => {
300+ const sessionKey = "agent:main:telegram:direct:5" ;
301+ const entry : Record < string , unknown > = {
302+ sessionId : "sess-stale-claude-cli" ,
303+ updatedAt : 1 ,
304+ agentRuntimeOverride : "claude-cli" ,
305+ } ;
306+
307+ const scan = scanSessionRouteStateOwners ( {
308+ owners : [
309+ {
310+ id : "anthropic" ,
311+ label : "Anthropic" ,
312+ providerIds : [ "anthropic" ] ,
313+ runtimeIds : [ "claude-cli" ] ,
314+ cliSessionKeys : [ "claude-cli" ] ,
315+ authProfilePrefixes : [ "anthropic:" , "claude-cli:" ] ,
316+ } ,
317+ ] ,
318+ store : { [ sessionKey ] : entry } ,
319+ routes : {
320+ [ sessionKey ] : {
321+ defaultProvider : "openai" ,
322+ configuredModelRefs : [ "openai/gpt-5.5" ] ,
323+ runtime : "pi" ,
324+ } ,
325+ } ,
326+ } ) ;
327+
328+ expect ( scan . manualReview ) . toStrictEqual ( [ ] ) ;
329+ expect ( scan . repairs ) . toEqual ( [
330+ {
331+ key : sessionKey ,
332+ ownerId : "anthropic" ,
333+ ownerLabel : "Anthropic" ,
334+ cliSessionKeys : [ "claude-cli" ] ,
335+ pinnedRuntimeKeys : [ "agentRuntimeOverride" ] ,
336+ reasons : [ "pinned runtime" ] ,
337+ } ,
338+ ] ) ;
339+
340+ expect ( applySessionRouteStateRepair ( { entry, repair : scan . repairs [ 0 ] , now : 123 } ) ) . toBe ( true ) ;
341+ expect ( entry . sessionId ) . toBe ( "sess-stale-claude-cli" ) ;
342+ expect ( entry . updatedAt ) . toBe ( 123 ) ;
343+ expect ( entry . agentRuntimeOverride ) . toBeUndefined ( ) ;
344+ } ) ;
345+
346+ it ( "keeps agentRuntimeOverride pins when owner runtime remains configured" , ( ) => {
347+ const sessionKey = "agent:main:telegram:direct:6" ;
348+ const entry : Record < string , unknown > = {
349+ sessionId : "sess-active-claude-cli" ,
350+ updatedAt : 1 ,
351+ agentRuntimeOverride : "claude-cli" ,
352+ } ;
353+
354+ const scan = scanSessionRouteStateOwners ( {
355+ owners : [
356+ {
357+ id : "anthropic" ,
358+ label : "Anthropic" ,
359+ providerIds : [ "anthropic" ] ,
360+ runtimeIds : [ "claude-cli" ] ,
361+ cliSessionKeys : [ "claude-cli" ] ,
362+ authProfilePrefixes : [ "anthropic:" , "claude-cli:" ] ,
363+ } ,
364+ ] ,
365+ store : { [ sessionKey ] : entry } ,
366+ routes : {
367+ [ sessionKey ] : {
368+ defaultProvider : "anthropic" ,
369+ configuredModelRefs : [ "anthropic/claude-opus-4.7" ] ,
370+ runtime : "claude-cli" ,
371+ } ,
372+ } ,
373+ } ) ;
374+
375+ expect ( scan ) . toEqual ( { repairs : [ ] , manualReview : [ ] } ) ;
376+ } ) ;
377+
378+ it ( "clears stale owner runtime pins when owner provider remains configured" , ( ) => {
379+ const sessionKey = "agent:main:telegram:direct:7" ;
380+ const entry : Record < string , unknown > = {
381+ sessionId : "sess-provider-active-runtime-stale" ,
382+ updatedAt : 1 ,
383+ agentRuntimeOverride : "claude-cli" ,
384+ } ;
385+
386+ const scan = scanSessionRouteStateOwners ( {
387+ owners : [
388+ {
389+ id : "anthropic" ,
390+ label : "Anthropic" ,
391+ providerIds : [ "anthropic" ] ,
392+ runtimeIds : [ "claude-cli" ] ,
393+ cliSessionKeys : [ "claude-cli" ] ,
394+ authProfilePrefixes : [ "anthropic:" , "claude-cli:" ] ,
395+ } ,
396+ ] ,
397+ store : { [ sessionKey ] : entry } ,
398+ routes : {
399+ [ sessionKey ] : {
400+ defaultProvider : "anthropic" ,
401+ configuredModelRefs : [ "anthropic/claude-opus-4.7" ] ,
402+ runtime : "pi" ,
403+ } ,
404+ } ,
405+ } ) ;
406+
407+ expect ( scan . manualReview ) . toStrictEqual ( [ ] ) ;
408+ expect ( scan . repairs ) . toEqual ( [
409+ {
410+ key : sessionKey ,
411+ ownerId : "anthropic" ,
412+ ownerLabel : "Anthropic" ,
413+ cliSessionKeys : [ "claude-cli" ] ,
414+ pinnedRuntimeKeys : [ "agentRuntimeOverride" ] ,
415+ reasons : [ "pinned runtime" ] ,
416+ } ,
417+ ] ) ;
418+
419+ expect ( applySessionRouteStateRepair ( { entry, repair : scan . repairs [ 0 ] , now : 123 } ) ) . toBe ( true ) ;
420+ expect ( entry . updatedAt ) . toBe ( 123 ) ;
421+ expect ( entry . agentRuntimeOverride ) . toBeUndefined ( ) ;
422+ } ) ;
423+
424+ it ( "preserves non-owner runtime overrides when clearing owner harness pins" , ( ) => {
425+ const sessionKey = "agent:main:telegram:direct:8" ;
426+ const entry : Record < string , unknown > = {
427+ sessionId : "sess-mixed-runtime-pins" ,
428+ updatedAt : 1 ,
429+ agentHarnessId : "codex-cli" ,
430+ agentRuntimeOverride : "claude-cli" ,
431+ } ;
432+
433+ const scan = scanSessionRouteStateOwners ( {
434+ owners : [ codexOwner ] ,
435+ store : { [ sessionKey ] : entry } ,
436+ routes : {
437+ [ sessionKey ] : {
438+ defaultProvider : "openai" ,
439+ configuredModelRefs : [ "openai/gpt-5.5" ] ,
440+ runtime : "pi" ,
441+ } ,
442+ } ,
443+ } ) ;
444+
445+ expect ( scan . manualReview ) . toStrictEqual ( [ ] ) ;
446+ expect ( scan . repairs ) . toEqual ( [
447+ {
448+ key : sessionKey ,
449+ ownerId : "codex" ,
450+ ownerLabel : "Codex" ,
451+ cliSessionKeys : [ "codex-cli" ] ,
452+ pinnedRuntimeKeys : [ "agentHarnessId" ] ,
453+ reasons : [ "pinned runtime" ] ,
454+ } ,
455+ ] ) ;
456+
457+ expect ( applySessionRouteStateRepair ( { entry, repair : scan . repairs [ 0 ] , now : 123 } ) ) . toBe ( true ) ;
458+ expect ( entry . updatedAt ) . toBe ( 123 ) ;
459+ expect ( entry . agentHarnessId ) . toBeUndefined ( ) ;
460+ expect ( entry . agentRuntimeOverride ) . toBe ( "claude-cli" ) ;
461+ } ) ;
266462} ) ;
0 commit comments