11import assert from "node:assert/strict" ;
2- import { test } from "vitest" ;
2+ import { mkdir , mkdtemp , readFile , rm , writeFile } from "node:fs/promises" ;
3+ import { tmpdir } from "node:os" ;
4+ import { join } from "node:path" ;
5+ import {
6+ ExtensionRunner ,
7+ ModelRegistry ,
8+ ModelRuntime ,
9+ SessionManager ,
10+ discoverAndLoadExtensions ,
11+ } from "@earendil-works/pi-coding-agent" ;
12+ import { test , vi } from "vitest" ;
313
414import { ModeController } from "../src/mode-controller.ts" ;
15+ import { ModePicker } from "../src/mode-picker.ts" ;
516
617function createController ( ) : ModeController {
718 return new ModeController ( {
@@ -27,6 +38,61 @@ test("mode controllers own independent runtime state", () => {
2738 assert . equal ( second . thinkingLevelStatusEnabled , false ) ;
2839} ) ;
2940
41+ test . each ( [ "__proto__" , "constructor" , "prototype" ] ) (
42+ "renaming a mode to %s is rejected without losing the saved mode" ,
43+ async ( name ) => {
44+ const root = await mkdtemp ( join ( tmpdir ( ) , "pi-model-modes-rename-" ) ) ;
45+ vi . stubEnv ( "PI_CODING_AGENT_DIR" , join ( root , "agent" ) ) ;
46+ const configPath = join ( root , "agent" , "extension-settings" , "pi-model-modes.json" ) ;
47+ const original = JSON . stringify ( {
48+ currentMode : "review" ,
49+ modes : { review : { provider : "local" , modelId : "small" } } ,
50+ } ) ;
51+ try {
52+ await mkdir ( join ( configPath , ".." ) , { recursive : true } ) ;
53+ await writeFile ( configPath , original ) ;
54+ const loaded = await discoverAndLoadExtensions ( [ ] , root , join ( root , "agent" ) ) ;
55+ const models = await ModelRuntime . create ( {
56+ authPath : join ( root , "auth.json" ) ,
57+ modelsPath : null ,
58+ refreshOnCreate : false ,
59+ } ) ;
60+ const runner = new ExtensionRunner (
61+ loaded . extensions ,
62+ loaded . runtime ,
63+ root ,
64+ SessionManager . inMemory ( root ) ,
65+ new ModelRegistry ( models ) ,
66+ ) ;
67+ const choices = [ "Configure modes…" , "review" , "Change name" ] ;
68+ const inputs = [ name ] ;
69+ const notifications : string [ ] = [ ] ;
70+ runner . setUIContext (
71+ {
72+ ...runner . getUIContext ( ) ,
73+ select : async ( ) => choices . shift ( ) ,
74+ input : async ( ) => inputs . shift ( ) ,
75+ notify : ( message ) => {
76+ notifications . push ( message ) ;
77+ } ,
78+ } ,
79+ "rpc" ,
80+ ) ;
81+ const controller = createController ( ) ;
82+
83+ await new ModePicker ( controller ) . select ( runner . createContext ( ) ) ;
84+ assert . equal ( await readFile ( configPath , "utf8" ) , original ) ;
85+ assert . deepEqual ( controller . modes . modes , {
86+ review : { provider : "local" , modelId : "small" } ,
87+ } ) ;
88+ assert . deepEqual ( notifications , [ `Mode name "${ name } " is reserved` ] ) ;
89+ } finally {
90+ vi . unstubAllEnvs ( ) ;
91+ await rm ( root , { recursive : true , force : true } ) ;
92+ }
93+ } ,
94+ ) ;
95+
3096test ( "mode controller derives settings reads from the explicit extension context" , ( ) => {
3197 const controller = createController ( ) ;
3298 assert . deepEqual (
0 commit comments