@thesashadev/girl-agent 0.3.2 → 0.4.1

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
@@ -503,6 +503,7 @@ __export(bot_exports, {
503
503
  makeBotAdapter: () => makeBotAdapter
504
504
  });
505
505
  import { Bot } from "grammy";
506
+ import path3 from "path";
506
507
  function makeBotAdapter(cfg) {
507
508
  const token = cfg.telegram.botToken;
508
509
  if (!token) throw new Error("BOT_TOKEN missing");
@@ -511,7 +512,7 @@ function makeBotAdapter(cfg) {
511
512
  return {
512
513
  async start(onMessage) {
513
514
  bot.on("message", async (ctx) => {
514
- const media = detectBotMedia(ctx.message);
515
+ const media = await detectBotMedia(bot, token, ctx.message);
515
516
  const text = ctx.message.text ?? ctx.message.caption ?? "";
516
517
  if (!text && !media) return;
517
518
  const msg = {
@@ -611,18 +612,45 @@ function makeBotAdapter(cfg) {
611
612
  }
612
613
  };
613
614
  }
614
- function detectBotMedia(message) {
615
+ async function detectBotMedia(bot, token, message) {
615
616
  if (message.photo?.length) {
616
617
  const p = message.photo[message.photo.length - 1];
617
- return { kind: "photo", caption: message.caption, fileId: p.file_id };
618
+ const out = { kind: "photo", caption: message.caption, fileId: p.file_id, mimeType: "image/jpeg" };
619
+ await hydrateBotImage(bot, token, out);
620
+ return out;
618
621
  }
619
622
  if (message.voice) return { kind: "voice", caption: message.caption, fileId: message.voice.file_id, mimeType: message.voice.mime_type };
620
623
  if (message.video_note) return { kind: "video_note", fileId: message.video_note.file_id };
621
624
  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 };
625
+ if (message.sticker) {
626
+ const out = { kind: "sticker", fileId: message.sticker.file_id, emoji: message.sticker.emoji, mimeType: message.sticker.mime_type };
627
+ if (!message.sticker.is_animated && !message.sticker.is_video) await hydrateBotImage(bot, token, out);
628
+ return out;
629
+ }
623
630
  if (message.document) return { kind: "document", caption: message.caption, fileId: message.document.file_id, mimeType: message.document.mime_type };
624
631
  return void 0;
625
632
  }
633
+ async function hydrateBotImage(bot, token, media) {
634
+ if (!media.fileId) return;
635
+ try {
636
+ const file = await bot.api.getFile(media.fileId);
637
+ if (!file.file_path) return;
638
+ const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
639
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
640
+ if (!res.ok) return;
641
+ const buf = Buffer.from(await res.arrayBuffer());
642
+ media.base64 = buf.toString("base64");
643
+ media.mimeType = media.mimeType ?? mimeTypeForTelegramPath(file.file_path);
644
+ } catch {
645
+ }
646
+ }
647
+ function mimeTypeForTelegramPath(filePath) {
648
+ const ext = path3.extname(filePath).toLowerCase();
649
+ if (ext === ".png") return "image/png";
650
+ if (ext === ".gif") return "image/gif";
651
+ if (ext === ".webp") return "image/webp";
652
+ return "image/jpeg";
653
+ }
626
654
  var init_bot = __esm({
627
655
  "src/telegram/bot.ts"() {
628
656
  "use strict";
@@ -651,29 +679,46 @@ function debug(message) {
651
679
  if (process.env.GIRL_AGENT_DEBUG === "1") process.stderr.write(`${message}
652
680
  `);
653
681
  }
682
+ function clientProxy(cfg) {
683
+ const proxy = cfg.telegram.proxy;
684
+ if (!proxy) return void 0;
685
+ if (proxy.MTProxy && proxy.secret) {
686
+ return {
687
+ ip: proxy.ip,
688
+ port: proxy.port,
689
+ MTProxy: true,
690
+ secret: proxy.secret,
691
+ timeout: proxy.timeout ?? 10
692
+ };
693
+ }
694
+ if (!proxy.socksType) throw new Error("Invalid Telegram proxy: socksType missing");
695
+ return {
696
+ ip: proxy.ip,
697
+ port: proxy.port,
698
+ socksType: proxy.socksType,
699
+ username: proxy.username,
700
+ password: proxy.password,
701
+ timeout: proxy.timeout ?? 10
702
+ };
703
+ }
654
704
  function makeUserbotAdapter(cfg) {
655
705
  const apiId = cfg.telegram.apiId;
656
706
  const apiHash = cfg.telegram.apiHash;
657
707
  const session = cfg.telegram.sessionString ?? "";
658
- if (!apiId || !apiHash) throw new Error("API_ID/API_HASH missing for userbot");
708
+ const effectiveApiId = apiId || OWNER_PROXY_API_ID;
709
+ const effectiveApiHash = apiHash || OWNER_PROXY_API_HASH;
710
+ 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
711
  const useWSS = cfg.telegram.useWSS !== false;
660
- const proxy = cfg.telegram.proxy;
712
+ const proxy = clientProxy(cfg);
661
713
  debug(`[userbot] creating TelegramClient (useWSS=${useWSS}${proxy ? ", proxy=on" : ""})\u2026`);
662
- const client = new TelegramClient(new StringSession(session), apiId, apiHash, {
714
+ const client = new TelegramClient(new StringSession(session), effectiveApiId, effectiveApiHash, {
663
715
  connectionRetries: 5,
664
716
  requestRetries: 5,
665
717
  retryDelay: 3e3,
666
718
  autoReconnect: true,
667
719
  floodSleepThreshold: 120,
668
720
  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
721
+ proxy
677
722
  });
678
723
  client.onError = async () => {
679
724
  };
@@ -979,17 +1024,28 @@ async function detectUserbotMedia(client, message) {
979
1024
  if (isVideoNote) return { kind: "video_note", caption, mimeType };
980
1025
  if (isSticker) {
981
1026
  const stickerAttr = attrs.find((a) => a.className === "DocumentAttributeSticker");
982
- return { kind: "sticker", caption, mimeType, emoji: stickerAttr?.alt };
1027
+ const out = { kind: "sticker", caption, mimeType, emoji: stickerAttr?.alt };
1028
+ if (mimeType?.startsWith("image/")) {
1029
+ try {
1030
+ const downloaded = await client.downloadMedia(message, {});
1031
+ if (Buffer.isBuffer(downloaded)) out.base64 = downloaded.toString("base64");
1032
+ } catch {
1033
+ }
1034
+ }
1035
+ return out;
983
1036
  }
984
1037
  if (isVideo) return { kind: "video", caption, mimeType };
985
1038
  return { kind: "document", caption, mimeType };
986
1039
  }
987
1040
  return void 0;
988
1041
  }
1042
+ var OWNER_PROXY_API_ID, OWNER_PROXY_API_HASH;
989
1043
  var init_userbot = __esm({
990
1044
  "src/telegram/userbot.ts"() {
991
1045
  "use strict";
992
1046
  init_esm_shims();
1047
+ OWNER_PROXY_API_ID = Number(process.env.GIRL_AGENT_OWNER_PROXY_API_ID ?? process.env.GIRL_AGENT_TG_API_ID ?? 0);
1048
+ OWNER_PROXY_API_HASH = process.env.GIRL_AGENT_OWNER_PROXY_API_HASH ?? process.env.GIRL_AGENT_TG_API_HASH ?? "";
993
1049
  }
994
1050
  });
995
1051
 
@@ -1113,35 +1169,45 @@ var init_communication = __esm({
1113
1169
 
1114
1170
  // src/storage/md.ts
1115
1171
  import { promises as fs2 } from "fs";
1116
- import { existsSync } from "fs";
1117
- import path3 from "path";
1172
+ import { existsSync, mkdirSync, accessSync, constants } from "fs";
1173
+ import path4 from "path";
1118
1174
  import os from "os";
1175
+ function canWriteDir(dir) {
1176
+ try {
1177
+ existsSync(dir) || mkdirSync(dir, { recursive: true });
1178
+ accessSync(dir, constants.W_OK);
1179
+ return true;
1180
+ } catch {
1181
+ return false;
1182
+ }
1183
+ }
1119
1184
  function defaultDataRoot() {
1120
1185
  const cwd = process.cwd();
1121
- if (looksLikeProjectRoot(cwd)) return path3.resolve(cwd, "data");
1186
+ const projectData = path4.resolve(cwd, "data");
1187
+ if (looksLikeProjectRoot(cwd) && canWriteDir(path4.dirname(projectData))) return projectData;
1122
1188
  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");
1189
+ const appdata = process.env.APPDATA ? path4.resolve(process.env.APPDATA) : path4.join(os.homedir(), "AppData", "Roaming");
1190
+ return path4.join(appdata, "girl-agent", "data");
1125
1191
  }
1126
1192
  if (process.platform === "darwin" && !process.env.XDG_DATA_HOME) {
1127
- return path3.join(os.homedir(), "Library", "Application Support", "girl-agent", "data");
1193
+ return path4.join(os.homedir(), "Library", "Application Support", "girl-agent", "data");
1128
1194
  }
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");
1195
+ const xdg = process.env.XDG_DATA_HOME ? path4.resolve(process.env.XDG_DATA_HOME) : path4.join(os.homedir(), ".local", "share");
1196
+ return path4.join(xdg, "girl-agent", "data");
1131
1197
  }
1132
1198
  function looksLikeProjectRoot(dir) {
1133
- return existsSync(path3.join(dir, "package.json")) && (existsSync(path3.join(dir, "src")) || existsSync(path3.join(dir, "dist")));
1199
+ return existsSync(path4.join(dir, "package.json")) && (existsSync(path4.join(dir, "src")) || existsSync(path4.join(dir, "dist")));
1134
1200
  }
1135
1201
  function profileDir(slug) {
1136
- return path3.join(DATA_ROOT, slug);
1202
+ return path4.join(DATA_ROOT, slug);
1137
1203
  }
1138
1204
  async function ensureProfile(slug) {
1139
1205
  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 });
1206
+ await fs2.mkdir(path4.join(dir, "memory", "episodes"), { recursive: true });
1207
+ await fs2.mkdir(path4.join(dir, "log"), { recursive: true });
1142
1208
  }
1143
1209
  async function readMd(slug, name) {
1144
- const p = path3.join(profileDir(slug), name);
1210
+ const p = path4.join(profileDir(slug), name);
1145
1211
  try {
1146
1212
  return await fs2.readFile(p, "utf8");
1147
1213
  } catch {
@@ -1149,18 +1215,18 @@ async function readMd(slug, name) {
1149
1215
  }
1150
1216
  }
1151
1217
  async function writeMd(slug, name, content) {
1152
- const p = path3.join(profileDir(slug), name);
1153
- await fs2.mkdir(path3.dirname(p), { recursive: true });
1218
+ const p = path4.join(profileDir(slug), name);
1219
+ await fs2.mkdir(path4.dirname(p), { recursive: true });
1154
1220
  await fs2.writeFile(p, content, "utf8");
1155
1221
  }
1156
1222
  async function appendMd(slug, name, content) {
1157
- const p = path3.join(profileDir(slug), name);
1158
- await fs2.mkdir(path3.dirname(p), { recursive: true });
1223
+ const p = path4.join(profileDir(slug), name);
1224
+ await fs2.mkdir(path4.dirname(p), { recursive: true });
1159
1225
  await fs2.appendFile(p, content, "utf8");
1160
1226
  }
1161
1227
  async function readConfig(slug) {
1162
1228
  try {
1163
- const raw = await fs2.readFile(path3.join(profileDir(slug), "config.json"), "utf8");
1229
+ const raw = await fs2.readFile(path4.join(profileDir(slug), "config.json"), "utf8");
1164
1230
  const parsed = JSON.parse(raw);
1165
1231
  const communication = normalizeCommunicationProfile(parsed);
1166
1232
  const ownerId = normalizeOwnerId(parsed.ownerId);
@@ -1185,7 +1251,7 @@ async function writeConfig(cfg) {
1185
1251
  const ownerId = normalizeOwnerId(cfg.ownerId ?? process.env.GIRL_AGENT_OWNER_ID);
1186
1252
  const normalized = ownerId === void 0 ? { ...cfg, ownerId: void 0, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) } : { ...cfg, ownerId, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) };
1187
1253
  await fs2.writeFile(
1188
- path3.join(profileDir(cfg.slug), "config.json"),
1254
+ path4.join(profileDir(cfg.slug), "config.json"),
1189
1255
  JSON.stringify(normalized, null, 2),
1190
1256
  "utf8"
1191
1257
  );
@@ -1210,7 +1276,7 @@ async function listProfiles() {
1210
1276
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1211
1277
  const valid = await Promise.all(dirs.map(async (name) => {
1212
1278
  try {
1213
- await fs2.access(path3.join(profileDir(name), "config.json"));
1279
+ await fs2.access(path4.join(profileDir(name), "config.json"));
1214
1280
  return name;
1215
1281
  } catch {
1216
1282
  return null;
@@ -1279,13 +1345,35 @@ function sessionDate(tz, now = /* @__PURE__ */ new Date()) {
1279
1345
  }
1280
1346
  return `${y}-${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
1281
1347
  }
1282
- async function appendSessionLog(slug, tz, line) {
1348
+ async function appendSessionLog(slug, tz, line, fromId) {
1349
+ const day = sessionDate(tz);
1350
+ const suffix = fromId ? ` <!--from:${fromId}-->` : "";
1351
+ await appendMd(slug, `log/${day}.md`, line + suffix + "\n");
1352
+ }
1353
+ async function appendSharedMemory(slug, tz, fromId, text) {
1283
1354
  const day = sessionDate(tz);
1284
- await appendMd(slug, `log/${day}.md`, line + "\n");
1355
+ const safe = text.replace(/\s+/g, " ").trim();
1356
+ if (!safe) return;
1357
+ const line = `- ${(/* @__PURE__ */ new Date()).toISOString()} user:${fromId} day:${day}: ${safe}`;
1358
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1359
+ const lines = raw.split(/\r?\n/).filter(Boolean);
1360
+ if (lines.slice(-20).some((existing) => existing.endsWith(`: ${safe}`))) return;
1361
+ await writeMd(slug, "memory/shared-cross-chat.md", [...lines.slice(-500), line].join("\n") + "\n");
1362
+ }
1363
+ async function readSharedMemory(slug, limit = 40) {
1364
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1365
+ return raw.split(/\r?\n/).filter(Boolean).slice(-limit).join("\n");
1366
+ }
1367
+ async function searchSharedMemory(slug, query, limit = 8) {
1368
+ const raw = await readMd(slug, "memory/shared-cross-chat.md");
1369
+ const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3);
1370
+ const lines = raw.split(/\r?\n/).filter(Boolean);
1371
+ const hits = tokens.length ? lines.filter((line) => tokens.some((t) => line.toLowerCase().includes(t))) : [];
1372
+ return (hits.length ? hits : lines).slice(-limit).join("\n");
1285
1373
  }
1286
1374
  async function listSessionDays(slug) {
1287
1375
  try {
1288
- const dir = path3.join(profileDir(slug), "log");
1376
+ const dir = path4.join(profileDir(slug), "log");
1289
1377
  const files = await fs2.readdir(dir);
1290
1378
  return files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
1291
1379
  } catch {
@@ -1300,7 +1388,7 @@ async function writeDailySummary(slug, day, content) {
1300
1388
  }
1301
1389
  async function listDailySummaries(slug) {
1302
1390
  try {
1303
- const dir = path3.join(profileDir(slug), "memory", "daily");
1391
+ const dir = path4.join(profileDir(slug), "memory", "daily");
1304
1392
  const files = await fs2.readdir(dir);
1305
1393
  return files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).map((f) => f.replace(/\.md$/, "")).sort();
1306
1394
  } catch {
@@ -1340,7 +1428,7 @@ function parseSessionLogTurns(raw, fromId, limit = 30) {
1340
1428
  if (user) {
1341
1429
  currentChatMatches = fromId == null || Number(user[2]) === fromId;
1342
1430
  if (currentChatMatches) {
1343
- turns.push({ role: "user", content: user[3] ?? "", ts: Date.parse(user[1] ?? "") || void 0 });
1431
+ turns.push({ role: "user", content: user[3] ?? "", ts: Date.parse(user[1] ?? "") || void 0, fromId: Number(user[2]) });
1344
1432
  }
1345
1433
  continue;
1346
1434
  }
@@ -1359,7 +1447,7 @@ async function readRecentSessionTurns(slug, tz, fromId, limit = 30) {
1359
1447
  }
1360
1448
  async function readAgenda(slug) {
1361
1449
  try {
1362
- const raw = await fs2.readFile(path3.join(profileDir(slug), "agenda.json"), "utf8");
1450
+ const raw = await fs2.readFile(path4.join(profileDir(slug), "agenda.json"), "utf8");
1363
1451
  return JSON.parse(raw);
1364
1452
  } catch {
1365
1453
  return [];
@@ -1367,7 +1455,7 @@ async function readAgenda(slug) {
1367
1455
  }
1368
1456
  async function writeAgenda(slug, items) {
1369
1457
  await ensureProfile(slug);
1370
- await fs2.writeFile(path3.join(profileDir(slug), "agenda.json"), JSON.stringify(items, null, 2), "utf8");
1458
+ await fs2.writeFile(path4.join(profileDir(slug), "agenda.json"), JSON.stringify(items, null, 2), "utf8");
1371
1459
  }
1372
1460
  var DATA_ROOT, SCORE_RE;
1373
1461
  var init_md = __esm({
@@ -1375,7 +1463,7 @@ var init_md = __esm({
1375
1463
  "use strict";
1376
1464
  init_esm_shims();
1377
1465
  init_communication();
1378
- DATA_ROOT = process.env.GIRL_AGENT_DATA ? path3.resolve(process.env.GIRL_AGENT_DATA) : defaultDataRoot();
1466
+ DATA_ROOT = process.env.GIRL_AGENT_DATA ? path4.resolve(process.env.GIRL_AGENT_DATA) : defaultDataRoot();
1379
1467
  SCORE_RE = /<!--score:(.+?)-->/;
1380
1468
  }
1381
1469
  });
@@ -1837,8 +1925,8 @@ phoneAvailable=false \u043A\u043E\u0433\u0434\u0430: \u0441\u043F\u0438\u0442, \
1837
1925
  }
1838
1926
  async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(), conflict = null) {
1839
1927
  const dateLocal = localDateStr(cfg.tz, now);
1840
- const path13 = `daily-life/${dateLocal}.md`;
1841
- const existing = await readMd(cfg.slug, path13);
1928
+ const path14 = `daily-life/${dateLocal}.md`;
1929
+ const existing = await readMd(cfg.slug, path14);
1842
1930
  if (existing) {
1843
1931
  try {
1844
1932
  const m = existing.match(/<!--daily:(.+?)-->/s);
@@ -1870,7 +1958,7 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
1870
1958
  dl = { dateLocal, vibe: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u043D\u044C", blocks: [], events: [], wants: [] };
1871
1959
  }
1872
1960
  const human = renderDailyLifeHuman(dl);
1873
- await writeMd(cfg.slug, path13, `${human}
1961
+ await writeMd(cfg.slug, path14, `${human}
1874
1962
 
1875
1963
  <!--daily:${JSON.stringify(dl)}-->
1876
1964
  `);
@@ -1936,10 +2024,10 @@ var init_daily_life = __esm({
1936
2024
 
1937
2025
  // src/engine/conflict.ts
1938
2026
  import { promises as fs3 } from "fs";
1939
- import path4 from "path";
2027
+ import path5 from "path";
1940
2028
  async function readConflict(slug) {
1941
2029
  try {
1942
- const raw = await fs3.readFile(path4.join(profileDir(slug), "conflict.json"), "utf8");
2030
+ const raw = await fs3.readFile(path5.join(profileDir(slug), "conflict.json"), "utf8");
1943
2031
  const parsed = JSON.parse(raw);
1944
2032
  return { ...empty, ...parsed, history: parsed.history ?? [] };
1945
2033
  } catch {
@@ -1948,7 +2036,7 @@ async function readConflict(slug) {
1948
2036
  }
1949
2037
  async function writeConflict(slug, c) {
1950
2038
  await ensureProfile(slug);
1951
- await fs3.writeFile(path4.join(profileDir(slug), "conflict.json"), JSON.stringify(c, null, 2), "utf8");
2039
+ await fs3.writeFile(path5.join(profileDir(slug), "conflict.json"), JSON.stringify(c, null, 2), "utf8");
1952
2040
  }
1953
2041
  function activeConflict(c, now = /* @__PURE__ */ new Date()) {
1954
2042
  const cold = c.coldUntil ? new Date(c.coldUntil).getTime() > now.getTime() : false;
@@ -2074,7 +2162,7 @@ var init_conflict = __esm({
2074
2162
  // src/engine/memory-palace.ts
2075
2163
  import { promises as fs4 } from "fs";
2076
2164
  import { createHash } from "crypto";
2077
- import path5 from "path";
2165
+ import path6 from "path";
2078
2166
  function wordsFrom(text) {
2079
2167
  return [...text.toLowerCase().matchAll(/[a-zа-яё0-9]{3,}/gi)].map((match) => match[0]).filter((token) => !STOP_WORDS.has(token));
2080
2168
  }
@@ -2204,9 +2292,9 @@ async function ensureDefaults(cfg) {
2204
2292
  ["time/promises.md", "# promises\n"],
2205
2293
  ["memory/uncertain.md", "# uncertain\n"]
2206
2294
  ];
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");
2295
+ await Promise.all(defaults.map(async ([path14, content]) => {
2296
+ const current = await readMd(cfg.slug, path14);
2297
+ if (!current.trim()) await writeMd(cfg.slug, path14, content + "\n");
2210
2298
  }));
2211
2299
  }
2212
2300
  function scoreDrawer(drawer, tokens, query) {
@@ -2243,7 +2331,7 @@ async function listPalaceDrawers(cfg) {
2243
2331
  }
2244
2332
  async function listChildDirs(slug, rel) {
2245
2333
  try {
2246
- const entries = await fs4.readdir(path5.join(profileDir(slug), rel), { withFileTypes: true });
2334
+ const entries = await fs4.readdir(path6.join(profileDir(slug), rel), { withFileTypes: true });
2247
2335
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
2248
2336
  } catch {
2249
2337
  return [];
@@ -2251,7 +2339,7 @@ async function listChildDirs(slug, rel) {
2251
2339
  }
2252
2340
  async function listChildFiles(slug, rel) {
2253
2341
  try {
2254
- const entries = await fs4.readdir(path5.join(profileDir(slug), rel), { withFileTypes: true });
2342
+ const entries = await fs4.readdir(path6.join(profileDir(slug), rel), { withFileTypes: true });
2255
2343
  return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
2256
2344
  } catch {
2257
2345
  return [];
@@ -2380,13 +2468,13 @@ async function appendCompatibilityMemory(cfg, drawer) {
2380
2468
  await appendMd(cfg.slug, "relationship/timeline.md", line);
2381
2469
  }
2382
2470
  }
2383
- async function recordInteractionMemory(llm, cfg, incoming, reply) {
2471
+ async function recordInteractionMemory(llm, cfg, incoming, reply, fromId, scope = "primary") {
2384
2472
  if (!incoming.trim()) return;
2385
2473
  await ensureDefaults(cfg);
2386
2474
  const raw = await llm.chat([
2387
2475
  {
2388
2476
  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.`
2477
+ 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
2478
  },
2391
2479
  {
2392
2480
  role: "user",
@@ -2416,7 +2504,14 @@ ${reply ?? ""}
2416
2504
  }
2417
2505
  ], { temperature: 0.1, maxTokens: 3500, json: true });
2418
2506
  const parsed = parseJsonObject(raw);
2419
- const drawers = parsedDrawers(parsed?.drawers).slice(0, 12);
2507
+ const drawers = parsedDrawers(parsed?.drawers).slice(0, scope === "acquaintance" ? 4 : 12);
2508
+ if (scope === "acquaintance") {
2509
+ if (!fromId) return;
2510
+ for (const drawer of drawers) {
2511
+ await appendSharedMemory(cfg.slug, cfg.tz, fromId, drawer.quote);
2512
+ }
2513
+ return;
2514
+ }
2420
2515
  for (const drawer of drawers) {
2421
2516
  await appendDrawer(cfg, "interaction", drawer);
2422
2517
  }
@@ -2659,15 +2754,19 @@ function describeIncomingMedia(media) {
2659
2754
  }
2660
2755
  function mediaPromptFragment(media) {
2661
2756
  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.";
2757
+ 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
2758
  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
2759
  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
2760
  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.";
2761
+ 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
2762
  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
2763
  }
2764
+ function memeDetectionInstruction(media) {
2765
+ if (!media || media.kind !== "photo" && media.kind !== "sticker") return "";
2766
+ 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.";
2767
+ }
2669
2768
  function imagePartFromMedia(media) {
2670
- if (!media || media.kind !== "photo" || !media.mimeType || !media.base64) return void 0;
2769
+ if (!media || media.kind !== "photo" && media.kind !== "sticker" || !media.mimeType || !media.base64) return void 0;
2671
2770
  return {
2672
2771
  type: "image",
2673
2772
  mimeType: media.mimeType,
@@ -2846,6 +2945,7 @@ async function buildSystemPrompt(cfg, ctx = {}) {
2846
2945
  const effectiveStageId = isAcquaintance ? "tg-given-cold" : cfg.stage;
2847
2946
  const rel = isAcquaintance ? { ...relRaw, stage: effectiveStageId, score: { interest: 0, trust: 0, attraction: 0, annoyance: 0, cringe: 0 } } : relRaw;
2848
2947
  const longTerm = isAcquaintance ? "" : await readMd(cfg.slug, "memory/long-term.md");
2948
+ const sharedMemory = isAcquaintance ? await readSharedMemory(cfg.slug, 8) : ctx.incoming ? await searchSharedMemory(cfg.slug, ctx.incoming, 12) : await readSharedMemory(cfg.slug, 20);
2849
2949
  const stage = findStage(effectiveStageId);
2850
2950
  const seed = [...cfg.name].reduce((a, c) => a + c.charCodeAt(0), 0);
2851
2951
  const stressLoad = Math.min(
@@ -2883,7 +2983,7 @@ ${hits.map((h) => `- ${h.day}: ${h.excerpt}`).join("\n")}
2883
2983
  \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
2984
  \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
2985
  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.
2986
+ \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
2987
  ${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
2988
  ${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
2989
  const communicationFragment = communicationPromptFragment(communication);
@@ -3058,6 +3158,8 @@ var init_prompt = __esm({
3058
3158
 
3059
3159
  # \u041F\u0410\u041C\u042F\u0422\u042C
3060
3160
  - \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.
3161
+ - \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.
3162
+ - \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
3163
  - \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
3164
 
3063
3165
  # \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 +3809,10 @@ var init_llm2 = __esm({
3707
3809
  }
3708
3810
  });
3709
3811
 
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
3812
  // src/engine/agenda.ts
3813
+ function agendaById(items) {
3814
+ return new Map(items.map((item) => [item.id, item]));
3815
+ }
3800
3816
  function localDateKey(tz, now = /* @__PURE__ */ new Date()) {
3801
3817
  try {
3802
3818
  return new Intl.DateTimeFormat("en-CA", { timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit" }).format(now);
@@ -3895,6 +3911,7 @@ ${persona}`;
3895
3911
  return { created: 0, updated: 0, cancelled: 0 };
3896
3912
  }
3897
3913
  let created = 0, updated = 0, cancelled = 0;
3914
+ const byId = agendaById(agenda);
3898
3915
  for (const a of actions) {
3899
3916
  if (a.action === "noop" || !a.action) continue;
3900
3917
  if (a.action === "create" && a.about && a.pingAt) {
@@ -3914,21 +3931,21 @@ ${persona}`;
3914
3931
  agenda.push(item);
3915
3932
  created++;
3916
3933
  } 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 ?? ""}`];
3934
+ const item = byId.get(a.id);
3935
+ if (item) {
3936
+ if (a.about) item.about = a.about;
3937
+ if (a.pingAt) item.pingAt = a.pingAt;
3938
+ if (a.reason) item.reason = a.reason;
3939
+ if (a.userEventTime) item.userEventTime = a.userEventTime;
3940
+ if (a.importance) item.importance = a.importance;
3941
+ item.history = [...item.history ?? [], `updated at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
3925
3942
  updated++;
3926
3943
  }
3927
3944
  } 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 ?? ""}`];
3945
+ const item = byId.get(a.id);
3946
+ if (item) {
3947
+ item.state = "cancelled";
3948
+ item.history = [...item.history ?? [], `cancelled at ${(/* @__PURE__ */ new Date()).toISOString()}: ${a.reason ?? ""}`];
3932
3949
  cancelled++;
3933
3950
  }
3934
3951
  }
@@ -4025,17 +4042,15 @@ async function reconcileAgendaAfterConflict(slug, conflict, prevLevel) {
4025
4042
  let rescheduled = 0;
4026
4043
  const now = Date.now();
4027
4044
  for (const item of pending2) {
4028
- const idx = agenda.findIndex((x) => x.id === item.id);
4029
- if (idx < 0) continue;
4030
4045
  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()}`];
4046
+ item.state = "cancelled";
4047
+ item.history = [...item.history ?? [], `cancelled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4033
4048
  cancelled++;
4034
4049
  } else if (conflict.level >= 2 && item.importance === 2) {
4035
4050
  const delayHours = 12 + Math.random() * 24;
4036
4051
  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()}`];
4052
+ item.pingAt = newPing;
4053
+ item.history = [...item.history ?? [], `rescheduled due to conflict level ${conflict.level} at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4039
4054
  rescheduled++;
4040
4055
  }
4041
4056
  }
@@ -4051,21 +4066,21 @@ async function dueAgendaItems(slug) {
4051
4066
  }
4052
4067
  async function markAgendaFired(slug, id) {
4053
4068
  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()}`];
4069
+ const item = agendaById(agenda).get(id);
4070
+ if (item) {
4071
+ item.state = "fired";
4072
+ item.attempts += 1;
4073
+ item.history = [...item.history ?? [], `fired at ${(/* @__PURE__ */ new Date()).toISOString()}`];
4059
4074
  await writeAgenda(slug, agenda);
4060
4075
  }
4061
4076
  }
4062
4077
  async function rescheduleAgenda(slug, id, newPingAt, note) {
4063
4078
  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}`];
4079
+ const item = agendaById(agenda).get(id);
4080
+ if (item) {
4081
+ item.pingAt = newPingAt;
4082
+ item.state = "pending";
4083
+ item.history = [...item.history ?? [], `rescheduled to ${newPingAt}: ${note}`];
4069
4084
  await writeAgenda(slug, agenda);
4070
4085
  }
4071
4086
  }
@@ -4345,12 +4360,12 @@ var init_daily_summarizer = __esm({
4345
4360
 
4346
4361
  // src/engine/stickers.ts
4347
4362
  import { promises as fs5 } from "fs";
4348
- import path6 from "path";
4363
+ import path7 from "path";
4349
4364
  async function libraryPath(cfg) {
4350
4365
  const rel = "stickers/library.md";
4351
4366
  const current = await readMd(cfg.slug, rel);
4352
4367
  if (!current.trim()) await writeMd(cfg.slug, rel, DEFAULT_LIBRARY);
4353
- return path6.join(profileDir(cfg.slug), rel);
4368
+ return path7.join(profileDir(cfg.slug), rel);
4354
4369
  }
4355
4370
  async function listStickers(cfg) {
4356
4371
  await libraryPath(cfg);
@@ -4370,7 +4385,9 @@ async function pickSticker(cfg, mood = "") {
4370
4385
  }
4371
4386
  async function addStickerToLibrary(cfg, fileId, emoji = "", tags = []) {
4372
4387
  await libraryPath(cfg);
4373
- const p = path6.join(profileDir(cfg.slug), "stickers/library.md");
4388
+ const existing = await listStickers(cfg);
4389
+ if (existing.some((s) => s.fileId === fileId)) return;
4390
+ const p = path7.join(profileDir(cfg.slug), "stickers/library.md");
4374
4391
  await fs5.appendFile(p, `${fileId} | ${emoji} | ${tags.join(",")}
4375
4392
  `, "utf8");
4376
4393
  }
@@ -5066,7 +5083,6 @@ var init_runtime = __esm({
5066
5083
  init_stages();
5067
5084
  init_communication();
5068
5085
  init_llm2();
5069
- init_client();
5070
5086
  init_agenda();
5071
5087
  init_presence();
5072
5088
  init_online_tick();
@@ -5093,7 +5109,6 @@ var init_runtime = __esm({
5093
5109
  cfg;
5094
5110
  llm;
5095
5111
  tg;
5096
- mcps = [];
5097
5112
  histories = /* @__PURE__ */ new Map();
5098
5113
  paused = false;
5099
5114
  agendaTimer;
@@ -5134,8 +5149,6 @@ var init_runtime = __esm({
5134
5149
  tgSelf = {};
5135
5150
  async start() {
5136
5151
  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
5152
  this.tg = await makeTgAdapter(this.cfg);
5140
5153
  await this.tg.start((m) => this.handleIncoming(m));
5141
5154
  if (this.tg.getSelf) this.tgSelf = this.tg.getSelf();
@@ -5172,7 +5185,6 @@ var init_runtime = __esm({
5172
5185
  await this.tg?.stop();
5173
5186
  } catch {
5174
5187
  }
5175
- for (const h of this.mcps) await h.close();
5176
5188
  }
5177
5189
  pause() {
5178
5190
  this.paused = true;
@@ -5239,7 +5251,6 @@ var init_runtime = __esm({
5239
5251
  if (!fromId) return;
5240
5252
  if (this.cfg.ownerId === fromId) return;
5241
5253
  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
5254
  return;
5244
5255
  }
5245
5256
  this.cfg.ownerId = fromId;
@@ -5318,6 +5329,13 @@ var init_runtime = __esm({
5318
5329
  return m.text ? `${media}
5319
5330
  ${m.text}` : media;
5320
5331
  }
5332
+ async rememberSharedCrossChat(fromId, incomingText) {
5333
+ const text = incomingText.trim();
5334
+ if (!text || text.length < 3) return;
5335
+ const safe = text.replace(/\s+/g, " ").slice(0, 280);
5336
+ await appendSharedMemory(this.cfg.slug, this.cfg.tz, fromId, safe).catch(() => {
5337
+ });
5338
+ }
5321
5339
  requestedOutgoingMedia(text) {
5322
5340
  if (/\b(фото|фотку|селфи|скинь себя|покажи себя)\b/i.test(text)) return "photo";
5323
5341
  if (/\b(видео|видос|запиши видео)\b/i.test(text)) return "video";
@@ -5371,10 +5389,8 @@ ${m.text}` : media;
5371
5389
  try {
5372
5390
  await this.tg.editText(chatId, messageId, rawText);
5373
5391
  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
- }
5392
+ 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(() => {
5393
+ });
5378
5394
  const rec = this.sentMessages.find((s) => s.messageId === messageId);
5379
5395
  if (rec) rec.text = rawText;
5380
5396
  const histEntry = hist[hist.length - 1];
@@ -5390,7 +5406,7 @@ ${m.text}` : media;
5390
5406
  this.lastHerReplyTs.set(this.histKey(chatId), Date.now());
5391
5407
  this.bumpStageStats("her");
5392
5408
  this.emit("event", { type: "outgoing", text, chatId });
5393
- if (scope === "primary") await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430: ${text}`);
5409
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430: ${text}`, typeof chatId === "number" ? chatId : void 0);
5394
5410
  sent.push(text);
5395
5411
  }
5396
5412
  return sent;
@@ -5400,7 +5416,7 @@ ${m.text}` : media;
5400
5416
  });
5401
5417
  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
5418
  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})`);
5419
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${reasonTag})`, typeof chatId === "number" ? chatId : void 0);
5404
5420
  }
5405
5421
  /**
5406
5422
  * Один ретрай с упрощённым system-промптом. Используется когда первый ответ оказался
@@ -5435,7 +5451,7 @@ ${m.text}` : media;
5435
5451
  this.lastHerReplyTs.set(this.histKey(chatId), Date.now());
5436
5452
  this.emit("event", { type: "outgoing", text: candidate, chatId });
5437
5453
  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}`);
5454
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> \u043E\u043D\u0430 (filler): ${candidate}`, typeof chatId === "number" ? chatId : void 0);
5439
5455
  hist.push({ role: "assistant", content: candidate, ts: Date.now() });
5440
5456
  this.setDecisionStatus(this.histKey(chatId), "sent", "neutral-filler");
5441
5457
  } catch (e) {
@@ -5603,11 +5619,15 @@ ${m.text}` : media;
5603
5619
  this.histories.set(key, hist);
5604
5620
  this.emit("event", { type: "incoming", text: incomingText, chatId: m.chatId });
5605
5621
  if (isPrimary) {
5606
- await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u043E\u043D(${m.fromId}): ${incomingText}`);
5622
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u043E\u043D(${m.fromId}): ${incomingText}`, m.fromId);
5623
+ } else {
5624
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[${(/* @__PURE__ */ new Date()).toISOString()}] \u0434\u0440\u0443\u0433\u043E\u0439(${m.fromId}): ${incomingText}`, m.fromId);
5625
+ await this.rememberSharedCrossChat(m.fromId, incomingText);
5626
+ recordInteractionMemory(this.llm, this.cfg, incomingText, void 0, m.fromId, "acquaintance").catch(() => {
5627
+ });
5607
5628
  }
5608
5629
  if (m.media?.kind === "sticker" && m.media.fileId && isPrimary) {
5609
- addStickerToLibrary(this.cfg, m.media.fileId, m.media.emoji ?? "", ["received"]).catch(() => {
5610
- });
5630
+ void addStickerToLibrary(this.cfg, m.media.fileId, m.media.emoji ?? "", ["received"]);
5611
5631
  }
5612
5632
  const requestedMedia = this.requestedOutgoingMedia(m.text);
5613
5633
  if (requestedMedia) {
@@ -5630,7 +5650,7 @@ ${m.text}` : media;
5630
5650
  }
5631
5651
  if (!bubbles.length) return;
5632
5652
  await this.sendBubbles(m.chatId, bubbles, hist, isPrimary ? "primary" : "acquaintance", true);
5633
- if (isPrimary) recordInteractionMemory(this.llm, this.cfg, incomingText, bubbles.join(" / ")).catch(() => {
5653
+ if (isPrimary) recordInteractionMemory(this.llm, this.cfg, incomingText, bubbles.join(" / "), m.fromId, "primary").catch(() => {
5634
5654
  });
5635
5655
  return;
5636
5656
  }
@@ -5761,7 +5781,7 @@ ${m.text}` : media;
5761
5781
  });
5762
5782
  const msgTag = target.messageId !== m.messageId ? ` (msgId=${target.messageId})` : "";
5763
5783
  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(() => {
5784
+ appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> reaction ${tick.reaction}${msgTag}`, m.fromId).catch(() => {
5765
5785
  });
5766
5786
  }, reactDelay).unref?.();
5767
5787
  }
@@ -5777,8 +5797,8 @@ ${m.text}` : media;
5777
5797
  });
5778
5798
  }
5779
5799
  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(() => {
5800
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> ignored (${tick.intent}: ${tick.ignoreReason ?? ""})`, m.fromId);
5801
+ recordInteractionMemory(this.llm, this.cfg, incomingText, void 0, m.fromId, "primary").catch(() => {
5782
5802
  });
5783
5803
  return;
5784
5804
  }
@@ -5823,10 +5843,12 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5823
5843
  ];
5824
5844
  const image = imagePartFromMedia(incoming?.media);
5825
5845
  if (image) {
5846
+ const memeHint = memeDetectionInstruction(incoming?.media);
5826
5847
  messages.push({
5827
5848
  role: "user",
5828
5849
  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." },
5850
+ { 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 ? `
5851
+ ${memeHint}` : ""}` },
5830
5852
  image
5831
5853
  ]
5832
5854
  });
@@ -5856,7 +5878,7 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5856
5878
  const sent = await this.sendBubbles(chatId, bubbles, hist, scope, tick.typing);
5857
5879
  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
5880
  if (scope === "primary") {
5859
- recordInteractionMemory(this.llm, this.cfg, lastUser ?? "", sent.join(" / ")).catch(() => {
5881
+ recordInteractionMemory(this.llm, this.cfg, lastUser ?? "", sent.join(" / "), typeof chatId === "number" ? chatId : void 0, "primary").catch(() => {
5860
5882
  });
5861
5883
  }
5862
5884
  if (this.tg.sendSticker && Math.random() < 0.08) {
@@ -5935,7 +5957,7 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5935
5957
  }
5936
5958
  hist.push({ role: "assistant", content: piece, ts: now });
5937
5959
  this.emit("event", { type: "outgoing", text: piece, chatId: item.chatId });
5938
- await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> [proactive] \u043E\u043D\u0430: ${piece}`);
5960
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> [proactive] \u043E\u043D\u0430: ${piece}`, typeof item.chatId === "number" ? item.chatId : void 0);
5939
5961
  }
5940
5962
  this.histories.set(key, hist);
5941
5963
  await markAgendaFired(this.cfg.slug, item.id);
@@ -5997,7 +6019,6 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5997
6019
  `communication: ${communicationProfileLabel(communication)}`,
5998
6020
  `config: ${profileDir(this.cfg.slug)}/config.json`,
5999
6021
  `score: ${JSON.stringify(rel.score)}`,
6000
- `mcp: ${this.mcps.map((m) => m.id).join(", ") || "\u2014"}`,
6001
6022
  `paused: ${this.paused}`
6002
6023
  ].join("\n");
6003
6024
  }
@@ -6451,7 +6472,7 @@ ${describeLLM(this.cfg)}`;
6451
6472
  await maybeAdvanceRelationshipTimeline(this.cfg, oldStage, decision.next);
6452
6473
  this.stageStats.set(decision.next, { herMsgs: 0, hisMsgs: 0, ignoresInStage: 0, lastCheckAt: 0, stageEnteredAt: Date.now() });
6453
6474
  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})`);
6475
+ await appendSessionLog(this.cfg.slug, this.cfg.tz, `[stage-transition] ${oldStage} \u2192 ${decision.next} (${decision.reason})`, this.cfg.ownerId);
6455
6476
  } catch {
6456
6477
  }
6457
6478
  }
@@ -6482,7 +6503,7 @@ ${describeLLM(this.cfg)}`;
6482
6503
  };
6483
6504
  this.emit("event", { type: "info", text: `delete: ${awareness}${m.deletion.text ? ` "${m.deletion.text.slice(0, 40)}"` : ""}` });
6484
6505
  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)}"`);
6506
+ 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
6507
  }
6487
6508
  if (!shouldRespondToDeletion(ctx)) return;
6488
6509
  if (!inHistory && awareness === "saw-and-read") {
@@ -6568,7 +6589,7 @@ ${buildDeletionPromptContext(this.cfg, ctx)}` },
6568
6589
  }
6569
6590
  this.emit("event", { type: "info", text: `emoji-react ${m.emojiReaction.emoji} (${decision.category}/${decision.intent}): ${decision.reason}` });
6570
6591
  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})`);
6592
+ 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
6593
  }
6573
6594
  if (decision.moodDelta && Object.keys(decision.moodDelta).length > 0) {
6574
6595
  const newScore = applyMoodDelta(rel.score, decision.moodDelta);
@@ -6708,7 +6729,7 @@ var init_memory_palace2 = __esm({
6708
6729
 
6709
6730
  // src/migrations/index.ts
6710
6731
  import { promises as fs6 } from "fs";
6711
- import path7 from "path";
6732
+ import path8 from "path";
6712
6733
  import { readFileSync } from "fs";
6713
6734
  import { fileURLToPath as fileURLToPath3 } from "url";
6714
6735
  async function readMigrationState() {
@@ -6814,15 +6835,15 @@ function formatUpdateWarnings(warnings) {
6814
6835
  function currentVersion() {
6815
6836
  try {
6816
6837
  const here = fileURLToPath3(import.meta.url);
6817
- let dir = path7.dirname(here);
6838
+ let dir = path8.dirname(here);
6818
6839
  for (let i = 0; i < 5; i++) {
6819
- const candidate = path7.join(dir, "package.json");
6840
+ const candidate = path8.join(dir, "package.json");
6820
6841
  try {
6821
6842
  const pkg = JSON.parse(readFileSync(candidate, "utf8"));
6822
6843
  if (pkg.name === "@thesashadev/girl-agent" && pkg.version) return pkg.version;
6823
6844
  } catch {
6824
6845
  }
6825
- dir = path7.dirname(dir);
6846
+ dir = path8.dirname(dir);
6826
6847
  }
6827
6848
  return "unknown";
6828
6849
  } catch {
@@ -6838,7 +6859,7 @@ var init_migrations = __esm({
6838
6859
  init_add_use_wss_default();
6839
6860
  init_ensure_communication_md();
6840
6861
  init_memory_palace2();
6841
- MIGRATIONS_FILE = () => path7.join(DATA_ROOT, ".migrations.json");
6862
+ MIGRATIONS_FILE = () => path8.join(DATA_ROOT, ".migrations.json");
6842
6863
  ALL_MIGRATIONS = [
6843
6864
  migration0112,
6844
6865
  migration0113,
@@ -7001,13 +7022,13 @@ __export(addons_exports, {
7001
7022
  validateManifest: () => validateManifest
7002
7023
  });
7003
7024
  import { promises as fs9 } from "fs";
7004
- import path10 from "path";
7025
+ import path11 from "path";
7005
7026
  import os3 from "os";
7006
7027
  import { execFile } from "child_process";
7007
7028
  import { promisify } from "util";
7008
7029
  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");
7030
+ const root = process.env.GIRL_AGENT_DATA ? path11.resolve(process.env.GIRL_AGENT_DATA, "..") : path11.join(os3.homedir(), ".local", "share", "girl-agent");
7031
+ return path11.join(root, "addons");
7011
7032
  }
7012
7033
  async function ensureDir() {
7013
7034
  const dir = addonsDir();
@@ -7024,12 +7045,12 @@ async function readJsonOrEmpty(p, fallback) {
7024
7045
  }
7025
7046
  async function listInstalled() {
7026
7047
  const dir = await ensureDir();
7027
- const indexPath = path10.join(dir, "installed.json");
7048
+ const indexPath = path11.join(dir, "installed.json");
7028
7049
  return await readJsonOrEmpty(indexPath, []);
7029
7050
  }
7030
7051
  async function writeInstalled(list) {
7031
7052
  const dir = await ensureDir();
7032
- await fs9.writeFile(path10.join(dir, "installed.json"), JSON.stringify(list, null, 2), "utf8");
7053
+ await fs9.writeFile(path11.join(dir, "installed.json"), JSON.stringify(list, null, 2), "utf8");
7033
7054
  }
7034
7055
  async function fetchRegistry() {
7035
7056
  try {
@@ -7043,15 +7064,15 @@ async function fetchRegistry() {
7043
7064
  }
7044
7065
  }
7045
7066
  async function unpackGaa(gaaPath) {
7046
- const tmpDir = path10.join(os3.tmpdir(), `gaa-${Date.now()}-${Math.random().toString(36).slice(2)}`);
7067
+ const tmpDir = path11.join(os3.tmpdir(), `gaa-${Date.now()}-${Math.random().toString(36).slice(2)}`);
7047
7068
  await fs9.mkdir(tmpDir, { recursive: true });
7048
7069
  await execFileAsync("unzip", ["-o", "-q", gaaPath, "-d", tmpDir]);
7049
7070
  const entries = await fs9.readdir(tmpDir);
7050
7071
  if (entries.length === 1) {
7051
- const sub = path10.join(tmpDir, entries[0]);
7072
+ const sub = path11.join(tmpDir, entries[0]);
7052
7073
  const st = await fs9.stat(sub);
7053
7074
  if (st.isDirectory()) {
7054
- const innerManifest = path10.join(sub, "manifest.json");
7075
+ const innerManifest = path11.join(sub, "manifest.json");
7055
7076
  try {
7056
7077
  await fs9.access(innerManifest);
7057
7078
  return sub;
@@ -7062,34 +7083,34 @@ async function unpackGaa(gaaPath) {
7062
7083
  return tmpDir;
7063
7084
  }
7064
7085
  async function packGaa(addonDir, outputPath) {
7065
- const manifestPath = path10.join(addonDir, "manifest.json");
7086
+ const manifestPath = path11.join(addonDir, "manifest.json");
7066
7087
  const manifestRaw = await fs9.readFile(manifestPath, "utf8");
7067
7088
  const manifest = JSON.parse(manifestRaw);
7068
7089
  validateManifest(manifest);
7069
- const out = outputPath ?? path10.join(process.cwd(), `${manifest.id}.gaa`);
7090
+ const out = outputPath ?? path11.join(process.cwd(), `${manifest.id}.gaa`);
7070
7091
  try {
7071
7092
  await fs9.unlink(out);
7072
7093
  } catch {
7073
7094
  }
7074
- const dirName = path10.basename(addonDir);
7075
- const parentDir = path10.dirname(addonDir);
7095
+ const dirName = path11.basename(addonDir);
7096
+ const parentDir = path11.dirname(addonDir);
7076
7097
  await execFileAsync("zip", ["-r", "-q", out, dirName], { cwd: parentDir });
7077
7098
  return out;
7078
7099
  }
7079
7100
  async function installFromDir(addonDir, profileSlug, source = "local") {
7080
- const manifestPath = path10.join(addonDir, "manifest.json");
7101
+ const manifestPath = path11.join(addonDir, "manifest.json");
7081
7102
  const manifestRaw = await fs9.readFile(manifestPath, "utf8");
7082
7103
  const manifest = JSON.parse(manifestRaw);
7083
7104
  validateManifest(manifest);
7084
7105
  const applied = [];
7085
7106
  const installedFiles = [];
7086
- const filesDir = path10.join(addonDir, "files");
7107
+ const filesDir = path11.join(addonDir, "files");
7087
7108
  try {
7088
7109
  const fileStat = await fs9.stat(filesDir);
7089
7110
  if (fileStat.isDirectory() && profileSlug) {
7090
7111
  const fileEntries = await walkDir(filesDir);
7091
7112
  for (const relPath of fileEntries) {
7092
- const content = await fs9.readFile(path10.join(filesDir, relPath), "utf8");
7113
+ const content = await fs9.readFile(path11.join(filesDir, relPath), "utf8");
7093
7114
  await writeMd(profileSlug, relPath, content);
7094
7115
  installedFiles.push(relPath);
7095
7116
  }
@@ -7097,7 +7118,7 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7097
7118
  }
7098
7119
  } catch {
7099
7120
  }
7100
- const patchPath = path10.join(addonDir, "config.patch.json");
7121
+ const patchPath = path11.join(addonDir, "config.patch.json");
7101
7122
  try {
7102
7123
  const patchRaw = await fs9.readFile(patchPath, "utf8");
7103
7124
  const patch = JSON.parse(patchRaw);
@@ -7111,11 +7132,11 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7111
7132
  }
7112
7133
  } catch {
7113
7134
  }
7114
- const codePatchPath = path10.join(addonDir, "code.patch");
7135
+ const codePatchPath = path11.join(addonDir, "code.patch");
7115
7136
  try {
7116
7137
  const patchContent = await fs9.readFile(codePatchPath, "utf8");
7117
7138
  if (patchContent.trim()) {
7118
- const projectRoot = path10.resolve(import.meta.url.replace("file://", ""), "../../../");
7139
+ const projectRoot = path11.resolve(import.meta.url.replace("file://", ""), "../../../");
7119
7140
  try {
7120
7141
  await execFileAsync("git", ["apply", "--check", codePatchPath], { cwd: projectRoot });
7121
7142
  await execFileAsync("git", ["apply", codePatchPath], { cwd: projectRoot });
@@ -7126,24 +7147,24 @@ async function installFromDir(addonDir, profileSlug, source = "local") {
7126
7147
  }
7127
7148
  } catch {
7128
7149
  }
7129
- const themePath = path10.join(addonDir, "theme.css");
7150
+ const themePath = path11.join(addonDir, "theme.css");
7130
7151
  try {
7131
7152
  const css = await fs9.readFile(themePath, "utf8");
7132
7153
  const dir2 = await ensureDir();
7133
- await fs9.writeFile(path10.join(dir2, `theme-${manifest.id}.css`), css, "utf8");
7154
+ await fs9.writeFile(path11.join(dir2, `theme-${manifest.id}.css`), css, "utf8");
7134
7155
  applied.push("\u0442\u0435\u043C\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430");
7135
7156
  } catch {
7136
7157
  }
7137
7158
  const dir = await ensureDir();
7138
- const addonStorePath = path10.join(dir, manifest.id);
7159
+ const addonStorePath = path11.join(dir, manifest.id);
7139
7160
  await fs9.mkdir(addonStorePath, { recursive: true });
7140
- await fs9.copyFile(manifestPath, path10.join(addonStorePath, "manifest.json"));
7161
+ await fs9.copyFile(manifestPath, path11.join(addonStorePath, "manifest.json"));
7141
7162
  const allFiles = await walkDir(addonDir);
7142
7163
  for (const f of allFiles) {
7143
7164
  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 });
7165
+ const src = path11.join(addonDir, f);
7166
+ const dst = path11.join(addonStorePath, f);
7167
+ await fs9.mkdir(path11.dirname(dst), { recursive: true });
7147
7168
  await fs9.copyFile(src, dst);
7148
7169
  }
7149
7170
  const list = await listInstalled();
@@ -7188,7 +7209,7 @@ async function installFromRegistry(id, registryManifest, profileSlug) {
7188
7209
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
7189
7210
  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
7211
  const buf = Buffer.from(await res.arrayBuffer());
7191
- const tmpGaa = path10.join(os3.tmpdir(), `${id}-${Date.now()}.gaa`);
7212
+ const tmpGaa = path11.join(os3.tmpdir(), `${id}-${Date.now()}.gaa`);
7192
7213
  await fs9.writeFile(tmpGaa, buf);
7193
7214
  try {
7194
7215
  return await installFromGaa(tmpGaa, profileSlug);
@@ -7202,10 +7223,10 @@ async function uninstall(id) {
7202
7223
  const next = list.filter((a) => a.manifest.id !== id);
7203
7224
  if (next.length === list.length) return false;
7204
7225
  const dir = addonsDir();
7205
- const addonStore = path10.join(dir, id);
7226
+ const addonStore = path11.join(dir, id);
7206
7227
  await fs9.rm(addonStore, { recursive: true, force: true }).catch(() => {
7207
7228
  });
7208
- const themePath = path10.join(dir, `theme-${id}.css`);
7229
+ const themePath = path11.join(dir, `theme-${id}.css`);
7209
7230
  await fs9.unlink(themePath).catch(() => {
7210
7231
  });
7211
7232
  await writeInstalled(next);
@@ -7241,7 +7262,7 @@ async function walkDir(dir, prefix = "") {
7241
7262
  for (const e of entries) {
7242
7263
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
7243
7264
  if (e.isDirectory()) {
7244
- result.push(...await walkDir(path10.join(dir, e.name), rel));
7265
+ result.push(...await walkDir(path11.join(dir, e.name), rel));
7245
7266
  } else {
7246
7267
  result.push(rel);
7247
7268
  }
@@ -7261,7 +7282,7 @@ function deepMerge(target, source) {
7261
7282
  }
7262
7283
  async function getAddonReadme(id) {
7263
7284
  const dir = addonsDir();
7264
- const readmePath = path10.join(dir, id, "README.md");
7285
+ const readmePath = path11.join(dir, id, "README.md");
7265
7286
  try {
7266
7287
  return await fs9.readFile(readmePath, "utf8");
7267
7288
  } catch {
@@ -7270,7 +7291,7 @@ async function getAddonReadme(id) {
7270
7291
  }
7271
7292
  async function getAddonFiles(id) {
7272
7293
  const dir = addonsDir();
7273
- const addonDir = path10.join(dir, id);
7294
+ const addonDir = path11.join(dir, id);
7274
7295
  try {
7275
7296
  return await walkDir(addonDir);
7276
7297
  } catch {
@@ -7299,6 +7320,7 @@ init_esm_shims();
7299
7320
  import http2 from "http";
7300
7321
  import { URL as URL2 } from "url";
7301
7322
  import os5 from "os";
7323
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
7302
7324
 
7303
7325
  // src/webui/http.ts
7304
7326
  init_esm_shims();
@@ -7313,9 +7335,9 @@ var HttpError = class extends Error {
7313
7335
  };
7314
7336
  var Router = class {
7315
7337
  routes = [];
7316
- add(method, path13, handler) {
7338
+ add(method, path14, handler) {
7317
7339
  const paramNames = [];
7318
- const parts = path13.split("/").map((part) => {
7340
+ const parts = path14.split("/").map((part) => {
7319
7341
  if (part.startsWith(":")) {
7320
7342
  paramNames.push(part.slice(1));
7321
7343
  return "([^/]+)";
@@ -7330,20 +7352,20 @@ var Router = class {
7330
7352
  handler
7331
7353
  });
7332
7354
  }
7333
- get(path13, h) {
7334
- this.add("GET", path13, h);
7355
+ get(path14, h) {
7356
+ this.add("GET", path14, h);
7335
7357
  }
7336
- post(path13, h) {
7337
- this.add("POST", path13, h);
7358
+ post(path14, h) {
7359
+ this.add("POST", path14, h);
7338
7360
  }
7339
- put(path13, h) {
7340
- this.add("PUT", path13, h);
7361
+ put(path14, h) {
7362
+ this.add("PUT", path14, h);
7341
7363
  }
7342
- delete(path13, h) {
7343
- this.add("DELETE", path13, h);
7364
+ delete(path14, h) {
7365
+ this.add("DELETE", path14, h);
7344
7366
  }
7345
- patch(path13, h) {
7346
- this.add("PATCH", path13, h);
7367
+ patch(path14, h) {
7368
+ this.add("PATCH", path14, h);
7347
7369
  }
7348
7370
  match(method, pathname) {
7349
7371
  for (const r of this.routes) {
@@ -7444,14 +7466,13 @@ async function findWebUIRoot() {
7444
7466
  }
7445
7467
  })();
7446
7468
  const candidates = [
7469
+ path2.resolve(process.cwd(), "dist", "webui"),
7470
+ path2.resolve(process.cwd(), "webui", "dist"),
7447
7471
  path2.resolve(here, "webui"),
7448
- // dist/cli.js -> dist/webui/
7449
7472
  path2.resolve(here, "..", "dist", "webui"),
7450
- // src/webui/static.ts -> dist/webui/
7451
7473
  path2.resolve(here, "..", "..", "dist", "webui"),
7452
- // src/webui/static.ts -> dist/webui/
7453
7474
  path2.resolve(here, "..", "..", "..", "dist", "webui"),
7454
- path2.resolve(process.cwd(), "dist", "webui")
7475
+ path2.resolve(here, "..", "..", "webui", "dist")
7455
7476
  ];
7456
7477
  for (const c of candidates) {
7457
7478
  try {
@@ -7527,10 +7548,62 @@ init_esm_shims();
7527
7548
  init_runtime_bus();
7528
7549
  init_md();
7529
7550
  import { WebSocketServer } from "ws";
7551
+
7552
+ // src/webui/auth.ts
7553
+ init_esm_shims();
7554
+ import crypto2 from "crypto";
7555
+ var COOKIE = "girl_agent_auth";
7556
+ var TOKEN_BYTES = 24;
7557
+ var authSecret = process.env.GIRL_AGENT_WEBUI_PASSWORD?.trim() || process.env.GIRL_AGENT_WEBUI_TOKEN?.trim() || "";
7558
+ var sessions = /* @__PURE__ */ new Set();
7559
+ function authEnabled() {
7560
+ return !!authSecret;
7561
+ }
7562
+ function authStatus() {
7563
+ return { enabled: authEnabled() };
7564
+ }
7565
+ function verifyPassword(password) {
7566
+ if (!authSecret) return true;
7567
+ const a = Buffer.from(password);
7568
+ const b = Buffer.from(authSecret);
7569
+ return a.length === b.length && crypto2.timingSafeEqual(a, b);
7570
+ }
7571
+ function createSession(res) {
7572
+ const token = crypto2.randomBytes(TOKEN_BYTES).toString("base64url");
7573
+ sessions.add(token);
7574
+ res.setHeader("Set-Cookie", `${COOKIE}=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`);
7575
+ }
7576
+ function clearSession(req, res) {
7577
+ const token = readCookie(req);
7578
+ if (token) sessions.delete(token);
7579
+ res.setHeader("Set-Cookie", `${COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
7580
+ }
7581
+ function isAuthorized(req) {
7582
+ if (!authSecret) return true;
7583
+ const bearer = String(req.headers.authorization ?? "").replace(/^Bearer\s+/i, "");
7584
+ if (bearer && verifyPassword(bearer)) return true;
7585
+ const token = readCookie(req);
7586
+ return !!token && sessions.has(token);
7587
+ }
7588
+ function readCookie(req) {
7589
+ const raw = req.headers.cookie;
7590
+ if (!raw) return void 0;
7591
+ for (const part of raw.split(";")) {
7592
+ const [key, ...rest] = part.trim().split("=");
7593
+ if (key === COOKIE) return rest.join("=");
7594
+ }
7595
+ return void 0;
7596
+ }
7597
+
7598
+ // src/webui/websocket.ts
7530
7599
  function attachWebSockets(server) {
7531
7600
  const wssLogs = new WebSocketServer({ noServer: true });
7532
7601
  const wssStatus = new WebSocketServer({ noServer: true });
7533
7602
  server.on("upgrade", (req, socket, head) => {
7603
+ if (!isAuthorized(req)) {
7604
+ socket.destroy();
7605
+ return;
7606
+ }
7534
7607
  const url = req.url ?? "";
7535
7608
  const logsMatch = url.match(/^\/ws\/logs\/([^/?#]+)/);
7536
7609
  if (logsMatch) {
@@ -7656,6 +7729,8 @@ var BUSY_SCHEDULE_SCHEMA = {
7656
7729
  }
7657
7730
  };
7658
7731
  async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "", onProgress) {
7732
+ const existing = await readExistingPersona(slug);
7733
+ if (existing) return existing;
7659
7734
  const country = nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413";
7660
7735
  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
7736
  const notes = personaNotes.trim() ? `
@@ -7772,10 +7847,93 @@ ${personaNotes.trim()}
7772
7847
  await writeMd(slug, "communication.md", boundaries);
7773
7848
  return { persona, speech, boundaries, busySchedule };
7774
7849
  }
7850
+ async function ensurePersonaPack(slug, name, age) {
7851
+ const existing = await readExistingPersona(slug);
7852
+ if (existing) return existing;
7853
+ const persona = fallbackPersona(name, age);
7854
+ const speech = fallbackSpeech(name, age);
7855
+ const boundaries = fallbackCommunication(name, age);
7856
+ const busySchedule = fallbackBusySchedule(name, age);
7857
+ await writeMd(slug, "persona.md", persona);
7858
+ await writeMd(slug, "speech.md", speech);
7859
+ await writeMd(slug, "communication.md", boundaries);
7860
+ return { persona, speech, boundaries, busySchedule };
7861
+ }
7862
+ async function readExistingPersona(slug) {
7863
+ try {
7864
+ const [persona, speech, boundaries] = await Promise.all([
7865
+ readMd(slug, "persona.md"),
7866
+ readMd(slug, "speech.md"),
7867
+ readMd(slug, "communication.md")
7868
+ ]);
7869
+ if (persona.trim() && speech.trim() && boundaries.trim()) {
7870
+ return { persona, speech, boundaries, busySchedule: [] };
7871
+ }
7872
+ } catch {
7873
+ }
7874
+ return null;
7875
+ }
7775
7876
  function sanitizeProfileText(text) {
7776
7877
  const cleaned = sanitizeModelReply(text).replace(/[^\S\r\n]{2,}/g, " ").replace(/\n{4,}/g, "\n\n\n").trim();
7777
7878
  return cleaned || text.trim();
7778
7879
  }
7880
+ function fallbackPersona(name, age) {
7881
+ 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";
7882
+ return `# ${name}, ${age}
7883
+ ## \u0411\u044D\u043A\u0433\u0440\u0430\u0443\u043D\u0434
7884
+ \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.
7885
+
7886
+ ## \u0425\u0430\u0440\u0430\u043A\u0442\u0435\u0440
7887
+ - \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
7888
+ - \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
7889
+ - \u043D\u0435 \u043B\u044E\u0431\u0438\u0442 \u0434\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0438 \u0434\u043E\u043F\u0440\u043E\u0441\u044B
7890
+ - \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
7891
+ - \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
7892
+
7893
+ ## \u0427\u0442\u043E \u0435\u0451 \u0431\u0435\u0441\u0438\u0442
7894
+ - \u043D\u0430\u0432\u044F\u0437\u0447\u0438\u0432\u043E\u0441\u0442\u044C
7895
+ - \u043F\u0430\u0441\u0441\u0438\u0432\u043D\u0430\u044F \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u044F
7896
+ - \u043A\u043E\u0433\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u044E\u0442 \u0431\u044B\u0441\u0442\u0440\u044B\u0439 \u043E\u0442\u0432\u0435\u0442
7897
+ - \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?"
7898
+ - \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
7899
+
7900
+ ## \u0427\u0442\u043E \u0435\u0439 \u043D\u0440\u0430\u0432\u0438\u0442\u0441\u044F
7901
+ \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.`;
7902
+ }
7903
+ function fallbackSpeech(name, age) {
7904
+ return `# \u041C\u0430\u043D\u0435\u0440\u0430 \u0440\u0435\u0447\u0438
7905
+ ${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.
7906
+
7907
+ ## \u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0438 \u043F\u0443\u043D\u043A\u0442\u0443\u0430\u0446\u0438\u044F
7908
+ \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.
7909
+
7910
+ ## \u0421\u043B\u0435\u043D\u0433
7911
+ \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.
7912
+
7913
+ ## \u0422\u0438\u043F\u0438\u0447\u043D\u044B\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u0438
7914
+ - "\u043D\u0443 \u0434\u0430"
7915
+ - "\u043D\u0435 \u0437\u043D\u0430\u044E"
7916
+ - "\u0441\u0442\u0440\u0430\u043D\u043D\u043E \u043D\u0435\u043C\u043D\u043E\u0433\u043E"
7917
+ - "\u0430\u0445\u0430\u0445"
7918
+ - "\u043B\u0430\u0434\u043D\u043E"
7919
+ - "\u043C\u043D\u0435 \u043B\u0435\u043D\u044C \u0449\u0430\u0441"
7920
+
7921
+ ## \u0417\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u043E\u0435
7922
+ \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}.`;
7923
+ }
7924
+ function fallbackCommunication(name, age) {
7925
+ return `# \u041F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0435\u043D\u0438\u044F \u0432 \u043E\u0431\u0449\u0435\u043D\u0438\u0438
7926
+ ${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.
7927
+
7928
+ ## \u0422\u0435\u043C\u044B \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u041D\u0415 \u043E\u0431\u0441\u0443\u0436\u0434\u0430\u0435\u0442
7929
+ \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.
7930
+
7931
+ ## \u0417\u0435\u043B\u0451\u043D\u044B\u0435 \u0444\u043B\u0430\u0433\u0438
7932
+ \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.
7933
+
7934
+ ## \u041A\u043E\u0433\u0434\u0430 \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0438\u0433\u043D\u043E\u0440
7935
+ \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.`;
7936
+ }
7779
7937
  function parseBusySchedule(raw, name, age) {
7780
7938
  try {
7781
7939
  const start = raw.indexOf("{");
@@ -7836,7 +7994,7 @@ init_llm();
7836
7994
  init_llm_update();
7837
7995
  init_llm2();
7838
7996
  import { promises as fs7 } from "fs";
7839
- import path8 from "path";
7997
+ import path9 from "path";
7840
7998
  var MEMORY_FILES = [
7841
7999
  "persona.md",
7842
8000
  "speech.md",
@@ -7853,7 +8011,7 @@ var MEMORY_FILES = [
7853
8011
  function isAllowedMemoryPath(p) {
7854
8012
  if (!p || typeof p !== "string") return false;
7855
8013
  if (p.includes("..")) return false;
7856
- if (path8.isAbsolute(p)) return false;
8014
+ if (path9.isAbsolute(p)) return false;
7857
8015
  if (p.startsWith("config.json")) return false;
7858
8016
  if (p.startsWith("agenda.json")) return false;
7859
8017
  if (MEMORY_FILES.includes(p)) return true;
@@ -7917,7 +8075,6 @@ function registerProfileRoutes(r) {
7917
8075
  stage: data.stage ?? "tg-given-cold",
7918
8076
  llm: data.llm ?? { presetId: "claudehub", proto: "anthropic", apiKey: "", model: "claude-sonnet-4.6" },
7919
8077
  telegram: data.telegram ?? {},
7920
- mcp: data.mcp ?? [],
7921
8078
  privacy: data.privacy ?? "owner-only",
7922
8079
  ownerId: normalizeOwnerId(data.ownerId),
7923
8080
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8029,29 +8186,29 @@ function registerProfileRoutes(r) {
8029
8186
  const entries = [];
8030
8187
  for (const f of MEMORY_FILES) entries.push({ rel: f });
8031
8188
  try {
8032
- const dailyDir = path8.join(dir, "memory", "daily");
8189
+ const dailyDir = path9.join(dir, "memory", "daily");
8033
8190
  const list = await fs7.readdir(dailyDir);
8034
8191
  for (const f of list) if (/^\d{4}-\d{2}-\d{2}\.md$/.test(f)) entries.push({ rel: `memory/daily/${f}` });
8035
8192
  } catch {
8036
8193
  }
8037
8194
  try {
8038
- const epDir = path8.join(dir, "memory", "episodes");
8195
+ const epDir = path9.join(dir, "memory", "episodes");
8039
8196
  const list = await fs7.readdir(epDir);
8040
8197
  for (const f of list) if (/^[\w\-]{1,80}\.md$/.test(f)) entries.push({ rel: `memory/episodes/${f}` });
8041
8198
  } catch {
8042
8199
  }
8043
8200
  try {
8044
- const palaceDir = path8.join(dir, "memory", "palace");
8201
+ const palaceDir = path9.join(dir, "memory", "palace");
8045
8202
  const wings = await fs7.readdir(palaceDir, { withFileTypes: true });
8046
8203
  for (const wing of wings) {
8047
8204
  if (!wing.isDirectory() || !/^[\w\-]{1,80}$/.test(wing.name)) continue;
8048
- const halls = await fs7.readdir(path8.join(palaceDir, wing.name), { withFileTypes: true });
8205
+ const halls = await fs7.readdir(path9.join(palaceDir, wing.name), { withFileTypes: true });
8049
8206
  for (const hall of halls) {
8050
8207
  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 });
8208
+ const rooms = await fs7.readdir(path9.join(palaceDir, wing.name, hall.name), { withFileTypes: true });
8052
8209
  for (const room of rooms) {
8053
8210
  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));
8211
+ const drawers = await fs7.readdir(path9.join(palaceDir, wing.name, hall.name, room.name));
8055
8212
  for (const drawer of drawers) {
8056
8213
  if (/^[\w\-]{1,120}\.md$/.test(drawer)) entries.push({ rel: `memory/palace/${wing.name}/${hall.name}/${room.name}/${drawer}` });
8057
8214
  }
@@ -8062,7 +8219,7 @@ function registerProfileRoutes(r) {
8062
8219
  }
8063
8220
  for (const e of entries) {
8064
8221
  try {
8065
- const stat = await fs7.stat(path8.join(dir, e.rel));
8222
+ const stat = await fs7.stat(path9.join(dir, e.rel));
8066
8223
  items.push({ path: e.rel, size: stat.size, mtime: stat.mtimeMs });
8067
8224
  } catch {
8068
8225
  }
@@ -8135,15 +8292,20 @@ function registerProfileRoutes(r) {
8135
8292
  const cfg = await readConfig(slug);
8136
8293
  if (!cfg) throw new HttpError(404, "profile not found");
8137
8294
  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
- );
8295
+ let generated;
8296
+ try {
8297
+ const llm = makeLLM(cfg.llm);
8298
+ generated = await generatePersonaPack(
8299
+ llm,
8300
+ cfg.slug,
8301
+ data.name ?? cfg.name,
8302
+ data.age ?? cfg.age,
8303
+ data.nationality ?? cfg.nationality,
8304
+ data.notes ?? cfg.personaNotes
8305
+ );
8306
+ } catch {
8307
+ generated = await ensurePersonaPack(cfg.slug, data.name ?? cfg.name, data.age ?? cfg.age);
8308
+ }
8147
8309
  cfg.busySchedule = generated.busySchedule;
8148
8310
  await writeConfig(cfg);
8149
8311
  return { ok: true, busySchedule: generated.busySchedule };
@@ -8160,7 +8322,6 @@ init_esm_shims();
8160
8322
  init_llm2();
8161
8323
  init_stages();
8162
8324
  init_communication();
8163
- init_mcp();
8164
8325
 
8165
8326
  // src/data/timezones.ts
8166
8327
  init_esm_shims();
@@ -8407,15 +8568,6 @@ function registerPresetRoutes(r) {
8407
8568
  profile: p.profile
8408
8569
  }))
8409
8570
  }));
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
8571
  r.get("/api/presets/timezones", ({ searchParams }) => {
8420
8572
  const q = searchParams.get("q") ?? "";
8421
8573
  const all = q ? findTzByQuery(q, 200) : TIMEZONES;
@@ -8433,7 +8585,7 @@ init_esm_shims();
8433
8585
  init_md();
8434
8586
  import { promises as fs8 } from "fs";
8435
8587
  import { fileURLToPath as fileURLToPath4 } from "url";
8436
- import path9 from "path";
8588
+ import path10 from "path";
8437
8589
  import os2 from "os";
8438
8590
  var cachedVersion = null;
8439
8591
  async function readPackageVersion() {
@@ -8441,12 +8593,12 @@ async function readPackageVersion() {
8441
8593
  const candidates = [];
8442
8594
  try {
8443
8595
  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"));
8596
+ candidates.push(path10.resolve(path10.dirname(here), "..", "package.json"));
8597
+ candidates.push(path10.resolve(path10.dirname(here), "..", "..", "package.json"));
8598
+ candidates.push(path10.resolve(path10.dirname(here), "..", "..", "..", "package.json"));
8447
8599
  } catch {
8448
8600
  }
8449
- candidates.push(path9.resolve(process.cwd(), "package.json"));
8601
+ candidates.push(path10.resolve(process.cwd(), "package.json"));
8450
8602
  for (const c of candidates) {
8451
8603
  try {
8452
8604
  const raw = await fs8.readFile(c, "utf8");
@@ -8501,7 +8653,7 @@ function registerSystemRoutes(r) {
8501
8653
  init_esm_shims();
8502
8654
  init_addons();
8503
8655
  import { promises as fs10 } from "fs";
8504
- import path11 from "path";
8656
+ import path12 from "path";
8505
8657
  import os4 from "os";
8506
8658
  function registerAddonRoutes(r) {
8507
8659
  r.get("/api/addons", async () => {
@@ -8528,7 +8680,7 @@ function registerAddonRoutes(r) {
8528
8680
  const data = body;
8529
8681
  if (!data?.gaaBase64) throw new HttpError(400, "gaaBase64 required");
8530
8682
  const buf = Buffer.from(data.gaaBase64, "base64");
8531
- const tmpPath = path11.join(os4.tmpdir(), `upload-${Date.now()}.gaa`);
8683
+ const tmpPath = path12.join(os4.tmpdir(), `upload-${Date.now()}.gaa`);
8532
8684
  await fs10.writeFile(tmpPath, buf);
8533
8685
  try {
8534
8686
  const result = await installFromGaa(tmpPath, data.profileSlug);
@@ -8546,7 +8698,7 @@ function registerAddonRoutes(r) {
8546
8698
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
8547
8699
  if (!res.ok) throw new HttpError(502, `fetch failed: HTTP ${res.status}`);
8548
8700
  const buf = Buffer.from(await res.arrayBuffer());
8549
- const tmpPath = path11.join(os4.tmpdir(), `url-${Date.now()}.gaa`);
8701
+ const tmpPath = path12.join(os4.tmpdir(), `url-${Date.now()}.gaa`);
8550
8702
  await fs10.writeFile(tmpPath, buf);
8551
8703
  try {
8552
8704
  const result = await installFromGaa(tmpPath, data.profileSlug);
@@ -8606,7 +8758,6 @@ init_runtime_bus();
8606
8758
  init_esm_shims();
8607
8759
  init_communication();
8608
8760
  init_llm2();
8609
- init_mcp();
8610
8761
  init_stages();
8611
8762
  var CORE_KNOWLEDGE_BASE = [
8612
8763
  {
@@ -8635,7 +8786,7 @@ var CORE_KNOWLEDGE_BASE = [
8635
8786
  subcategory: "project-structure",
8636
8787
  title: "\u041A\u0430\u0440\u0442\u0430 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0439",
8637
8788
  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."
8789
+ 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
8790
  },
8640
8791
  {
8641
8792
  category: "overview",
@@ -8677,7 +8828,7 @@ var CORE_KNOWLEDGE_BASE = [
8677
8828
  subcategory: "profile-config",
8678
8829
  title: "ProfileConfig",
8679
8830
  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."
8831
+ 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
8832
  },
8682
8833
  {
8683
8834
  category: "config",
@@ -9036,13 +9187,6 @@ var CORE_KNOWLEDGE_BASE = [
9036
9187
  keywords: ["manifest", "addon", "id", "version", "compatibility", "settings"],
9037
9188
  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
9189
  },
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
9190
  {
9047
9191
  category: "migrations",
9048
9192
  subcategory: "data-migrations",
@@ -9089,11 +9233,11 @@ function generatedKnowledge() {
9089
9233
  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
9234
  },
9091
9235
  {
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")
9236
+ category: "addons",
9237
+ subcategory: "integration-index",
9238
+ title: "\u0410\u0434\u0434\u043E\u043D\u044B \u0432\u043C\u0435\u0441\u0442\u043E MCP",
9239
+ keywords: ["addons", "\u0430\u0434\u0434\u043E\u043D\u044B", "\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438", "gaa", "mcp"],
9240
+ 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
9241
  }
9098
9242
  ];
9099
9243
  }
@@ -9563,8 +9707,8 @@ function tail(text, limit) {
9563
9707
  if (text.length <= limit) return text;
9564
9708
  return text.slice(-limit);
9565
9709
  }
9566
- function setNested(obj, path13, value) {
9567
- const parts = path13.split(".");
9710
+ function setNested(obj, path14, value) {
9711
+ const parts = path14.split(".");
9568
9712
  let cur = obj;
9569
9713
  for (let i = 0; i < parts.length - 1; i++) {
9570
9714
  const p = parts[i];
@@ -9583,14 +9727,14 @@ var DEFAULT_PROXY = "https://tgproxy.girl-agent.com";
9583
9727
  function proxyUrl() {
9584
9728
  return process.env.GIRL_AGENT_AUTH_PROXY ?? DEFAULT_PROXY;
9585
9729
  }
9586
- async function post(path13, body) {
9587
- const res = await fetch(`${proxyUrl()}${path13}`, {
9730
+ async function post(path14, body) {
9731
+ const res = await fetch(`${proxyUrl()}${path14}`, {
9588
9732
  method: "POST",
9589
9733
  headers: { "Content-Type": "application/json" },
9590
9734
  body: JSON.stringify(body)
9591
9735
  });
9592
9736
  const data = await res.json();
9593
- if (!res.ok) throw new Error(data.error ?? `proxy ${path13} failed (${res.status})`);
9737
+ if (!res.ok) throw new Error(data.error ?? `proxy ${path14} failed (${res.status})`);
9594
9738
  return data;
9595
9739
  }
9596
9740
  function remoteSendCode(phone) {
@@ -9701,12 +9845,59 @@ function registerTgAuthRoutes(r) {
9701
9845
  });
9702
9846
  }
9703
9847
 
9848
+ // src/webui/routes/auth.ts
9849
+ init_esm_shims();
9850
+ function registerAuthRoutes(r) {
9851
+ r.get("/api/auth/status", () => authStatus());
9852
+ r.post("/api/auth/login", ({ body, res }) => {
9853
+ const { password } = body ?? {};
9854
+ if (!verifyPassword(password ?? "")) throw new HttpError(401, "\u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043F\u0430\u0440\u043E\u043B\u044C");
9855
+ createSession(res);
9856
+ return { ok: true };
9857
+ });
9858
+ r.post("/api/auth/logout", ({ req, res }) => {
9859
+ clearSession(req, res);
9860
+ return { ok: true };
9861
+ });
9862
+ }
9863
+
9704
9864
  // src/webui/server.ts
9705
9865
  init_md();
9706
9866
  var DEFAULT_PORT = Number(process.env.GIRL_AGENT_PORT ?? 3e3);
9707
- var DEFAULT_HOST = process.env.GIRL_AGENT_HOST ?? "127.0.0.1";
9867
+ function isLikelyDocker() {
9868
+ if (process.env.GIRL_AGENT_DOCKER || process.env.DOCKER_CONTAINER) return true;
9869
+ try {
9870
+ return os5.release().toLowerCase().includes("docker") || existsSync2("/.dockerenv") || readFileSync2("/proc/1/cgroup", "utf8").toLowerCase().includes("docker");
9871
+ } catch {
9872
+ return false;
9873
+ }
9874
+ }
9875
+ function firstExternalIPv4() {
9876
+ for (const items of Object.values(os5.networkInterfaces())) {
9877
+ for (const item of items ?? []) {
9878
+ if (item.family === "IPv4" && !item.internal) return item.address;
9879
+ }
9880
+ }
9881
+ return void 0;
9882
+ }
9883
+ function publicUrlForPort(port) {
9884
+ const explicit = process.env.GIRL_AGENT_PUBLIC_URL?.trim();
9885
+ if (explicit) {
9886
+ try {
9887
+ const url = new URL2(explicit);
9888
+ if (!url.port) url.port = String(port);
9889
+ return url.toString().replace(/\/$/, "");
9890
+ } catch {
9891
+ const clean = explicit.replace(/^https?:\/\//, "").replace(/\/+$/, "");
9892
+ return `http://${clean.includes(":") ? clean : `${clean}:${port}`}`;
9893
+ }
9894
+ }
9895
+ return `http://${firstExternalIPv4() ?? "0.0.0.0"}:${port}`;
9896
+ }
9897
+ var DEFAULT_HOST = process.env.GIRL_AGENT_HOST ?? (isLikelyDocker() ? "0.0.0.0" : "127.0.0.1");
9708
9898
  function buildRouter() {
9709
9899
  const r = new Router();
9900
+ registerAuthRoutes(r);
9710
9901
  registerProfileRoutes(r);
9711
9902
  registerPresetRoutes(r);
9712
9903
  registerSystemRoutes(r);
@@ -9730,6 +9921,10 @@ async function startWebUIServer(opts = {}) {
9730
9921
  const url2 = new URL2(req.url ?? "/", `http://${req.headers.host ?? host}`);
9731
9922
  const pathname = url2.pathname;
9732
9923
  if (pathname.startsWith("/api/")) {
9924
+ if (!pathname.startsWith("/api/auth/") && !isAuthorized(req)) {
9925
+ sendJson(res, 401, { error: "auth required" });
9926
+ return;
9927
+ }
9733
9928
  const matched = router.match(req.method ?? "GET", pathname);
9734
9929
  if (!matched) {
9735
9930
  sendJson(res, 404, { error: "not found", path: pathname });
@@ -9789,20 +9984,12 @@ async function startWebUIServer(opts = {}) {
9789
9984
  resolve();
9790
9985
  });
9791
9986
  });
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}`;
9987
+ const urls = {
9988
+ loopback: `http://127.0.0.1:${port}`,
9989
+ localhost: `http://localhost:${port}`,
9990
+ public: publicUrlForPort(port)
9991
+ };
9992
+ const url = urls.localhost;
9806
9993
  if (opts.autoStart) {
9807
9994
  try {
9808
9995
  const slugs = await listProfiles();
@@ -9820,6 +10007,7 @@ async function startWebUIServer(opts = {}) {
9820
10007
  port,
9821
10008
  host,
9822
10009
  url,
10010
+ urls,
9823
10011
  async stop() {
9824
10012
  await bus.stopAll();
9825
10013
  await new Promise((resolve) => server.close(() => resolve()));
@@ -9978,7 +10166,7 @@ init_llm2();
9978
10166
  init_stages();
9979
10167
  init_communication();
9980
10168
  import fs11 from "fs/promises";
9981
- import path12 from "path";
10169
+ import path13 from "path";
9982
10170
  import os6 from "os";
9983
10171
  init_md();
9984
10172
  init_runtime();
@@ -10010,7 +10198,7 @@ env-vars (\u0434\u043B\u044F CI / docker secrets / k8s):
10010
10198
  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
10199
 
10012
10200
  \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.
10201
+ \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
10202
  `;
10015
10203
  function parseServerArgs(argv) {
10016
10204
  return {
@@ -10061,7 +10249,7 @@ async function runServer(rawArgv) {
10061
10249
  }
10062
10250
  if (!args.yes) {
10063
10251
  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)}
10252
+ \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u043E: ${path13.join(DATA_ROOT, args.profile)}
10065
10253
  `);
10066
10254
  process.exit(1);
10067
10255
  }
@@ -10123,7 +10311,7 @@ data dir: ${DATA_ROOT}
10123
10311
  }
10124
10312
  async function persistAndMaybeStart(cfg, args) {
10125
10313
  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)}
10314
+ process.stderr.write(`[server] \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D: ${path13.join(DATA_ROOT, cfg.slug)}
10127
10315
  `);
10128
10316
  if (cfg.llm.apiKey || findPreset(cfg.llm.presetId)?.apiKeyRequired === false) {
10129
10317
  try {
@@ -10226,7 +10414,6 @@ function configFromEnv() {
10226
10414
  phone: e.GIRL_AGENT_TG_PHONE ?? "",
10227
10415
  proxy: parseTelegramProxy(e.GIRL_AGENT_TG_PROXY)
10228
10416
  },
10229
- mcp: [],
10230
10417
  ownerId: normalizeOwnerId(e.GIRL_AGENT_OWNER_ID),
10231
10418
  privacy: "owner-only",
10232
10419
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10240,7 +10427,7 @@ function configFromEnv() {
10240
10427
  };
10241
10428
  }
10242
10429
  async function loadConfigFile(file) {
10243
- const abs = path12.isAbsolute(file) ? file : path12.join(process.cwd(), file);
10430
+ const abs = path13.isAbsolute(file) ? file : path13.join(process.cwd(), file);
10244
10431
  let raw;
10245
10432
  try {
10246
10433
  raw = await fs11.readFile(abs, "utf-8");
@@ -10294,7 +10481,6 @@ function validateConfig(raw) {
10294
10481
  model: c.llm.model
10295
10482
  },
10296
10483
  telegram: c.telegram ?? {},
10297
- mcp: c.mcp ?? [],
10298
10484
  ownerId: normalizeOwnerId(c.ownerId ?? process.env.GIRL_AGENT_OWNER_ID),
10299
10485
  privacy: c.privacy ?? "owner-only",
10300
10486
  createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -10313,6 +10499,14 @@ function parseTelegramProxy(raw) {
10313
10499
  if (!raw?.trim()) return void 0;
10314
10500
  try {
10315
10501
  const url = new URL(raw);
10502
+ if (url.protocol === "tg:" && url.hostname === "proxy") {
10503
+ const ip = url.searchParams.get("server")?.trim();
10504
+ const port2 = Number(url.searchParams.get("port"));
10505
+ const secret = url.searchParams.get("secret")?.trim();
10506
+ if (!ip || !Number.isInteger(port2) || port2 <= 0 || !secret) return void 0;
10507
+ return { ip, port: port2, MTProxy: true, secret };
10508
+ }
10509
+ if (url.protocol !== "socks4:" && url.protocol !== "socks5:") return void 0;
10316
10510
  const socksType = url.protocol === "socks4:" ? 4 : 5;
10317
10511
  const port = Number(url.port);
10318
10512
  if (!url.hostname || !Number.isInteger(port) || port <= 0) return void 0;
@@ -10347,7 +10541,6 @@ function buildConfigTemplate() {
10347
10541
  model: "claude-sonnet-4.6"
10348
10542
  },
10349
10543
  telegram: { botToken: "REPLACE_ME" },
10350
- mcp: [],
10351
10544
  ownerId: void 0,
10352
10545
  privacy: "owner-only",
10353
10546
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10426,9 +10619,11 @@ docker run -d --name girl-agent --restart=unless-stopped \\
10426
10619
  # services:
10427
10620
  # girl-agent:
10428
10621
  # image: ghcr.io/thesashadev/girl-agent:latest
10622
+ # # interactive WebUI: command: [] and ports: ["3000:3000"]
10429
10623
  # command: ["server", "--config", "/config/bot.json", "--headless"]
10430
10624
  # environment:
10431
10625
  # GIRL_AGENT_DATA: /data
10626
+ # GIRL_AGENT_HOST: 0.0.0.0
10432
10627
  # volumes:
10433
10628
  # - girl-agent-data:/data
10434
10629
  # - ./bot.json:/config/bot.json:ro
@@ -10447,6 +10642,16 @@ init_llm();
10447
10642
  init_llm2();
10448
10643
  init_stages();
10449
10644
  init_communication();
10645
+ var nodeMajor = Number(process.versions.node.split(".")[0] ?? 0);
10646
+ if (nodeMajor < 18) {
10647
+ 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
10648
+ `);
10649
+ process.exit(1);
10650
+ }
10651
+ if (nodeMajor < 20) {
10652
+ 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.
10653
+ `);
10654
+ }
10450
10655
  var HELP = `
10451
10656
  girl-agent \u2014 AI girl for Telegram (WebUI)
10452
10657
 
@@ -10454,6 +10659,7 @@ usage:
10454
10659
  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
10660
  npx girl-agent --port=8080 # \u043A\u0430\u0441\u0442\u043E\u043C\u043D\u044B\u0439 \u043F\u043E\u0440\u0442
10456
10661
  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
10662
+ GIRL_AGENT_PUBLIC_URL=https://example.com npx girl-agent # URL \u0434\u043B\u044F reverse proxy/docker
10457
10663
  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
10664
  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
10665
 
@@ -10492,7 +10698,6 @@ async function main() {
10492
10698
  "proto",
10493
10699
  "name",
10494
10700
  "stage",
10495
- "mcp",
10496
10701
  "nationality",
10497
10702
  "tz",
10498
10703
  "vibe",
@@ -10651,20 +10856,19 @@ async function main() {
10651
10856
  autoStart: !argv.profile,
10652
10857
  noBrowser: !!argv["no-browser"]
10653
10858
  });
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
10859
  process.stdout.write(`
10657
10860
  \u{1F310} girl-agent WebUI \u0437\u0430\u043F\u0443\u0449\u0435\u043D
10658
- ${instance.url}
10659
10861
  `);
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}
10862
+ process.stdout.write(` 1) ${instance.urls.loopback}
10863
+ `);
10864
+ process.stdout.write(` 2) ${instance.urls.localhost}
10865
+ `);
10866
+ process.stdout.write(` 3) ${instance.urls.public}
10662
10867
  `);
10663
- }
10664
10868
  process.stdout.write(`
10665
- REST API: ${localUrl}/api/system/health
10869
+ REST API: ${instance.urls.loopback}/api/system/health
10666
10870
  `);
10667
- process.stdout.write(` WebSocket logs: ws://${showHost}:${port}/ws/logs/<slug>
10871
+ process.stdout.write(` WebSocket logs: ws://127.0.0.1:${port}/ws/logs/<slug>
10668
10872
  `);
10669
10873
  process.stdout.write(` Ctrl+C \u0434\u043B\u044F \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438
10670
10874
 
@@ -10734,17 +10938,11 @@ async function buildConfigFromFlags(argv) {
10734
10938
  const slug = String(argv.profile ?? slugifyLocal(name));
10735
10939
  const mode = argv.mode === "userbot" ? "userbot" : "bot";
10736
10940
  const tz = (argv.tz ? parseTzFlag(String(argv.tz)) : void 0) ?? defaultTzForNationality(nationality);
10737
- const mcpFlags = [].concat(argv.mcp ?? []);
10738
10941
  const communication = (() => {
10739
10942
  const preset2 = findCommunicationPreset(typeof argv["communication-preset"] === "string" ? argv["communication-preset"] : void 0);
10740
10943
  return preset2?.profile ?? normalizeCommunicationProfile({});
10741
10944
  })();
10742
10945
  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
10946
  return {
10749
10947
  slug,
10750
10948
  name,
@@ -10759,7 +10957,6 @@ async function buildConfigFromFlags(argv) {
10759
10957
  apiHash: String(argv["api-hash"] ?? ""),
10760
10958
  phone: String(argv.phone ?? "")
10761
10959
  },
10762
- mcp: mcps,
10763
10960
  privacy,
10764
10961
  ownerId: normalizeOwnerId(argv["owner-id"] ?? process.env.GIRL_AGENT_OWNER_ID),
10765
10962
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),