briyah 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/data/common/config/image_models.json +141 -21
  2. package/data/common/config/image_models_full.json +135 -48
  3. package/data/common/config/markup +1 -1
  4. package/data/common/config/story_models.json +41 -38
  5. package/data/common/prompts/character/progress_character.prompt +4 -0
  6. package/data/common/prompts/character/update_portrait.prompt +31 -0
  7. package/data/common/prompts/narrator/perceive.prompt +23 -1
  8. package/data/common/prompts/narrator/progress_plot.prompt +1 -3
  9. package/data/common/prompts/story_moderator/moderate.json +36 -29
  10. package/data/common/prompts/story_moderator/moderate.prompt +63 -18
  11. package/dist-sdk/server/src/ai/LLM/base-ai.service.d.ts +5 -7
  12. package/dist-sdk/server/src/ai/LLM/base-ai.service.js +7 -1
  13. package/dist-sdk/server/src/ai/LLM/fal.service.d.ts +5 -1
  14. package/dist-sdk/server/src/ai/LLM/fal.service.js +93 -21
  15. package/dist-sdk/server/src/ai/LLM/googleai.service.d.ts +1 -1
  16. package/dist-sdk/server/src/ai/LLM/googleai.service.js +20 -29
  17. package/dist-sdk/server/src/ai/LLM/openai.service.d.ts +5 -1
  18. package/dist-sdk/server/src/ai/LLM/openai.service.js +58 -71
  19. package/dist-sdk/server/src/ai/LLM/together.service.d.ts +5 -1
  20. package/dist-sdk/server/src/ai/LLM/together.service.js +57 -31
  21. package/dist-sdk/server/src/ai/agent-config.d.ts +1 -0
  22. package/dist-sdk/server/src/ai/agent-store.service.js +4 -0
  23. package/dist-sdk/server/src/ai/agent.d.ts +8 -1
  24. package/dist-sdk/server/src/ai/agent.js +21 -3
  25. package/dist-sdk/server/src/ai/model_prices.js +21 -1
  26. package/dist-sdk/server/src/app.controller.d.ts +4 -0
  27. package/dist-sdk/server/src/app.controller.js +51 -1
  28. package/dist-sdk/server/src/app.service.d.ts +3 -1
  29. package/dist-sdk/server/src/app.service.js +22 -14
  30. package/dist-sdk/server/src/room/message.d.ts +1 -0
  31. package/dist-sdk/server/src/room/message.js +1 -0
  32. package/dist-sdk/server/src/room/room-store.service.d.ts +1 -0
  33. package/dist-sdk/server/src/room/room-store.service.js +3 -0
  34. package/dist-sdk/server/src/room/room.d.ts +3 -0
  35. package/dist-sdk/server/src/room/room.js +27 -7
  36. package/dist-sdk/server/src/story/story-store.service.d.ts +1 -0
  37. package/dist-sdk/server/src/story/story-store.service.js +38 -0
  38. package/dist-sdk/server/src/story/story.service.d.ts +4 -1
  39. package/dist-sdk/server/src/story/story.service.js +233 -55
  40. package/dist-sdk/shared/types/app.types.d.ts +17 -0
  41. package/docs/assets/hierarchy.js +1 -1
  42. package/docs/assets/search.js +1 -1
  43. package/docs/classes/Agent.html +19 -15
  44. package/docs/classes/Briyah.html +12 -12
  45. package/docs/classes/BriyahConfigService.html +5 -5
  46. package/docs/classes/Room.html +26 -24
  47. package/docs/classes/RoomMessage.html +11 -10
  48. package/docs/enums/MessageAction.html +3 -3
  49. package/docs/hierarchy.html +1 -1
  50. package/docs/index.html +2 -2
  51. package/docs/interfaces/AgentInfo.html +2 -2
  52. package/docs/interfaces/AgentMessagesResponse.html +2 -2
  53. package/docs/interfaces/AppService.html +128 -113
  54. package/docs/interfaces/Artifact.html +3 -3
  55. package/docs/interfaces/ArtifactMetadata.html +2 -2
  56. package/docs/interfaces/AttachDocumentResponse.html +2 -2
  57. package/docs/interfaces/BriyahConfigOptions.html +6 -6
  58. package/docs/interfaces/ChapterInfo.html +2 -2
  59. package/docs/interfaces/Character.html +2 -2
  60. package/docs/interfaces/CreateAgentResponse.html +2 -2
  61. package/docs/interfaces/CreateRoomResponse.html +2 -2
  62. package/docs/interfaces/CreateStoryResponse.html +2 -2
  63. package/docs/interfaces/FileList.html +2 -2
  64. package/docs/interfaces/LoggingOptions.html +5 -5
  65. package/docs/interfaces/Message.html +2 -2
  66. package/docs/interfaces/ModelInfo.html +3 -2
  67. package/docs/interfaces/PreparedPromptResponse.html +2 -2
  68. package/docs/interfaces/ProcessTextResponse.html +2 -2
  69. package/docs/interfaces/PromptFile.html +2 -2
  70. package/docs/interfaces/PromptFileContent.html +2 -2
  71. package/docs/interfaces/PromptFilesResponse.html +2 -2
  72. package/docs/interfaces/PromptFolder.html +2 -2
  73. package/docs/interfaces/PromptFoldersResponse.html +2 -2
  74. package/docs/interfaces/RoomDetails.html +2 -2
  75. package/docs/interfaces/RoomInfo.html +2 -2
  76. package/docs/interfaces/RoomMessagesResponse.html +2 -2
  77. package/docs/interfaces/StoryErrorEvent.html +3 -3
  78. package/docs/interfaces/StoryIdea.html +2 -2
  79. package/docs/interfaces/StoryInfo.html +5 -3
  80. package/docs/interfaces/StoryIntroduceCharacterEvent.html +3 -3
  81. package/docs/interfaces/StoryProgressChapterEvent.html +3 -3
  82. package/docs/interfaces/StoryState.html +5 -5
  83. package/docs/interfaces/StoryStateEvent.html +3 -3
  84. package/docs/interfaces/Transaction.html +2 -2
  85. package/docs/interfaces/TransactionHistoryResponse.html +2 -2
  86. package/docs/modules.html +1 -1
  87. package/docs/types/PromptScope.html +1 -1
  88. package/package.json +1 -1
