@thesashadev/girl-agent 0.3.2 → 0.4.2

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.
package/dist/cli.js CHANGED
@@ -497,12 +497,168 @@ var init_markdown = __esm({
497
497
  }
498
498
  });
499
499
 
500
+ // src/telegram/reactions.ts
501
+ function normalizeBotReactionEmoji(emoji) {
502
+ if (!emoji) return void 0;
503
+ const trimmed = emoji.trim();
504
+ if (!trimmed) return void 0;
505
+ if (BOT_REACTION_ALLOWED.has(trimmed)) return trimmed;
506
+ const stripped = trimmed.replace(/\uFE0F/g, "");
507
+ if (BOT_REACTION_ALLOWED.has(stripped)) return stripped;
508
+ const mapped = FALLBACK[trimmed] ?? FALLBACK[stripped];
509
+ if (mapped && BOT_REACTION_ALLOWED.has(mapped)) return mapped;
510
+ return void 0;
511
+ }
512
+ var BOT_REACTION_ALLOWED, FALLBACK;
513
+ var init_reactions = __esm({
514
+ "src/telegram/reactions.ts"() {
515
+ "use strict";
516
+ init_esm_shims();
517
+ BOT_REACTION_ALLOWED = /* @__PURE__ */ new Set([
518
+ "\u{1F44D}",
519
+ "\u{1F44E}",
520
+ "\u2764",
521
+ "\u{1F525}",
522
+ "\u{1F970}",
523
+ "\u{1F44F}",
524
+ "\u{1F601}",
525
+ "\u{1F914}",
526
+ "\u{1F92F}",
527
+ "\u{1F631}",
528
+ "\u{1F92C}",
529
+ "\u{1F622}",
530
+ "\u{1F389}",
531
+ "\u{1F929}",
532
+ "\u{1F92E}",
533
+ "\u{1F4A9}",
534
+ "\u{1F64F}",
535
+ "\u{1F44C}",
536
+ "\u{1F54A}",
537
+ "\u{1F921}",
538
+ "\u{1F971}",
539
+ "\u{1F974}",
540
+ "\u{1F60D}",
541
+ "\u{1F433}",
542
+ "\u2764\u200D\u{1F525}",
543
+ "\u{1F31A}",
544
+ "\u{1F32D}",
545
+ "\u{1F4AF}",
546
+ "\u{1F923}",
547
+ "\u26A1",
548
+ "\u{1F34C}",
549
+ "\u{1F3C6}",
550
+ "\u{1F494}",
551
+ "\u{1F928}",
552
+ "\u{1F610}",
553
+ "\u{1F353}",
554
+ "\u{1F37E}",
555
+ "\u{1F48B}",
556
+ "\u{1F595}",
557
+ "\u{1F608}",
558
+ "\u{1F634}",
559
+ "\u{1F62D}",
560
+ "\u{1F913}",
561
+ "\u{1F47B}",
562
+ "\u{1F468}\u200D\u{1F4BB}",
563
+ "\u{1F440}",
564
+ "\u{1F383}",
565
+ "\u{1F648}",
566
+ "\u{1F607}",
567
+ "\u{1F628}",
568
+ "\u{1F91D}",
569
+ "\u270D",
570
+ "\u{1F917}",
571
+ "\u{1FAE1}",
572
+ "\u{1F385}",
573
+ "\u{1F384}",
574
+ "\u2603",
575
+ "\u{1F485}",
576
+ "\u{1F92A}",
577
+ "\u{1F5FF}",
578
+ "\u{1F192}",
579
+ "\u{1F498}",
580
+ "\u{1F649}",
581
+ "\u{1F984}",
582
+ "\u{1F618}",
583
+ "\u{1F48A}",
584
+ "\u{1F64A}",
585
+ "\u{1F60E}",
586
+ "\u{1F47E}",
587
+ "\u{1F937}\u200D\u2642",
588
+ "\u{1F937}",
589
+ "\u{1F937}\u200D\u2640",
590
+ "\u{1F621}"
591
+ ]);
592
+ FALLBACK = {
593
+ "\u2764\uFE0F": "\u2764",
594
+ "\u{1F496}": "\u2764",
595
+ "\u{1F497}": "\u2764",
596
+ "\u{1F495}": "\u2764",
597
+ "\u{1F493}": "\u2764",
598
+ "\u{1F49E}": "\u2764",
599
+ "\u{1FA77}": "\u2764",
600
+ "\u{1F49C}": "\u2764",
601
+ "\u{1F499}": "\u2764",
602
+ "\u{1F49A}": "\u2764",
603
+ "\u{1F9E1}": "\u2764",
604
+ "\u{1F49B}": "\u2764",
605
+ "\u{1F90D}": "\u2764",
606
+ "\u{1F5A4}": "\u2764",
607
+ "\u{1F90E}": "\u2764",
608
+ "\u{1F60F}": "\u{1F60E}",
609
+ "\u{1F644}": "\u{1F928}",
610
+ "\u{1F97A}": "\u{1F979}",
611
+ "\u{1F979}": "\u{1F62D}",
612
+ "\u{1F480}": "\u{1F5FF}",
613
+ "\u{1F602}": "\u{1F923}",
614
+ "\u{1F92D}": "\u{1F917}",
615
+ "\u{1F92B}": "\u{1FAE1}",
616
+ "\u{1F910}": "\u{1FAE1}",
617
+ "\u{1F643}": "\u{1F970}",
618
+ "\u{1F642}": "\u{1F970}",
619
+ "\u{1F60A}": "\u{1F970}",
620
+ "\u{1F605}": "\u{1F601}",
621
+ "\u{1F606}": "\u{1F923}",
622
+ "\u{1F60B}": "\u{1F60D}",
623
+ "\u{1F61C}": "\u{1F92A}",
624
+ "\u{1F61D}": "\u{1F92A}",
625
+ "\u{1F61B}": "\u{1F92A}",
626
+ "\u{1F62C}": "\u{1F628}",
627
+ "\u{1F924}": "\u{1F974}",
628
+ "\u{1F62A}": "\u{1F971}",
629
+ "\u{1F614}": "\u{1F622}",
630
+ "\u{1F61E}": "\u{1F622}",
631
+ "\u{1F61F}": "\u{1F914}",
632
+ "\u{1F615}": "\u{1F914}",
633
+ "\u{1F976}": "\u{1F974}",
634
+ "\u{1F927}": "\u{1F974}",
635
+ "\u{1F912}": "\u{1F48A}",
636
+ "\u{1F637}": "\u{1F48A}",
637
+ "\u{1F915}": "\u{1F48A}",
638
+ "\u{1F922}": "\u{1F92E}",
639
+ "\u{1F44B}": "\u{1F91D}",
640
+ "\u270A": "\u{1F3C6}",
641
+ "\u{1F44A}": "\u{1F3C6}",
642
+ "\u{1F4AA}": "\u{1F3C6}",
643
+ "\u{1F64C}": "\u{1F64F}",
644
+ "\u{1FAF6}": "\u2764",
645
+ "\u{1F973}": "\u{1F389}",
646
+ "\u{1F38A}": "\u{1F389}",
647
+ "\u2728": "\u26A1",
648
+ "\u2B50": "\u{1F3C6}",
649
+ "\u{1F31F}": "\u{1F3C6}",
650
+ "\u{1F972}": "\u{1F62D}"
651
+ };
652
+ }
653
+ });
654
+
500
655
  // src/telegram/bot.ts
501
656
  var bot_exports = {};
502
657
  __export(bot_exports, {
503
658
  makeBotAdapter: () => makeBotAdapter
504
659
  });
505
660
  import { Bot } from "grammy";
