@@ -4132,6 +4132,80 @@ describe("createStudioServer daemon lifecycle", () => {
41324132 await expect ( after . json ( ) ) . resolves . toMatchObject ( { mode : "manual" } ) ;
41334133 } ) ;
41344134
4135+ it ( "stores chapter review mode per book without changing the project default" , async ( ) => {
4136+ await writeCompleteBookFixture ( root , "demo-book" , "Demo Book" ) ;
4137+ const { createStudioServer } = await import ( "./server.js" ) ;
4138+ const app = createStudioServer ( cloneProjectConfig ( ) as never , root ) ;
4139+
4140+ const saveBookMode = await app . request ( "http://localhost/api/v1/books/demo-book/chapter-review-mode" , {
4141+ method : "PUT" ,
4142+ headers : { "Content-Type" : "application/json" } ,
4143+ body : JSON . stringify ( { mode : "manual" } ) ,
4144+ } ) ;
4145+ await expect ( saveBookMode . json ( ) ) . resolves . toMatchObject ( {
4146+ ok : true ,
4147+ mode : "manual" ,
4148+ bookMode : "manual" ,
4149+ projectMode : "auto" ,
4150+ } ) ;
4151+
4152+ const bookMode = await app . request ( "http://localhost/api/v1/books/demo-book/chapter-review-mode" ) ;
4153+ await expect ( bookMode . json ( ) ) . resolves . toMatchObject ( {
4154+ mode : "manual" ,
4155+ bookMode : "manual" ,
4156+ projectMode : "auto" ,
4157+ } ) ;
4158+
4159+ const projectMode = await app . request ( "http://localhost/api/v1/project/chapter-review-mode" ) ;
4160+ await expect ( projectMode . json ( ) ) . resolves . toMatchObject ( { mode : "auto" } ) ;
4161+ const rawBook = JSON . parse ( await readFile ( join ( root , "books" , "demo-book" , "book.json" ) , "utf-8" ) ) ;
4162+ expect ( rawBook . writing . reviewMode ) . toBe ( "manual" ) ;
4163+ } ) ;
4164+
4165+ it ( "uses a book-level manual review override when writing the next chapter" , async ( ) => {
4166+ await writeCompleteBookFixture ( root , "demo-book" , "Demo Book" ) ;
4167+ const rawBookPath = join ( root , "books" , "demo-book" , "book.json" ) ;
4168+ const rawBook = JSON . parse ( await readFile ( rawBookPath , "utf-8" ) ) ;
4169+ await writeFile ( rawBookPath , JSON . stringify ( {
4170+ ...rawBook ,
4171+ writing : { reviewMode : "manual" } ,
4172+ } , null , 2 ) , "utf-8" ) ;
4173+
4174+ const { createStudioServer } = await import ( "./server.js" ) ;
4175+ const app = createStudioServer ( cloneProjectConfig ( ) as never , root ) ;
4176+
4177+ const response = await app . request ( "http://localhost/api/v1/books/demo-book/write-next" , { method : "POST" } ) ;
4178+
4179+ expect ( response . status ) . toBe ( 200 ) ;
4180+ expect ( pipelineConfigs . at ( - 1 ) ) . toMatchObject ( { chapterReviewMode : "manual" } ) ;
4181+ } ) ;
4182+
4183+ it ( "exposes a global default model endpoint backed by llm.defaultModel" , async ( ) => {
4184+ const { createStudioServer } = await import ( "./server.js" ) ;
4185+ const app = createStudioServer ( cloneProjectConfig ( ) as never , root ) ;
4186+
4187+ const initial = await app . request ( "http://localhost/api/v1/project/default-model" ) ;
4188+ await expect ( initial . json ( ) ) . resolves . toMatchObject ( {
4189+ defaultModel : "gpt-5.4" ,
4190+ } ) ;
4191+
4192+ const save = await app . request ( "http://localhost/api/v1/project/default-model" , {
4193+ method : "PUT" ,
4194+ headers : { "Content-Type" : "application/json" } ,
4195+ body : JSON . stringify ( { service : "kkaiapi" , defaultModel : "deepseek-v4-flash" } ) ,
4196+ } ) ;
4197+ await expect ( save . json ( ) ) . resolves . toMatchObject ( {
4198+ ok : true ,
4199+ service : "kkaiapi" ,
4200+ defaultModel : "deepseek-v4-flash" ,
4201+ } ) ;
4202+
4203+ const raw = JSON . parse ( await readFile ( join ( root , "inkos.json" ) , "utf-8" ) ) ;
4204+ expect ( raw . llm . service ) . toBe ( "kkaiapi" ) ;
4205+ expect ( raw . llm . defaultModel ) . toBe ( "deepseek-v4-flash" ) ;
4206+ expect ( raw . llm . model ) . toBe ( "deepseek-v4-flash" ) ;
4207+ } ) ;
4208+
41354209 it ( "project advanced settings expose input governance and detection config" , async ( ) => {
41364210 const { createStudioServer } = await import ( "./server.js" ) ;
41374211 const app = createStudioServer ( cloneProjectConfig ( ) as never , root ) ;
0 commit comments