@@ -345,6 +345,44 @@ let StoryStoreService = class StoryStoreService {
345
345
  return false;
346
346
  }
347
347
  }
348
+ restoreRoomFilesFromChapter(storyId, chapterIndex) {
349
+ const storyFolder = path.join(this.storiesDir, storyId);
350
+ const chapterFolder = path.join(storyFolder, 'chapters', `chapter_${chapterIndex}`);
351
+ try {
352
+ const story = this.getStory(storyId);
353
+ if (!story || !story.roomId) {
354
+ logger_1.logger.error(`Cannot restore room files: story ${storyId} has no room ID`);
355
+ return -1;
356
+ }
357
+ if (!fs.existsSync(chapterFolder)) {
358
+ logger_1.logger.warn(`Chapter ${chapterIndex} folder not found for story ${storyId}`);
359
+ return -1;
360
+ }
361
+ const roomFolder = path.join(storyFolder, 'rooms', story.roomId);
362
+ if (!fs.existsSync(roomFolder)) {
363
+ fs.mkdirSync(roomFolder, { recursive: true });
364
+ }
365
+ const items = fs.readdirSync(chapterFolder);
366
+ let restored = 0;
367
+ for (const item of items) {
368
+ if (item === 'metadata.json' || item === 'agents') {
369
+ continue;
370
+ }
371
+ const sourcePath = path.join(chapterFolder, item);
372
+ const targetPath = path.join(roomFolder, item);
373
+ if (fs.statSync(sourcePath).isFile()) {
374
+ fs.copyFileSync(sourcePath, targetPath);
375
+ restored++;
376
+ }
377
+ }
378
+ logger_1.logger.log(`Restored ${restored} room files from chapter ${chapterIndex} for story ${storyId}`);
379
+ return restored;
380
+ }
381
+ catch (error) {
382
+ logger_1.logger.error(`Error restoring room files from chapter ${chapterIndex} for story ${storyId}:`, error);
383
+ return -1;
384
+ }
385
+ }
348
386
  async restoreAgentsFromChapter(storyId, chapterIndex, agentStore) {
349
387
  const storyFolder = path.join(this.storiesDir, storyId);
350
388
  const chapterAgentsFolder = path.join(storyFolder, 'chapters', `chapter_${chapterIndex}`, 'agents');
@@ -36,7 +36,7 @@ export declare class StoryService {
36
36
  private getStoryRoom;
37
37
  private ensureStoryLoaded;
38
38
  getStoryInfo(storyId: string): Promise<StoryInfo | null>;
39
- createStory(name: string, idea: string, userCharacterDesc: string, otherCharactersDesc: string, storyModel?: string, isImport?: boolean, imageModelName?: string): Promise<StoryInfo>;
39
+ createStory(name: string, idea: string, userCharacterDesc: string, otherCharactersDesc: string, illustrateStory: boolean, storyModel?: string, isImport?: boolean, imageModelName?: string, imageEditModelName?: string): Promise<StoryInfo>;
40
40
  private createStoryAsync;
41
41
  generateCharacterPortraits(storyInfo: StoryInfo, room: Room): Promise<void>[];
42
42
  generateOpeningSceneImage(storyInfo: StoryInfo, room: Room): Promise<{
@@ -56,6 +56,7 @@ export declare class StoryService {
56
56
  savePlotPlan(storyId: string, content: string): Promise<void>;
57
57
  publishArtifact(storyId: string, name: string, creator: string, body: string, viewers: string[]): Promise<void>;
58
58
  respondToStory(storyId: string, content: string): Promise<void>;
59
+ interruptStory(storyId: string): Promise<void>;
59
60
  introduceCharacter(storyId: string, characterName: string, characterDescription: string, storyModel?: string, fromNarratorSuggestion?: boolean): Promise<void>;
60
61
  declineCharacter(storyId: string, characterName: string): Promise<void>;
61
62
  deleteCharacter(storyId: string, characterName: string): Promise<void>;
@@ -70,6 +71,7 @@ export declare class StoryService {
70
71
  progressStoryToNextChapter(storyId: string): Promise<{
71
72
  chapterIndex: number;
72
73
  }>;
74
+ revertStoryChapter(storyId: string): Promise<void>;
73
75
  private progressStoryToNextChapterAsync;
74
76
  private generateOpeningScene;
75
77
  private compactAgentHistories;
@@ -79,6 +81,7 @@ export declare class StoryService {
79
81
  private getImageModelConfig;
80
82
  private createArtistAgent;
81
83
  private generateCharacterPortrait;
84
+ private updateCharacterPortrait;
82
85
  private generateSceneIllustration;
83
86
  private generateSceneReference;
84
87
  private generateStoryImage;
@@ -191,6 +191,7 @@ let StoryService = class StoryService {
191
191
  return {
192
192
  service: modelConfig.service,
193
193
  model: modelConfig.model,
194
+ smallModel: modelConfig.small_model,
194
195
  };
195
196
  }
196
197
  logger_1.logger.warn(`Story model "${storyModel}" not found in story_models.json`);
@@ -240,6 +241,21 @@ let StoryService = class StoryService {
240
241
  room.setOnSituationUpdate((situation) => {
241
242
  this.updateStorySituation(storyId, situation);
242
243
  });
244
+ room.setOnModeratorResponse((message) => {
245
+ if (message.updatePortrait?.length) {
246
+ for (const nickname of message.updatePortrait) {
247
+ const characterAgent = room
248
+ .getAgents()
249
+ .find((a) => a.agentNickname === nickname);
250
+ if (characterAgent) {
251
+ this.updateCharacterPortrait(room, characterAgent, storyId).catch((e) => logger_1.logger.error(`Error updating portrait for ${nickname}:`, e));
252
+ }
253
+ else {
254
+ logger_1.logger.warn(`update_portrait: agent "${nickname}" not found in room`);
255
+ }
256
+ }
257
+ }
258
+ });
243
259
  if (story.situation) {
244
260
  room.setSituation(story.situation);
245
261
  }
@@ -249,7 +265,7 @@ let StoryService = class StoryService {
249
265
  async getStoryInfo(storyId) {
250
266
  return this.storyStore.getStory(storyId);
251
267
  }
252
- async createStory(name, idea, userCharacterDesc, otherCharactersDesc, storyModel, isImport = false, imageModelName) {
268
+ async createStory(name, idea, userCharacterDesc, otherCharactersDesc, illustrateStory, storyModel, isImport = false, imageModelName, imageEditModelName) {
253
269
  if (!name || name.trim().length === 0) {
254
270
  throw new Error('Story name is required');
255
271
  }
@@ -280,7 +296,18 @@ let StoryService = class StoryService {
280
296
  moderator.markupRate = 0;
281
297
  moderator.save();
282
298
  logger_1.logger.log(`Created moderator agent: ${moderator.agentName}`);
299
+ if (illustrateStory) {
300
+ if (!imageModelName)
301
+ imageModelName = process.env.DEFAULT_IMAGE_MODEL;
302
+ if (!imageEditModelName)
303
+ imageEditModelName = process.env.DEFAULT_IMAGE_EDIT_MODEL;
304
+ }
305
+ else {
306
+ imageModelName = undefined;
307
+ imageEditModelName = undefined;
308
+ }
283
309
  const artist = this.createArtistAgent(storyInfo.id, storyAgentsDir, storyArtifactService, imageModelName);
310
+ const artistEdit = this.createArtistAgent(storyInfo.id, storyAgentsDir, storyArtifactService, imageEditModelName, 'Artist (Edit)');
284
311
  let illustrator = null;
285
312
  if (artist) {
286
313
  illustrator = this.agentFactory.createAgent(process.env.MODERATOR_AI_SERVICE || aiServiceName, `${storyTitle} - Illustrator`, 'Illustrator', 'Visual artist for generating scene illustrations', 'illustrator', process.env.MODERATOR_AI_MODEL || aiModelName, storyAgentsDir, storyArtifactService);
@@ -295,6 +322,7 @@ let StoryService = class StoryService {
295
322
  narrator.allowSearch = true;
296
323
  narrator.promptCacheTTL = 60;
297
324
  narrator.ownerRoomId = storyInfo.id;
325
+ narrator.smallModelName = modelConfig.smallModel;
298
326
  narrator.save();
299
327
  logger_1.logger.log(`Created narrator agent: ${narrator.agentName}`);
300
328
  narrator.disableMarkup = true;
@@ -348,6 +376,7 @@ let StoryService = class StoryService {
348
376
  userAgent = this.agentFactory.createAgent(aiServiceName, `${name} - ${userNickname}`, userNickname, userCharacterInfo.description || userCharacterDesc, 'character', aiModelName, storyAgentsDir, storyArtifactService);
349
377
  userAgent.isControlledByHuman = true;
350
378
  userAgent.ownerRoomId = storyInfo.id;
379
+ userAgent.smallModelName = modelConfig.smallModel;
351
380
  userAgent.save();
352
381
  logger_1.logger.log(`Created human-controlled character agent: ${userCharacterName}`);
353
382
  userAgent.disableMarkup = true;
@@ -366,6 +395,7 @@ let StoryService = class StoryService {
366
395
  const agent = this.agentFactory.createAgent(aiServiceName, `${name} - ${characterNickname}`, characterNickname, charInfo.description, 'character', aiModelName, storyAgentsDir, storyArtifactService);
367
396
  agent.promptCacheTTL = 60;
368
397
  agent.ownerRoomId = storyInfo.id;
398
+ agent.smallModelName = modelConfig.smallModel;
369
399
  agent.save();
370
400
  logger_1.logger.log(`Created character agent: ${charInfo.name}`);
371
401
  agent.disableMarkup = true;
@@ -409,11 +439,28 @@ let StoryService = class StoryService {
409
439
  room.setOnSituationUpdate((situation) => {
410
440
  this.updateStorySituation(storyInfo.id, situation);
411
441
  });
442
+ room.setOnModeratorResponse((message) => {
443
+ if (message.updatePortrait?.length) {
444
+ for (const nickname of message.updatePortrait) {
445
+ const characterAgent = room
446
+ .getAgents()
447
+ .find((a) => a.agentNickname === nickname);
448
+ if (characterAgent) {
449
+ this.updateCharacterPortrait(room, characterAgent, storyInfo.id).catch((e) => logger_1.logger.error(`Error updating portrait for ${nickname}:`, e));
450
+ }
451
+ else {
452
+ logger_1.logger.warn(`update_portrait: agent "${nickname}" not found in room`);
453
+ }
454
+ }
455
+ }
456
+ });
412
457
  storyInfo.roomId = roomId;
413
458
  storyInfo.moderatorAgentId = moderator.id;
414
459
  storyInfo.illustratorAgentId = illustrator?.id;
415
460
  storyInfo.artistAgentId = artist?.id;
461
+ storyInfo.artistEditAgentId = artistEdit?.id;
416
462
  storyInfo.imageModelName = imageModelName;
463
+ storyInfo.imageEditModelName = imageEditModelName;
417
464
  if (userAgent) {
418
465
  storyInfo.userAgentId = userAgent.id;
419
466
  }
@@ -936,6 +983,10 @@ let StoryService = class StoryService {
936
983
  const responseMessage = new message_1.RoomMessage(humanAgent.agentNickname, app_types_1.MessageAction.MODERATE, content, [], null);
937
984
  room.addPendingMessage(responseMessage);
938
985
  }
986
+ async interruptStory(storyId) {
987
+ const room = await this.getStoryRoom(storyId);
988
+ room.interrupt();
989
+ }
939
990
  async introduceCharacter(storyId, characterName, characterDescription, storyModel, fromNarratorSuggestion) {
940
991
  const room = await this.getStoryRoom(storyId);
941
992
  if (!characterName)
@@ -970,6 +1021,7 @@ let StoryService = class StoryService {
970
1021
  const characterNickname = characterName.trim().split(/\s+/)[0];
971
1022
  const characterAgent = this.agentFactory.createAgent(aiServiceName, `${storyTitle} - ${characterNickname}`, characterNickname, characterDescription, 'character', aiModelName, storyAgentsDir, storyArtifactService);
972
1023
  characterAgent.promptCacheTTL = 60;
1024
+ characterAgent.smallModelName = modelConfig.smallModel;
973
1025
  characterAgent.save();
974
1026
  room.arrive(characterAgent);
975
1027
  const introducedCharArtifacts = room.getArtifacts();
@@ -1238,6 +1290,49 @@ let StoryService = class StoryService {
1238
1290
  throw err;
1239
1291
  }
1240
1292
  }
1293
+ async revertStoryChapter(storyId) {
1294
+ const story = this.storyStore.getStory(storyId);
1295
+ if (!story || !story.roomId) {
1296
+ throw new errors_1.NotFoundError('Story not found');
1297
+ }
1298
+ const room = await this.getStoryRoom(storyId);
1299
+ if (!room) {
1300
+ throw new errors_1.NotFoundError('Story room not found');
1301
+ }
1302
+ if (this.chaptersInProgress.has(storyId)) {
1303
+ throw new errors_1.OperationFailedError('Chapter progression is in progress; cannot revert');
1304
+ }
1305
+ const chapters = this.storyStore.listChapters(storyId);
1306
+ if (chapters.length === 0) {
1307
+ throw new errors_1.OperationFailedError('No previous chapter to revert to');
1308
+ }
1309
+ const liveMessages = room.getLatestRoomMessages(0, true);
1310
+ const nonOpening = liveMessages.filter((m) => m.sender !== 'Opening Scene' || m.action !== app_types_1.MessageAction.SYSTEM);
1311
+ if (nonOpening.length > 0) {
1312
+ throw new errors_1.OperationFailedError('Cannot revert: chapter has progressed past the opening scene');
1313
+ }
1314
+ const targetChapter = Math.max(...chapters.map((c) => c.chapterNum));
1315
+ await this.storyStore.restoreAgentsFromChapter(storyId, targetChapter, this.agentStore);
1316
+ const restoredCount = this.storyStore.restoreRoomFilesFromChapter(storyId, targetChapter);
1317
+ if (restoredCount < 0) {
1318
+ throw new errors_1.OperationFailedError(`Failed to restore room files from chapter ${targetChapter}`);
1319
+ }
1320
+ const storageDir = room.getStorageDir();
1321
+ const messagesPath = path.join(storageDir, story.roomId, 'messages');
1322
+ const restoredMessages = await this.roomStore.readRoomMessagesFromFileAsync(messagesPath);
1323
+ room.setRoomMessages(restoredMessages || []);
1324
+ const restoredArtifacts = this.roomStore.loadArtifactFiles(story.roomId, storageDir);
1325
+ room.setArtifacts(restoredArtifacts);
1326
+ const humanAgent = room.getAgents().find((a) => a.isControlledByHuman);
1327
+ if (humanAgent) {
1328
+ room.setCurrentSpeaker(humanAgent.agentNickname);
1329
+ }
1330
+ const deleted = this.storyStore.deleteChapter(storyId, targetChapter);
1331
+ if (!deleted) {
1332
+ logger_1.logger.warn(`Reverted chapter ${targetChapter} for story ${storyId} but failed to delete backup directory`);
1333
+ }
1334
+ logger_1.logger.log(`Reverted story ${storyId} to chapter ${targetChapter} (${restoredMessages?.length ?? 0} messages, ${restoredArtifacts.length} artifacts)`);
1335
+ }
1241
1336
  async progressStoryToNextChapterAsync(storyId) {
1242
1337
  let room = null;
1243
1338
  let agents = [];
@@ -1322,12 +1417,12 @@ let StoryService = class StoryService {
1322
1417
  const artifacts = room.getArtifacts();
1323
1418
  const plotPlanArtifact = artifacts.find((a) => a.name === 'Plot Plan');
1324
1419
  const currentPlotPlan = plotPlanArtifact ? plotPlanArtifact.body : 'No plot plan found.';
1325
- const formattedHistory = narrator.getRawMessages();
1420
+ const previousAllowSearch = narrator.allowSearch;
1421
+ narrator.allowSearch = true;
1326
1422
  try {
1327
- const progressPlotResponse = await narrator.preparedPrompt('progress_plot', {
1423
+ const progressPlotResponse = await narrator.withSmallModel(() => narrator.preparedPrompt('progress_plot', {
1328
1424
  currentPlotPlan: currentPlotPlan,
1329
- formattedHistory: formattedHistory,
1330
- }, false);
1425
+ }, false));
1331
1426
  if (progressPlotResponse) {
1332
1427
  const cleanedPlotPlan = this.extractMarkdownContent(progressPlotResponse);
1333
1428
  room.publishArtifact('Plot Plan', narrator.agentNickname, cleanedPlotPlan, [
@@ -1340,6 +1435,9 @@ let StoryService = class StoryService {
1340
1435
  catch (error) {
1341
1436
  logger_1.logger.error(`Error updating plot plan:`, error);
1342
1437
  }
1438
+ finally {
1439
+ narrator.allowSearch = previousAllowSearch;
1440
+ }
1343
1441
  return '';
1344
1442
  }
1345
1443
  async updateCharacterProfiles(room, characterAgents, plotPlan = '') {
@@ -1348,21 +1446,22 @@ let StoryService = class StoryService {
1348
1446
  try {
1349
1447
  const profileArtifact = artifacts.find((a) => a.name === `Character Profile - ${character.agentNickname}`);
1350
1448
  let currentProfile = profileArtifact ? profileArtifact.body : null;
1351
- let formattedHistory = character.getFormattedMessages(0);
1449
+ let formattedHistory = [];
1450
+ const hasNativeHistory = (character.history?.length ?? 0) > 0;
1451
+ if (!hasNativeHistory) {
1452
+ formattedHistory = room
1453
+ .getLatestRoomMessages(0, true)
1454
+ .map((m) => `${m.sender}: ${m.content}\n`);
1455
+ }
1352
1456
  if (!currentProfile) {
1353
1457
  currentProfile = `There is not yet a character profile for ${character.agentNickname}. Use the updated plot plan and recent story messages to create a character profile.`;
1354
- if (!formattedHistory || formattedHistory.length === 0) {
1355
- formattedHistory = room
1356
- .getLatestRoomMessages(0, true)
1357
- .map((m) => `${m.sender}: ${m.content}\n`);
1358
- }
1359
1458
  }
1360
- const progressCharacterResponse = await character.preparedPrompt('progress_character', {
1459
+ const progressCharacterResponse = await character.withSmallModel(() => character.preparedPrompt('progress_character', {
1361
1460
  agentName: character.agentNickname,
1362
1461
  currentProfile: currentProfile,
1363
1462
  formattedHistory: formattedHistory,
1364
1463
  plotPlan,
1365
- }, false);
1464
+ }, false));
1366
1465
  if (progressCharacterResponse.description) {
1367
1466
  character.description = progressCharacterResponse.description;
1368
1467
  character.save();
@@ -1375,37 +1474,30 @@ let StoryService = class StoryService {
1375
1474
  });
1376
1475
  await Promise.all(updatePromises);
1377
1476
  logger_1.logger.log(`Updated ${characterAgents.length} character profiles in parallel`);
1378
- const portraitPromises = characterAgents.map(async (characterAgent) => {
1379
- try {
1380
- const pathParts = room.getStorageDir().split(path.sep);
1381
- const storiesIndex = pathParts.findIndex((p) => p === 'stories');
1382
- const storyId = storiesIndex >= 0 ? pathParts[storiesIndex + 1] : null;
1383
- if (!storyId) {
1384
- logger_1.logger.error('Could not extract story ID from room path');
1385
- return { character: characterAgent.agentNickname, success: false };
1477
+ const pathParts = room.getStorageDir().split(path.sep);
1478
+ const storiesIndex = pathParts.findIndex((p) => p === 'stories');
1479
+ const storyId = storiesIndex >= 0 ? pathParts[storiesIndex + 1] : null;
1480
+ if (!storyId) {
1481
+ logger_1.logger.error('Could not extract story ID from room path; skipping portrait updates');
1482
+ }
1483
+ else {
1484
+ const portraitPromises = characterAgents.map(async (characterAgent) => {
1485
+ try {
1486
+ const artifactId = await this.updateCharacterPortrait(room, characterAgent, storyId);
1487
+ return {
1488
+ character: characterAgent.agentNickname,
1489
+ success: !!artifactId,
1490
+ artifactId,
1491
+ };
1386
1492
  }
1387
- const storyArtifactService = this.getStoryArtifactService(storyId);
1388
- const oldPortraitName = `Portrait - ${characterAgent.agentNickname}`;
1389
- const oldArtifacts = storyArtifactService.listArtifacts();
1390
- const oldPortrait = oldArtifacts.find((a) => a.name === oldPortraitName);
1391
- if (oldPortrait) {
1392
- storyArtifactService.deleteArtifact(oldPortrait.artifactId);
1393
- logger_1.logger.log(`Deleted old portrait for ${characterAgent.agentName}`);
1493
+ catch (error) {
1494
+ logger_1.logger.error(`Error updating portrait for ${characterAgent.agentName}:`, error);
1495
+ return { character: characterAgent.agentNickname, success: false, error };
1394
1496
  }
1395
- const artifactId = await this.generateCharacterPortrait(room, characterAgent, storyId);
1396
- return {
1397
- character: characterAgent.agentNickname,
1398
- success: !!artifactId,
1399
- artifactId,
1400
- };
1401
- }
1402
- catch (error) {
1403
- logger_1.logger.error(`Error regenerating portrait for ${characterAgent.agentName}:`, error);
1404
- return { character: characterAgent.agentNickname, success: false, error };
1405
- }
1406
- });
1407
- await Promise.all(portraitPromises);
1408
- logger_1.logger.log(`Regenerated portraits for ${characterAgents.length} characters`);
1497
+ });
1498
+ await Promise.all(portraitPromises);
1499
+ logger_1.logger.log(`Updated portraits for ${characterAgents.length} characters`);
1500
+ }
1409
1501
  }
1410
1502
  getImageModelConfig(modelName) {
1411
1503
  try {
@@ -1423,23 +1515,32 @@ let StoryService = class StoryService {
1423
1515
  logger_1.logger.log(`Images disabled: selected "${modelName}"`);
1424
1516
  return null;
1425
1517
  }
1426
- return { service: modelConfig.service, model: modelConfig.model };
1518
+ return {
1519
+ service: modelConfig.service,
1520
+ model: modelConfig.model,
1521
+ imageProperties: modelConfig.imageProperties,
1522
+ centsPerImage: modelConfig.centsPerImage,
1523
+ };
1427
1524
  }
1428
1525
  catch (error) {
1429
1526
  logger_1.logger.error('Error loading image model config:', error);
1430
1527
  return null;
1431
1528
  }
1432
1529
  }
1433
- createArtistAgent(storyId, storyAgentsDir, storyArtifactService, imageModelName) {
1530
+ createArtistAgent(storyId, storyAgentsDir, storyArtifactService, imageModelName, nickname = 'Artist') {
1434
1531
  const storyInfo = this.storyStore.getStory(storyId);
1435
1532
  const imageConfig = this.getImageModelConfig(imageModelName);
1436
1533
  if (!imageConfig) {
1437
- logger_1.logger.log('Artist agent creation skipped: images disabled');
1534
+ logger_1.logger.log(`Artist agent creation skipped (${nickname}): images disabled`);
1438
1535
  return null;
1439
1536
  }
1440
- const artistAgent = this.agentFactory.createAgent(imageConfig.service, `${storyInfo.name} - Artist`, 'Artist', 'AI artist for generating character portraits and scene illustrations', 'default', imageConfig.model, storyAgentsDir, storyArtifactService);
1537
+ const artistAgent = this.agentFactory.createAgent(imageConfig.service, `${storyInfo.name} - ${nickname}`, nickname, 'AI artist for generating character portraits and scene illustrations', 'default', imageConfig.model, storyAgentsDir, storyArtifactService);
1441
1538
  artistAgent.maxHistoryMessages = 10;
1442
1539
  artistAgent.promptCacheTTL = 0;
1540
+ artistAgent.imageProperties = {
1541
+ ...(imageConfig?.imageProperties ?? {}),
1542
+ ...(imageConfig?.centsPerImage != null ? { centsPerImage: imageConfig.centsPerImage } : {}),
1543
+ };
1443
1544
  artistAgent.save();
1444
1545
  logger_1.logger.log(`Created artist agent: ${artistAgent.agentName} (${imageConfig.service}/${imageConfig.model})`);
1445
1546
  return artistAgent;
@@ -1456,11 +1557,13 @@ let StoryService = class StoryService {
1456
1557
  logger_1.logger.warn(`No profile for ${characterAgent.agentNickname}, skipping portrait`);
1457
1558
  return null;
1458
1559
  }
1560
+ const storyArtifactService = this.getStoryArtifactService(storyId);
1459
1561
  const artistAgent = await this.agentStore.getAgent(storyInfo.artistAgentId);
1460
1562
  if (!artistAgent) {
1461
1563
  logger_1.logger.warn(`Artist agent not found: ${storyInfo.artistAgentId}`);
1462
1564
  return null;
1463
1565
  }
1566
+ artistAgent.artifactService = storyArtifactService;
1464
1567
  const visualDescription = await characterAgent.preparedPrompt('describe_character', {
1465
1568
  characterName: characterAgent.agentNickname,
1466
1569
  characterProfile: characterDescription,
@@ -1470,13 +1573,12 @@ let StoryService = class StoryService {
1470
1573
  logger_1.logger.warn(`Failed to generate visual description for ${characterAgent.agentNickname}`);
1471
1574
  return null;
1472
1575
  }
1473
- const imageResult = await artistAgent.generateImage(visualDescription, 1024, 1536, 'high');
1576
+ const imageResult = await artistAgent.generateImage(visualDescription);
1474
1577
  if (imageResult.error) {
1475
1578
  return null;
1476
1579
  }
1477
1580
  artistAgent.save();
1478
1581
  const artifactId = imageResult.artifactId;
1479
- let storyArtifactService = this.getStoryArtifactService(storyId);
1480
1582
  storyArtifactService.renameArtifact(artifactId, `Portrait - ${characterAgent.agentNickname}`);
1481
1583
  storyArtifactService.updateArtifactDescription(artifactId, visualDescription);
1482
1584
  logger_1.logger.log(`Generated portrait for ${characterAgent.agentNickname}: ${artifactId}`);
@@ -1487,7 +1589,66 @@ let StoryService = class StoryService {
1487
1589
  return null;
1488
1590
  }
1489
1591
  }
1490
- async generateSceneIllustration(storyId, situation) {
1592
+ async updateCharacterPortrait(room, characterAgent, storyId) {
1593
+ try {
1594
+ const artifacts = room.getArtifacts();
1595
+ const profileArtifact = artifacts.find((a) => a.name === `Character Profile - ${characterAgent.agentNickname}`);
1596
+ const storyInfo = this.storyStore.getStory(storyId);
1597
+ const scenario = storyInfo?.scenario || '';
1598
+ const character = storyInfo.characters.find((c) => c.name === characterAgent.agentNickname);
1599
+ let characterDescription = profileArtifact?.body || character?.description || '';
1600
+ if (!characterDescription) {
1601
+ logger_1.logger.warn(`No profile for ${characterAgent.agentNickname}, skipping portrait update`);
1602
+ return null;
1603
+ }
1604
+ const storyArtifactService = this.getStoryArtifactService(storyId);
1605
+ if (!storyInfo.artistEditAgentId) {
1606
+ logger_1.logger.warn(`No edit artist configured for story ${storyId}, skipping portrait update`);
1607
+ return null;
1608
+ }
1609
+ const artistEditAgent = await this.agentStore.getAgent(storyInfo.artistEditAgentId);
1610
+ if (!artistEditAgent) {
1611
+ logger_1.logger.warn(`Edit artist agent not found: ${storyInfo.artistEditAgentId}`);
1612
+ return null;
1613
+ }
1614
+ artistEditAgent.artifactService = storyArtifactService;
1615
+ const portraitName = `Portrait - ${characterAgent.agentNickname}`;
1616
+ const existingPortrait = storyArtifactService
1617
+ .listArtifacts()
1618
+ .find((a) => a.name === portraitName);
1619
+ if (!existingPortrait) {
1620
+ logger_1.logger.warn(`No existing portrait for ${characterAgent.agentNickname}, cannot update`);
1621
+ return null;
1622
+ }
1623
+ const visualDescription = await characterAgent.preparedPrompt('update_portrait', {
1624
+ characterName: characterAgent.agentNickname,
1625
+ characterProfile: characterDescription,
1626
+ scenario: scenario,
1627
+ }, false);
1628
+ if (!visualDescription || typeof visualDescription !== 'string') {
1629
+ logger_1.logger.warn(`Failed to generate update description for ${characterAgent.agentNickname}`);
1630
+ return null;
1631
+ }
1632
+ const imageResult = await artistEditAgent.editImage(visualDescription, [
1633
+ existingPortrait.artifactId,
1634
+ ]);
1635
+ if (imageResult.error) {
1636
+ return null;
1637
+ }
1638
+ storyArtifactService.deleteArtifact(existingPortrait.artifactId);
1639
+ artistEditAgent.save();
1640
+ const artifactId = imageResult.artifactId;
1641
+ storyArtifactService.renameArtifact(artifactId, portraitName);
1642
+ storyArtifactService.updateArtifactDescription(artifactId, visualDescription);
1643
+ logger_1.logger.log(`Updated portrait for ${characterAgent.agentNickname}: ${artifactId}`);
1644
+ return artifactId;
1645
+ }
1646
+ catch (error) {
1647
+ logger_1.logger.error(`Error updating portrait for ${characterAgent.agentNickname}:`, error);
1648
+ return null;
1649
+ }
1650
+ }
1651
+ async generateSceneIllustration(storyId, situation, allowReference = true) {
1491
1652
  try {
1492
1653
  const story = this.storyStore.getStory(storyId);
1493
1654
  if (!story?.illustratorAgentId) {
@@ -1529,8 +1690,12 @@ let StoryService = class StoryService {
1529
1690
  return;
1530
1691
  }
1531
1692
  if (sceneAnalysis.isReference) {
1693
+ if (!allowReference) {
1694
+ logger_1.logger.warn(`Illustrator requested another reference (${sceneAnalysis.sceneName}) after one was just created; skipping to avoid loop`);
1695
+ return;
1696
+ }
1532
1697
  await this.generateSceneReference(storyId, sceneAnalysis.sceneName, sceneAnalysis.sceneDescription);
1533
- await this.generateSceneIllustration(storyId, situation);
1698
+ await this.generateSceneIllustration(storyId, situation, false);
1534
1699
  }
1535
1700
  else {
1536
1701
  await this.generateStoryImage(storyId, story.turnNumber, sceneAnalysis.imageCaption, sceneAnalysis.sceneName, sceneAnalysis.sceneDescription, sceneAnalysis.characterNames);
@@ -1553,12 +1718,13 @@ let StoryService = class StoryService {
1553
1718
  logger_1.logger.error(`Artist agent not found: ${storyInfo.artistAgentId}`);
1554
1719
  return;
1555
1720
  }
1556
- let storyArtifactService = this.getStoryArtifactService(storyId);
1721
+ const storyArtifactService = this.getStoryArtifactService(storyId);
1557
1722
  if (!storyArtifactService) {
1558
1723
  logger_1.logger.error(`Story artifact service not found: ${storyId}`);
1559
1724
  return;
1560
1725
  }
1561
- const imageResult = await artistAgent.generateImage(sceneDescription, 1024, 1024, 'auto');
1726
+ artistAgent.artifactService = storyArtifactService;
1727
+ const imageResult = await artistAgent.generateImage(sceneDescription);
1562
1728
  if (imageResult.error) {
1563
1729
  return;
1564
1730
  }
@@ -1603,9 +1769,16 @@ let StoryService = class StoryService {
1603
1769
  logger_1.logger.error(`Artist agent not found: ${storyInfo.artistAgentId}`);
1604
1770
  return;
1605
1771
  }
1606
- let quality = 'high';
1607
- let width = 1536;
1608
- const imageResult = await artistAgent.generateImage(sceneDescription, width, 1024, quality, referenceImageIds);
1772
+ artistAgent.artifactService = storyArtifactService;
1773
+ const editAgent = storyInfo.artistEditAgentId
1774
+ ? await this.agentStore.getAgent(storyInfo.artistEditAgentId)
1775
+ : null;
1776
+ if (editAgent) {
1777
+ editAgent.artifactService = storyArtifactService;
1778
+ }
1779
+ const imageResult = referenceImageIds?.length && editAgent
1780
+ ? await editAgent.editImage(sceneDescription, referenceImageIds)
1781
+ : await artistAgent.generateImage(sceneDescription);
1609
1782
  if (imageResult.error) {
1610
1783
  return;
1611
1784
  }
@@ -1654,6 +1827,11 @@ let StoryService = class StoryService {
1654
1827
  if (introduce) {
1655
1828
  let newCharacterName = situation.substring(11).trim();
1656
1829
  if (newCharacterName) {
1830
+ const existingAgent = room.getAgents().find((a) => room.checkAgentNicknameMatch(a.agentNickname, newCharacterName));
1831
+ if (existingAgent) {
1832
+ logger_1.logger.log(`INTRODUCE: ${newCharacterName} ignored — character already in story`);
1833
+ return;
1834
+ }
1657
1835
  const declinedCharacters = story?.declinedCharacters || [];
1658
1836
  if (declinedCharacters.includes(newCharacterName)) {
1659
1837
  logger_1.logger.log(`Character ${newCharacterName} was declined, skipping prompt`);
@@ -101,12 +101,27 @@ export interface ModelInfo {
101
101
  description: string;
102
102
  model: string;
103
103
  service: string;
104
+ type?: string;
104
105
  }
105
106
  export interface StoryModelDefaults {
106
107
  selectedStoryModel: string;
107
108
  selectedImageModel: string;
109
+ selectedImageEditModel: string;
108
110
  enforceDefaultModels: boolean;
109
111
  }
112
+ export interface StoryModelEntry {
113
+ name: string;
114
+ description: string;
115
+ service: string;
116
+ model: string;
117
+ small_model?: string;
118
+ cost: string;
119
+ }
120
+ export interface StoryModelConfig {
121
+ service: string;
122
+ model: string;
123
+ smallModel?: string;
124
+ }
110
125
  export type PromptScope = 'common' | 'user';
111
126
  export interface PromptFolder {
112
127
  name: string;
@@ -234,8 +249,10 @@ export interface StoryInfo {
234
249
  moderatorAgentId: string;
235
250
  illustratorAgentId?: string;
236
251
  artistAgentId?: string;
252
+ artistEditAgentId?: string;
237
253
  userAgentId?: string;
238
254
  imageModelName?: string;
255
+ imageEditModelName?: string;
239
256
  isImport: boolean;
240
257
  totalCost: number;
241
258
  totalInputTokens: number;
@@ -1 +1 @@
1
- window.hierarchyData = "eJx1jc0KgzAQhN9lzrGa0ErJO/TSq3gIutbQmEA2niTvXtI/pNDTwM63822IISSG7lrV9AKRJkdDssEz9IZWNSW8WQgaF2I2N4LA3foRWp1agTU6aFifKE5mIK7f1GFOi4PA4AwzNBKPVXmrvmgpZ+vGSB66k/Io+yxQcue8hrD8eqU6f7zPdeJ6h/0Vvw455wf4B1Aw"
1
+ window.hierarchyData = "eJx1jTsKwzAQRO8ytRz/sBN0hzRpjQshr2MRWQKtUhndPSg/TCDVwM7beRuC95Ehh76tRoFAsyUdjXcMuaFvqxxOrQSJMzGrK0HgZtwE2XS9wD1YSBgXKcxKE5dv6rDE1UJAW8UMichTkd+KL5rLxdgpkIMc6vrYjUkg58558X799dbN6eN9rhOXO+yv+HVIKT0A/0RQQA=="