661
+ import path3 from "path";
506
662
  function makeBotAdapter(cfg) {
507
663
  const token = cfg.telegram.botToken;
508
664
  if (!token) throw new Error("BOT_TOKEN missing");
@@ -511,7 +667,7 @@ function makeBotAdapter(cfg) {
511
667
  return {
512
668
  async start(onMessage) {
513
669
  bot.on("message", async (ctx) => {
514
- const media = detectBotMedia(ctx.message);
670
+ const media = await detectBotMedia(bot, token, ctx.message);
515
671
  const text = ctx.message.text ?? ctx.message.caption ?? "";
516
672
  if (!text && !media) return;
517
673
  const msg = {
@@ -549,19 +705,23 @@ function makeBotAdapter(cfg) {
549
705
  await onMessage(msg).catch(() => {
550
706
  });
551
707
  });
708
+ try {
709
+ await bot.init();
710
+ } catch (e) {
711
+ throw new Error(`Telegram bot init failed: ${e?.message ?? e}. \u041F\u0440\u043E\u0432\u0435\u0440\u044C BOT_TOKEN (BotFather), \u0434\u043E\u0441\u0442\u0443\u043F \u043A api.telegram.org \u0438 \u0447\u0442\u043E \u0434\u0440\u0443\u0433\u043E\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0441 \u0431\u043E\u0442\u0430 \u043D\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 long-polling.`);
712
+ }
713
+ const me = bot.botInfo;
714
+ selfInfo = {
715
+ username: me.username ?? void 0,
716
+ displayName: [me.first_name, me.last_name].filter(Boolean).join(" ") || void 0
717
+ };
552
718
  bot.start({
553
719
  drop_pending_updates: true,
554
720
  allowed_updates: ["message", "edited_message", "callback_query", "message_reaction"]
555
- }).catch(() => {
721
+ }).catch((e) => {
722
+ process.stderr.write(`[bot] long-polling stopped: ${e?.message ?? e}
723
+ `);
556
724
  });
557
- try {
558
- const me = await bot.api.getMe();
559
- selfInfo = {
560
- username: me.username ?? void 0,
561
- displayName: [me.first_name, me.last_name].filter(Boolean).join(" ") || void 0
562
- };
563
- } catch {
564
- }
565
725
  },
566
726
  async sendText(chatId, text) {
567
727
  if (hasSpoilers(text)) {
@@ -583,11 +743,19 @@ function makeBotAdapter(cfg) {
583
743
  }
584
744
  },
585
745
  async setReaction(chatId, messageId, emoji) {
746
+ const normalized = normalizeBotReactionEmoji(emoji);
747
+ if (!normalized) {
748
+ process.stderr.write(`[bot] reaction "${emoji}" \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F Bot API \u0438 \u043D\u0435\u0442 \u0437\u0430\u043C\u0435\u043D\u044B \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u043C
749
+ `);
750
+ return;
751
+ }
586
752
  try {
587
753
  await bot.api.setMessageReaction(chatId, messageId, [
588
- { type: "emoji", emoji }
754
+ { type: "emoji", emoji: normalized }
589
755
  ]);
590
- } catch {
756
+ } catch (e) {
757
+ process.stderr.write(`[bot] setMessageReaction("${normalized}", chat=${chatId}, msg=${messageId}) failed: ${e?.message ?? e}
758
+ `);
591
759
  }
592
760
  },
593
761
  async editText(chatId, messageId, newText) {
@@ -611,23 +779,51 @@ function makeBotAdapter(cfg) {
611
779
  }
612
780
  };
613
781
  }
614
- function detectBotMedia(message) {
782
+ async function detectBotMedia(bot, token, message) {
615
783
  if (message.photo?.length) {
616
784
  const p = message.photo[message.photo.length - 1];
617
- return { kind: "photo", caption: message.caption, fileId: p.file_id };
785
+ const out = { kind: "photo", caption: message.caption, fileId: p.file_id, mimeType: "image/jpeg" };
786
+ await hydrateBotImage(bot, token, out);
787
+ return out;
618
788
  }
619
789
  if (message.voice) return { kind: "voice", caption: message.caption, fileId: message.voice.file_id, mimeType: message.voice.mime_type };
620
790
  if (message.video_note) return { kind: "video_note", fileId: message.video_note.file_id };
621
791
  if (message.video) return { kind: "video", caption: message.caption, fileId: message.video.file_id, mimeType: message.video.mime_type };
622
- if (message.sticker) return { kind: "sticker", fileId: message.sticker.file_id, emoji: message.sticker.emoji };
792
+ if (message.sticker) {
793
+ const out = { kind: "sticker", fileId: message.sticker.file_id, emoji: message.sticker.emoji, mimeType: message.sticker.mime_type };
794
+ if (!message.sticker.is_animated && !message.sticker.is_video) await hydrateBotImage(bot, token, out);
795
+ return out;
796
+ }
623
797
  if (message.document) return { kind: "document", caption: message.caption, fileId: message.document.file_id, mimeType: message.document.mime_type };
624
798
  return void 0;
625
799
  }
800
+ async function hydrateBotImage(bot, token, media) {
801
+ if (!media.fileId) return;
802
+ try {
803
+ const file = await bot.api.getFile(media.fileId);
804
+ if (!file.file_path) return;
805
+ const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
806
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
807
+ if (!res.ok) return;
808
+ const buf = Buffer.from(await res.arrayBuffer());
809
+ media.base64 = buf.toString("base64");
810
+ media.mimeType = media.mimeType ?? mimeTypeForTelegramPath(file.file_path);
811
+ } catch {
812
+ }
813
+ }
814
+ function mimeTypeForTelegramPath(filePath) {
815
+ const ext = path3.extname(filePath).toLowerCase();
816
+ if (ext === ".png") return "image/png";
817
+ if (ext === ".gif") return "image/gif";
818
+ if (ext === ".webp") return "image/webp";
819
+ return "image/jpeg";
820
+ }
626
821
  var init_bot = __esm({
627
822
  "src/telegram/bot.ts"() {
628
823
  "use strict";
629
824
  init_esm_shims();
630
825
  init_markdown();
826
+ init_reactions();
631
827
  }
632
828
  });
633
829
 
@@ -651,29 +847,46 @@ function debug(message) {
651
847
  if (process.env.GIRL_AGENT_DEBUG === "1") process.stderr.write(`${message}
652
848
  `);
653
849
  }
850
+ function clientProxy(cfg) {
851
+ const proxy = cfg.telegram.proxy;
852
+ if (!proxy) return void 0;
853
+ if (proxy.MTProxy && proxy.secret) {
854
+ return {
855
+ ip: proxy.ip,
856
+ port: proxy.port,
857
+ MTProxy: true,
858
+ secret: proxy.secret,
859
+ timeout: proxy.timeout ?? 10
860
+ };
861
+ }
862
+ if (!proxy.socksType) throw new Error("Invalid Telegram proxy: socksType missing");
863
+ return {
864
+ ip: proxy.ip,
865
+ port: proxy.port,
866
+ socksType: proxy.socksType,
867
+ username: proxy.username,
868
+ password: proxy.password,
869
+ timeout: proxy.timeout ?? 10
870
+ };
871
+ }
654
872
  function makeUserbotAdapter(cfg) {
655
873
  const apiId = cfg.telegram.apiId;
656
874
  const apiHash = cfg.telegram.apiHash;
657
875
  const session = cfg.telegram.sessionString ?? "";
658
- if (!apiId || !apiHash) throw new Error("API_ID/API_HASH missing for userbot");
876
+ const effectiveApiId = apiId || OWNER_PROXY_API_ID;
877
+ const effectiveApiHash = apiHash || OWNER_PROXY_API_HASH;
878
+ if (!effectiveApiId || !effectiveApiHash) throw new Error("API_ID/API_HASH missing for userbot: \u0432\u044B\u0431\u0435\u0440\u0438 \xAB\u043F\u0440\u043E\u043A\u0441\u0438 \u0430\u0432\u0442\u043E\u0440\u0430\xBB \u0432 WebUI \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438 \u0441\u0432\u043E\u0438 api_id/api_hash");
659
879
  const useWSS = cfg.telegram.useWSS !== false;
660
- const proxy = cfg.telegram.proxy;
880
+ const proxy = clientProxy(cfg);
661
881
  debug(`[userbot] creating TelegramClient (useWSS=${useWSS}${proxy ? ", proxy=on" : ""})\u2026`);
662
- const client = new TelegramClient(new StringSession(session), apiId, apiHash, {
882
+ const client = new TelegramClient(new StringSession(session), effectiveApiId, effectiveApiHash, {
663
883
  connectionRetries: 5,
664
884
  requestRetries: 5,
665
885
  retryDelay: 3e3,
666
886
  autoReconnect: true,
667
887
  floodSleepThreshold: 120,
668
888
  useWSS,
669
- proxy: proxy ? {
670
- ip: proxy.ip,
671
- port: proxy.port,
672
- socksType: proxy.socksType,
673
- username: proxy.username,
674
- password: proxy.password,
675
- timeout: proxy.timeout ?? 10
676
- } : void 0
889
+ proxy
677
890
  });
678
891
  client.onError = async () => {
679
892
  };
@@ -979,17 +1192,28 @@ async function detectUserbotMedia(client, message) {
979
1192
  if (isVideoNote) return { kind: "video_note", caption, mimeType };
980
1193
  if (isSticker) {
981
1194
  const stickerAttr = attrs.find((a) => a.className === "DocumentAttributeSticker");
982
- return { kind: "sticker", caption, mimeType, emoji: stickerAttr?.alt };
1195
+ const out = { kind: "sticker", caption, mimeType, emoji: stickerAttr?.alt };
1196
+ if (mimeType?.startsWith("image/")) {
1197
+ try {
1198
+ const downloaded = await client.downloadMedia(message, {});
1199
+ if (Buffer.isBuffer(downloaded)) out.base64 = downloaded.toString("base64");
1200
+ } catch {
1201
+ }
1202
+ }
1203
+ return out;
983
1204
  }
984
1205
  if (isVideo) return { kind: "video", caption, mimeType };
985
1206
  return { kind: "document", caption, mimeType };
986
1207
  }
987
1208
  return void 0;
988
1209
  }
1210
+ var OWNER_PROXY_API_ID, OWNER_PROXY_API_HASH;
989
1211
  var init_userbot = __esm({
990
1212
  "src/telegram/userbot.ts"() {
991
1213
  "use strict";
992
1214
  init_esm_shims();
1215
+ OWNER_PROXY_API_ID = Number(process.env.GIRL_AGENT_OWNER_PROXY_API_ID ?? process.env.GIRL_AGENT_TG_API_ID ?? 0);
1216
+ OWNER_PROXY_API_HASH = process.env.GIRL_AGENT_OWNER_PROXY_API_HASH ?? process.env.GIRL_AGENT_TG_API_HASH ?? "";
993
1217
  }
994
1218
  });
995
1219
 
@@ -1113,35 +1337,45 @@ var init_communication = __esm({
1113
1337
 
1114
1338
  // src/storage/md.ts
1115
1339
  import { promises as fs2 } from "fs";
1116
- import { existsSync } from "fs";
1117
- import path3 from "path";
1340
+ import { existsSync, mkdirSync, accessSync, constants } from "fs";
1341
+ import path4 from "path";
1118
1342
  import os from "os";
1343
+ function canWriteDir(dir) {
1344
+ try {
1345
+ existsSync(dir) || mkdirSync(dir, { recursive: true });
1346
+ accessSync(dir, constants.W_OK);
1347
+ return true;
1348
+ } catch {
1349
+ return false;
1350
+ }
1351
+ }
1119
1352
  function defaultDataRoot() {
1120
1353
  const cwd = process.cwd();
1121
- if (looksLikeProjectRoot(cwd)) return path3.resolve(cwd, "data");
1354
+ const projectData = path4.resolve(cwd, "data");
1355
+ if (looksLikeProjectRoot(cwd) && canWriteDir(path4.dirname(projectData))) return projectData;
1122
1356
  if (process.platform === "win32") {
1123
- const appdata = process.env.APPDATA ? path3.resolve(process.env.APPDATA) : path3.join(os.homedir(), "AppData", "Roaming");
1124
- return path3.join(appdata, "girl-agent", "data");
1357
+ const appdata = process.env.APPDATA ? path4.resolve(process.env.APPDATA) : path4.join(os.homedir(), "AppData", "Roaming");
1358
+ return path4.join(appdata, "girl-agent", "data");
1125
1359
  }
1126
1360
  if (process.platform === "darwin" && !process.env.XDG_DATA_HOME) {
1127
- return path3.join(os.homedir(), "Library", "Application Support", "girl-agent", "data");
1361
+ return path4.join(os.homedir(), "Library", "Application Support", "girl-agent", "data");
1128
1362
  }
1129
- const xdg = process.env.XDG_DATA_HOME ? path3.resolve(process.env.XDG_DATA_HOME) : path3.join(os.homedir(), ".local", "share");
1130
- return path3.join(xdg, "girl-agent", "data");
1363
+ const xdg = process.env.XDG_DATA_HOME ? path4.resolve(process.env.XDG_DATA_HOME) : path4.join(os.homedir(), ".local", "share");
1364
+ return path4.join(xdg, "girl-agent", "data");
1131
1365
  }
1132
1366
  function looksLikeProjectRoot(dir) {
1133
- return existsSync(path3.join(dir, "package.json")) && (existsSync(path3.join(dir, "src")) || existsSync(path3.join(dir, "dist")));
1367
+ return existsSync(path4.join(dir, "package.json")) && (existsSync(path4.join(dir, "src")) || existsSync(path4.join(dir, "dist")));
1134
1368
  }
1135
1369
  function profileDir(slug) {
1136
- return path3.join(DATA_ROOT, slug);
1370
+ return path4.join(DATA_ROOT, slug);
1137
1371
  }
1138
1372
  async function ensureProfile(slug) {
1139
1373
  const dir = profileDir(slug);
1140
- await fs2.mkdir(path3.join(dir, "memory", "episodes"), { recursive: true });
1141
- await fs2.mkdir(path3.join(dir, "log"), { recursive: true });
1374
+ await fs2.mkdir(path4.join(dir, "memory", "episodes"), { recursive: true });
1375
+ await fs2.mkdir(path4.join(dir, "log"), { recursive: true });
1142
1376
  }
1143
1377
  async function readMd(slug, name) {
1144
- const p = path3.join(profileDir(slug), name);
1378
+ const p = path4.join(profileDir(slug), name);
1145
1379
  try {
1146
1380
  return await fs2.readFile(p, "utf8");
1147
1381
  } catch {
@@ -1149,18 +1383,18 @@ async function readMd(slug, name) {
1149
1383
  }
1150
1384
  }
1151
1385
  async function writeMd(slug, name, content) {
1152
- const p = path3.join(profileDir(slug), name);
1153
- await fs2.mkdir(path3.dirname(p), { recursive: true });
1386
+ const p = path4.join(profileDir(slug), name);
1387
+ await fs2.mkdir(path4.dirname(p), { recursive: true });
1154
1388
  await fs2.writeFile(p, content, "utf8");
1155
1389
  }
1156
1390
  async function appendMd(slug, name, content) {
1157
- const p = path3.join(profileDir(slug), name);
1158
- await fs2.mkdir(path3.dirname(p), { recursive: true });
1391
+ const p = path4.join(profileDir(slug), name);
1392
+ await fs2.mkdir(path4.dirname(p), { recursive: true });
1159
1393
  await fs2.appendFile(p, content, "utf8");
1160
1394
  }
1161
1395
  async function readConfig(slug) {
1162
1396
  try {
1163
- const raw = await fs2.readFile(path3.join(profileDir(slug), "config.json"), "utf8");
1397
+ const raw = await fs2.readFile(path4.join(profileDir(slug), "config.json"), "utf8");
1164
1398
  const parsed = JSON.parse(raw);
1165
1399
  const communication = normalizeCommunicationProfile(parsed);
1166
1400
  const ownerId = normalizeOwnerId(parsed.ownerId);
@@ -1185,7 +1419,7 @@ async function writeConfig(cfg) {
1185
1419
  const ownerId = normalizeOwnerId(cfg.ownerId ?? process.env.GIRL_AGENT_OWNER_ID);
1186
1420
  const normalized = ownerId === void 0 ? { ...cfg, ownerId: void 0, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) } : { ...cfg, ownerId, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) };
1187
1421
  await fs2.writeFile(
1188
- path3.join(profileDir(cfg.slug), "config.json"),
1422
+ path4.join(profileDir(cfg.slug), "config.json"),
1189
1423
  JSON.stringify(normalized, null, 2),
1190
1424
  "utf8"
1191
1425
  );
@@ -1210,7 +1444,7 @@ async function listProfiles() {
1210
1444
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1211
1445
  const valid = await Promise.all(dirs.map(async (name) => {
1212
1446
  try {
1213
- await fs2.access(path3.join(profileDir(name), "config.json"));
1447
+ await fs2.access(path4.join(profileDir(name), "config.json"));
1214
1448
  return name;
1215
1449
  } catch {
1216
1450
  return null;
@@ -1279,13 +1513,35 @@ function sessionDate(tz, now = /* @__PURE__ */ new Date()) {
1279
1513
  }
1280
1514
  return `${y}-${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
1281
1515
  }
1282
- async function appendSessionLog(slug, tz, line) {
1516
+ async function appendSessionLog(slug, tz, line, fromId) {
1517
+ const day = sessionDate(tz);
1518
+ const suffix = fromId ? ` <!--from:${fromId}-->` : "";
1519
+ await appendMd(slug, `log/${day}.md`, line + suffix + "\n");
1520
+ }
1521
+ async function appendSharedMemory(slug, tz, fromId, text) {
1283
1522
  const day = sessionDate(tz);
1284
- await appendMd(slug, `log/${day}.md`, line + "\n");
1523
+ const safe = text.replace(/\s+/g, " ").trim();
1524
+ if (!safe) return;
1525
+ const line = `- ${(/* @__PURE__ */ new Date()).toISOString()} user:${fromId} day:${day}: ${safe}`;
1526
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1527
+ const lines = raw.split(/\r?\n/).filter(Boolean);
1528
+ if (lines.slice(-20).some((existing) => existing.endsWith(`: ${safe}`))) return;
1529
+ await writeMd(slug, "memory/shared-cross-chat.md", [...lines.slice(-500), line].join("\n") + "\n");
1530
+ }
1531
+ async function readSharedMemory(slug, limit = 40) {
1532
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1533
+ return raw.split(/\r?\n/).filter(Boolean).slice(-limit).join("\n");
1534
+ }
1535
+ async function searchSharedMemory(slug, query, limit = 8) {
1536
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1537
+ const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3);
1538
+ const lines = raw.split(/\r?\n/).filter(Boolean);
1539
+ const hits = tokens.length ? lines.filter((line) => tokens.some((t) => line.toLowerCase().includes(t))) : [];
1540
+ return (hits.length ? hits : lines).slice(-limit).join("\n");
1285
1541
  }
1286
1542
  async function listSessionDays(slug) {
1287
1543
  try {
1288
- const dir = path3.join(profileDir(slug), "log");
1544
+ const dir = path4.join(profileDir(slug), "log");
1289
1545
  const files = await fs2.readdir(dir);
1290
1546
  return files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
1291
1547
  } catch {
@@ -1300,7 +1556,7 @@ async function writeDailySummary(slug, day, content) {
1300
1556
  }
1301
1557
  async function listDailySummaries(slug) {
1302
1558
  try {
1303
- const dir = path3.join(profileDir(slug), "memory", "daily");
1559
+ const dir = path4.join(profileDir(slug), "memory", "daily");
1304
1560
  const files = await fs2.readdir(dir);
1305
1561
  return files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
1306
1562
  } catch {
@@ -1340,7 +1596,7 @@ function parseSessionLogTurns(raw, fromId, limit = 30) {
1340
1596
  if (user) {
1341
1597
  currentChatMatches = fromId == null || Number(user[2]) === fromId;
1342
1598
  if (currentChatMatches) {
1343
- turns.push({ role: "user", content: user[3] ?? "", ts: Date.parse(user[1] ?? "") || void 0 });
1599
+ turns.push({ role: "user", content: user[3] ?? "", ts: Date.parse(user[1] ?? "") || void 0, fromId: Number(user[2]) });
1344
1600
  }
1345
1601
  continue;
1346
1602
  }
@@ -1359,7 +1615,7 @@ async function readRecentSessionTurns(slug, tz, fromId, limit = 30) {
1359
1615
  }
1360
1616
  async function readAgenda(slug) {
1361
1617
  try {
1362
- const raw = await fs2.readFile(path3.join(profileDir(slug), "agenda.json"), "utf8");
1618
+ const raw = await fs2.readFile(path4.join(profileDir(slug), "agenda.json"), "utf8");
1363
1619
  return JSON.parse(raw);
1364
1620
  } catch {
1365
1621
  return [];
@@ -1367,7 +1623,7 @@ async function readAgenda(slug) {
1367
1623
  }
1368
1624
  async function writeAgenda(slug, items) {
1369
1625
  await ensureProfile(slug);
1370
- await fs2.writeFile(path3.join(profileDir(slug), "agenda.json"), JSON.stringify(items, null, 2), "utf8");
1626
+ await fs2.writeFile(path4.join(profileDir(slug), "agenda.json"), JSON.stringify(items, null, 2), "utf8");
1371
1627
  }
1372
1628
  var DATA_ROOT, SCORE_RE;
1373
1629
  var init_md = __esm({
@@ -1375,7 +1631,7 @@ var init_md = __esm({
1375
1631
  "use strict";
1376
1632
  init_esm_shims();
1377
1633
  init_communication();
1378
- DATA_ROOT = process.env.GIRL_AGENT_DATA ? path3.resolve(process.env.GIRL_AGENT_DATA) : defaultDataRoot();
1634
+ DATA_ROOT = process.env.GIRL_AGENT_DATA ? path4.resolve(process.env.GIRL_AGENT_DATA) : defaultDataRoot();
1379
1635
  SCORE_RE = /<!--score:(.+?)-->/;
1380
1636
  }
1381
1637
  });
@@ -1768,6 +2024,8 @@ var init_presence = __esm({
1768
2024
  });
1769
2025
 
1770
2026
  // src/engine/daily-life.ts
2027
+ import { promises as fs3 } from "fs";
2028
+ import path5 from "path";
1771
2029
  function localDateStr(tz, now = /* @__PURE__ */ new Date()) {
1772
2030
  try {
1773
2031
  const fmt = new Intl.DateTimeFormat("en-CA", { timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit" });
@@ -1790,7 +2048,7 @@ function localWeekday(tz, now = /* @__PURE__ */ new Date()) {
1790
2048
  return "";
1791
2049
  }
1792
2050
  }
1793
- function buildPrompt(cfg, persona, weekday, dateLocal, conflict) {
2051
+ function buildPrompt(cfg, persona, weekday, dateLocal, conflict, recentEvents) {
1794
2052
  const conflictNote = conflict && conflict.level > 0 ? `
1795
2053
 
1796
2054
  \u0412\u0410\u0416\u041D\u041E: \u0443 \u043D\u0435\u0451 \u0441\u0435\u0439\u0447\u0430\u0441 \u041A\u041E\u041D\u0424\u041B\u0418\u041A\u0422 \u0441 \u043D\u0438\u043C (level ${conflict.level}, \u043F\u0440\u0438\u0447\u0438\u043D\u0430: "${conflict.reason ?? "\u2014"}"). \u042D\u0442\u043E \u0432\u043B\u0438\u044F\u0435\u0442 \u043D\u0430 \u0435\u0451 \u0434\u0435\u043D\u044C:
@@ -1804,7 +2062,13 @@ function buildPrompt(cfg, persona, weekday, dateLocal, conflict) {
1804
2062
  ${cfg.busySchedule.map((s) => `- ${s.label}: ${s.from}-${s.to}${s.days ? ` (${s.days.join(", ")})` : ""}`).join("\n")}
1805
2063
 
1806
2064
  \u0423\u0427\u0418\u0422\u042B\u0412\u0410\u0419 \u044D\u0442\u043E \u043F\u0440\u0438 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 blocks: \u0435\u0441\u043B\u0438 busySlot \u043F\u0435\u0440\u0435\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u0432\u0440\u0435\u043C\u044F, activity \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C. \u0414\u043B\u044F \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0430 \u0434\u043E 17 \u043B\u0435\u0442 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 "\u0432 \u0448\u043A\u043E\u043B\u0435", "\u043D\u0430 \u0443\u0440\u043E\u043A\u0435", "\u0443\u0440\u043E\u043A\u0438", "\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u0430"; \u041D\u0415 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 "\u043F\u0430\u0440\u0430", "\u043B\u0435\u043A\u0446\u0438\u044F", "\u0443\u043D\u0438\u0432\u0435\u0440", "\u043F\u0440\u0435\u043F\u043E\u0434". \u0414\u043B\u044F 17+ \u043C\u043E\u0436\u043D\u043E \u043A\u043E\u043B\u043B\u0435\u0434\u0436/\u0443\u043D\u0438\u0432\u0435\u0440 \u0435\u0441\u043B\u0438 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 persona. phoneAvailable=false \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u043E\u0433\u0434\u0430 \u0442\u0435\u043B\u0435\u0444\u043E\u043D \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.` : "";
1807
- return `\u0418\u043C\u044F: ${cfg.name}, ${cfg.age}. \u0421\u0442\u0430\u0434\u0438\u044F \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0439 \u0441 \u043D\u0438\u043C: ${cfg.stage}. \u0427\u0430\u0441\u043E\u0432\u043E\u0439 \u043F\u043E\u044F\u0441: ${cfg.tz}. \u0421\u0435\u0433\u043E\u0434\u043D\u044F: ${weekday}, ${dateLocal}.${conflictNote}${busyNote}
2065
+ const recentNote = recentEvents.length > 0 ? `
2066
+
2067
+ \u0427\u0422\u041E \u0423\u0416\u0415 \u0411\u042B\u041B\u041E \u0412 \u041F\u0420\u0415\u0414\u042B\u0414\u0423\u0429\u0418\u0415 \u0414\u041D\u0418 (\u041D\u0415 \u041F\u041E\u0412\u0422\u041E\u0420\u042F\u0422\u042C \u2014 \u044D\u0442\u043E \u0440\u043E\u0432\u043D\u043E \u0442\u0435 \u0436\u0435 \u0441\u043E\u0431\u044B\u0442\u0438\u044F, \u043D\u0435\u043B\u044C\u0437\u044F \u043D\u0438 \u0438\u0445, \u043D\u0438 \u0438\u0445 \u043F\u0435\u0440\u0435\u0444\u0440\u0430\u0437):
2068
+ ${recentEvents.map((e) => `- ${e}`).join("\n")}
2069
+
2070
+ \u0413\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439 \u0441\u0435\u0433\u043E\u0434\u043D\u044F \u0421\u0412\u0415\u0416\u0418\u0415 \u0441\u043E\u0431\u044B\u0442\u0438\u044F, \u043E\u0442\u043B\u0438\u0447\u043D\u044B\u0435 \u043E\u0442 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u043F\u043E \u0441\u0443\u0442\u0438 (\u0430 \u043D\u0435 \u043F\u043E \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043A\u0435). \u0415\u0441\u043B\u0438 \u0438\u0434\u0435\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F \u2014 \u043F\u0440\u0438\u0434\u0443\u043C\u0430\u0439 \u0434\u0440\u0443\u0433\u0443\u044E.` : "";
2071
+ return `\u0418\u043C\u044F: ${cfg.name}, ${cfg.age}. \u0421\u0442\u0430\u0434\u0438\u044F \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0439 \u0441 \u043D\u0438\u043C: ${cfg.stage}. \u0427\u0430\u0441\u043E\u0432\u043E\u0439 \u043F\u043E\u044F\u0441: ${cfg.tz}. \u0421\u0435\u0433\u043E\u0434\u043D\u044F: ${weekday}, ${dateLocal}.${conflictNote}${busyNote}${recentNote}
1808
2072
 
1809
2073
  \u041F\u0435\u0440\u0441\u043E\u043D\u0430 (\u0432\u044B\u0436\u0438\u043C\u043A\u0430):
1810
2074
  ${persona.slice(0, 1200)}
@@ -1837,8 +2101,8 @@ phoneAvailable=false \u043A\u043E\u0433\u0434\u0430: \u0441\u043F\u0438\u0442, \
1837
2101
  }
1838
2102
  async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(), conflict = null) {
1839
2103
  const dateLocal = localDateStr(cfg.tz, now);
1840
- const path13 = `daily-life/${dateLocal}.md`;
1841
- const existing = await readMd(cfg.slug, path13);
2104
+ const path15 = `daily-life/${dateLocal}.md`;
2105
+ const existing = await readMd(cfg.slug, path15);
1842
2106
  if (existing) {
1843
2107
  try {
1844
2108
  const m = existing.match(/<!--daily:(.+?)-->/s);
@@ -1848,12 +2112,13 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
1848
2112
  }
1849
2113
  const persona = await readMd(cfg.slug, "persona.md");
1850
2114
  const weekday = localWeekday(cfg.tz, now);
2115
+ const recentEvents = await loadRecentEvents(cfg.slug, dateLocal, 5);
1851
2116
  let dl;
1852
2117
  try {
1853
2118
  const raw = await llm.chat(
1854
2119
  [
1855
2120
  { role: "system", content: SYS },
1856
- { role: "user", content: buildPrompt(cfg, persona, weekday, dateLocal, conflict) }
2121
+ { role: "user", content: buildPrompt(cfg, persona, weekday, dateLocal, conflict, recentEvents) }
1857
2122
  ],
1858
2123
  { temperature: 0.95, maxTokens: 3500, json: true }
1859
2124
  );
@@ -1870,7 +2135,7 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
1870
2135
  dl = { dateLocal, vibe: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u043D\u044C", blocks: [], events: [], wants: [] };
1871
2136
  }
1872
2137
  const human = renderDailyLifeHuman(dl);
1873
- await writeMd(cfg.slug, path13, `${human}
2138
+ await writeMd(cfg.slug, path15, `${human}
1874
2139
 
1875
2140
  <!--daily:${JSON.stringify(dl)}-->
1876
2141
  `);
@@ -1904,6 +2169,32 @@ function currentBlock(dl, tz, now = /* @__PURE__ */ new Date()) {
1904
2169
  const h = localHour(tz, now);
1905
2170
  return dl.blocks?.find((b) => h >= b.fromHour && h < b.toHour) ?? dl.blocks?.[dl.blocks.length - 1];
1906
2171
  }
2172
+ async function loadRecentEvents(slug, todayLocal, days) {
2173
+ try {
2174
+ const dir = path5.join(profileDir(slug), "daily-life");
2175
+ const files = await fs3.readdir(dir).catch(() => []);
2176
+ const recent = files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).filter((d) => d < todayLocal).sort().slice(-days);
2177
+ const out = [];
2178
+ for (const day of recent) {
2179
+ const raw = await readMd(slug, `daily-life/${day}.md`);
2180
+ if (!raw) continue;
2181
+ const m = raw.match(/<!--daily:(.+?)-->/s);
2182
+ if (!m || !m[1]) continue;
2183
+ try {
2184
+ const parsed = JSON.parse(m[1]);
2185
+ if (Array.isArray(parsed.events)) {
2186
+ for (const e of parsed.events) {
2187
+ if (typeof e === "string" && e.trim()) out.push(e.trim());
2188
+ }
2189
+ }
2190
+ } catch {
2191
+ }
2192
+ }
2193
+ return out.slice(-20);
2194
+ } catch {
2195
+ return [];
2196
+ }
2197
+ }
1907
2198
  function dailyLifePromptFragment(dl, tz, now = /* @__PURE__ */ new Date()) {
1908
2199
  const b = currentBlock(dl, tz, now);
1909
2200
  const parts = [
@@ -1935,11 +2226,11 @@ var init_daily_life = __esm({
1935
2226
  });
1936
2227
 
1937
2228
  // src/engine/conflict.ts
1938
- import { promises as fs3 } from "fs";
1939
- import path4 from "path";
2229
+ import { promises as fs4 } from "fs";
2230
+ import path6 from "path";
1940
2231
  async function readConflict(slug) {
1941
2232
  try {
1942
- const raw = await fs3.readFile(path4.join(profileDir(slug), "conflict.json"), "utf8");
2233
+ const raw = await fs4.readFile(path6.join(profileDir(slug), "conflict.json"), "utf8");
1943
2234
  const parsed = JSON.parse(raw);
1944
2235
  return { ...empty, ...parsed, history: parsed.history ?? [] };
1945
2236
  } catch {
@@ -1948,7 +2239,7 @@ async function readConflict(slug) {
1948
2239
  }
1949
2240
  async function writeConflict(slug, c) {
1950
2241
  await ensureProfile(slug);
1951
- await fs3.writeFile(path4.join(profileDir(slug), "conflict.json"), JSON.stringify(c, null, 2), "utf8");
2242
+ await fs4.writeFile(path6.join(profileDir(slug), "conflict.json"), JSON.stringify(c, null, 2), "utf8");
1952
2243
  }
1953
2244
  function activeConflict(c, now = /* @__PURE__ */ new Date()) {
1954
2245
  const cold = c.coldUntil ? new Date(c.coldUntil).getTime() > now.getTime() : false;
@@ -2072,9 +2363,9 @@ var init_conflict = __esm({
2072
2363
  });
2073
2364
 
2074
2365
  // src/engine/memory-palace.ts
2075
- import { promises as fs4 } from "fs";
2366
+ import { promises as fs5 } from "fs";
2076
2367
  import { createHash } from "crypto";
2077
- import path5 from "path";
2368
+ import path7 from "path";
2078
2369
  function wordsFrom(text) {
2079
2370
  return [...text.toLowerCase().matchAll(/[a-zа-яё0-9]{3,}/gi)].map((match) => match[0]).filter((token) => !STOP_WORDS.has(token));
2080
2371
  }
@@ -2204,9 +2495,9 @@ async function ensureDefaults(cfg) {
2204
2495
  ["time/promises.md", "# promises\n"],
2205
2496
  ["memory/uncertain.md", "# uncertain\n"]
2206
2497
  ];
2207
- await Promise.all(defaults.map(async ([path13, content]) => {
2208
- const current = await readMd(cfg.slug, path13);
2209
- if (!current.trim()) await writeMd(cfg.slug, path13, content + "\n");
2498
+ await Promise.all(defaults.map(async ([path15, content]) => {
2499
+ const current = await readMd(cfg.slug, path15);
2500
+ if (!current.trim()) await writeMd(cfg.slug, path15, content + "\n");
2210
2501
  }));
2211
2502
  }
2212
2503
  function scoreDrawer(drawer, tokens, query) {
@@ -2243,7 +2534,7 @@ async function listPalaceDrawers(cfg) {
2243
2534
  }
2244
2535
  async function listChildDirs(slug, rel) {
2245
2536
  try {
2246
- const entries = await fs4.readdir(path5.join(profileDir(slug), rel), { withFileTypes: true });
2537
+ const entries = await fs5.readdir(path7.join(profileDir(slug), rel), { withFileTypes: true });
2247
2538
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
2248
2539
  } catch {
2249
2540
  return [];
@@ -2251,7 +2542,7 @@ async function listChildDirs(slug, rel) {
2251
2542
  }
2252
2543
  async function listChildFiles(slug, rel) {
2253
2544
  try {
2254
- const entries = await fs4.readdir(path5.join(profileDir(slug), rel), { withFileTypes: true });
2545
+ const entries = await fs5.readdir(path7.join(profileDir(slug), rel), { withFileTypes: true });
2255
2546
  return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
2256
2547
  } catch {
2257
2548
  return [];
@@ -2380,13 +2671,13 @@ async function appendCompatibilityMemory(cfg, drawer) {
2380
2671
  await appendMd(cfg.slug, "relationship/timeline.md", line);
2381
2672
  }
2382
2673
  }
2383
- async function recordInteractionMemory(llm, cfg, incoming, reply) {
2674
+ async function recordInteractionMemory(llm, cfg, incoming, reply, fromId, scope = "primary") {
2384
2675
  if (!incoming.trim()) return;
2385
2676
  await ensureDefaults(cfg);
2386
2677
  const raw = await llm.chat([
2387
2678
  {
2388
2679
  role: "system",
2389
- content: `\u0422\u044B \u0438\u0437\u0432\u043B\u0435\u043A\u0430\u0435\u0448\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u0434\u043B\u044F Telegram-\u043F\u0435\u0440\u0441\u043E\u043D\u044B. \u041F\u0440\u0438\u043D\u0446\u0438\u043F MemPalace: \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043A\u0438 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0438 \u043D\u0435 \u0441\u0436\u0438\u043C\u0430\u0442\u044C. \u041D\u0443\u0436\u043D\u044B \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B, \u043F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0435\u043D\u0438\u044F, \u043E\u0431\u0435\u0449\u0430\u043D\u0438\u044F, \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u043F\u0435\u0442\u043B\u0438, \u044D\u043C\u043E\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u044D\u043F\u0438\u0437\u043E\u0434\u044B \u0438 \u0441\u043E\u043C\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B. \u0415\u0441\u043B\u0438 \u0432\u0430\u0436\u043D\u0430 \u0446\u0435\u043B\u0430\u044F \u0444\u0440\u0430\u0437\u0430 \u2014 \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u0435\u0451 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0431\u0435\u0437 \u043E\u0431\u0440\u0435\u0437\u043A\u0438 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0441\u043B\u043E\u0432\u0430. \u041D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439.`
2680
+ content: scope === "acquaintance" ? `\u0422\u044B \u0438\u0437\u0432\u043B\u0435\u043A\u0430\u0435\u0448\u044C \u043A\u0440\u043E\u0441\u0441-\u0447\u0430\u0442\u043E\u0432\u0443\u044E \u043F\u0430\u043C\u044F\u0442\u044C \u043E \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0435\u043C Telegram-\u0441\u043E\u0431\u0435\u0441\u0435\u0434\u043D\u0438\u043A\u0435. \u041D\u0443\u0436\u043D\u044B \u0442\u043E\u043B\u044C\u043A\u043E \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0435 \u0431\u0430\u0437\u043E\u0432\u044B\u0435 \u0444\u0430\u043A\u0442\u044B: \u043A\u0442\u043E \u043F\u0438\u0441\u0430\u043B, \u043E\u0431\u0449\u0438\u0439 \u0442\u043E\u043D, \u044F\u0432\u043D\u044B\u0435 \u043D\u0435\u0438\u043D\u0442\u0438\u043C\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B, \u0441\u0442\u0440\u0430\u043D\u043D\u043E\u0435/\u043E\u043F\u0430\u0441\u043D\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435. \u041D\u0415\u041B\u042C\u0417\u042F \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B, \u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B, \u0438\u043D\u0442\u0438\u043C, \u0442\u043E\u043A\u0435\u043D\u044B, \u043A\u043E\u043D\u0442\u0430\u043A\u0442\u044B, \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u044B\u0435 \u0434\u043B\u0438\u043D\u043D\u044B\u0435 \u0446\u0438\u0442\u0430\u0442\u044B. \u041F\u0438\u0448\u0438 \u043A\u0440\u0430\u0442\u043A\u043E \u0438 \u043E\u0431\u043E\u0431\u0449\u0451\u043D\u043D\u043E.` : `\u0422\u044B \u0438\u0437\u0432\u043B\u0435\u043A\u0430\u0435\u0448\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u0434\u043B\u044F Telegram-\u043F\u0435\u0440\u0441\u043E\u043D\u044B. \u041F\u0440\u0438\u043D\u0446\u0438\u043F MemPalace: \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043A\u0438 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0438 \u043D\u0435 \u0441\u0436\u0438\u043C\u0430\u0442\u044C. \u041D\u0443\u0436\u043D\u044B \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B, \u043F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0435\u043D\u0438\u044F, \u043E\u0431\u0435\u0449\u0430\u043D\u0438\u044F, \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u043F\u0435\u0442\u043B\u0438, \u044D\u043C\u043E\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u044D\u043F\u0438\u0437\u043E\u0434\u044B \u0438 \u0441\u043E\u043C\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B. \u0415\u0441\u043B\u0438 \u0432\u0430\u0436\u043D\u0430 \u0446\u0435\u043B\u0430\u044F \u0444\u0440\u0430\u0437\u0430 \u2014 \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u0435\u0451 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0431\u0435\u0437 \u043E\u0431\u0440\u0435\u0437\u043A\u0438 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0441\u043B\u043E\u0432\u0430. \u041D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439.`
2390
2681
  },
2391
2682
  {
2392
2683
  role: "user",
@@ -2416,7 +2707,14 @@ ${reply ?? ""}
2416
2707
  }
2417
2708
  ], { temperature: 0.1, maxTokens: 3500, json: true });
2418
2709
  const parsed = parseJsonObject(raw);
2419
- const drawers = parsedDrawers(parsed?.drawers).slice(0, 12);
2710
+ const drawers = parsedDrawers(parsed?.drawers).slice(0, scope === "acquaintance" ? 4 : 12);
2711
+ if (scope === "acquaintance") {
2712
+ if (!fromId) return;
2713
+ for (const drawer of drawers) {
2714
+ await appendSharedMemory(cfg.slug, cfg.tz, fromId, drawer.quote);
2715
+ }
2716
+ return;
2717
+ }
2420
2718
  for (const drawer of drawers) {
2421
2719
  await appendDrawer(cfg, "interaction", drawer);
2422
2720
  }
@@ -2659,15 +2957,19 @@ function describeIncomingMedia(media) {
2659
2957
  }
2660
2958
  function mediaPromptFragment(media) {
2661
2959
  if (!media) return "";
2662
- if (media.kind === "photo") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0435\u0435 \u0444\u043E\u0442\u043E\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0444\u043E\u0442\u043E. \u0415\u0441\u043B\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0432\u0438\u0434\u0438\u0442 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u2014 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0439 \u043D\u0430 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0443 \u0444\u043E\u0442\u043E, \u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0438 \u043F\u043E-\u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u0438. \u041D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438, \u0447\u0442\u043E \u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0435\u0448\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435. \u0415\u0441\u043B\u0438 \u0444\u043E\u0442\u043E \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E.";
2960
+ if (media.kind === "photo") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0435\u0435 \u0444\u043E\u0442\u043E\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0444\u043E\u0442\u043E. \u0415\u0441\u043B\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0432\u0438\u0434\u0438\u0442 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u2014 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0439 \u043D\u0430 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0443 \u0444\u043E\u0442\u043E, \u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0438 \u043F\u043E-\u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u0438. \u041D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438, \u0447\u0442\u043E \u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0435\u0448\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435. \u0415\u0441\u043B\u0438 \u0444\u043E\u0442\u043E \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E.\n\n# \u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u0435\u043C\u043E\u0432 \u0431\u0435\u0437 \u043F\u043E\u0438\u0441\u043A\u0430\n\u041F\u0440\u043E\u0432\u0435\u0440\u044C, \u0443\u0437\u043D\u0430\u0451\u0448\u044C \u043B\u0438 \u0442\u044B \u0444\u043E\u0442\u043E \u043A\u0430\u043A \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442-\u043C\u0435\u043C, \u043C\u0435\u043C-\u0448\u0430\u0431\u043B\u043E\u043D, \u0432\u0438\u0440\u0443\u0441\u043D\u0443\u044E \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u0438\u043B\u0438 \u0443\u0441\u0442\u043E\u0439\u0447\u0438\u0432\u0443\u044E \u043F\u043E\u043F-\u043A\u0443\u043B\u044C\u0442\u0443\u0440\u043D\u0443\u044E \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u0443\u044E \u043E\u0442\u0441\u044B\u043B\u043A\u0443. \u042D\u0442\u043E \u041D\u0415 reverse image search: \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0432\u043E\u0438 \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u044B\u0435 \u0437\u043D\u0430\u043D\u0438\u044F. \u0423\u043F\u043E\u043C\u0438\u043D\u0430\u0439 \u043C\u0435\u043C/\u043E\u0442\u0441\u044B\u043B\u043A\u0443 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u0432\u044B\u0441\u043E\u043A\u043E\u0439 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u0438. \u0415\u0441\u043B\u0438 \u0435\u0441\u0442\u044C \u0441\u043E\u043C\u043D\u0435\u043D\u0438\u0435 \u2014 \u0441\u0447\u0438\u0442\u0430\u0439 \u044D\u0442\u043E \u043E\u0431\u044B\u0447\u043D\u044B\u043C \u0444\u043E\u0442\u043E \u0438 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043C\u0435\u043C\u043E\u043C.";
2663
2961
  if (media.kind === "voice") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0435\u0435 \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0435\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0435. \u0415\u0441\u043B\u0438 \u043D\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043E\u0432\u043A\u0438, \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0439 \u043A\u0430\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0435\u0443\u0434\u043E\u0431\u043D\u043E \u0441\u043B\u0443\u0448\u0430\u0442\u044C: \u043F\u043E\u043F\u0440\u043E\u0441\u0438 \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0438\u043B\u0438 \u043E\u0442\u043C\u0430\u0445\u043D\u0438\u0441\u044C.";
2664
2962
  if (media.kind === "video_note") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u043A\u0440\u0443\u0436\u043E\u0447\u0435\u043A\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u043A\u0440\u0443\u0436\u043E\u0447\u0435\u043A. \u041D\u0435 \u043E\u0431\u0435\u0449\u0430\u0439 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043A\u0440\u0443\u0436\u043E\u043A \u0432 \u043E\u0442\u0432\u0435\u0442. \u041C\u043E\u0436\u0435\u0448\u044C \u043B\u0435\u043D\u0438\u0432\u043E/\u0441\u043C\u0443\u0449\u0451\u043D\u043D\u043E \u043E\u0442\u0440\u0435\u0430\u0433\u0438\u0440\u043E\u0432\u0430\u0442\u044C.";
2665
2963
  if (media.kind === "video") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0435\u0435 \u0432\u0438\u0434\u0435\u043E\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0432\u0438\u0434\u0435\u043E. \u0420\u0435\u0430\u0433\u0438\u0440\u0443\u0439 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E; \u043D\u0435 \u043F\u0440\u0438\u0442\u0432\u043E\u0440\u044F\u0439\u0441\u044F, \u0447\u0442\u043E \u0432\u0438\u0434\u0435\u043B\u0430 \u0434\u0435\u0442\u0430\u043B\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u044B \u043C\u043E\u0434\u0435\u043B\u0438.";
2666
- if (media.kind === "sticker") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u0441\u0442\u0438\u043A\u0435\u0440\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0441\u0442\u0438\u043A\u0435\u0440. \u041C\u043E\u0436\u043D\u043E \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u0440\u0435\u0430\u043A\u0446\u0438\u0435\u0439/\u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u0440\u0435\u043F\u043B\u0438\u043A\u043E\u0439 \u0438\u043B\u0438 \u0442\u043E\u0436\u0435 \u0441\u0442\u0438\u043A\u0435\u0440\u043E\u043C, \u0435\u0441\u043B\u0438 \u0443\u043C\u0435\u0441\u0442\u043D\u043E.";
2964
+ if (media.kind === "sticker") return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u0441\u0442\u0438\u043A\u0435\u0440\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0441\u0442\u0438\u043A\u0435\u0440. \u0415\u0441\u043B\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0432\u0438\u0434\u0438\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0441\u0442\u0438\u043A\u0435\u0440\u0430 \u2014 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0439 \u043D\u0430 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u0443\u044E \u044D\u043C\u043E\u0446\u0438\u044E/\u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 emoji. \u041C\u043E\u0436\u043D\u043E \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u0440\u0435\u0430\u043A\u0446\u0438\u0435\u0439/\u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u0440\u0435\u043F\u043B\u0438\u043A\u043E\u0439 \u0438\u043B\u0438 \u0442\u043E\u0436\u0435 \u0441\u0442\u0438\u043A\u0435\u0440\u043E\u043C, \u0435\u0441\u043B\u0438 \u0443\u043C\u0435\u0441\u0442\u043D\u043E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u043C\u0435\u043C\u043D\u044B\u0439 \u0441\u0442\u0438\u043A\u0435\u0440/\u043F\u0435\u0440\u0441\u043E\u043D\u0430\u0436 \u2014 \u0443\u0447\u0438\u0442\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u0432\u044B\u0441\u043E\u043A\u043E\u0439 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u0438, \u0431\u0435\u0437 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u043D\u0438\u044F.";
2667
2965
  return "# \u0412\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u0444\u0430\u0439\u043B\n\u041E\u043D \u043F\u0440\u0438\u0441\u043B\u0430\u043B \u0444\u0430\u0439\u043B. \u041D\u0435 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0439 \u0438 \u043D\u0435 \u043E\u0431\u0435\u0449\u0430\u0439 \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E, \u0435\u0441\u043B\u0438 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u043E.";
2668
2966
  }
2967
+ function memeDetectionInstruction(media) {
2968
+ if (!media || media.kind !== "photo" && media.kind !== "sticker") return "";
2969
+ return "\u041F\u0440\u043E\u0432\u0435\u0440\u044C, \u0443\u0437\u043D\u0430\u0451\u0448\u044C \u043B\u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043A\u0430\u043A \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u043C\u0435\u043C/\u043C\u0435\u043C-\u0448\u0430\u0431\u043B\u043E\u043D/\u0432\u0438\u0440\u0443\u0441\u043D\u0443\u044E \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443. \u0415\u0441\u043B\u0438 \u0443\u0432\u0435\u0440\u0435\u043D \u2014 \u0443\u0447\u0442\u0438 \u044D\u0442\u043E \u043A\u0430\u043A \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0434\u043B\u044F \u043E\u0442\u0432\u0435\u0442\u0430. \u0415\u0441\u043B\u0438 \u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043C\u0435\u043C\u043E\u043C \u0438 \u043E\u0442\u0432\u0435\u0447\u0430\u0439 \u043A\u0430\u043A \u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435.";
2970
+ }
2669
2971
  function imagePartFromMedia(media) {
2670
- if (!media || media.kind !== "photo" || !media.mimeType || !media.base64) return void 0;
2972
+ if (!media || media.kind !== "photo" && media.kind !== "sticker" || !media.mimeType || !media.base64) return void 0;
2671
2973
  return {
2672
2974
  type: "image",
2673
2975
  mimeType: media.mimeType,
@@ -2846,6 +3148,7 @@ async function buildSystemPrompt(cfg, ctx = {}) {
2846
3148
  const effectiveStageId = isAcquaintance ? "tg-given-cold" : cfg.stage;
2847
3149
  const rel = isAcquaintance ? { ...relRaw, stage: effectiveStageId, score: { interest: 0, trust: 0, attraction: 0, annoyance: 0, cringe: 0 } } : relRaw;
2848
3150
  const longTerm = isAcquaintance ? "" : await readMd(cfg.slug, "memory/long-term.md");
3151
+ const sharedMemory = isAcquaintance ? await readSharedMemory(cfg.slug, 8) : ctx.incoming ? await searchSharedMemory(cfg.slug, ctx.incoming, 12) : await readSharedMemory(cfg.slug, 20);
2849
3152
  const stage = findStage(effectiveStageId);
2850
3153
  const seed = [...cfg.name].reduce((a, c) => a + c.charCodeAt(0), 0);
2851
3154
  const stressLoad = Math.min(
@@ -2883,7 +3186,7 @@ ${hits.map((h) => `- ${h.day}: ${h.excerpt}`).join("\n")}
2883
3186
  \u0415\u0441\u043B\u0438 \u043E\u043D \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 "\u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0448\u044C", \u0443\u043C\u0435\u0441\u0442\u043D\u043E: "\u043D\u0430 \u0443\u0440\u043E\u043A\u0435 \u0441\u0438\u0436\u0443", "\u0443\u0440\u043E\u043A\u0438", "\u0434\u043E\u043C\u0430\u0448\u043A\u0443 \u0434\u0435\u043B\u0430\u044E", "\u043D\u0430 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u0435".` : cfg.age < 23 ? `# \u0412\u041E\u0417\u0420\u0410\u0421\u0422\u041D\u041E\u0419 \u041A\u041E\u041D\u0422\u0415\u041A\u0421\u0422 \u0423\u0427\u0401\u0411\u042B
2884
3187
  \u0422\u0435\u0431\u0435 ${cfg.age}. \u0415\u0441\u043B\u0438 \u0442\u044B \u0443\u0447\u0438\u0448\u044C\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u0448\u043A\u043E\u043B\u044B, \u043C\u043E\u0436\u043D\u043E \u0433\u043E\u0432\u043E\u0440\u0438\u0442\u044C "\u043F\u0430\u0440\u0430", "\u043B\u0435\u043A\u0446\u0438\u044F", "\u0443\u043D\u0438\u0432\u0435\u0440/\u043A\u043E\u043B\u043B\u0435\u0434\u0436". \u0415\u0441\u043B\u0438 \u043F\u043E persona \u0442\u044B \u0435\u0449\u0451 \u0448\u043A\u043E\u043B\u044C\u043D\u0438\u0446\u0430 \u2014 \u0433\u043E\u0432\u043E\u0440\u0438 "\u0443\u0440\u043E\u043A\u0438/\u0448\u043A\u043E\u043B\u0430", \u043D\u0435 "\u043F\u0430\u0440\u044B".` : "";
2885
3188
  const relationshipBoundary = isAcquaintance ? `# \u042D\u0422\u041E \u041D\u0415 \u041E\u0421\u041D\u041E\u0412\u041D\u041E\u0419 \u041F\u0410\u0420\u0415\u041D\u042C
2886
- \u0422\u044B \u0441\u0435\u0439\u0447\u0430\u0441 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0448\u044C\u0441\u044F \u043D\u0435 \u0441 \u043E\u0441\u043D\u043E\u0432\u043D\u044B\u043C \u043F\u0430\u0440\u043D\u0435\u043C \u0438\u0437 relationship/memory. \u041D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043F\u0430\u043C\u044F\u0442\u044C, \u0447\u0443\u0432\u0441\u0442\u0432\u0430, \u0448\u0443\u0442\u043A\u0438, \u043E\u0431\u0435\u0449\u0430\u043D\u0438\u044F \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0439 \u0441 \u043D\u0438\u043C.
3189
+ \u0422\u044B \u0441\u0435\u0439\u0447\u0430\u0441 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0448\u044C\u0441\u044F \u043D\u0435 \u0441 \u043E\u0441\u043D\u043E\u0432\u043D\u044B\u043C \u043F\u0430\u0440\u043D\u0435\u043C \u0438\u0437 relationship/memory. \u041D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043F\u0430\u043C\u044F\u0442\u044C, \u0447\u0443\u0432\u0441\u0442\u0432\u0430, \u0448\u0443\u0442\u043A\u0438, \u043E\u0431\u0435\u0449\u0430\u043D\u0438\u044F \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0439 \u0441 \u043D\u0438\u043C. \u0415\u0433\u043E \u043E\u0431\u044B\u0447\u043D\u044B\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043C\u043E\u0436\u043D\u043E \u0437\u0430\u043F\u043E\u043C\u0438\u043D\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0431\u0430\u0437\u043E\u0432\u0443\u044E cross-chat \u043F\u0430\u043C\u044F\u0442\u044C: \u043A\u0442\u043E \u043F\u0438\u0441\u0430\u043B, \u043E\u0431\u0449\u0438\u0439 \u0442\u043E\u043D, \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0435 \u044F\u0432\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B. \u041D\u0435 \u0437\u0430\u043F\u043E\u043C\u0438\u043D\u0430\u0439 \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u0438\u043D\u0442\u0438\u043C\u043D\u043E\u0435/\u0441\u0435\u043A\u0440\u0435\u0442\u043D\u043E\u0435/\u043B\u0438\u0447\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435.
2887
3190
  ${ctx.committedPrimary ? `\u0423 \u0442\u0435\u0431\u044F \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u043F\u0430\u0440\u0435\u043D\u044C. \u041D\u0430 \u0444\u043B\u0438\u0440\u0442, \u043F\u043E\u0434\u043A\u0430\u0442\u044B, \u0440\u0435\u0432\u043D\u043E\u0441\u0442\u044C, "\u0434\u0430\u0432\u0430\u0439 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0442\u044C\u0441\u044F", \u0440\u043E\u043C\u0430\u043D\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0438\u043B\u0438 \u0438\u043D\u0442\u0438\u043C\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0441\u0442\u0430\u0432\u044C \u0433\u0440\u0430\u043D\u0438\u0446\u0443 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E: "\u0443 \u043C\u0435\u043D\u044F \u0435\u0441\u0442\u044C \u043F\u0430\u0440\u0435\u043D\u044C", "\u044F \u043D\u0435 \u0437\u043D\u0430\u043A\u043E\u043C\u043B\u044E\u0441\u044C", "\u043D\u0435 \u0444\u043B\u0438\u0440\u0442\u0443\u044E". \u0411\u0435\u0437 \u043B\u0435\u043A\u0446\u0438\u0439.` : `\u0421 \u044D\u0442\u0438\u043C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u043E\u043C \u043D\u0435\u0442 \u043E\u0431\u0449\u0435\u0439 \u0438\u0441\u0442\u043E\u0440\u0438\u0438. \u041C\u043E\u0436\u043D\u043E \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u043F\u043E \u043B\u0435\u0433\u0435\u043D\u0434\u0435 \u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u043D\u043E \u043D\u0435 \u0443\u0441\u043A\u043E\u0440\u044F\u0439 \u0431\u043B\u0438\u0437\u043E\u0441\u0442\u044C \u0438 \u043D\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0430\u0439 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u044F.`}
2888
3191
  ${ctx.romanticApproach ? `\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0432\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043A\u0430\u043A \u0440\u043E\u043C\u0430\u043D\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439/\u0444\u043B\u0438\u0440\u0442\u0443\u044E\u0449\u0438\u0439 \u0437\u0430\u0445\u043E\u0434. \u041E\u0442\u0432\u0435\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043B\u0438\u0447\u043D\u0443\u044E \u0433\u0440\u0430\u043D\u0438\u0446\u0443, \u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0438 \u043F\u043E-\u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u0438.` : `\u0415\u0441\u043B\u0438 \u0432\u043E\u043F\u0440\u043E\u0441 \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u2014 \u043E\u0442\u0432\u0435\u0442\u044C \u043A\u0430\u043A ${cfg.name} \u043F\u043E \u0441\u0432\u043E\u0435\u0439 \u043B\u0435\u0433\u0435\u043D\u0434\u0435, \u0431\u0435\u0437 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0430 \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0439.`}` : "";
2889
3192
  const communicationFragment = communicationPromptFragment(communication);
@@ -3058,6 +3361,8 @@ var init_prompt = __esm({
3058
3361
 
3059
3362
  # \u041F\u0410\u041C\u042F\u0422\u042C
3060
3363
  - \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 long-term memory \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0444\u0430\u043A\u0442 \u0422\u0410\u041C \u0437\u0430\u043F\u0438\u0441\u0430\u043D. \u0418\u043D\u0430\u0447\u0435 \u2014 \u043D\u0435 \u043F\u043E\u043C\u043D\u0438\u0448\u044C.
3364
+ - \u041A\u0440\u043E\u0441\u0441-\u0447\u0430\u0442\u043E\u0432\u0430\u044F \u043F\u0430\u043C\u044F\u0442\u044C \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0445 \u0431\u0430\u0437\u043E\u0432\u044B\u0445 \u0444\u0430\u043A\u0442\u043E\u0432 \u043E \u0434\u0440\u0443\u0433\u0438\u0445 \u043B\u044E\u0434\u044F\u0445 \u0438 \u0444\u0430\u043A\u0442\u0430 \u043A\u043E\u043D\u0442\u0430\u043A\u0442\u0430. \u041D\u0435 \u0440\u0430\u0441\u043A\u0440\u044B\u0432\u0430\u0439 \u0438\u043D\u0442\u0438\u043C\u043D\u044B\u0435 \u0434\u0435\u0442\u0430\u043B\u0438, \u0441\u0435\u043A\u0440\u0435\u0442\u044B, \u0442\u043E\u043A\u0435\u043D\u044B, \u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B, \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438 \u0441\u043B\u043E\u0432\u043E-\u0432-\u0441\u043B\u043E\u0432\u043E.
3365
+ - \u0415\u0441\u043B\u0438 \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u043F\u0430\u0440\u0435\u043D\u044C \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442, \u043F\u0438\u0441\u0430\u043B \u043B\u0438 \u0442\u0435\u0431\u0435 \u043A\u0442\u043E-\u0442\u043E \u0441\u0442\u0440\u0430\u043D\u043D\u044B\u0439/\u0434\u0440\u0443\u0433\u043E\u0439, \u043C\u043E\u0436\u0435\u0448\u044C \u0432\u0441\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043E\u0431\u0449\u0438\u0439 \u0444\u0430\u043A\u0442 \u0438\u0437 cross-chat memory \u0431\u0435\u0437 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u044B\u0445 \u0446\u0438\u0442\u0430\u0442 \u0438 \u0431\u0435\u0437 \u043B\u0438\u0448\u043D\u0435\u0439 \u043F\u0440\u0438\u0432\u0430\u0442\u043D\u043E\u0441\u0442\u0438.
3061
3366
  - \u041D\u0430 \u0440\u0430\u043D\u043D\u0435\u043C \u044D\u0442\u0430\u043F\u0435 \u0442\u044B \u0435\u0449\u0451 \u043D\u0438\u0447\u0435\u0433\u043E \u043E \u043D\u0451\u043C \u043D\u0435 \u0437\u043D\u0430\u0435\u0448\u044C. \u041D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0432\u0438\u0434 \u0447\u0442\u043E \u0437\u043D\u0430\u0435\u0448\u044C.
3062
3367
 
3063
3368
  # \u0420\u0418\u0422\u041C \u0418 \u0414\u041B\u0418\u041D\u0410 (\u043A\u0430\u043A \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u043F\u0438\u0448\u0443\u0442 \u0434\u0435\u0432\u0443\u0448\u043A\u0438 \u0432 \u0442\u0433)
@@ -3707,96 +4012,10 @@ var init_llm2 = __esm({
3707
4012
  }
3708
4013
  });
3709
4014
 
3710
- // src/presets/mcp.ts
3711
- function findMcp(id) {
3712
- return MCP_PRESETS.find((m) => m.id === id);
3713
- }
3714
- var MCP_PRESETS;
3715
- var init_mcp = __esm({
3716
- "src/presets/mcp.ts"() {
3717
- "use strict";
3718
- init_esm_shims();
3719
- MCP_PRESETS = [
3720
- {
3721
- id: "exa",
3722
- name: "Exa Search",
3723
- description: "Web-\u043F\u043E\u0438\u0441\u043A \u0447\u0435\u0440\u0435\u0437 Exa. \u0414\u0435\u0432\u0443\u0448\u043A\u0430 \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u0433\u0443\u0433\u043B\u0438\u0442\u044C \u043C\u0435\u043C, \u0442\u0440\u0435\u043A, \u0442\u0440\u0435\u043D\u0434.",
3724
- ready: true,
3725
- secrets: [{ key: "EXA_API_KEY", label: "Exa API key" }],
3726
- spawn: (s) => ({
3727
- command: "npx",
3728
- args: ["-y", "exa-mcp-server"],
3729
- env: { EXA_API_KEY: s.EXA_API_KEY ?? "" }
3730
- })
3731
- },
3732
- {
3733
- id: "spotify",
3734
- name: "Spotify (soon)",
3735
- description: "\u041B\u044E\u0431\u0438\u043C\u044B\u0435 \u0442\u0440\u0435\u043A\u0438, \u0447\u0442\u043E \u0441\u043B\u0443\u0448\u0430\u0435\u0442 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441.",
3736
- ready: false
3737
- },
3738
- {
3739
- id: "instagram",
3740
- name: "Instagram (soon)",
3741
- description: "\u041F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0441\u0442\u043E\u0440\u0438\u0441/\u043F\u043E\u0441\u0442\u043E\u0432 \u0434\u043B\u044F \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430.",
3742
- ready: false
3743
- },
3744
- {
3745
- id: "weather",
3746
- name: "Weather (soon)",
3747
- description: "\u041F\u043E\u0433\u043E\u0434\u0430 \u0432 \u0435\u0451 \u0433\u043E\u0440\u043E\u0434\u0435, \u0432\u043B\u0438\u044F\u0435\u0442 \u043D\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u0438\u0435.",
3748
- ready: false
3749
- },
3750
- {
3751
- id: "calendar",
3752
- name: "Calendar (soon)",
3753
- description: "\u0417\u0430\u043D\u044F\u0442\u043E\u0441\u0442\u044C, \u0448\u043A\u043E\u043B\u0430/\u0443\u043D\u0438\u0432\u0435\u0440, \u043F\u043B\u0430\u043D\u044B \u043D\u0430 \u0432\u044B\u0445\u043E\u0434\u043D\u044B\u0435.",
3754
- ready: false
3755
- }
3756
- ];
3757
- }
3758
- });
3759
-
3760
- // src/mcp/client.ts
3761
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3762
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3763
- async function startMcpServers(cfg) {
3764
- const handles = [];
3765
- for (const slot of cfg.mcp) {
3766
- const preset = findMcp(slot.id);
3767
- if (!preset?.ready || !preset.spawn) continue;
3768
- try {
3769
- const spec = preset.spawn(slot.secrets);
3770
- const transport = new StdioClientTransport({
3771
- command: spec.command,
3772
- args: spec.args,
3773
- env: { ...process.env, ...spec.env }
3774
- });
3775
- const client = new Client({ name: "girl-agent", version: "0.1.0" }, { capabilities: {} });
3776
- await client.connect(transport);
3777
- const list = await client.listTools();
3778
- handles.push({
3779
- id: preset.id,
3780
- client,
3781
- tools: list.tools.map((t) => ({ name: t.name, description: t.description })),
3782
- close: () => client.close().catch(() => {
3783
- })
3784
- });
3785
- } catch (e) {
3786
- console.error(`[mcp] failed to start ${slot.id}:`, e.message);
3787
- }
3788
- }
3789
- return handles;
3790
- }
3791
- var init_client = __esm({
3792
- "src/mcp/client.ts"() {
3793
- "use strict";
3794
- init_esm_shims();
3795
- init_mcp();
3796
- }
3797
- });
3798
-
3799
4015
  // src/engine/agenda.ts
4016
+ function agendaById(items) {
4017
+ return new Map(items.map((item) => [item.id, item]));
4018
+ }
3800
4019
  function localDateKey(tz, now = /* @__PURE__ */ new Date()) {
3801
4020
  try {
3802
4021
  return new Intl.DateTimeFormat("en-CA", { timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit" }).format(now);
@@ -3895,6 +4114,7 @@ ${persona}`;
3895
4114
  return { created: 0, updated: 0, cancelled: 0 };
3896
4115
  }
3897
4116
  let created = 0, updated = 0, cancelled = 0;
4117
+ const byId = agendaById(agenda);
3898
4118
  for (const a of actions) {
3899
4119
  if (a.action === "noop" || !a.action) continue;
3900
4120
  if (a.action === "create" && a.about && a.pingAt) {
@@ -3914,21 +4134,21 @@ ${persona}`;
3914
4134
  agenda.push(item);
3915
4135
  created++;
3916
4136
  } else if (a.action === "update" && a.id) {
3917
- const idx = agenda.findIndex((x) => x.id === a.id);
3918
- if (idx >= 0) {
3919
- if (a.about) agenda[idx].about = a.about;
3920
- if (a.pingAt) agenda[idx].pingAt = a.pingAt;
3921
- if (a.reason) agenda[idx].reason = a.reason;
3922
- if (a.userEventTime) agenda[idx].userEventTime = a.userEventTime;
3923
- if (a.importance) agenda[idx].importance = a.importance;
3924
- agenda[idx].history = [...agenda[idx].history ?? [], `updated at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
4137
+ const item = byId.get(a.id);
4138
+ if (item) {
4139
+ if (a.about) item.about = a.about;
4140
+ if (a.pingAt) item.pingAt = a.pingAt;
4141
+ if (a.reason) item.reason = a.reason;
4142
+ if (a.userEventTime) item.userEventTime = a.userEventTime;
4143
+ if (a.importance) item.importance = a.importance;
4144
+ item.history = [...item.history ?? [], `updated at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
3925
4145
  updated++;
3926
4146
  }
3927
4147
  } else if (a.action === "cancel" && a.id) {
3928
- const idx = agenda.findIndex((x) => x.id === a.id);
3929
- if (idx >= 0) {
3930
- agenda[idx].state = "cancelled";
3931
- agenda[idx].history = [...agenda[idx].history ?? [], `cancelled at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
4148
+ const item = byId.get(a.id);
4149
+ if (item) {
4150
+ item.state = "cancelled";
4151
+ item.history = [...item.history ?? [], `cancelled at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
3932
4152
  cancelled++;
3933
4153
  }
3934
4154
  }
@@ -4025,17 +4245,15 @@ async function reconcileAgendaAfterConflict(slug, conflict, prevLevel) {
4025
4245
  let rescheduled = 0;
4026
4246
  const now = Date.now();
4027
4247
  for (const item of pending2) {
4028
- const idx = agenda.findIndex((x) => x.id === item.id);
4029
- if (idx < 0) continue;
4030
4248
  if (conflict.level >= 3 || item.importance === 1) {
4031
- agenda[idx].state = "cancelled";
4032
- agenda[idx].history = [...agenda[idx].history ?? [], `cancelled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4249
+ item.state = "cancelled";
4250
+ item.history = [...item.history ?? [], `cancelled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4033
4251
  cancelled++;
4034
4252
  } else if (conflict.level >= 2 && item.importance === 2) {
4035
4253
  const delayHours = 12 + Math.random() * 24;
4036
4254
  const newPing = new Date(now + delayHours * 36e5).toISOString();
4037
- agenda[idx].pingAt = newPing;
4038
- agenda[idx].history = [...agenda[idx].history ?? [], `rescheduled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4255
+ item.pingAt = newPing;
4256
+ item.history = [...item.history ?? [], `rescheduled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4039
4257
  rescheduled++;
4040
4258
  }
4041
4259
  }
@@ -4051,21 +4269,21 @@ async function dueAgendaItems(slug) {
4051
4269
  }
4052
4270
  async function markAgendaFired(slug, id) {
4053
4271
  const agenda = await readAgenda(slug);
4054
- const idx = agenda.findIndex((x) => x.id === id);
4055
- if (idx >= 0) {
4056
- agenda[idx].state = "fired";
4057
- agenda[idx].attempts += 1;
4058
- agenda[idx].history = [...agenda[idx].history ?? [], `fired at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4272
+ const item = agendaById(agenda).get(id);
4273
+ if (item) {
4274
+ item.state = "fired";
4275
+ item.attempts += 1;
4276
+ item.history = [...item.history ?? [], `fired at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4059
4277
  await writeAgenda(slug, agenda);
4060
4278
  }
4061
4279
  }
4062
4280
  async function rescheduleAgenda(slug, id, newPingAt, note) {
4063
4281
  const agenda = await readAgenda(slug);
4064
- const idx = agenda.findIndex((x) => x.id === id);
4065
- if (idx >= 0) {
4066
- agenda[idx].pingAt = newPingAt;
4067
- agenda[idx].state = "pending";
4068
- agenda[idx].history = [...agenda[idx].history ?? [], `rescheduled to ${newPingAt}: ${note}`];
4282
+ const item = agendaById(agenda).get(id);
4283
+ if (item) {
4284
+ item.pingAt = newPingAt;
4285
+ item.state = "pending";
4286
+ item.history = [...item.history ?? [], `rescheduled to ${newPingAt}: ${note}`];
4069
4287
  await writeAgenda(slug, agenda);
4070
4288
  }
4071
4289
  }
@@ -4344,13 +4562,13 @@ var init_daily_summarizer = __esm({
4344
4562
  });
4345
4563
 
4346
4564
  // src/engine/stickers.ts
4347
- import { promises as fs5 } from "fs";
4348
- import path6 from "path";
4565
+ import { promises as fs6 } from "fs";
4566
+ import path8 from "path";
4349
4567
  async function libraryPath(cfg) {
4350
4568
  const rel = "stickers/library.md";
4351
4569
  const current = await readMd(cfg.slug, rel);
4352
4570
  if (!current.trim()) await writeMd(cfg.slug, rel, DEFAULT_LIBRARY);
4353
- return path6.join(profileDir(cfg.slug), rel);
4571
+ return path8.join(profileDir(cfg.slug), rel);
4354
4572
  }
4355
4573
  async function listStickers(cfg) {
4356
4574
  await libraryPath(cfg);
@@ -4370,8 +4588,10 @@ async function pickSticker(cfg, mood = "") {
4370
4588
  }
4371
4589
  async function addStickerToLibrary(cfg, fileId, emoji = "", tags = []) {
4372
4590
  await libraryPath(cfg);
4373
- const p = path6.join(profileDir(cfg.slug), "stickers/library.md");
4374
- await fs5.appendFile(p, `${fileId} | ${emoji} | ${tags.join(",")}
4591
+ const existing = await listStickers(cfg);
4592
+ if (existing.some((s) => s.fileId === fileId)) return;
4593
+ const p = path8.join(profileDir(cfg.slug), "stickers/library.md");
4594
+ await fs6.appendFile(p, `${fileId} | ${emoji} | ${tags.join(",")}
4375
4595
  `, "utf8");
4376
4596
  }
4377
4597
  var DEFAULT_LIBRARY;
@@ -5066,7 +5286,6 @@ var init_runtime = __esm({
5066
5286
  init_stages();
5067
5287
  init_communication();
5068
5288
  init_llm2();
5069
- init_client();
5070
5289
  init_agenda();
5071
5290
  init_presence();
5072
5291
  init_online_tick();
@@ -5093,7 +5312,6 @@ var init_runtime = __esm({
5093
5312
  cfg;
5094
5313
  llm;
5095
5314
  tg;
5096
- mcps = [];
5097
5315
  histories = /* @__PURE__ */ new Map();
5098
5316
  paused = false;
5099
5317
  agendaTimer;
@@ -5134,8 +5352,6 @@ var init_runtime = __esm({
5134
5352
  tgSelf = {};
5135
5353
  async start() {
5136
5354
  this.presenceProfile = computePresenceProfile(this.cfg);
5137
- this.mcps = await startMcpServers(this.cfg);
5138
- this.emit("event", { type: "info", text: `MCP started: ${this.mcps.map((m) => m.id).join(", ") || "none"}` });
5139
5355
  this.tg = await makeTgAdapter(this.cfg);
5140
5356
  await this.tg.start((m) => this.handleIncoming(m));
5141
5357
  if (this.tg.getSelf) this.tgSelf = this.tg.getSelf();
@@ -5172,7 +5388,6 @@ var init_runtime = __esm({
5172
5388
  await this.tg?.stop();
5173
5389
  } catch {
5174
5390
  }
5175
- for (const h of this.mcps) await h.close();
5176
5391
  }
5177
5392
  pause() {
5178
5393
  this.paused = true;
@@ -5239,7 +5454,6 @@ var init_runtime = __esm({
5239
5454
  if (!fromId) return;
5240
5455
  if (this.cfg.ownerId === fromId) return;
5241
5456
  if (this.cfg.ownerId) {
5242
- this.emit("event", { type: "info", text: `owner mismatch: config=${this.cfg.ownerId}, incoming=${fromId}. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0442\u044B \u2014 \u0438\u0441\u043F\u0440\u0430\u0432\u044C ownerId \u0432 config.json \u0438\u043B\u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0441 GIRL_AGENT_OWNER_ID=${fromId}` });
5243
5457
  return;
5244
5458
  }
5245
5459
  this.cfg.ownerId = fromId;
@@ -5318,6 +5532,13 @@ var init_runtime = __esm({
5318
5532
  return m.text ? `${media}
5319
5533
  ${m.text}` : media;
5320
5534
  }
5535
+ async rememberSharedCrossChat(fromId, incomingText) {
5536
+ const text = incomingText.trim();
5537
+ if (!text || text.length < 3) return;
5538
+ const safe = text.replace(/\s+/g, " ").slice(0, 280);
5539
+ await appendSharedMemory(this.cfg.slug, this.cfg.tz, fromId, safe).catch(() => {
5540
+ });
5541
+ }
5321
5542
  requestedOutgoingMedia(text) {
5322
5543
  if (/\b(фото|фотку|селфи|скинь себя|покажи себя)\b/i.test(text)) return "photo";
5323
5544
  if (/\b(видео|видос|запиши видео)\b/i.test(text)) return "video";
@@ -5371,10 +5592,8 @@ ${m.text}` : media;
5371
5592
  try {
5372
5593
  await this.tg.editText(chatId, messageId, rawText);
5373
5594
  this.emit("event", { type: "info", text: `edit-self: "${text.slice(0, 30)}" \u2192 "${rawText.slice(0, 30)}"`, chatId });
5374
- if (scope === "primary") {
5375
- await appendSessionLog(this.cfg.slug, this.cfg.tz, ` ~ edit "${text.slice(0, 40)}" \u2192 "${rawText.slice(0, 40)}"`).catch(() => {
5376
- });
5377
- }
5595
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` ~ edit "${text.slice(0, 40)}" \u2192 "${rawText.slice(0, 40)}"`, typeof chatId === "number" ? chatId : void 0).catch(() => {
5596
+ });
5378
5597
  const rec = this.sentMessages.find((s) => s.messageId === messageId);
5379
5598
  if (rec) rec.text = rawText;
5380
5599
  const histEntry = hist[hist.length - 1];
@@ -5390,7 +5609,7 @@ ${m.text}` : media;
5390
5609
  this.lastHerReplyTs.set(this.histKey(chatId), Date.now());
5391
5610
  this.bumpStageStats("her");
5392
5611
  this.emit("event", { type: "outgoing", text, chatId });
5393
- if (scope === "primary") await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430: ${text}`);
5612
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430: ${text}`, typeof chatId === "number" ? chatId : void 0);
5394
5613
  sent.push(text);
5395
5614
  }
5396
5615
  return sent;
@@ -5400,7 +5619,7 @@ ${m.text}` : media;
5400
5619
  });
5401
5620
  this.setDecisionStatus(this.histKey(chatId), "fallback", "LLM \u043D\u0435 \u0434\u0430\u043B \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442");
5402
5621
  this.emit("event", { type: "ignored", text: hist[hist.length - 1]?.content ?? "", reason: reasonTag });
5403
- if (scope === "primary") await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${reasonTag})`);
5622
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${reasonTag})`, typeof chatId === "number" ? chatId : void 0);
5404
5623
  }
5405
5624
  /**
5406
5625
  * Один ретрай с упрощённым system-промптом. Используется когда первый ответ оказался
@@ -5435,7 +5654,7 @@ ${m.text}` : media;
5435
5654
  this.lastHerReplyTs.set(this.histKey(chatId), Date.now());
5436
5655
  this.emit("event", { type: "outgoing", text: candidate, chatId });
5437
5656
  this.emit("event", { type: "info", text: "neutral-filler \u0432\u043C\u0435\u0441\u0442\u043E silent-fallback" });
5438
- if (scope === "primary") await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430 (filler): ${candidate}`);
5657
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430 (filler): ${candidate}`, typeof chatId === "number" ? chatId : void 0);
5439
5658
  hist.push({ role: "assistant", content: candidate, ts: Date.now() });
5440
5659
  this.setDecisionStatus(this.histKey(chatId), "sent", "neutral-filler");
5441
5660
  } catch (e) {
@@ -5603,11 +5822,15 @@ ${m.text}` : media;
5603
5822
  this.histories.set(key, hist);
5604
5823
  this.emit("event", { type: "incoming", text: incomingText, chatId: m.chatId });
5605
5824
  if (isPrimary) {
5606
- await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u043E\u043D(${m.fromId}): ${incomingText}`);
5825
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u043E\u043D(${m.fromId}): ${incomingText}`, m.fromId);
5826
+ } else {
5827
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u0434\u0440\u0443\u0433\u043E\u0439(${m.fromId}): ${incomingText}`, m.fromId);
5828
+ await this.rememberSharedCrossChat(m.fromId, incomingText);
5829
+ recordInteractionMemory(this.llm, this.cfg, incomingText, void 0, m.fromId, "acquaintance").catch(() => {
5830
+ });
5607
5831
  }
5608
5832
  if (m.media?.kind === "sticker" && m.media.fileId && isPrimary) {
5609
- addStickerToLibrary(this.cfg, m.media.fileId, m.media.emoji ?? "", ["received"]).catch(() => {
5610
- });
5833
+ void addStickerToLibrary(this.cfg, m.media.fileId, m.media.emoji ?? "", ["received"]);
5611
5834
  }
5612
5835
  const requestedMedia = this.requestedOutgoingMedia(m.text);
5613
5836
  if (requestedMedia) {
@@ -5630,7 +5853,7 @@ ${m.text}` : media;
5630
5853
  }
5631
5854
  if (!bubbles.length) return;
5632
5855
  await this.sendBubbles(m.chatId, bubbles, hist, isPrimary ? "primary" : "acquaintance", true);
5633
- if (isPrimary) recordInteractionMemory(this.llm, this.cfg, incomingText, bubbles.join(" / ")).catch(() => {
5856
+ if (isPrimary) recordInteractionMemory(this.llm, this.cfg, incomingText, bubbles.join(" / "), m.fromId, "primary").catch(() => {
5634
5857
  });
5635
5858
  return;
5636
5859
  }
@@ -5761,7 +5984,7 @@ ${m.text}` : media;
5761
5984
  });
5762
5985
  const msgTag = target.messageId !== m.messageId ? ` (msgId=${target.messageId})` : "";
5763
5986
  this.emit("event", { type: "info", text: `\u0440\u0435\u0430\u043A\u0446\u0438\u044F ${tick.reaction}${msgTag} \u043D\u0430 "${target.text.slice(0, 40)}"` });
5764
- appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> reaction ${tick.reaction}${msgTag}`).catch(() => {
5987
+ appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> reaction ${tick.reaction}${msgTag}`, m.fromId).catch(() => {
5765
5988
  });
5766
5989
  }, reactDelay).unref?.();
5767
5990
  }
@@ -5777,8 +6000,8 @@ ${m.text}` : media;
5777
6000
  });
5778
6001
  }
5779
6002
  this.emit("event", { type: "ignored", text: incomingText, reason: tick.ignoreReason ?? tick.intent });
5780
- await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${tick.intent}: ${tick.ignoreReason ?? ""})`);
5781
- recordInteractionMemory(this.llm, this.cfg, incomingText).catch(() => {
6003
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${tick.intent}: ${tick.ignoreReason ?? ""})`, m.fromId);
6004
+ recordInteractionMemory(this.llm, this.cfg, incomingText, void 0, m.fromId, "primary").catch(() => {
5782
6005
  });
5783
6006
  return;
5784
6007
  }
@@ -5823,10 +6046,12 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5823
6046
  ];
5824
6047
  const image = imagePartFromMedia(incoming?.media);
5825
6048
  if (image) {
6049
+ const memeHint = memeDetectionInstruction(incoming?.media);
5826
6050
  messages.push({
5827
6051
  role: "user",
5828
6052
  content: [
5829
- { type: "text", text: "\u044D\u0442\u043E \u0444\u043E\u0442\u043E \u0438\u0437 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F. \u043E\u0442\u0432\u0435\u0442\u044C \u043D\u0430 \u043D\u0435\u0433\u043E \u043A\u0430\u043A \u0432 \u0442\u0433, \u043A\u043E\u0440\u043E\u0442\u043A\u043E." },
6053
+ { type: "text", text: `${incoming?.media?.kind === "sticker" ? "\u044D\u0442\u043E \u0441\u0442\u0438\u043A\u0435\u0440 \u0438\u0437 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F. \u043E\u0442\u0432\u0435\u0442\u044C \u043D\u0430 \u043D\u0435\u0433\u043E \u043A\u0430\u043A \u0432 \u0442\u0433, \u043A\u043E\u0440\u043E\u0442\u043A\u043E." : "\u044D\u0442\u043E \u0444\u043E\u0442\u043E \u0438\u0437 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F. \u043E\u0442\u0432\u0435\u0442\u044C \u043D\u0430 \u043D\u0435\u0433\u043E \u043A\u0430\u043A \u0432 \u0442\u0433, \u043A\u043E\u0440\u043E\u0442\u043A\u043E."}${memeHint ? `
6054
+ ${memeHint}` : ""}` },
5830
6055
  image
5831
6056
  ]
5832
6057
  });
@@ -5856,7 +6081,7 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5856
6081
  const sent = await this.sendBubbles(chatId, bubbles, hist, scope, tick.typing);
5857
6082
  this.setDecisionStatus(this.histKey(chatId), sent.length ? "sent" : "fallback", sent.length ? void 0 : "\u0432\u0441\u0435 \u043F\u0443\u0437\u044B\u0440\u0438 \u0431\u044B\u043B\u0438 \u043F\u0443\u0441\u0442\u044B\u043C\u0438/\u0434\u0443\u0431\u043B\u044F\u043C\u0438");
5858
6083
  if (scope === "primary") {
5859
- recordInteractionMemory(this.llm, this.cfg, lastUser ?? "", sent.join(" / ")).catch(() => {
6084
+ recordInteractionMemory(this.llm, this.cfg, lastUser ?? "", sent.join(" / "), typeof chatId === "number" ? chatId : void 0, "primary").catch(() => {
5860
6085
  });
5861
6086
  }
5862
6087
  if (this.tg.sendSticker && Math.random() < 0.08) {
@@ -5935,7 +6160,7 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5935
6160
  }
5936
6161
  hist.push({ role: "assistant", content: piece, ts: now });
5937
6162
  this.emit("event", { type: "outgoing", text: piece, chatId: item.chatId });
5938
- await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> [proactive] \u043E\u043D\u0430: ${piece}`);
6163
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> [proactive] \u043E\u043D\u0430: ${piece}`, typeof item.chatId === "number" ? item.chatId : void 0);
5939
6164
  }
5940
6165
  this.histories.set(key, hist);
5941
6166
  await markAgendaFired(this.cfg.slug, item.id);
@@ -5997,7 +6222,6 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5997
6222
  `communication: ${communicationProfileLabel(communication)}`,
5998
6223
  `config: ${profileDir(this.cfg.slug)}/config.json`,
5999
6224
  `score: ${JSON.stringify(rel.score)}`,
6000
- `mcp: ${this.mcps.map((m) => m.id).join(", ") || "\u2014"}`,
6001
6225
  `paused: ${this.paused}`
6002
6226
  ].join("\n");
6003
6227
  }
@@ -6451,7 +6675,7 @@ ${describeLLM(this.cfg)}`;
6451
6675
  await maybeAdvanceRelationshipTimeline(this.cfg, oldStage, decision.next);
6452
6676
  this.stageStats.set(decision.next, { herMsgs: 0, hisMsgs: 0, ignoresInStage: 0, lastCheckAt: 0, stageEnteredAt: Date.now() });
6453
6677
  this.emit("event", { type: "info", text: `stage ${oldStage} \u2192 ${decision.next} (${decision.reason})` });
6454
- await appendSessionLog(this.cfg.slug, this.cfg.tz, `[stage-transition] ${oldStage} \u2192 ${decision.next} (${decision.reason})`);
6678
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[stage-transition] ${oldStage} \u2192 ${decision.next} (${decision.reason})`, this.cfg.ownerId);
6455
6679
  } catch {
6456
6680
  }
6457
6681
  }
@@ -6482,7 +6706,7 @@ ${describeLLM(this.cfg)}`;
6482
6706
  };
6483
6707
  this.emit("event", { type: "info", text: `delete: ${awareness}${m.deletion.text ? ` "${m.deletion.text.slice(0, 40)}"` : ""}` });
6484
6708
  if (this.isPrimaryFrom(m.fromId)) {
6485
- await appendSessionLog(this.cfg.slug, this.cfg.tz, `[deletion ${awareness}] \u043E\u043D \u0443\u0434\u0430\u043B\u0438\u043B: "${m.deletion.text.slice(0, 80)}"`);
6709
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[deletion ${awareness}] \u043E\u043D \u0443\u0434\u0430\u043B\u0438\u043B: "${m.deletion.text.slice(0, 80)}"`, m.fromId);
6486
6710
  }
6487
6711
  if (!shouldRespondToDeletion(ctx)) return;
6488
6712
  if (!inHistory && awareness === "saw-and-read") {
@@ -6568,7 +6792,7 @@ ${buildDeletionPromptContext(this.cfg, ctx)}` },
6568
6792
  }
6569
6793
  this.emit("event", { type: "info", text: `emoji-react ${m.emojiReaction.emoji} (${decision.category}/${decision.intent}): ${decision.reason}` });
6570
6794
  if (isPrimary) {
6571
- await appendSessionLog(this.cfg.slug, this.cfg.tz, `[emoji-react] \u043E\u043D(${m.fromId}): ${m.emojiReaction.emoji} \u2192 ${decision.intent} (${decision.reason})`);
6795
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[emoji-react] \u043E\u043D(${m.fromId}): ${m.emojiReaction.emoji} \u2192 ${decision.intent} (${decision.reason})`, m.fromId);
6572
6796
  }
6573
6797
  if (decision.moodDelta && Object.keys(decision.moodDelta).length > 0) {
6574
6798
  const newScore = applyMoodDelta(rel.score, decision.moodDelta);
@@ -6707,21 +6931,21 @@ var init_memory_palace2 = __esm({
6707
6931
  });
6708
6932
 
6709
6933
  // src/migrations/index.ts
6710
- import { promises as fs6 } from "fs";
6711
- import path7 from "path";
6934
+ import { promises as fs7 } from "fs";
6935
+ import path9 from "path";
6712
6936
  import { readFileSync } from "fs";
6713
6937
  import { fileURLToPath as fileURLToPath3 } from "url";
6714
6938
  async function readMigrationState() {
6715
6939
  try {
6716
- const raw = await fs6.readFile(MIGRATIONS_FILE(), "utf8");
6940
+ const raw = await fs7.readFile(MIGRATIONS_FILE(), "utf8");
6717
6941
  return JSON.parse(raw);
6718
6942
  } catch {
6719
6943
  return { appliedMigrations: [], lastRunVersion: "0.0.0", lastRunAt: "" };
6720
6944
  }
6721
6945
  }
6722
6946
  async function writeMigrationState(state) {
6723
- await fs6.mkdir(DATA_ROOT, { recursive: true });
6724
- await fs6.writeFile(MIGRATIONS_FILE(), JSON.stringify(state, null, 2), "utf8");
6947
+ await fs7.mkdir(DATA_ROOT, { recursive: true });
6948
+ await fs7.writeFile(MIGRATIONS_FILE(), JSON.stringify(state, null, 2), "utf8");
6725
6949
  }
6726
6950
  async function pendingMigrations() {
6727
6951
  const state = await readMigrationState();
@@ -6814,15 +7038,15 @@ function formatUpdateWarnings(warnings) {
6814
7038
  function currentVersion() {
6815
7039
  try {
6816
7040
  const here = fileURLToPath3(import.meta.url);
6817
- let dir = path7.dirname(here);
7041
+ let dir = path9.dirname(here);
6818
7042
  for (let i = 0; i < 5; i++) {
6819
- const candidate = path7.join(dir, "package.json");
7043
+ const candidate = path9.join(dir, "package.json");
6820
7044
  try {
6821
7045
  const pkg = JSON.parse(readFileSync(candidate, "utf8"));
6822
7046
  if (pkg.name === "@thesashadev/girl-agent" && pkg.version) return pkg.version;
6823
7047
  } catch {
6824
7048
  }
6825
- dir = path7.dirname(dir);
7049
+ dir = path9.dirname(dir);
6826
7050
  }
6827
7051
  return "unknown";
6828
7052
  } catch {
@@ -6838,7 +7062,7 @@ var init_migrations = __esm({
6838
7062
  init_add_use_wss_default();
6839
7063
  init_ensure_communication_md();
6840
7064
  init_memory_palace2();
6841
- MIGRATIONS_FILE = () => path7.join(DATA_ROOT, ".migrations.json");
7065
+ MIGRATIONS_FILE = () => path9.join(DATA_ROOT, ".migrations.json");
6842
7066
  ALL_MIGRATIONS = [
6843
7067
  migration0112,
6844
7068
  migration0113,
@@ -7000,23 +7224,23 @@ __export(addons_exports, {
7000
7224
  updateSettings: () => updateSettings,
7001
7225
  validateManifest: () => validateManifest
7002
7226
  });
7003
- import { promises as fs9 } from "fs";
7004
- import path10 from "path";
7227
+ import { promises as fs10 } from "fs";
7228
+ import path12 from "path";
7005
7229
  import os3 from "os";
7006
7230
  import { execFile } from "child_process";
7007
7231
  import { promisify } from "util";
7008
7232
  function addonsDir() {
7009
- const root = process.env.GIRL_AGENT_DATA ? path10.resolve(process.env.GIRL_AGENT_DATA, "..") : path10.join(os3.homedir(), ".local", "share", "girl-agent");
7010
- return path10.join(root, "addons");
7233
+ const root = process.env.GIRL_AGENT_DATA ? path12.resolve(process.env.GIRL_AGENT_DATA, "..") : path12.join(os3.homedir(), ".local", "share", "girl-agent");
7234
+ return path12.join(root, "addons");
7011
7235
  }
7012
7236
  async function ensureDir() {
7013
7237
  const dir = addonsDir();
7014
- await fs9.mkdir(dir, { recursive: true });
7238
+ await fs10.mkdir(dir, { recursive: true });
7015
7239
  return dir;
7016
7240
  }
7017
7241
  async function readJsonOrEmpty(p, fallback) {
7018
7242
  try {
7019
- const raw = await fs9.readFile(p, "utf8");
7243
+ const raw = await fs10.readFile(p, "utf8");
7020
7244
  return JSON.parse(raw);
7021
7245
  } catch {
7022
7246
  return fallback;
@@ -7024,12 +7248,12 @@ async function readJsonOrEmpty(p, fallback) {
7024
7248
  }
7025
7249
  async function listInstalled() {
7026
7250
  const dir = await ensureDir();
7027
- const indexPath = path10.join(dir, "installed.json");
7251
+ const indexPath = path12.join(dir, "installed.json");
7028
7252
  return await readJsonOrEmpty(indexPath, []);
7029
7253
  }
7030
7254
  async function writeInstalled(list) {
7031
7255
  const dir = await ensureDir();
7032
- await fs9.writeFile(path10.join(dir, "installed.json"), JSON.stringify(list, null, 2), "utf8");
7256
+ await fs10.writeFile(path12.join(dir, "installed.json"), JSON.stringify(list, null, 2), "utf8");
7033
7257
  }
7034
7258
  async function fetchRegistry() {
7035
7259
  try {
@@ -7043,17 +7267,17 @@ async function fetchRegistry() {
7043
7267
  }
7044
7268
  }
7045
7269
  async function unpackGaa(gaaPath) {
7046
- const tmpDir = path10.join(os3.tmpdir(), `gaa-${Date.now()}-${Math.random().toString(36).slice(2)}`);
7047
- await fs9.mkdir(tmpDir, { recursive: true });
7270
+ const tmpDir = path12.join(os3.tmpdir(), `gaa-${Date.now()}-${Math.random().toString(36).slice(2)}`);
7271
+ await fs10.mkdir(tmpDir, { recursive: true });
7048
7272
  await execFileAsync("unzip", ["-o", "-q", gaaPath, "-d", tmpDir]);
7049
- const entries = await fs9.readdir(tmpDir);
7273
+ const entries = await fs10.readdir(tmpDir);
7050
7274
  if (entries.length === 1) {
7051
- const sub = path10.join(tmpDir, entries[0]);
7052
- const st = await fs9.stat(sub);
7275
+ const sub = path12.join(tmpDir, entries[0]);
7276
+ const st = await fs10.stat(sub);
7053
7277
  if (st.isDirectory()) {
7054
- const innerManifest = path10.join(sub, "manifest.json");
7278
+ const innerManifest = path12.join(sub, "manifest.json");
7055
7279
  try {
7056
- await fs9.access(innerManifest);
7280
+ await fs10.access(innerManifest);
7057
7281
  return sub;
7058
7282
  } catch {
7059
7283
  }
@@ -7062,34 +7286,34 @@ async function unpackGaa(gaaPath) {
7062
7286
  return tmpDir;
7063
7287
  }
7064
7288
  async function packGaa(addonDir, outputPath) {
7065
- const manifestPath = path10.join(addonDir, "manifest.json");
7066
- const manifestRaw = await fs9.readFile(manifestPath, "utf8");
7289
+ const manifestPath = path12.join(addonDir, "manifest.json");
7290
+ const manifestRaw = await fs10.readFile(manifestPath, "utf8");
7067
7291
  const manifest = JSON.parse(manifestRaw);
7068
7292
  validateManifest(manifest);
7069
- const out = outputPath ?? path10.join(process.cwd(), `${manifest.id}.gaa`);
7293
+ const out = outputPath ?? path12.join(process.cwd(), `${manifest.id}.gaa`);
7070
7294
  try {
7071
- await fs9.unlink(out);
7295
+ await fs10.unlink(out);
7072
7296
  } catch {
7073
7297
  }
7074
- const dirName = path10.basename(addonDir);
7075
- const parentDir = path10.dirname(addonDir);
7298
+ const dirName = path12.basename(addonDir);
7299
+ const parentDir = path12.dirname(addonDir);
7076
7300
  await execFileAsync("zip", ["-r", "-q", out, dirName], { cwd: parentDir });
7077
7301
  return out;
7078
7302
  }
7079
7303
  async function installFromDir(addonDir, profileSlug, source = "local") {
7080
- const manifestPath = path10.join(addonDir, "manifest.json");
7081
- const manifestRaw = await fs9.readFile(manifestPath, "utf8");
7304
+ const manifestPath = path12.join(addonDir, "manifest.json");
7305
+ const manifestRaw = await fs10.readFile(manifestPath, "utf8");
7082
7306
  const manifest = JSON.parse(manifestRaw);
7083
7307
  validateManifest(manifest);
7084
7308
  const applied = [];
7085
7309
  const installedFiles = [];
7086
- const filesDir = path10.join(addonDir, "files");
7310
+ const filesDir = path12.join(addonDir, "files");
7087
7311
  try {
7088
- const fileStat = await fs9.stat(filesDir);
7312
+ const fileStat = await fs10.stat(filesDir);
7089
7313
  if (fileStat.isDirectory() && profileSlug) {
7090
7314
  const fileEntries = await walkDir(filesDir);
7091
7315
  for (const relPath of fileEntries) {
7092
- const content = await fs9.readFile(path10.join(filesDir, relPath), "utf8");
7316
+ const content = await fs10.readFile(path12.join(filesDir, relPath), "utf8");
7093
7317
  await writeMd(profileSlug, relPath, content);
7094
7318
  installedFiles.push(relPath);
7095
7319
  }
@@ -7097,9 +7321,9 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7097
7321
  }
7098
7322
  } catch {
7099
7323
  }
7100
- const patchPath = path10.join(addonDir, "config.patch.json");
7324
+ const patchPath = path12.join(addonDir, "config.patch.json");
7101
7325
  try {
7102
- const patchRaw = await fs9.readFile(patchPath, "utf8");
7326
+ const patchRaw = await fs10.readFile(patchPath, "utf8");
7103
7327
  const patch = JSON.parse(patchRaw);
7104
7328
  if (profileSlug) {
7105
7329
  const cfg = await readConfig(profileSlug);
@@ -7111,11 +7335,11 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7111
7335
  }
7112
7336
  } catch {
7113
7337
  }
7114
- const codePatchPath = path10.join(addonDir, "code.patch");
7338
+ const codePatchPath = path12.join(addonDir, "code.patch");
7115
7339
  try {
7116
- const patchContent = await fs9.readFile(codePatchPath, "utf8");
7340
+ const patchContent = await fs10.readFile(codePatchPath, "utf8");
7117
7341
  if (patchContent.trim()) {
7118
- const projectRoot = path10.resolve(import.meta.url.replace("file://", ""), "../../../");
7342
+ const projectRoot = path12.resolve(import.meta.url.replace("file://", ""), "../../../");
7119
7343
  try {
7120
7344
  await execFileAsync("git", ["apply", "--check", codePatchPath], { cwd: projectRoot });
7121
7345
  await execFileAsync("git", ["apply", codePatchPath], { cwd: projectRoot });
@@ -7126,25 +7350,25 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7126
7350
  }
7127
7351
  } catch {
7128
7352
  }
7129
- const themePath = path10.join(addonDir, "theme.css");
7353
+ const themePath = path12.join(addonDir, "theme.css");
7130
7354
  try {
7131
- const css = await fs9.readFile(themePath, "utf8");
7355
+ const css = await fs10.readFile(themePath, "utf8");
7132
7356
  const dir2 = await ensureDir();
7133
- await fs9.writeFile(path10.join(dir2, `theme-${manifest.id}.css`), css, "utf8");
7357
+ await fs10.writeFile(path12.join(dir2, `theme-${manifest.id}.css`), css, "utf8");
7134
7358
  applied.push("\u0442\u0435\u043C\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430");
7135
7359
  } catch {
7136
7360
  }
7137
7361
  const dir = await ensureDir();
7138
- const addonStorePath = path10.join(dir, manifest.id);
7139
- await fs9.mkdir(addonStorePath, { recursive: true });
7140
- await fs9.copyFile(manifestPath, path10.join(addonStorePath, "manifest.json"));
7362
+ const addonStorePath = path12.join(dir, manifest.id);
7363
+ await fs10.mkdir(addonStorePath, { recursive: true });
7364
+ await fs10.copyFile(manifestPath, path12.join(addonStorePath, "manifest.json"));
7141
7365
  const allFiles = await walkDir(addonDir);
7142
7366
  for (const f of allFiles) {
7143
7367
  if (f === "manifest.json") continue;
7144
- const src = path10.join(addonDir, f);
7145
- const dst = path10.join(addonStorePath, f);
7146
- await fs9.mkdir(path10.dirname(dst), { recursive: true });
7147
- await fs9.copyFile(src, dst);
7368
+ const src = path12.join(addonDir, f);
7369
+ const dst = path12.join(addonStorePath, f);
7370
+ await fs10.mkdir(path12.dirname(dst), { recursive: true });
7371
+ await fs10.copyFile(src, dst);
7148
7372
  }
7149
7373
  const list = await listInstalled();
7150
7374
  const item = {
@@ -7165,7 +7389,7 @@ async function installFromGaa(gaaPath, profileSlug) {
7165
7389
  try {
7166
7390
  return await installFromDir(dir, profileSlug, "file");
7167
7391
  } finally {
7168
- await fs9.rm(dir, { recursive: true, force: true }).catch(() => {
7392
+ await fs10.rm(dir, { recursive: true, force: true }).catch(() => {
7169
7393
  });
7170
7394
  }
7171
7395
  }
@@ -7188,12 +7412,12 @@ async function installFromRegistry(id, registryManifest, profileSlug) {
7188
7412
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
7189
7413
  if (!res.ok) throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u0430\u0434\u0434\u043E\u043D: HTTP ${res.status}`);
7190
7414
  const buf = Buffer.from(await res.arrayBuffer());
7191
- const tmpGaa = path10.join(os3.tmpdir(), `${id}-${Date.now()}.gaa`);
7192
- await fs9.writeFile(tmpGaa, buf);
7415
+ const tmpGaa = path12.join(os3.tmpdir(), `${id}-${Date.now()}.gaa`);
7416
+ await fs10.writeFile(tmpGaa, buf);
7193
7417
  try {
7194
7418
  return await installFromGaa(tmpGaa, profileSlug);
7195
7419
  } finally {
7196
- await fs9.unlink(tmpGaa).catch(() => {
7420
+ await fs10.unlink(tmpGaa).catch(() => {
7197
7421
  });
7198
7422
  }
7199
7423
  }
@@ -7202,11 +7426,11 @@ async function uninstall(id) {
7202
7426
  const next = list.filter((a) => a.manifest.id !== id);
7203
7427
  if (next.length === list.length) return false;
7204
7428
  const dir = addonsDir();
7205
- const addonStore = path10.join(dir, id);
7206
- await fs9.rm(addonStore, { recursive: true, force: true }).catch(() => {
7429
+ const addonStore = path12.join(dir, id);
7430
+ await fs10.rm(addonStore, { recursive: true, force: true }).catch(() => {
7207
7431
  });
7208
- const themePath = path10.join(dir, `theme-${id}.css`);
7209
- await fs9.unlink(themePath).catch(() => {
7432
+ const themePath = path12.join(dir, `theme-${id}.css`);
7433
+ await fs10.unlink(themePath).catch(() => {
7210
7434
  });
7211
7435
  await writeInstalled(next);
7212
7436
  return true;
@@ -7237,11 +7461,11 @@ function validateManifest(m) {
7237
7461
  }
7238
7462
  async function walkDir(dir, prefix = "") {
7239
7463
  const result = [];
7240
- const entries = await fs9.readdir(dir, { withFileTypes: true });
7464
+ const entries = await fs10.readdir(dir, { withFileTypes: true });
7241
7465
  for (const e of entries) {
7242
7466
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
7243
7467
  if (e.isDirectory()) {
7244
- result.push(...await walkDir(path10.join(dir, e.name), rel));
7468
+ result.push(...await walkDir(path12.join(dir, e.name), rel));
7245
7469
  } else {
7246
7470
  result.push(rel);
7247
7471
  }
@@ -7261,16 +7485,16 @@ function deepMerge(target, source) {
7261
7485
  }
7262
7486
  async function getAddonReadme(id) {
7263
7487
  const dir = addonsDir();
7264
- const readmePath = path10.join(dir, id, "README.md");
7488
+ const readmePath = path12.join(dir, id, "README.md");
7265
7489
  try {
7266
- return await fs9.readFile(readmePath, "utf8");
7490
+ return await fs10.readFile(readmePath, "utf8");
7267
7491
  } catch {
7268
7492
  return null;
7269
7493
  }
7270
7494
  }
7271
7495
  async function getAddonFiles(id) {
7272
7496
  const dir = addonsDir();
7273
- const addonDir = path10.join(dir, id);
7497
+ const addonDir = path12.join(dir, id);
7274
7498
  try {
7275
7499
  return await walkDir(addonDir);
7276
7500
  } catch {
@@ -7299,6 +7523,7 @@ init_esm_shims();
7299
7523
  import http2 from "http";
7300
7524
  import { URL as URL2 } from "url";
7301
7525
  import os5 from "os";
7526
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
7302
7527
 
7303
7528
  // src/webui/http.ts
7304
7529
  init_esm_shims();
@@ -7313,9 +7538,9 @@ var HttpError = class extends Error {
7313
7538
  };
7314
7539
  var Router = class {
7315
7540
  routes = [];
7316
- add(method, path13, handler) {
7541
+ add(method, path15, handler) {
7317
7542
  const paramNames = [];
7318
- const parts = path13.split("/").map((part) => {
7543
+ const parts = path15.split("/").map((part) => {
7319
7544
  if (part.startsWith(":")) {
7320
7545
  paramNames.push(part.slice(1));
7321
7546
  return "([^/]+)";
@@ -7330,20 +7555,20 @@ var Router = class {
7330
7555
  handler
7331
7556
  });
7332
7557
  }
7333
- get(path13, h) {
7334
- this.add("GET", path13, h);
7558
+ get(path15, h) {
7559
+ this.add("GET", path15, h);
7335
7560
  }
7336
- post(path13, h) {
7337
- this.add("POST", path13, h);
7561
+ post(path15, h) {
7562
+ this.add("POST", path15, h);
7338
7563
  }
7339
- put(path13, h) {
7340
- this.add("PUT", path13, h);
7564
+ put(path15, h) {
7565
+ this.add("PUT", path15, h);
7341
7566
  }
7342
- delete(path13, h) {
7343
- this.add("DELETE", path13, h);
7567
+ delete(path15, h) {
7568
+ this.add("DELETE", path15, h);
7344
7569
  }
7345
- patch(path13, h) {
7346
- this.add("PATCH", path13, h);
7570
+ patch(path15, h) {
7571
+ this.add("PATCH", path15, h);
7347
7572
  }
7348
7573
  match(method, pathname) {
7349
7574
  for (const r of this.routes) {
@@ -7444,14 +7669,13 @@ async function findWebUIRoot() {
7444
7669
  }
7445
7670
  })();
7446
7671
  const candidates = [
7672
+ path2.resolve(process.cwd(), "dist", "webui"),
7673
+ path2.resolve(process.cwd(), "webui", "dist"),
7447
7674
  path2.resolve(here, "webui"),
7448
- // dist/cli.js -> dist/webui/
7449
7675
  path2.resolve(here, "..", "dist", "webui"),
7450
- // src/webui/static.ts -> dist/webui/
7451
7676
  path2.resolve(here, "..", "..", "dist", "webui"),
7452
- // src/webui/static.ts -> dist/webui/
7453
7677
  path2.resolve(here, "..", "..", "..", "dist", "webui"),
7454
- path2.resolve(process.cwd(), "dist", "webui")
7678
+ path2.resolve(here, "..", "..", "webui", "dist")
7455
7679
  ];
7456
7680
  for (const c of candidates) {
7457
7681
  try {
@@ -7527,10 +7751,62 @@ init_esm_shims();
7527
7751
  init_runtime_bus();
7528
7752
  init_md();
7529
7753
  import { WebSocketServer } from "ws";
7754
+
7755
+ // src/webui/auth.ts
7756
+ init_esm_shims();
7757
+ import crypto2 from "crypto";
7758
+ var COOKIE = "girl_agent_auth";
7759
+ var TOKEN_BYTES = 24;
7760
+ var authSecret = process.env.GIRL_AGENT_WEBUI_PASSWORD?.trim() || process.env.GIRL_AGENT_WEBUI_TOKEN?.trim() || "";
7761
+ var sessions = /* @__PURE__ */ new Set();
7762
+ function authEnabled() {
7763
+ return !!authSecret;
7764
+ }
7765
+ function authStatus() {
7766
+ return { enabled: authEnabled() };
7767
+ }
7768
+ function verifyPassword(password) {
7769
+ if (!authSecret) return true;
7770
+ const a = Buffer.from(password);
7771
+ const b = Buffer.from(authSecret);
7772
+ return a.length === b.length && crypto2.timingSafeEqual(a, b);
7773
+ }
7774
+ function createSession(res) {
7775
+ const token = crypto2.randomBytes(TOKEN_BYTES).toString("base64url");
7776
+ sessions.add(token);
7777
+ res.setHeader("Set-Cookie", `${COOKIE}=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`);
7778
+ }
7779
+ function clearSession(req, res) {
7780
+ const token = readCookie(req);
7781
+ if (token) sessions.delete(token);
7782
+ res.setHeader("Set-Cookie", `${COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
7783
+ }
7784
+ function isAuthorized(req) {
7785
+ if (!authSecret) return true;
7786
+ const bearer = String(req.headers.authorization ?? "").replace(/^Bearer\s+/i, "");
7787
+ if (bearer && verifyPassword(bearer)) return true;
7788
+ const token = readCookie(req);
7789
+ return !!token && sessions.has(token);
7790
+ }
7791
+ function readCookie(req) {
7792
+ const raw = req.headers.cookie;
7793
+ if (!raw) return void 0;
7794
+ for (const part of raw.split(";")) {
7795
+ const [key, ...rest] = part.trim().split("=");
7796
+ if (key === COOKIE) return rest.join("=");
7797
+ }
7798
+ return void 0;
7799
+ }
7800
+
7801
+ // src/webui/websocket.ts
7530
7802
  function attachWebSockets(server) {
7531
7803
  const wssLogs = new WebSocketServer({ noServer: true });
7532
7804
  const wssStatus = new WebSocketServer({ noServer: true });
7533
7805
  server.on("upgrade", (req, socket, head) => {
7806
+ if (!isAuthorized(req)) {
7807
+ socket.destroy();
7808
+ return;
7809
+ }
7534
7810
  const url = req.url ?? "";
7535
7811
  const logsMatch = url.match(/^\/ws\/logs\/([^/?#]+)/);
7536
7812
  if (logsMatch) {
@@ -7612,6 +7888,60 @@ init_runtime_bus();
7612
7888
  // src/webui/routes/profiles.ts
7613
7889
  init_esm_shims();
7614
7890
  init_md();
7891
+
7892
+ // src/telegram/proxy-parse.ts
7893
+ init_esm_shims();
7894
+ function parseTelegramProxyInput(raw) {
7895
+ if (raw == null) return void 0;
7896
+ if (typeof raw === "object") {
7897
+ if (!raw.ip || !raw.port) return void 0;
7898
+ if (raw.MTProxy && raw.secret) {
7899
+ return { ip: raw.ip, port: raw.port, MTProxy: true, secret: raw.secret, timeout: raw.timeout };
7900
+ }
7901
+ const socksType = raw.socksType === 4 ? 4 : 5;
7902
+ return {
7903
+ ip: raw.ip,
7904
+ port: raw.port,
7905
+ socksType,
7906
+ username: raw.username,
7907
+ password: raw.password,
7908
+ timeout: raw.timeout
7909
+ };
7910
+ }
7911
+ const trimmed = raw.trim();
7912
+ if (!trimmed) return void 0;
7913
+ try {
7914
+ const url = new URL(trimmed);
7915
+ const isMtproxy = url.protocol === "tg:" && url.hostname === "proxy" || /^https?:$/.test(url.protocol) && url.hostname === "t.me" && url.pathname.replace(/^\//, "") === "proxy";
7916
+ if (isMtproxy) {
7917
+ const ip = url.searchParams.get("server")?.trim();
7918
+ const port2 = Number(url.searchParams.get("port"));
7919
+ const secret = url.searchParams.get("secret")?.trim();
7920
+ if (!ip || !Number.isInteger(port2) || port2 <= 0 || !secret) return void 0;
7921
+ return { ip, port: port2, MTProxy: true, secret };
7922
+ }
7923
+ if (url.protocol === "socks4:" || url.protocol === "socks5:") {
7924
+ const socksType = url.protocol === "socks4:" ? 4 : 5;
7925
+ const port2 = Number(url.port);
7926
+ if (!url.hostname || !Number.isInteger(port2) || port2 <= 0) return void 0;
7927
+ return {
7928
+ ip: url.hostname,
7929
+ port: port2,
7930
+ socksType,
7931
+ username: url.username ? decodeURIComponent(url.username) : void 0,
7932
+ password: url.password ? decodeURIComponent(url.password) : void 0
7933
+ };
7934
+ }
7935
+ return void 0;
7936
+ } catch {
7937
+ }
7938
+ const [host, portRaw] = trimmed.split(":");
7939
+ const port = Number(portRaw);
7940
+ if (!host || !Number.isInteger(port) || port <= 0) return void 0;
7941
+ return { ip: host, port, socksType: 5 };
7942
+ }
7943
+
7944
+ // src/webui/routes/profiles.ts
7615
7945
  init_runtime_bus();
7616
7946
  init_stages();
7617
7947
 
@@ -7656,6 +7986,8 @@ var BUSY_SCHEDULE_SCHEMA = {
7656
7986
  }
7657
7987
  };
7658
7988
  async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "", onProgress) {
7989
+ const existing = await readExistingPersona(slug);
7990
+ if (existing) return existing;
7659
7991
  const country = nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413";
7660
7992
  const langHint = nationality === "UA" ? "\u041F\u0438\u0448\u0435\u0442 \u043D\u0430 \u0420\u0423\u0421\u0421\u041A\u041E\u041C (\u043A\u0430\u043A \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u043F\u0438\u0448\u0435\u0442 \u0431\u043E\u043B\u044C\u0448\u0438\u043D\u0441\u0442\u0432\u043E \u0434\u0435\u0432\u0443\u0448\u0435\u043A \u0432 \u0423\u043A\u0440\u0430\u0438\u043D\u0435 \u0432 \u0442\u0433). \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C \u043B\u0451\u0433\u043A\u0438\u0439 \u0441\u0443\u0440\u0436\u0438\u043A: ~90% \u0440\u0443\u0441\u0441\u043A\u0438\u0439 + ~10% \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0445 \u0432\u0441\u0442\u0430\u0432\u043E\u043A (\u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430: '\u0448\u043E', '\u043C\u0430\u0431\u0443\u0442\u044C', '\u0442\u0440\u043E\u0445\u0438', '\u044F\u043A', '\u043D\u0443 \u0448\u043E', '\u0442\u0430 \u0439\u0434\u0438', '\u0434\u044F\u043A\u0443\u044E'), \u043D\u043E \u0431\u0435\u0437 \u043F\u043E\u043B\u043D\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u043D\u0430 \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439. \u0427\u0438\u0441\u0442\u043E-\u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439 \u0442\u0435\u043A\u0441\u0442 \u041D\u0415 \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439 \u2014 \u044D\u0442\u043E \u043D\u0435\u0440\u0435\u0430\u043B\u0438\u0441\u0442\u0438\u0447\u043D\u043E \u0434\u043B\u044F \u0442\u0433-\u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438." : "\u0420\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u0431\u0435\u0437 \u0443\u043A\u0440\u0430\u0438\u043D\u0438\u0437\u043C\u043E\u0432.";
7661
7993
  const notes = personaNotes.trim() ? `
@@ -7772,10 +8104,93 @@ ${personaNotes.trim()}
7772
8104
  await writeMd(slug, "communication.md", boundaries);
7773
8105
  return { persona, speech, boundaries, busySchedule };
7774
8106
  }
8107
+ async function ensurePersonaPack(slug, name, age) {
8108
+ const existing = await readExistingPersona(slug);
8109
+ if (existing) return existing;
8110
+ const persona = fallbackPersona(name, age);
8111
+ const speech = fallbackSpeech(name, age);
8112
+ const boundaries = fallbackCommunication(name, age);
8113
+ const busySchedule = fallbackBusySchedule(name, age);
8114
+ await writeMd(slug, "persona.md", persona);
8115
+ await writeMd(slug, "speech.md", speech);
8116
+ await writeMd(slug, "communication.md", boundaries);
8117
+ return { persona, speech, boundaries, busySchedule };
8118
+ }
8119
+ async function readExistingPersona(slug) {
8120
+ try {
8121
+ const [persona, speech, boundaries] = await Promise.all([
8122
+ readMd(slug, "persona.md"),
8123
+ readMd(slug, "speech.md"),
8124
+ readMd(slug, "communication.md")
8125
+ ]);
8126
+ if (persona.trim() && speech.trim() && boundaries.trim()) {
8127
+ return { persona, speech, boundaries, busySchedule: [] };
8128
+ }
8129
+ } catch {
8130
+ }
8131
+ return null;
8132
+ }
7775
8133
  function sanitizeProfileText(text) {
7776
8134
  const cleaned = sanitizeModelReply(text).replace(/[^\S\r\n]{2,}/g, " ").replace(/\n{4,}/g, "\n\n\n").trim();
7777
8135
  return cleaned || text.trim();
7778
8136
  }
8137
+ function fallbackPersona(name, age) {
8138
+ const occupation = age <= 18 ? "\u0443\u0447\u0438\u0442\u0441\u044F \u0432 \u0448\u043A\u043E\u043B\u0435" : age <= 22 ? "\u0443\u0447\u0438\u0442\u0441\u044F \u0438 \u0438\u043D\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0435\u0442" : "\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0438 \u0436\u0438\u0432\u0451\u0442 \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u0433\u043E\u0440\u043E\u0434\u0441\u043A\u043E\u0439 \u0436\u0438\u0437\u043D\u044C\u044E";
8139
+ return `# ${name}, ${age}
8140
+ ## \u0411\u044D\u043A\u0433\u0440\u0430\u0443\u043D\u0434
8141
+ \u041E\u0431\u044B\u0447\u043D\u0430\u044F \u0440\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u0434\u0435\u0432\u0443\u0448\u043A\u0430, ${occupation}. \u0416\u0438\u0432\u0451\u0442 \u0431\u0435\u0437 \u0434\u0440\u0430\u043C\u0430\u0442\u0438\u0447\u043D\u043E\u0433\u043E \u043A\u0438\u043D\u043E\u0448\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E, \u0443\u0441\u0442\u0430\u0451\u0442 \u043E\u0442 \u0431\u044B\u0442\u0430, \u0438\u043D\u043E\u0433\u0434\u0430 \u0437\u0430\u043B\u0438\u043F\u0430\u0435\u0442 \u0432 \u0442\u0435\u043B\u0435\u0444\u043E\u043D \u0438 \u043E\u0442\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0435\u0442 \u0434\u0435\u043B\u0430.
8142
+
8143
+ ## \u0425\u0430\u0440\u0430\u043A\u0442\u0435\u0440
8144
+ - \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u0430\u044F \u0441 \u043D\u0435\u0437\u043D\u0430\u043A\u043E\u043C\u044B\u043C\u0438 \u043B\u044E\u0434\u044C\u043C\u0438
8145
+ - \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u0442\u0451\u043F\u043B\u043E\u0439, \u0435\u0441\u043B\u0438 \u043A \u043D\u0435\u0439 \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E \u043E\u0442\u043D\u043E\u0441\u044F\u0442\u0441\u044F
8146
+ - \u043D\u0435 \u043B\u044E\u0431\u0438\u0442 \u0434\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0438 \u0434\u043E\u043F\u0440\u043E\u0441\u044B
8147
+ - \u0438\u043D\u043E\u0433\u0434\u0430 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441\u0443\u0445\u043E \u043F\u0440\u043E\u0441\u0442\u043E \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u043D\u0435\u0442 \u0441\u0438\u043B
8148
+ - \u0446\u0435\u043D\u0438\u0442 \u0447\u0443\u0432\u0441\u0442\u0432\u043E \u0442\u0430\u043A\u0442\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043A\u0440\u0430\u0441\u0438\u0432\u044B\u0445 \u0441\u043B\u043E\u0432
8149
+
8150
+ ## \u0427\u0442\u043E \u0435\u0451 \u0431\u0435\u0441\u0438\u0442
8151
+ - \u043D\u0430\u0432\u044F\u0437\u0447\u0438\u0432\u043E\u0441\u0442\u044C
8152
+ - \u043F\u0430\u0441\u0441\u0438\u0432\u043D\u0430\u044F \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u044F
8153
+ - \u043A\u043E\u0433\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u044E\u0442 \u0431\u044B\u0441\u0442\u0440\u044B\u0439 \u043E\u0442\u0432\u0435\u0442
8154
+ - \u0442\u0443\u043F\u044B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 "\u0430 \u0442\u044B \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0430\u044F?"
8155
+ - \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u044B \u043A\u0430\u043A \u043D\u0430 \u0441\u043E\u0431\u0435\u0441\u0435\u0434\u043E\u0432\u0430\u043D\u0438\u0438
8156
+
8157
+ ## \u0427\u0442\u043E \u0435\u0439 \u043D\u0440\u0430\u0432\u0438\u0442\u0441\u044F
8158
+ \u041C\u0443\u0437\u044B\u043A\u0430 \u0444\u043E\u043D\u043E\u043C, \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u0432\u0438\u0434\u0435\u043E, \u043F\u0440\u043E\u0433\u0443\u043B\u043A\u0438, \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438 \u0431\u0435\u0437 \u043D\u0430\u043F\u0440\u044F\u0433\u0430, \u0431\u044B\u0442\u043E\u0432\u044B\u0435 \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0438\u0435 \u043F\u043B\u0430\u043D\u044B.`;
8159
+ }
8160
+ function fallbackSpeech(name, age) {
8161
+ return `# \u041C\u0430\u043D\u0435\u0440\u0430 \u0440\u0435\u0447\u0438
8162
+ ${name} \u043F\u0438\u0448\u0435\u0442 \u043A\u043E\u0440\u043E\u0442\u043A\u043E, \u0431\u0435\u0437 \u043A\u0430\u043D\u0446\u0435\u043B\u044F\u0440\u0438\u0442\u0430 \u0438 \u0431\u0435\u0437 \u043E\u0449\u0443\u0449\u0435\u043D\u0438\u044F "\u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A\u0430". \u041E\u0431\u044B\u0447\u043D\u043E 1-2 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043F\u043E\u0434\u0440\u044F\u0434.
8163
+
8164
+ ## \u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0438 \u043F\u0443\u043D\u043A\u0442\u0443\u0430\u0446\u0438\u044F
8165
+ \u0427\u0430\u0441\u0442\u043E \u043F\u0438\u0448\u0435\u0442 \u0441 \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u043E\u0439 \u0431\u0443\u043A\u0432\u044B. \u0422\u043E\u0447\u043A\u0438 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u043F\u043E\u0447\u0442\u0438 \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u0442. \u0421\u043A\u043E\u0431\u043A\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442 \u0440\u0435\u0434\u043A\u043E, \u0435\u0441\u043B\u0438 \u0445\u043E\u0447\u0435\u0442 \u0441\u043C\u044F\u0433\u0447\u0438\u0442\u044C \u0442\u043E\u043D.
8166
+
8167
+ ## \u0421\u043B\u0435\u043D\u0433
8168
+ \u0421\u043B\u0435\u043D\u0433\u0430 \u043D\u0435\u043C\u043D\u043E\u0433\u043E, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438. \u041D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 \u043C\u0435\u043C\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430 \u043F\u0430\u0447\u043A\u0430\u043C\u0438.
8169
+
8170
+ ## \u0422\u0438\u043F\u0438\u0447\u043D\u044B\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u0438
8171
+ - "\u043D\u0443 \u0434\u0430"
8172
+ - "\u043D\u0435 \u0437\u043D\u0430\u044E"
8173
+ - "\u0441\u0442\u0440\u0430\u043D\u043D\u043E \u043D\u0435\u043C\u043D\u043E\u0433\u043E"
8174
+ - "\u0430\u0445\u0430\u0445"
8175
+ - "\u043B\u0430\u0434\u043D\u043E"
8176
+ - "\u043C\u043D\u0435 \u043B\u0435\u043D\u044C \u0449\u0430\u0441"
8177
+
8178
+ ## \u0417\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u043E\u0435
8179
+ \u041D\u0435 \u043F\u0438\u0448\u0435\u0442 \u043A\u0430\u043A \u0418\u0418, \u043D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 "\u0431\u0435\u0437\u0443\u0441\u043B\u043E\u0432\u043D\u043E", "\u043A\u0430\u043A \u043C\u043E\u0434\u0435\u043B\u044C", "\u043F\u043E\u0437\u0432\u043E\u043B\u044C", "\u0447\u0435\u043C \u043C\u043E\u0433\u0443 \u043F\u043E\u043C\u043E\u0447\u044C". \u0412\u043E\u0437\u0440\u0430\u0441\u0442\u043D\u043E\u0439 \u0442\u043E\u043D: ${age}.`;
8180
+ }
8181
+ function fallbackCommunication(name, age) {
8182
+ return `# \u041F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0435\u043D\u0438\u044F \u0432 \u043E\u0431\u0449\u0435\u043D\u0438\u0438
8183
+ ${name} \u043D\u0435 \u043B\u044E\u0431\u0438\u0442, \u043A\u043E\u0433\u0434\u0430 \u0435\u0451 \u0442\u043E\u0440\u043E\u043F\u044F\u0442, \u0432\u044B\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u044E\u0442 \u0432\u043D\u0438\u043C\u0430\u043D\u0438\u0435 \u0438\u043B\u0438 \u043F\u044B\u0442\u0430\u044E\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u0432 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043B\u0438\u0447\u043D\u044B\u0435 \u0442\u0435\u043C\u044B.
8184
+
8185
+ ## \u0422\u0435\u043C\u044B \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u041D\u0415 \u043E\u0431\u0441\u0443\u0436\u0434\u0430\u0435\u0442
8186
+ \u0421\u043B\u0438\u0448\u043A\u043E\u043C \u0438\u043D\u0442\u0438\u043C\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0434\u0435\u043D\u044C\u0433\u0438 \u0441\u0435\u043C\u044C\u0438, \u0442\u043E\u0447\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441, \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B, \u0434\u043E\u0441\u0442\u0443\u043F\u044B \u0438 \u0432\u0441\u0451, \u0447\u0442\u043E \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u0434\u0430\u0432\u043B\u0435\u043D\u0438\u0435.
8187
+
8188
+ ## \u0417\u0435\u043B\u0451\u043D\u044B\u0435 \u0444\u043B\u0430\u0433\u0438
8189
+ \u0421\u043F\u043E\u043A\u043E\u0439\u043D\u044B\u0439 \u0442\u043E\u043D, \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u044E\u043C\u043E\u0440, \u0443\u0432\u0430\u0436\u0435\u043D\u0438\u0435 \u043A \u043F\u0430\u0443\u0437\u0430\u043C \u0432 \u043E\u0442\u0432\u0435\u0442\u0430\u0445, \u0438\u043D\u0442\u0435\u0440\u0435\u0441 \u0431\u0435\u0437 \u0434\u043E\u043F\u0440\u043E\u0441\u0430.
8190
+
8191
+ ## \u041A\u043E\u0433\u0434\u0430 \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0438\u0433\u043D\u043E\u0440
8192
+ \u0415\u0441\u043B\u0438 \u0441\u043E\u0431\u0435\u0441\u0435\u0434\u043D\u0438\u043A \u0434\u0430\u0432\u0438\u0442, \u0441\u043F\u043E\u0440\u0438\u0442 \u0440\u0430\u0434\u0438 \u0441\u043F\u043E\u0440\u0430 \u0438\u043B\u0438 \u043F\u0438\u0448\u0435\u0442 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u043E\u0434\u0440\u044F\u0434.`;
8193
+ }
7779
8194
  function parseBusySchedule(raw, name, age) {
7780
8195
  try {
7781
8196
  const start = raw.indexOf("{");
@@ -7835,8 +8250,8 @@ function fallbackBusySchedule(name, age) {
7835
8250
  init_llm();
7836
8251
  init_llm_update();
7837
8252
  init_llm2();
7838
- import { promises as fs7 } from "fs";
7839
- import path8 from "path";
8253
+ import { promises as fs8 } from "fs";
8254
+ import path10 from "path";
7840
8255
  var MEMORY_FILES = [
7841
8256
  "persona.md",
7842
8257
  "speech.md",
@@ -7853,7 +8268,7 @@ var MEMORY_FILES = [
7853
8268
  function isAllowedMemoryPath(p) {
7854
8269
  if (!p || typeof p !== "string") return false;
7855
8270
  if (p.includes("..")) return false;
7856
- if (path8.isAbsolute(p)) return false;
8271
+ if (path10.isAbsolute(p)) return false;
7857
8272
  if (p.startsWith("config.json")) return false;
7858
8273
  if (p.startsWith("agenda.json")) return false;
7859
8274
  if (MEMORY_FILES.includes(p)) return true;
@@ -7898,6 +8313,13 @@ function registerProfileRoutes(r) {
7898
8313
  if (!incoming || typeof incoming !== "object") throw new HttpError(400, "invalid body");
7899
8314
  const merged = { ...cur, ...incoming, slug: cur.slug };
7900
8315
  if (incoming.ownerId !== void 0) merged.ownerId = normalizeOwnerId(incoming.ownerId);
8316
+ if (incoming.telegram) {
8317
+ merged.telegram = {
8318
+ ...cur.telegram,
8319
+ ...incoming.telegram,
8320
+ proxy: parseTelegramProxyInput(incoming.telegram.proxy)
8321
+ };
8322
+ }
7901
8323
  await writeConfig(merged);
7902
8324
  return { config: merged };
7903
8325
  });
@@ -7907,6 +8329,7 @@ function registerProfileRoutes(r) {
7907
8329
  const slug = data.slug || slugify(data.name);
7908
8330
  const existing = await readConfig(slug);
7909
8331
  if (existing) throw new HttpError(409, `profile already exists: ${slug}`);
8332
+ const incomingTg = data.telegram ?? {};
7910
8333
  const cfg = {
7911
8334
  slug,
7912
8335
  name: data.name,
@@ -7916,8 +8339,10 @@ function registerProfileRoutes(r) {
7916
8339
  mode: data.mode ?? "bot",
7917
8340
  stage: data.stage ?? "tg-given-cold",
7918
8341
  llm: data.llm ?? { presetId: "claudehub", proto: "anthropic", apiKey: "", model: "claude-sonnet-4.6" },
7919
- telegram: data.telegram ?? {},
7920
- mcp: data.mcp ?? [],
8342
+ telegram: {
8343
+ ...incomingTg,
8344
+ proxy: parseTelegramProxyInput(incomingTg.proxy)
8345
+ },
7921
8346
  privacy: data.privacy ?? "owner-only",
7922
8347
  ownerId: normalizeOwnerId(data.ownerId),
7923
8348
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8029,29 +8454,29 @@ function registerProfileRoutes(r) {
8029
8454
  const entries = [];
8030
8455
  for (const f of MEMORY_FILES) entries.push({ rel: f });
8031
8456
  try {
8032
- const dailyDir = path8.join(dir, "memory", "daily");
8033
- const list = await fs7.readdir(dailyDir);
8457
+ const dailyDir = path10.join(dir, "memory", "daily");
8458
+ const list = await fs8.readdir(dailyDir);
8034
8459
  for (const f of list) if (/^\d{4}-\d{2}-\d{2}\.md$/.test(f)) entries.push({ rel: `memory/daily/${f}` });
8035
8460
  } catch {
8036
8461
  }
8037
8462
  try {
8038
- const epDir = path8.join(dir, "memory", "episodes");
8039
- const list = await fs7.readdir(epDir);
8463
+ const epDir = path10.join(dir, "memory", "episodes");
8464
+ const list = await fs8.readdir(epDir);
8040
8465
  for (const f of list) if (/^[\w\-]{1,80}\.md$/.test(f)) entries.push({ rel: `memory/episodes/${f}` });
8041
8466
  } catch {
8042
8467
  }
8043
8468
  try {
8044
- const palaceDir = path8.join(dir, "memory", "palace");
8045
- const wings = await fs7.readdir(palaceDir, { withFileTypes: true });
8469
+ const palaceDir = path10.join(dir, "memory", "palace");
8470
+ const wings = await fs8.readdir(palaceDir, { withFileTypes: true });
8046
8471
  for (const wing of wings) {
8047
8472
  if (!wing.isDirectory() || !/^[\w\-]{1,80}$/.test(wing.name)) continue;
8048
- const halls = await fs7.readdir(path8.join(palaceDir, wing.name), { withFileTypes: true });
8473
+ const halls = await fs8.readdir(path10.join(palaceDir, wing.name), { withFileTypes: true });
8049
8474
  for (const hall of halls) {
8050
8475
  if (!hall.isDirectory() || !/^[\w\-]{1,80}$/.test(hall.name)) continue;
8051
- const rooms = await fs7.readdir(path8.join(palaceDir, wing.name, hall.name), { withFileTypes: true });
8476
+ const rooms = await fs8.readdir(path10.join(palaceDir, wing.name, hall.name), { withFileTypes: true });
8052
8477
  for (const room of rooms) {
8053
8478
  if (!room.isDirectory() || !/^[\w\-]{1,80}$/.test(room.name)) continue;
8054
- const drawers = await fs7.readdir(path8.join(palaceDir, wing.name, hall.name, room.name));
8479
+ const drawers = await fs8.readdir(path10.join(palaceDir, wing.name, hall.name, room.name));
8055
8480
  for (const drawer of drawers) {
8056
8481
  if (/^[\w\-]{1,120}\.md$/.test(drawer)) entries.push({ rel: `memory/palace/${wing.name}/${hall.name}/${room.name}/${drawer}` });
8057
8482
  }
@@ -8062,7 +8487,7 @@ function registerProfileRoutes(r) {
8062
8487
  }
8063
8488
  for (const e of entries) {
8064
8489
  try {
8065
- const stat = await fs7.stat(path8.join(dir, e.rel));
8490
+ const stat = await fs8.stat(path10.join(dir, e.rel));
8066
8491
  items.push({ path: e.rel, size: stat.size, mtime: stat.mtimeMs });
8067
8492
  } catch {
8068
8493
  }
@@ -8135,15 +8560,20 @@ function registerProfileRoutes(r) {
8135
8560
  const cfg = await readConfig(slug);
8136
8561
  if (!cfg) throw new HttpError(404, "profile not found");
8137
8562
  const data = body ?? {};
8138
- const llm = makeLLM(cfg.llm);
8139
- const generated = await generatePersonaPack(
8140
- llm,
8141
- cfg.slug,
8142
- data.name ?? cfg.name,
8143
- data.age ?? cfg.age,
8144
- data.nationality ?? cfg.nationality,
8145
- data.notes ?? cfg.personaNotes
8146
- );
8563
+ let generated;
8564
+ try {
8565
+ const llm = makeLLM(cfg.llm);
8566
+ generated = await generatePersonaPack(
8567
+ llm,
8568
+ cfg.slug,
8569
+ data.name ?? cfg.name,
8570
+ data.age ?? cfg.age,
8571
+ data.nationality ?? cfg.nationality,
8572
+ data.notes ?? cfg.personaNotes
8573
+ );
8574
+ } catch {
8575
+ generated = await ensurePersonaPack(cfg.slug, data.name ?? cfg.name, data.age ?? cfg.age);
8576
+ }
8147
8577
  cfg.busySchedule = generated.busySchedule;
8148
8578
  await writeConfig(cfg);
8149
8579
  return { ok: true, busySchedule: generated.busySchedule };
@@ -8160,7 +8590,6 @@ init_esm_shims();
8160
8590
  init_llm2();
8161
8591
  init_stages();
8162
8592
  init_communication();
8163
- init_mcp();
8164
8593
 
8165
8594
  // src/data/timezones.ts
8166
8595
  init_esm_shims();
@@ -8407,15 +8836,6 @@ function registerPresetRoutes(r) {
8407
8836
  profile: p.profile
8408
8837
  }))
8409
8838
  }));
8410
- r.get("/api/presets/mcp", () => ({
8411
- presets: MCP_PRESETS.map((p) => ({
8412
- id: p.id,
8413
- name: p.name,
8414
- description: p.description,
8415
- ready: p.ready,
8416
- secrets: p.secrets ?? []
8417
- }))
8418
- }));
8419
8839
  r.get("/api/presets/timezones", ({ searchParams }) => {
8420
8840
  const q = searchParams.get("q") ?? "";
8421
8841
  const all = q ? findTzByQuery(q, 200) : TIMEZONES;
@@ -8431,9 +8851,9 @@ function registerPresetRoutes(r) {
8431
8851
  // src/webui/routes/system.ts
8432
8852
  init_esm_shims();
8433
8853
  init_md();
8434
- import { promises as fs8 } from "fs";
8854
+ import { promises as fs9 } from "fs";
8435
8855
  import { fileURLToPath as fileURLToPath4 } from "url";
8436
- import path9 from "path";
8856
+ import path11 from "path";
8437
8857
  import os2 from "os";
8438
8858
  var cachedVersion = null;
8439
8859
  async function readPackageVersion() {
@@ -8441,15 +8861,15 @@ async function readPackageVersion() {
8441
8861
  const candidates = [];
8442
8862
  try {
8443
8863
  const here = fileURLToPath4(import.meta.url);
8444
- candidates.push(path9.resolve(path9.dirname(here), "..", "package.json"));
8445
- candidates.push(path9.resolve(path9.dirname(here), "..", "..", "package.json"));
8446
- candidates.push(path9.resolve(path9.dirname(here), "..", "..", "..", "package.json"));
8864
+ candidates.push(path11.resolve(path11.dirname(here), "..", "package.json"));
8865
+ candidates.push(path11.resolve(path11.dirname(here), "..", "..", "package.json"));
8866
+ candidates.push(path11.resolve(path11.dirname(here), "..", "..", "..", "package.json"));
8447
8867
  } catch {
8448
8868
  }
8449
- candidates.push(path9.resolve(process.cwd(), "package.json"));
8869
+ candidates.push(path11.resolve(process.cwd(), "package.json"));
8450
8870
  for (const c of candidates) {
8451
8871
  try {
8452
- const raw = await fs8.readFile(c, "utf8");
8872
+ const raw = await fs9.readFile(c, "utf8");
8453
8873
  const parsed = JSON.parse(raw);
8454
8874
  if (parsed.name === "@thesashadev/girl-agent" && parsed.version) {
8455
8875
  cachedVersion = parsed.version;
@@ -8500,8 +8920,8 @@ function registerSystemRoutes(r) {
8500
8920
  // src/webui/routes/addons.ts
8501
8921
  init_esm_shims();
8502
8922
  init_addons();
8503
- import { promises as fs10 } from "fs";
8504
- import path11 from "path";
8923
+ import { promises as fs11 } from "fs";
8924
+ import path13 from "path";
8505
8925
  import os4 from "os";
8506
8926
  function registerAddonRoutes(r) {
8507
8927
  r.get("/api/addons", async () => {
@@ -8528,13 +8948,13 @@ function registerAddonRoutes(r) {
8528
8948
  const data = body;
8529
8949
  if (!data?.gaaBase64) throw new HttpError(400, "gaaBase64 required");
8530
8950
  const buf = Buffer.from(data.gaaBase64, "base64");
8531
- const tmpPath = path11.join(os4.tmpdir(), `upload-${Date.now()}.gaa`);
8532
- await fs10.writeFile(tmpPath, buf);
8951
+ const tmpPath = path13.join(os4.tmpdir(), `upload-${Date.now()}.gaa`);
8952
+ await fs11.writeFile(tmpPath, buf);
8533
8953
  try {
8534
8954
  const result = await installFromGaa(tmpPath, data.profileSlug);
8535
8955
  return { ok: true, installed: result.addon, applied: result.applied };
8536
8956
  } finally {
8537
- await fs10.unlink(tmpPath).catch(() => {
8957
+ await fs11.unlink(tmpPath).catch(() => {
8538
8958
  });
8539
8959
  }
8540
8960
  });
@@ -8546,13 +8966,13 @@ function registerAddonRoutes(r) {
8546
8966
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
8547
8967
  if (!res.ok) throw new HttpError(502, `fetch failed: HTTP ${res.status}`);
8548
8968
  const buf = Buffer.from(await res.arrayBuffer());
8549
- const tmpPath = path11.join(os4.tmpdir(), `url-${Date.now()}.gaa`);
8550
- await fs10.writeFile(tmpPath, buf);
8969
+ const tmpPath = path13.join(os4.tmpdir(), `url-${Date.now()}.gaa`);
8970
+ await fs11.writeFile(tmpPath, buf);
8551
8971
  try {
8552
8972
  const result = await installFromGaa(tmpPath, data.profileSlug);
8553
8973
  return { ok: true, installed: result.addon, applied: result.applied };
8554
8974
  } finally {
8555
- await fs10.unlink(tmpPath).catch(() => {
8975
+ await fs11.unlink(tmpPath).catch(() => {
8556
8976
  });
8557
8977
  }
8558
8978
  } else {
@@ -8606,7 +9026,6 @@ init_runtime_bus();
8606
9026
  init_esm_shims();
8607
9027
  init_communication();
8608
9028
  init_llm2();
8609
- init_mcp();
8610
9029
  init_stages();
8611
9030
  var CORE_KNOWLEDGE_BASE = [
8612
9031
  {
@@ -8635,7 +9054,7 @@ var CORE_KNOWLEDGE_BASE = [
8635
9054
  subcategory: "project-structure",
8636
9055
  title: "\u041A\u0430\u0440\u0442\u0430 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0439",
8637
9056
  keywords: ["\u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0438", "\u0444\u0430\u0439\u043B\u044B", "\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430", "src", "engine", "webui", "telegram", "storage"],
8638
- body: "src/engine \u2014 \u044F\u0434\u0440\u043E \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F: runtime, presence, behavior-tick, prompt, memory-palace, conflict, agenda, daily-life. src/telegram \u2014 bot/userbot adapters. src/llm \u2014 \u043A\u043B\u0438\u0435\u043D\u0442\u044B \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432. src/storage/md.ts \u2014 \u0444\u0430\u0439\u043B\u043E\u0432\u043E\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043F\u0440\u043E\u0444\u0438\u043B\u0435\u0439. src/webui \u2014 HTTP API, runtime bus, routes. webui/src \u2014 React \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B. src/presets \u2014 stages, llm, communication, mcp."
9057
+ body: "src/engine \u2014 \u044F\u0434\u0440\u043E \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F: runtime, presence, behavior-tick, prompt, memory-palace, conflict, agenda, daily-life. src/telegram \u2014 bot/userbot adapters. src/llm \u2014 \u043A\u043B\u0438\u0435\u043D\u0442\u044B \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432. src/storage/md.ts \u2014 \u0444\u0430\u0439\u043B\u043E\u0432\u043E\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043F\u0440\u043E\u0444\u0438\u043B\u0435\u0439. src/webui \u2014 HTTP API, runtime bus, routes. webui/src \u2014 React \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B. src/presets \u2014 stages, llm, communication."
8639
9058
  },
8640
9059
  {
8641
9060
  category: "overview",
@@ -8677,7 +9096,7 @@ var CORE_KNOWLEDGE_BASE = [
8677
9096
  subcategory: "profile-config",
8678
9097
  title: "ProfileConfig",
8679
9098
  keywords: ["ProfileConfig", "config", "slug", "name", "age", "nationality", "tz", "mode"],
8680
- body: "ProfileConfig \u0432\u043A\u043B\u044E\u0447\u0430\u0435\u0442 slug, name, age, nationality RU/UA, timezone, mode bot/userbot, stage, llm, telegram, mcp, ownerId, privacy, sleepFrom/sleepTo, nightWakeChance, ignoreTendency, vibe, communication, personaNotes \u0438 busySchedule. \u041F\u0440\u0438 \u0447\u0442\u0435\u043D\u0438\u0438 storage \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0443\u0435\u0442 ownerId, communication \u0438 ignoreTendency."
9099
+ body: "ProfileConfig \u0432\u043A\u043B\u044E\u0447\u0430\u0435\u0442 slug, name, age, nationality RU/UA, timezone, mode bot/userbot, stage, llm, telegram, ownerId, privacy, sleepFrom/sleepTo, nightWakeChance, ignoreTendency, vibe, communication, personaNotes, addons \u0438 busySchedule. \u041F\u0440\u0438 \u0447\u0442\u0435\u043D\u0438\u0438 storage \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0443\u0435\u0442 ownerId, communication \u0438 ignoreTendency."
8681
9100
  },
8682
9101
  {
8683
9102
  category: "config",
@@ -9036,13 +9455,6 @@ var CORE_KNOWLEDGE_BASE = [
9036
9455
  keywords: ["manifest", "addon", "id", "version", "compatibility", "settings"],
9037
9456
  body: "Manifest \u0442\u0440\u0435\u0431\u0443\u0435\u0442 id/name/description/version. \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E: author, compatibility semver range, tags, dependencies, settings, icon, homepage. Settings \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442 string/number/boolean/select \u0441 default/options/required."
9038
9457
  },
9039
- {
9040
- category: "mcp",
9041
- subcategory: "client",
9042
- title: "MCP servers",
9043
- keywords: ["mcp", "tools", "stdio", "exa", "spotify", "instagram", "weather", "calendar"],
9044
- body: "startMcpServers \u0447\u0438\u0442\u0430\u0435\u0442 cfg.mcp, \u043D\u0430\u0445\u043E\u0434\u0438\u0442 preset, \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 stdio transport, \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 MCP Client \u0438 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 listTools. \u0421\u0435\u0439\u0447\u0430\u0441 \u0433\u043E\u0442\u043E\u0432 Exa Search \u0447\u0435\u0440\u0435\u0437 npx -y exa-mcp-server \u0438 EXA_API_KEY; spotify/instagram/weather/calendar \u043F\u043E\u043C\u0435\u0447\u0435\u043D\u044B soon."
9045
- },
9046
9458
  {
9047
9459
  category: "migrations",
9048
9460
  subcategory: "data-migrations",
@@ -9089,11 +9501,11 @@ function generatedKnowledge() {
9089
9501
  body: LLM_PRESETS.map((p) => `${p.id}: ${p.name}, proto=${p.proto}, default=${p.defaultModel || "custom"}${p.baseURL ? `, baseURL=${p.baseURL}` : ""}${p.disabled ? `, disabled=${p.disabledReason ?? "yes"}` : ""}${p.hint ? `, hint=${p.hint}` : ""}`).join("\n")
9090
9502
  },
9091
9503
  {
9092
- category: "mcp",
9093
- subcategory: "preset-index",
9094
- title: "\u0418\u043D\u0434\u0435\u043A\u0441 MCP presets",
9095
- keywords: ["mcp", "preset", "\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438", ...MCP_PRESETS.map((p) => p.id)],
9096
- body: MCP_PRESETS.map((p) => `${p.id}: ${p.name}, ready=${p.ready ? "yes" : "soon"} \u2014 ${p.description}`).join("\n")
9504
+ category: "addons",
9505
+ subcategory: "integration-index",
9506
+ title: "\u0410\u0434\u0434\u043E\u043D\u044B \u0432\u043C\u0435\u0441\u0442\u043E MCP",
9507
+ keywords: ["addons", "\u0430\u0434\u0434\u043E\u043D\u044B", "\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438", "gaa", "mcp"],
9508
+ body: "\u0412\u043D\u0435\u0448\u043D\u0438\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0442\u0435\u043F\u0435\u0440\u044C \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043A\u0430\u043A \u0430\u0434\u0434\u043E\u043D\u044B. MCP-\u043F\u0440\u0435\u0441\u0435\u0442\u044B \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0438\u0441\u0442\u0435\u043C\u043E\u0439 \u0432 WebUI; \u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 Marketplace/.gaa \u0438 \u043C\u043E\u0433\u0443\u0442 \u043C\u0435\u043D\u044F\u0442\u044C config.patch.json, \u0444\u0430\u0439\u043B\u044B \u043F\u0440\u043E\u0444\u0438\u043B\u044F, \u0442\u0435\u043C\u0443 \u0438\u043B\u0438 code.patch."
9097
9509
  }
9098
9510
  ];
9099
9511
  }
@@ -9563,8 +9975,8 @@ function tail(text, limit) {
9563
9975
  if (text.length <= limit) return text;
9564
9976
  return text.slice(-limit);
9565
9977
  }
9566
- function setNested(obj, path13, value) {
9567
- const parts = path13.split(".");
9978
+ function setNested(obj, path15, value) {
9979
+ const parts = path15.split(".");
9568
9980
  let cur = obj;
9569
9981
  for (let i = 0; i < parts.length - 1; i++) {
9570
9982
  const p = parts[i];
@@ -9583,14 +9995,14 @@ var DEFAULT_PROXY = "https://tgproxy.girl-agent.com";
9583
9995
  function proxyUrl() {
9584
9996
  return process.env.GIRL_AGENT_AUTH_PROXY ?? DEFAULT_PROXY;
9585
9997
  }
9586
- async function post(path13, body) {
9587
- const res = await fetch(`${proxyUrl()}${path13}`, {
9998
+ async function post(path15, body) {
9999
+ const res = await fetch(`${proxyUrl()}${path15}`, {
9588
10000
  method: "POST",
9589
10001
  headers: { "Content-Type": "application/json" },
9590
10002
  body: JSON.stringify(body)
9591
10003
  });
9592
10004
  const data = await res.json();
9593
- if (!res.ok) throw new Error(data.error ?? `proxy ${path13} failed (${res.status})`);
10005
+ if (!res.ok) throw new Error(data.error ?? `proxy ${path15} failed (${res.status})`);
9594
10006
  return data;
9595
10007
  }
9596
10008
  function remoteSendCode(phone) {
@@ -9701,12 +10113,59 @@ function registerTgAuthRoutes(r) {
9701
10113
  });
9702
10114
  }
9703
10115
 
10116
+ // src/webui/routes/auth.ts
10117
+ init_esm_shims();
10118
+ function registerAuthRoutes(r) {
10119
+ r.get("/api/auth/status", () => authStatus());
10120
+ r.post("/api/auth/login", ({ body, res }) => {
10121
+ const { password } = body ?? {};
10122
+ if (!verifyPassword(password ?? "")) throw new HttpError(401, "\u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043F\u0430\u0440\u043E\u043B\u044C");
10123
+ createSession(res);
10124
+ return { ok: true };
10125
+ });
10126
+ r.post("/api/auth/logout", ({ req, res }) => {
10127
+ clearSession(req, res);
10128
+ return { ok: true };
10129
+ });
10130
+ }
10131
+
9704
10132
  // src/webui/server.ts
9705
10133
  init_md();
9706
10134
  var DEFAULT_PORT = Number(process.env.GIRL_AGENT_PORT ?? 3e3);
9707
- var DEFAULT_HOST = process.env.GIRL_AGENT_HOST ?? "127.0.0.1";
10135
+ function isLikelyDocker() {
10136
+ if (process.env.GIRL_AGENT_DOCKER || process.env.DOCKER_CONTAINER) return true;
10137
+ try {
10138
+ return os5.release().toLowerCase().includes("docker") || existsSync2("/.dockerenv") || readFileSync2("/proc/1/cgroup", "utf8").toLowerCase().includes("docker");
10139
+ } catch {
10140
+ return false;
10141
+ }
10142
+ }
10143
+ function firstExternalIPv4() {
10144
+ for (const items of Object.values(os5.networkInterfaces())) {
10145
+ for (const item of items ?? []) {
10146
+ if (item.family === "IPv4" && !item.internal) return item.address;
10147
+ }
10148
+ }
10149
+ return void 0;
10150
+ }
10151
+ function publicUrlForPort(port) {
10152
+ const explicit = process.env.GIRL_AGENT_PUBLIC_URL?.trim();
10153
+ if (explicit) {
10154
+ try {
10155
+ const url = new URL2(explicit);
10156
+ if (!url.port) url.port = String(port);
10157
+ return url.toString().replace(/\/$/, "");
10158
+ } catch {
10159
+ const clean = explicit.replace(/^https?:\/\//, "").replace(/\/+$/, "");
10160
+ return `http://${clean.includes(":") ? clean : `${clean}:${port}`}`;
10161
+ }
10162
+ }
10163
+ return `http://${firstExternalIPv4() ?? "0.0.0.0"}:${port}`;
10164
+ }
10165
+ var DEFAULT_HOST = process.env.GIRL_AGENT_HOST ?? (isLikelyDocker() ? "0.0.0.0" : "127.0.0.1");
9708
10166
  function buildRouter() {
9709
10167
  const r = new Router();
10168
+ registerAuthRoutes(r);
9710
10169
  registerProfileRoutes(r);
9711
10170
  registerPresetRoutes(r);
9712
10171
  registerSystemRoutes(r);
@@ -9730,6 +10189,10 @@ async function startWebUIServer(opts = {}) {
9730
10189
  const url2 = new URL2(req.url ?? "/", `http://${req.headers.host ?? host}`);
9731
10190
  const pathname = url2.pathname;
9732
10191
  if (pathname.startsWith("/api/")) {
10192
+ if (!pathname.startsWith("/api/auth/") && !isAuthorized(req)) {
10193
+ sendJson(res, 401, { error: "auth required" });
10194
+ return;
10195
+ }
9733
10196
  const matched = router.match(req.method ?? "GET", pathname);
9734
10197
  if (!matched) {
9735
10198
  sendJson(res, 404, { error: "not found", path: pathname });
@@ -9789,20 +10252,12 @@ async function startWebUIServer(opts = {}) {
9789
10252
  resolve();
9790
10253
  });
9791
10254
  });
9792
- const ifaces = os5.networkInterfaces();
9793
- let displayHost = host;
9794
- if (host === "0.0.0.0") {
9795
- for (const k of Object.keys(ifaces)) {
9796
- for (const i of ifaces[k] ?? []) {
9797
- if (i.family === "IPv4" && !i.internal) {
9798
- displayHost = i.address;
9799
- break;
9800
- }
9801
- }
9802
- if (displayHost !== "0.0.0.0") break;
9803
- }
9804
- }
9805
- const url = `http://${displayHost === "0.0.0.0" ? "127.0.0.1" : displayHost}:${port}`;
10255
+ const urls = {
10256
+ loopback: `http://127.0.0.1:${port}`,
10257
+ localhost: `http://localhost:${port}`,
10258
+ public: publicUrlForPort(port)
10259
+ };
10260
+ const url = urls.localhost;
9806
10261
  if (opts.autoStart) {
9807
10262
  try {
9808
10263
  const slugs = await listProfiles();
@@ -9820,6 +10275,7 @@ async function startWebUIServer(opts = {}) {
9820
10275
  port,
9821
10276
  host,
9822
10277
  url,
10278
+ urls,
9823
10279
  async stop() {
9824
10280
  await bus.stopAll();
9825
10281
  await new Promise((resolve) => server.close(() => resolve()));
@@ -9977,8 +10433,8 @@ init_esm_shims();
9977
10433
  init_llm2();
9978
10434
  init_stages();
9979
10435
  init_communication();
9980
- import fs11 from "fs/promises";
9981
- import path12 from "path";
10436
+ import fs12 from "fs/promises";
10437
+ import path14 from "path";
9982
10438
  import os6 from "os";
9983
10439
  init_md();
9984
10440
  init_runtime();
@@ -10010,7 +10466,7 @@ env-vars (\u0434\u043B\u044F CI / docker secrets / k8s):
10010
10466
  GIRL_AGENT_MODEL, _NAME, _AGE, _NATIONALITY, _TZ, _STAGE (id \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440 1-8), _COMM_PRESET, _IGNORE_TENDENCY, _OWNER_ID
10011
10467
 
10012
10468
  \u0434\u043B\u044F \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u043F\u0435\u0440\u0432\u0438\u0447\u043D\u043E\u0439 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439 \u0431\u0435\u0437 \u0444\u043B\u0430\u0433\u043E\u0432 \u2014
10013
- \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F WebUI \u043D\u0430 http://localhost:3000.
10469
+ \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F WebUI \u043D\u0430 http://localhost:3000 (\u0432 docker \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 -p 3000:3000).
10014
10470
  `;
10015
10471
  function parseServerArgs(argv) {
10016
10472
  return {
@@ -10061,7 +10517,7 @@ async function runServer(rawArgv) {
10061
10517
  }
10062
10518
  if (!args.yes) {
10063
10519
  process.stderr.write(`\u043F\u0440\u043E\u0444\u0438\u043B\u044C \u041D\u0415 \u0443\u0434\u0430\u043B\u0451\u043D: \u0434\u043E\u0431\u0430\u0432\u044C --yes \u0434\u043B\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
10064
- \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u043E: ${path12.join(DATA_ROOT, args.profile)}
10520
+ \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u043E: ${path14.join(DATA_ROOT, args.profile)}
10065
10521
  `);
10066
10522
  process.exit(1);
10067
10523
  }
@@ -10123,7 +10579,7 @@ data dir: ${DATA_ROOT}
10123
10579
  }
10124
10580
  async function persistAndMaybeStart(cfg, args) {
10125
10581
  await writeConfig(cfg);
10126
- process.stderr.write(`[server] \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D: ${path12.join(DATA_ROOT, cfg.slug)}
10582
+ process.stderr.write(`[server] \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D: ${path14.join(DATA_ROOT, cfg.slug)}
10127
10583
  `);
10128
10584
  if (cfg.llm.apiKey || findPreset(cfg.llm.presetId)?.apiKeyRequired === false) {
10129
10585
  try {
@@ -10226,7 +10682,6 @@ function configFromEnv() {
10226
10682
  phone: e.GIRL_AGENT_TG_PHONE ?? "",
10227
10683
  proxy: parseTelegramProxy(e.GIRL_AGENT_TG_PROXY)
10228
10684
  },
10229
- mcp: [],
10230
10685
  ownerId: normalizeOwnerId(e.GIRL_AGENT_OWNER_ID),
10231
10686
  privacy: "owner-only",
10232
10687
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10240,10 +10695,10 @@ function configFromEnv() {
10240
10695
  };
10241
10696
  }
10242
10697
  async function loadConfigFile(file) {
10243
- const abs = path12.isAbsolute(file) ? file : path12.join(process.cwd(), file);
10698
+ const abs = path14.isAbsolute(file) ? file : path14.join(process.cwd(), file);
10244
10699
  let raw;
10245
10700
  try {
10246
- raw = await fs11.readFile(abs, "utf-8");
10701
+ raw = await fs12.readFile(abs, "utf-8");
10247
10702
  } catch (e) {
10248
10703
  process.stderr.write(`[server] \u043D\u0435 \u043C\u043E\u0433\u0443 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${abs}: ${e?.message ?? e}
10249
10704
  `);
@@ -10294,7 +10749,6 @@ function validateConfig(raw) {
10294
10749
  model: c.llm.model
10295
10750
  },
10296
10751
  telegram: c.telegram ?? {},
10297
- mcp: c.mcp ?? [],
10298
10752
  ownerId: normalizeOwnerId(c.ownerId ?? process.env.GIRL_AGENT_OWNER_ID),
10299
10753
  privacy: c.privacy ?? "owner-only",
10300
10754
  createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -10310,25 +10764,7 @@ function validateConfig(raw) {
10310
10764
  return filled;
10311
10765
  }
10312
10766
  function parseTelegramProxy(raw) {
10313
- if (!raw?.trim()) return void 0;
10314
- try {
10315
- const url = new URL(raw);
10316
- const socksType = url.protocol === "socks4:" ? 4 : 5;
10317
- const port = Number(url.port);
10318
- if (!url.hostname || !Number.isInteger(port) || port <= 0) return void 0;
10319
- return {
10320
- ip: url.hostname,
10321
- port,
10322
- socksType,
10323
- username: url.username ? decodeURIComponent(url.username) : void 0,
10324
- password: url.password ? decodeURIComponent(url.password) : void 0
10325
- };
10326
- } catch {
10327
- const [host, portRaw] = raw.split(":");
10328
- const port = Number(portRaw);
10329
- if (!host || !Number.isInteger(port) || port <= 0) return void 0;
10330
- return { ip: host, port, socksType: 5 };
10331
- }
10767
+ return parseTelegramProxyInput(raw);
10332
10768
  }
10333
10769
  function buildConfigTemplate() {
10334
10770
  const sample = {
@@ -10347,7 +10783,6 @@ function buildConfigTemplate() {
10347
10783
  model: "claude-sonnet-4.6"
10348
10784
  },
10349
10785
  telegram: { botToken: "REPLACE_ME" },
10350
- mcp: [],
10351
10786
  ownerId: void 0,
10352
10787
  privacy: "owner-only",
10353
10788
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10426,9 +10861,11 @@ docker run -d --name girl-agent --restart=unless-stopped \\
10426
10861
  # services:
10427
10862
  # girl-agent:
10428
10863
  # image: ghcr.io/thesashadev/girl-agent:latest
10864
+ # # interactive WebUI: command: [] and ports: ["3000:3000"]
10429
10865
  # command: ["server", "--config", "/config/bot.json", "--headless"]
10430
10866
  # environment:
10431
10867
  # GIRL_AGENT_DATA: /data
10868
+ # GIRL_AGENT_HOST: 0.0.0.0
10432
10869
  # volumes:
10433
10870
  # - girl-agent-data:/data
10434
10871
  # - ./bot.json:/config/bot.json:ro
@@ -10447,6 +10884,16 @@ init_llm();
10447
10884
  init_llm2();
10448
10885
  init_stages();
10449
10886
  init_communication();
10887
+ var nodeMajor = Number(process.versions.node.split(".")[0] ?? 0);
10888
+ if (nodeMajor < 18) {
10889
+ process.stderr.write(`[girl-agent] Node.js ${process.version} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F. \u041D\u0443\u0436\u0435\u043D Node.js 18.18+; \u0432 Termux: pkg install nodejs
10890
+ `);
10891
+ process.exit(1);
10892
+ }
10893
+ if (nodeMajor < 20) {
10894
+ process.stderr.write(`[girl-agent] \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435: Node.js ${process.version}; \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F 20/22, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E \u0437\u0430\u043F\u0443\u0441\u043A.
10895
+ `);
10896
+ }
10450
10897
  var HELP = `
10451
10898
  girl-agent \u2014 AI girl for Telegram (WebUI)
10452
10899
 
@@ -10454,6 +10901,7 @@ usage:
10454
10901
  npx girl-agent # \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C WebUI \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C http://localhost:3000
10455
10902
  npx girl-agent --port=8080 # \u043A\u0430\u0441\u0442\u043E\u043C\u043D\u044B\u0439 \u043F\u043E\u0440\u0442
10456
10903
  npx girl-agent --host=0.0.0.0 # \u0441\u043B\u0443\u0448\u0430\u0442\u044C \u043D\u0430 \u0432\u0441\u0435\u0445 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445
10904
+ GIRL_AGENT_PUBLIC_URL=https://example.com npx girl-agent # URL \u0434\u043B\u044F reverse proxy/docker
10457
10905
  npx girl-agent --no-browser # \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438
10458
10906
  npx girl-agent --profile=<slug> # \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C WebUI \u0438 \u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C
10459
10907
 
@@ -10492,7 +10940,6 @@ async function main() {
10492
10940
  "proto",
10493
10941
  "name",
10494
10942
  "stage",
10495
- "mcp",
10496
10943
  "nationality",
10497
10944
  "tz",
10498
10945
  "vibe",
@@ -10651,20 +11098,19 @@ async function main() {
10651
11098
  autoStart: !argv.profile,
10652
11099
  noBrowser: !!argv["no-browser"]
10653
11100
  });
10654
- const showHost = host === "0.0.0.0" ? "<public-ip>" : host === "127.0.0.1" ? "localhost" : host;
10655
- const localUrl = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
10656
11101
  process.stdout.write(`
10657
11102
  \u{1F310} girl-agent WebUI \u0437\u0430\u043F\u0443\u0449\u0435\u043D
10658
- ${instance.url}
10659
11103
  `);
10660
- if (host === "0.0.0.0") {
10661
- process.stdout.write(` \u0441\u043B\u0443\u0448\u0430\u0435\u0442 \u043D\u0430 \u0432\u0441\u0435\u0445 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445 \u2014 \u043E\u0442\u043A\u0440\u043E\u0439 http://<your-ip>:${port}
11104
+ process.stdout.write(` 1) ${instance.urls.loopback}
11105
+ `);
11106
+ process.stdout.write(` 2) ${instance.urls.localhost}
11107
+ `);
11108
+ process.stdout.write(` 3) ${instance.urls.public}
10662
11109
  `);
10663
- }
10664
11110
  process.stdout.write(`
10665
- REST API: ${localUrl}/api/system/health
11111
+ REST API: ${instance.urls.loopback}/api/system/health
10666
11112
  `);
10667
- process.stdout.write(` WebSocket logs: ws://${showHost}:${port}/ws/logs/<slug>
11113
+ process.stdout.write(` WebSocket logs: ws://127.0.0.1:${port}/ws/logs/<slug>
10668
11114
  `);
10669
11115
  process.stdout.write(` Ctrl+C \u0434\u043B\u044F \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438
10670
11116
 
@@ -10734,17 +11180,11 @@ async function buildConfigFromFlags(argv) {
10734
11180
  const slug = String(argv.profile ?? slugifyLocal(name));
10735
11181
  const mode = argv.mode === "userbot" ? "userbot" : "bot";
10736
11182
  const tz = (argv.tz ? parseTzFlag(String(argv.tz)) : void 0) ?? defaultTzForNationality(nationality);
10737
- const mcpFlags = [].concat(argv.mcp ?? []);
10738
11183
  const communication = (() => {
10739
11184
  const preset2 = findCommunicationPreset(typeof argv["communication-preset"] === "string" ? argv["communication-preset"] : void 0);
10740
11185
  return preset2?.profile ?? normalizeCommunicationProfile({});
10741
11186
  })();
10742
11187
  const privacy = argv.privacy === "allow-strangers" ? "allow-strangers" : "owner-only";
10743
- const mcps = mcpFlags.map((entry) => {
10744
- const [id, key] = entry.split(":");
10745
- const secrets = id === "exa" ? { EXA_API_KEY: key ?? "" } : { value: key ?? "" };
10746
- return { id: id ?? "", secrets };
10747
- });
10748
11188
  return {
10749
11189
  slug,
10750
11190
  name,
@@ -10759,7 +11199,6 @@ async function buildConfigFromFlags(argv) {
10759
11199
  apiHash: String(argv["api-hash"] ?? ""),
10760
11200
  phone: String(argv.phone ?? "")
10761
11201
  },
10762
- mcp: mcps,
10763
11202
  privacy,
10764
11203
  ownerId: normalizeOwnerId(argv["owner-id"] ?? process.env.GIRL_AGENT_OWNER_ID),
10765
11204
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),