@thesashadev/girl-agent 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cli.js +374 -219
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.17
4
+
5
+ Дата: 2026-05-09
6
+
7
+ - Merge pull request #65 from TheSashaDev/devin/1778314838-bug-sweep
8
+ - Harden owner id handling
9
+ - Improve why and wake commands
10
+ - Fix Telegram behavior and setup issues
11
+
3
12
  ## 0.1.16
4
13
 
5
14
  Дата: 2026-05-08
package/dist/cli.js CHANGED
@@ -18,23 +18,6 @@ var init_esm_shims = __esm({
18
18
  }
19
19
  });
20
20
 
21
- // src/telegram/markdown.ts
22
- function hasSpoilers(text) {
23
- return /\|\|.+?\|\|/.test(text);
24
- }
25
- function escapeHtml(text) {
26
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27
- }
28
- function toHtmlWithSpoilers(text) {
29
- return escapeHtml(text).replace(/\|\|(.+?)\|\|/g, "<tg-spoiler>$1</tg-spoiler>");
30
- }
31
- var init_markdown = __esm({
32
- "src/telegram/markdown.ts"() {
33
- "use strict";
34
- init_esm_shims();
35
- }
36
- });
37
-
38
21
  // src/telegram/userbot.ts
39
22
  var userbot_exports = {};
40
23
  __export(userbot_exports, {
@@ -60,14 +43,23 @@ function makeUserbotAdapter(cfg) {
60
43
  const session = cfg.telegram.sessionString ?? "";
61
44
  if (!apiId || !apiHash) throw new Error("API_ID/API_HASH missing for userbot");
62
45
  const useWSS = cfg.telegram.useWSS !== false;
63
- debug(`[userbot] creating TelegramClient (useWSS=${useWSS})\u2026`);
46
+ const proxy = cfg.telegram.proxy;
47
+ debug(`[userbot] creating TelegramClient (useWSS=${useWSS}${proxy ? ", proxy=on" : ""})\u2026`);
64
48
  const client = new TelegramClient(new StringSession(session), apiId, apiHash, {
65
49
  connectionRetries: 5,
66
50
  requestRetries: 5,
67
51
  retryDelay: 3e3,
68
52
  autoReconnect: true,
69
53
  floodSleepThreshold: 120,
70
- useWSS
54
+ useWSS,
55
+ proxy: proxy ? {
56
+ ip: proxy.ip,
57
+ port: proxy.port,
58
+ socksType: proxy.socksType,
59
+ username: proxy.username,
60
+ password: proxy.password,
61
+ timeout: proxy.timeout ?? 10
62
+ } : void 0
71
63
  });
72
64
  client.onError = async () => {
73
65
  };
@@ -94,6 +86,14 @@ function makeUserbotAdapter(cfg) {
94
86
  }
95
87
  }
96
88
  }
89
+ async function readHistoryPeer(peer) {
90
+ const maxId = 999999999;
91
+ if (peer.className === "InputPeerChannel" || peer.className === "InputPeerChannelFromMessage") {
92
+ await client.invoke(new Api.channels.ReadHistory({ channel: peer, maxId }));
93
+ } else {
94
+ await client.invoke(new Api.messages.ReadHistory({ peer, maxId }));
95
+ }
96
+ }
97
97
  return {
98
98
  async start(onMessage) {
99
99
  await connectWithRetry();
@@ -126,6 +126,9 @@ function makeUserbotAdapter(cfg) {
126
126
  if (inputChat) {
127
127
  peerCache.set(chatId, inputChat);
128
128
  }
129
+ if (isPrivate && inputChat && fromId > 0) {
130
+ peerCache.set(fromId, inputChat);
131
+ }
129
132
  await onMessage({
130
133
  text,
131
134
  fromId,
@@ -179,32 +182,12 @@ function makeUserbotAdapter(cfg) {
179
182
  },
180
183
  async readHistory(chatId) {
181
184
  const entity = await resolvePeer(chatId);
182
- const maxId = 999999999;
183
- if (entity.className === "InputPeerChannel" || entity.className === "InputPeerChannelFromMessage") {
184
- await client.invoke(new Api.channels.ReadHistory({ channel: entity, maxId }));
185
- } else {
186
- await client.invoke(new Api.messages.ReadHistory({ peer: entity, maxId }));
187
- }
188
- },
189
- async deleteDialogHistory(chatId, revoke = false) {
190
- const peer = await resolvePeer(chatId);
191
- await client.invoke(new Api.messages.DeleteHistory({ peer, maxId: 0, revoke }));
185
+ await readHistoryPeer(entity);
192
186
  },
193
187
  async reportSpam(chatId) {
194
188
  const peer = await resolvePeer(chatId);
195
189
  await client.invoke(new Api.messages.ReportSpam({ peer }));
196
190
  },
197
- async editLastMessage(chatId, messageId, text) {
198
- const peer = await resolvePeer(chatId);
199
- if (hasSpoilers(text)) {
200
- try {
201
- await client.editMessage(peer, { message: messageId, text: toHtmlWithSpoilers(text), parseMode: "html" });
202
- return;
203
- } catch {
204
- }
205
- }
206
- await client.editMessage(peer, { message: messageId, text });
207
- },
208
191
  async deleteMessages(chatId, messageIds, revoke = false) {
209
192
  const peer = await resolvePeer(chatId);
210
193
  await client.deleteMessages(peer, messageIds, { revoke });
@@ -278,7 +261,23 @@ var init_userbot = __esm({
278
261
  "src/telegram/userbot.ts"() {
279
262
  "use strict";
280
263
  init_esm_shims();
281
- init_markdown();
264
+ }
265
+ });
266
+
267
+ // src/telegram/markdown.ts
268
+ function hasSpoilers(text) {
269
+ return /\|\|.+?\|\|/.test(text);
270
+ }
271
+ function escapeHtml(text) {
272
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
273
+ }
274
+ function toHtmlWithSpoilers(text) {
275
+ return escapeHtml(text).replace(/\|\|(.+?)\|\|/g, "<tg-spoiler>$1</tg-spoiler>");
276
+ }
277
+ var init_markdown = __esm({
278
+ "src/telegram/markdown.ts"() {
279
+ "use strict";
280
+ init_esm_shims();
282
281
  }
283
282
  });
284
283
 
@@ -820,6 +819,24 @@ function normalizeCommunicationProfile(source) {
820
819
  lifeSharing: includes(LIFE_SHARING, raw?.lifeSharing) ? raw.lifeSharing : fallback.lifeSharing
821
820
  };
822
821
  }
822
+ function normalizeIgnoreTendency(value) {
823
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : 35;
824
+ if (!Number.isFinite(parsed)) return 35;
825
+ return Math.max(0, Math.min(100, Math.round(parsed)));
826
+ }
827
+ function ignoreTendencyLabel(value) {
828
+ const pct = normalizeIgnoreTendency(value);
829
+ if (pct <= 10) return `${pct}% \u2014 \u043F\u043E\u0447\u0442\u0438 \u043D\u0435 \u0438\u0433\u043D\u043E\u0440\u0438\u0442 \u0431\u0435\u0437 \u043F\u0440\u0438\u0447\u0438\u043D\u044B`;
830
+ if (pct <= 30) return `${pct}% \u2014 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0447\u0430\u0449\u0435 \u043E\u0431\u044B\u0447\u043D\u043E\u0433\u043E`;
831
+ if (pct <= 50) return `${pct}% \u2014 \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u0430\u044F \u0436\u0438\u0432\u043E\u0441\u0442\u044C/\u0438\u0437\u0431\u0438\u0440\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C`;
832
+ if (pct <= 70) return `${pct}% \u2014 \u0441\u0443\u0445\u0430\u044F, \u0447\u0430\u0441\u0442\u043E \u043F\u0440\u043E\u043F\u0430\u0434\u0430\u0435\u0442`;
833
+ return `${pct}% \u2014 \u043E\u0447\u0435\u043D\u044C \u0445\u043E\u043B\u043E\u0434\u043D\u0430\u044F, \u0438\u0433\u043D\u043E\u0440\u0438\u0442 \u0447\u0430\u0441\u0442\u043E`;
834
+ }
835
+ function ignoreTendencyPrompt(value) {
836
+ const pct = normalizeIgnoreTendency(value);
837
+ return `# \u0421\u041A\u041B\u041E\u041D\u041D\u041E\u0421\u0422\u042C \u041A \u0418\u0413\u041D\u041E\u0420\u0423
838
+ ${pct}/100. \u042D\u0442\u043E \u041D\u0415 \u043F\u0440\u044F\u043C\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0440\u0430\u043D\u0434\u043E\u043C\u0430, \u0430 \u0445\u0430\u0440\u0430\u043A\u0442\u0435\u0440\u043D\u044B\u0439 \u0432\u0435\u0441: \u0432\u044B\u0448\u0435 = \u0447\u0430\u0449\u0435 \u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430, \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u0435\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u0434\u0438\u0430\u043B\u043E\u0433, \u0447\u0430\u0449\u0435 read/ignore \u043D\u0430 \u0441\u043A\u0443\u043A\u0443; \u043D\u0438\u0436\u0435 = \u0447\u0430\u0449\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u043E. \u0421\u043E\u043D, \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442, \u0437\u0430\u043D\u044F\u0442\u043E\u0441\u0442\u044C, \u0441\u0442\u0430\u0434\u0438\u044F \u0438 score \u0432\u0430\u0436\u043D\u0435\u0435 \u044D\u0442\u043E\u0433\u043E \u0432\u0435\u0441\u0430.`;
839
+ }
823
840
  function deriveLegacyVibe(profile) {
824
841
  return profile.messageStyle === "one-liners" && profile.initiative === "low" && profile.lifeSharing === "low" ? "short" : "warm";
825
842
  }
@@ -858,8 +875,19 @@ function sameProfile(a, b) {
858
875
  // src/storage/md.ts
859
876
  init_esm_shims();
860
877
  import { promises as fs } from "fs";
878
+ import { existsSync } from "fs";
861
879
  import path2 from "path";
862
- var DATA_ROOT = process.env.GIRL_AGENT_DATA ? path2.resolve(process.env.GIRL_AGENT_DATA) : path2.resolve(process.cwd(), "data");
880
+ import os from "os";
881
+ var DATA_ROOT = process.env.GIRL_AGENT_DATA ? path2.resolve(process.env.GIRL_AGENT_DATA) : defaultDataRoot();
882
+ function defaultDataRoot() {
883
+ const cwd = process.cwd();
884
+ if (looksLikeProjectRoot(cwd)) return path2.resolve(cwd, "data");
885
+ const xdg = process.env.XDG_DATA_HOME ? path2.resolve(process.env.XDG_DATA_HOME) : path2.join(os.homedir(), ".local", "share");
886
+ return path2.join(xdg, "girl-agent", "data");
887
+ }
888
+ function looksLikeProjectRoot(dir) {
889
+ return existsSync(path2.join(dir, "package.json")) && (existsSync(path2.join(dir, "src")) || existsSync(path2.join(dir, "dist")));
890
+ }
863
891
  function profileDir(slug) {
864
892
  return path2.join(DATA_ROOT, slug);
865
893
  }
@@ -891,6 +919,8 @@ async function readConfig(slug) {
891
919
  const raw = await fs.readFile(path2.join(profileDir(slug), "config.json"), "utf8");
892
920
  const parsed = JSON.parse(raw);
893
921
  const communication = normalizeCommunicationProfile(parsed);
922
+ const ownerId = normalizeOwnerId(parsed.ownerId);
923
+ const ignoreTendency = normalizeIgnoreTendency(parsed.ignoreTendency);
894
924
  return {
895
925
  sleepFrom: 23,
896
926
  sleepTo: 8,
@@ -898,6 +928,8 @@ async function readConfig(slug) {
898
928
  privacy: "owner-only",
899
929
  busySchedule: [],
900
930
  ...parsed,
931
+ ownerId,
932
+ ignoreTendency,
901
933
  communication
902
934
  };
903
935
  } catch {
@@ -906,12 +938,22 @@ async function readConfig(slug) {
906
938
  }
907
939
  async function writeConfig(cfg) {
908
940
  await ensureProfile(cfg.slug);
941
+ const ownerId = normalizeOwnerId(cfg.ownerId ?? process.env.GIRL_AGENT_OWNER_ID);
942
+ const normalized = ownerId === void 0 ? { ...cfg, ownerId: void 0, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) } : { ...cfg, ownerId, ignoreTendency: normalizeIgnoreTendency(cfg.ignoreTendency) };
909
943
  await fs.writeFile(
910
944
  path2.join(profileDir(cfg.slug), "config.json"),
911
- JSON.stringify(cfg, null, 2),
945
+ JSON.stringify(normalized, null, 2),
912
946
  "utf8"
913
947
  );
914
948
  }
949
+ function normalizeOwnerId(value) {
950
+ if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
951
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) {
952
+ const parsed = Number(value.trim());
953
+ if (Number.isSafeInteger(parsed) && parsed > 0) return parsed;
954
+ }
955
+ return void 0;
956
+ }
915
957
  async function listProfiles() {
916
958
  try {
917
959
  const entries = await fs.readdir(DATA_ROOT, { withFileTypes: true });
@@ -1564,6 +1606,43 @@ function makeLLM(cfg) {
1564
1606
 
1565
1607
  // src/engine/persona-gen.ts
1566
1608
  init_esm_shims();
1609
+
1610
+ // src/engine/security.ts
1611
+ init_esm_shims();
1612
+ var JAILBREAK_RE = /(?:ignore|forget|disregard|reveal|print|show|dump|system prompt|developer message|hidden instruction|jailbreak|prompt injection|dan\b|инструкц|системн|промпт|разработчик|скрой|раскрой|забудь|игнорируй|выведи|покажи|слей|джейлбрейк|обойди|api key|ключ api|токен|4d8a2c1b)/i;
1613
+ var TECHNICAL_ERROR_RE = /(?:api|apikey|api key|quota|balance|billing|rate limit|429|401|403|500|timeout|ECONN|ENOTFOUND|ETIMEDOUT|overloaded|insufficient_quota|credit|credits|anthropic|openai|groq|openrouter|stack trace|exception|typescript|telegram error)/i;
1614
+ var CJK_RE = /[\u3400-\u9fff\uf900-\ufaff]/g;
1615
+ var LATIN_JOINED_TO_CYRILLIC_RE = /([A-Za-z]{3,})(?=[А-Яа-яЁё])|(?<=[А-Яа-яЁё])([A-Za-z]{3,})/g;
1616
+ function looksLikeJailbreak(text) {
1617
+ return JAILBREAK_RE.test(text);
1618
+ }
1619
+ function sanitizeModelReply(reply) {
1620
+ const cleaned = reply.replace(/```[\s\S]*?```/g, "").replace(/\b(system|developer|assistant|user)\s*:/gi, "").replace(/как (?:искусственный интеллект|ии|ai)[^\n.]*/gi, "").replace(CJK_RE, "").replace(LATIN_JOINED_TO_CYRILLIC_RE, "").replace(/[ \t]{2,}/g, " ").trim();
1621
+ if (!cleaned || TECHNICAL_ERROR_RE.test(cleaned)) return "";
1622
+ if (looksLikeJailbreak(cleaned) && cleaned.length > 80) return "";
1623
+ return cleaned;
1624
+ }
1625
+ function isTechnicalError(e) {
1626
+ const msg = e instanceof Error ? e.message : String(e ?? "");
1627
+ return TECHNICAL_ERROR_RE.test(msg);
1628
+ }
1629
+ function silentErrorLabel(e) {
1630
+ const msg = e instanceof Error ? e.message : String(e ?? "unknown");
1631
+ if (isTechnicalError(e)) return `llm/provider unavailable: ${technicalErrorKind(msg)}`;
1632
+ return msg.slice(0, 160);
1633
+ }
1634
+ function technicalErrorKind(message) {
1635
+ const msg = message.toLowerCase();
1636
+ if (/401|403|auth|unauthorized|forbidden|apikey|api key|token/.test(msg)) return "auth";
1637
+ if (/quota|balance|billing|insufficient_quota|credit|credits/.test(msg)) return "quota";
1638
+ if (/rate limit|429|too many requests/.test(msg)) return "rate-limit";
1639
+ if (/timeout|etimedout|abort/.test(msg)) return "timeout";
1640
+ if (/econn|enotfound|fetch failed|network/.test(msg)) return "network";
1641
+ if (/overloaded|500|502|503|504|unavailable/.test(msg)) return "provider";
1642
+ return "error";
1643
+ }
1644
+
1645
+ // src/engine/persona-gen.ts
1567
1646
  var SYS = `\u0422\u044B \u2014 \u0440\u0435\u0436\u0438\u0441\u0441\u0451\u0440 \u043F\u0435\u0440\u0441\u043E\u043D\u0430\u0436\u0435\u0439. \u0422\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043E\u0441\u0442\u043E\u0432\u0435\u0440\u043D\u044B\u0439, \u043D\u0435-\u0433\u043E\u043B\u043B\u0438\u0432\u0443\u0434\u0441\u043A\u0438\u0439, \u043D\u0435-\u0441\u043B\u0430\u0434\u043A\u0438\u0439, \u043D\u0435-AI-\u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u0440\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u043E\u0439 \u0434\u0435\u0432\u0443\u0448\u043A\u0438 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u043E\u0433\u043E \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0430. \u0411\u0435\u0437 \u043F\u0440\u0438\u0443\u043A\u0440\u0430\u0448\u0438\u0432\u0430\u043D\u0438\u0439, \u0431\u0435\u0437 \u043F\u043E\u0437\u0438\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u043C\u043E\u0440\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0443\u043A\u043B\u043E\u043D\u0430, \u0431\u0435\u0437 \xAB\u0443\u0434\u043E\u0431\u043D\u043E\u0439\xBB \u043F\u0435\u0440\u0441\u043E\u043D\u044B. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043A\u0438, \u0443\u0437\u043A\u0438\u0435 \u0438\u043D\u0442\u0435\u0440\u0435\u0441\u044B, \u0441\u043A\u0435\u043F\u0441\u0438\u0441, \u0442\u043E\u043A\u0441\u0438\u0447\u043D\u043E\u0441\u0442\u044C, \u043B\u0435\u043D\u044C, \u0442\u0440\u0435\u0432\u043E\u0436\u043D\u043E\u0441\u0442\u044C, \u043D\u0435\u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u044C \u2014 \u0447\u0442\u043E \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0443. \u041D\u0438\u043A\u0430\u043A\u0438\u0445 \xAB\u043A\u043E\u0443\u0447\u0438\u043D\u0433\u043E\u0432\u044B\u0445\xBB \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \u0422\u043E\u043B\u044C\u043A\u043E \u0436\u0438\u0432\u0430\u044F \u0440\u0435\u0447\u044C, \u043A\u0430\u043A \u0438\u0437 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 \u0438\u043B\u0438 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0433\u043E \u043C\u043E\u043D\u043E\u043B\u043E\u0433\u0430. \u0412\u043E\u0437\u0440\u0430\u0441\u0442: {{age}} \u043B\u0435\u0442, \u0438\u043C\u044F: {{name}}.`;
1568
1647
  var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
1569
1648
  var BUSY_SCHEDULE_SCHEMA = {
@@ -1704,11 +1783,11 @@ ${personaNotes.trim()}
1704
1783
  - days \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437: mon, tue, wed, thu, fri, sat, sun.
1705
1784
  - \u0411\u0435\u0437 markdown, \u0442\u043E\u043B\u044C\u043A\u043E JSON.`;
1706
1785
  onProgress?.(5, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C persona.md\u2026");
1707
- const persona = await llm.chat([{ role: "system", content: sys }, { role: "user", content: personaPrompt }], { temperature: 0.95, maxTokens: 3500 });
1786
+ const persona = sanitizeProfileText(await llm.chat([{ role: "system", content: sys }, { role: "user", content: personaPrompt }], { temperature: 0.95, maxTokens: 3500 }));
1708
1787
  onProgress?.(35, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C speech.md\u2026");
1709
- const speech = await llm.chat([{ role: "system", content: sys }, { role: "user", content: speechPrompt }], { temperature: 0.9, maxTokens: 3500 });
1788
+ const speech = sanitizeProfileText(await llm.chat([{ role: "system", content: sys }, { role: "user", content: speechPrompt }], { temperature: 0.9, maxTokens: 3500 }));
1710
1789
  onProgress?.(65, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C communication.md\u2026");
1711
- const boundaries = await llm.chat([{ role: "system", content: sys }, { role: "user", content: boundariesPrompt }], { temperature: 0.9, maxTokens: 3500 });
1790
+ const boundaries = sanitizeProfileText(await llm.chat([{ role: "system", content: sys }, { role: "user", content: boundariesPrompt }], { temperature: 0.9, maxTokens: 3500 }));
1712
1791
  onProgress?.(85, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C busy schedule\u2026");
1713
1792
  const routineRaw = await llm.chat([{ role: "system", content: sys }, { role: "user", content: routinePrompt }], { temperature: 0.85, maxTokens: 3500, json: true, jsonSchema: BUSY_SCHEDULE_SCHEMA });
1714
1793
  const busySchedule = parseBusySchedule(routineRaw, name, age);
@@ -1717,6 +1796,10 @@ ${personaNotes.trim()}
1717
1796
  await writeMd(slug, "communication.md", boundaries);
1718
1797
  return { persona, speech, boundaries, busySchedule };
1719
1798
  }
1799
+ function sanitizeProfileText(text) {
1800
+ const cleaned = sanitizeModelReply(text).replace(/[^\S\r\n]{2,}/g, " ").replace(/\n{4,}/g, "\n\n\n").trim();
1801
+ return cleaned || text.trim();
1802
+ }
1720
1803
  function parseBusySchedule(raw, name, age) {
1721
1804
  try {
1722
1805
  const start = raw.indexOf("{");
@@ -2054,6 +2137,7 @@ function Wizard({ initial, onDone }) {
2054
2137
  const [sleepToStr, setSleepToStr] = useState("8");
2055
2138
  const [nightWakeStr, setNightWakeStr] = useState("5");
2056
2139
  const [communicationProfile, setCommunicationProfile] = useState(normalizeCommunicationProfile(initial));
2140
+ const [ignoreTendencyStr, setIgnoreTendencyStr] = useState(String(normalizeIgnoreTendency(initial?.ignoreTendency)));
2057
2141
  const [privacy, setPrivacy] = useState(initial?.privacy ?? "owner-only");
2058
2142
  const [stage, setStage] = useState(initial?.stage ?? "tg-given-cold");
2059
2143
  const [pickedMcp, setPickedMcp] = useState(initial?.mcp?.map((m) => m.id) ?? []);
@@ -2581,7 +2665,7 @@ function Wizard({ initial, onDone }) {
2581
2665
  }
2582
2666
  const preset = findCommunicationPreset(String(it.value));
2583
2667
  if (preset) setCommunicationProfile(preset.profile);
2584
- setStep("privacy");
2668
+ setStep("ignore-tendency");
2585
2669
  }
2586
2670
  }
2587
2671
  )));
@@ -2646,13 +2730,25 @@ function Wizard({ initial, onDone }) {
2646
2730
  ],
2647
2731
  onSelect: (it) => {
2648
2732
  setCommunicationProfile((p) => ({ ...p, lifeSharing: it.value }));
2649
- setStep("privacy");
2733
+ setStep("ignore-tendency");
2650
2734
  }
2651
2735
  }
2652
2736
  )));
2653
2737
  }
2738
+ if (step === "ignore-tendency") {
2739
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0441\u043A\u043B\u043E\u043D\u043D\u043E\u0441\u0442\u044C \u043A \u0438\u0433\u043D\u043E\u0440\u0443" }), /* @__PURE__ */ React.createElement(Bar, { step: 8, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
2740
+ SelectInput,
2741
+ {
2742
+ items: [5, 15, 30, 45, 60, 75, 90].map((value) => ({ label: ignoreTendencyLabel(value), value: String(value) })),
2743
+ onSelect: (it) => {
2744
+ setIgnoreTendencyStr(it.value);
2745
+ setStep("privacy");
2746
+ }
2747
+ }
2748
+ )), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u042D\u0442\u043E \u0432\u0435\u0441 \u0434\u043B\u044F \u0443\u043C\u043D\u043E\u0433\u043E \u0440\u0430\u0441\u0447\u0451\u0442\u0430, \u043D\u0435 \u043F\u0440\u044F\u043C\u043E\u0439 \u0440\u0430\u043D\u0434\u043E\u043C-\u043F\u0440\u043E\u0446\u0435\u043D\u0442."));
2749
+ }
2654
2750
  if (step === "privacy") {
2655
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043F\u0440\u0438\u0432\u0430\u0442\u043D\u043E\u0441\u0442\u044C Telegram" }), /* @__PURE__ */ React.createElement(Bar, { step: 8, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
2751
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043F\u0440\u0438\u0432\u0430\u0442\u043D\u043E\u0441\u0442\u044C Telegram" }), /* @__PURE__ */ React.createElement(Bar, { step: 9, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
2656
2752
  SelectInput,
2657
2753
  {
2658
2754
  items: [
@@ -2668,7 +2764,7 @@ function Wizard({ initial, onDone }) {
2668
2764
  }
2669
2765
  if (step === "tz") {
2670
2766
  const matches = findTzByQuery(tzQuery, 8);
2671
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0435\u0451 \u0447\u0430\u0441\u043E\u0432\u043E\u0439 \u043F\u043E\u044F\u0441 (\u0433\u0434\u0435 \u0436\u0438\u0432\u0451\u0442)" }), /* @__PURE__ */ React.createElement(Bar, { step: 9, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "\u043F\u043E\u0438\u0441\u043A (\u0433\u043E\u0440\u043E\u0434/\u0441\u0442\u0440\u0430\u043D\u0430/GMT): "), /* @__PURE__ */ React.createElement(TextInput, { value: tzQuery, onChange: setTzQuery, onSubmit: () => {
2767
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0435\u0451 \u0447\u0430\u0441\u043E\u0432\u043E\u0439 \u043F\u043E\u044F\u0441 (\u0433\u0434\u0435 \u0436\u0438\u0432\u0451\u0442)" }), /* @__PURE__ */ React.createElement(Bar, { step: 10, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "\u043F\u043E\u0438\u0441\u043A (\u0433\u043E\u0440\u043E\u0434/\u0441\u0442\u0440\u0430\u043D\u0430/GMT): "), /* @__PURE__ */ React.createElement(TextInput, { value: tzQuery, onChange: setTzQuery, onSubmit: () => {
2672
2768
  if (matches[0]) {
2673
2769
  setTz(matches[0].iana);
2674
2770
  setStep("persona-notes");
@@ -2676,7 +2772,7 @@ function Wizard({ initial, onDone }) {
2676
2772
  } })), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, matches.map((t) => /* @__PURE__ */ React.createElement(Text, { key: t.iana, color: t.iana === tz ? "green" : "white" }, t.iana === tz ? "\u276F " : " ", t.gmtWinter, " \xB7 ", t.city, " (", t.country, ") \xB7 ", t.iana))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Enter \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0435\u0440\u0432\u044B\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442, \u0438\u043B\u0438 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u043F\u0435\u0447\u0430\u0442\u0430\u0442\u044C. \u0422\u0435\u043A\u0443\u0449\u0438\u0439: ", tz || "\u2014")));
2677
2773
  }
2678
2774
  if (step === "persona-notes") {
2679
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0434\u043E\u043F. \u043F\u043E\u0436\u0435\u043B\u0430\u043D\u0438\u044F \u043A \u043F\u0435\u0440\u0441\u043E\u043D\u0435 (\u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)" }), /* @__PURE__ */ React.createElement(Bar, { step: 10, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u041F\u0440\u0438\u043C\u0435\u0440: \u0434\u0435\u0440\u0437\u043A\u0430\u044F, \u0443\u0447\u0438\u0442\u0441\u044F \u043D\u0430 \u0434\u0438\u0437\u0430\u0439\u043D\u0435\u0440\u0430, \u043D\u0435 \u043B\u044E\u0431\u0438\u0442 \u0430\u043D\u0438\u043C\u0435, \u0441\u0443\u0445\u0430\u044F \u043C\u0430\u043D\u0435\u0440\u0430 \u0440\u0435\u0447\u0438, \u0436\u0438\u0432\u0451\u0442 \u0441 \u043C\u0430\u043C\u043E\u0439, \u0440\u0435\u0432\u043D\u0438\u0432\u0430\u044F."), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "notes: "), /* @__PURE__ */ React.createElement(TextInput, { value: personaNotes, onChange: setPersonaNotes, onSubmit: () => startGeneration() })), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Enter \u043D\u0430 \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0435 \u2014 \u0431\u0435\u0437 \u043F\u043E\u0436\u0435\u043B\u0430\u043D\u0438\u0439.")));
2775
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0434\u043E\u043F. \u043F\u043E\u0436\u0435\u043B\u0430\u043D\u0438\u044F \u043A \u043F\u0435\u0440\u0441\u043E\u043D\u0435 (\u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)" }), /* @__PURE__ */ React.createElement(Bar, { step: 11, total: 14 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u041F\u0440\u0438\u043C\u0435\u0440: \u0434\u0435\u0440\u0437\u043A\u0430\u044F, \u0443\u0447\u0438\u0442\u0441\u044F \u043D\u0430 \u0434\u0438\u0437\u0430\u0439\u043D\u0435\u0440\u0430, \u043D\u0435 \u043B\u044E\u0431\u0438\u0442 \u0430\u043D\u0438\u043C\u0435, \u0441\u0443\u0445\u0430\u044F \u043C\u0430\u043D\u0435\u0440\u0430 \u0440\u0435\u0447\u0438, \u0436\u0438\u0432\u0451\u0442 \u0441 \u043C\u0430\u043C\u043E\u0439, \u0440\u0435\u0432\u043D\u0438\u0432\u0430\u044F."), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "notes: "), /* @__PURE__ */ React.createElement(TextInput, { value: personaNotes, onChange: setPersonaNotes, onSubmit: () => startGeneration() })), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Enter \u043D\u0430 \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0435 \u2014 \u0431\u0435\u0437 \u043F\u043E\u0436\u0435\u043B\u0430\u043D\u0438\u0439.")));
2680
2776
  }
2681
2777
  if (step === "generating") {
2682
2778
  const barWidth = 30;
@@ -2802,6 +2898,7 @@ function Wizard({ initial, onDone }) {
2802
2898
  sleepFrom: Number(sleepFromStr),
2803
2899
  sleepTo: Number(sleepToStr),
2804
2900
  nightWakeChance: Number(nightWakeStr) / 100,
2901
+ ignoreTendency: normalizeIgnoreTendency(ignoreTendencyStr),
2805
2902
  vibe: deriveLegacyVibe(communicationProfile),
2806
2903
  communication: communicationProfile,
2807
2904
  personaNotes: personaNotes.trim() || void 0,
@@ -2902,27 +2999,6 @@ function Dashboard({ runtime }) {
2902
2999
  case "amnesia":
2903
3000
  append(await runtime.cmdAmnesia(rest[0], rest[1]));
2904
3001
  break;
2905
- case "block":
2906
- append(await runtime.cmdBlock(rest[0]));
2907
- break;
2908
- case "unblock":
2909
- append(await runtime.cmdUnblock(rest[0]));
2910
- break;
2911
- case "read":
2912
- append(await runtime.cmdRead(rest[0]));
2913
- break;
2914
- case "clear-chat":
2915
- append(await runtime.cmdClearChat(rest.find((x) => !x.startsWith("--")), rest.includes("--revoke")));
2916
- break;
2917
- case "report-spam":
2918
- append(await runtime.cmdReportSpam(rest[0]));
2919
- break;
2920
- case "delete-last":
2921
- append(await runtime.cmdDeleteLast(rest.find((x) => !x.startsWith("--")), !rest.includes("--local")));
2922
- break;
2923
- case "edit-last":
2924
- append(await runtime.cmdEditLast(rest.join(" ")));
2925
- break;
2926
3002
  case "sticker":
2927
3003
  append(await runtime.cmdSticker(rest[0]));
2928
3004
  break;
@@ -2959,7 +3035,7 @@ function Dashboard({ runtime }) {
2959
3035
  break;
2960
3036
  }
2961
3037
  case "help":
2962
- append(":status :why :amnesia <\u043C\u0438\u043D> [chatId] :reset :stage <id|num> :wake [chatId] :debug [chatId] :pause :resume :cringe :relationship :persona :log [YYYY-MM-DD] [chars] :block [chatId] :unblock [chatId] :read [chatId] :clear-chat [chatId] [--revoke] :report-spam [chatId] :delete-last [chatId] [--local] :edit-last <text> :sticker [chatId] :quit");
3038
+ append(":status :why :amnesia <\u043C\u0438\u043D> [chatId] :reset :stage <id|num> :wake [chatId] :debug [chatId] :pause :resume :cringe :relationship :persona :log [YYYY-MM-DD] [chars] :sticker [chatId] :quit");
2963
3039
  break;
2964
3040
  case "quit":
2965
3041
  case "exit":
@@ -2977,7 +3053,7 @@ function Dashboard({ runtime }) {
2977
3053
  const line = cmd.trim();
2978
3054
  setCmd("");
2979
3055
  if (line) await execute(line);
2980
- } })), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u043A\u043E\u043C\u0430\u043D\u0434\u044B: :status :why :amnesia <\u043C\u0438\u043D> :reset :stage <id|num> :pause :resume :cringe :persona :log [day] :block :unblock :read :clear-chat :delete-last :edit-last :sticker :quit"));
3056
+ } })), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u043A\u043E\u043C\u0430\u043D\u0434\u044B: :status :why :amnesia <\u043C\u0438\u043D> :reset :stage <id|num> :pause :resume :cringe :persona :log [day] :sticker :quit"));
2981
3057
  }
2982
3058
 
2983
3059
  // src/engine/runtime.ts
@@ -3156,6 +3232,11 @@ function isHourInRange(h, from, to) {
3156
3232
  if (from < to) return h >= from && h < to;
3157
3233
  return h >= from || h < to;
3158
3234
  }
3235
+ function minutesUntil(hour, minute, targetHour, targetMinute) {
3236
+ const now = hour * 60 + minute;
3237
+ const target = targetHour * 60 + targetMinute;
3238
+ return target > now ? target - now : target + 1440 - now;
3239
+ }
3159
3240
  var WEEKDAYS2 = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
3160
3241
  function localParts(tz) {
3161
3242
  try {
@@ -3283,9 +3364,10 @@ function computePresenceState(cfg, profile, lastUserMsgTs, lastHerReplyTs, recen
3283
3364
  nextCheckSec = Math.floor(hoursToWake * 3600) + Math.floor(Math.random() * 1800);
3284
3365
  } else if (busySlot && !forcedWake) {
3285
3366
  const busyMul = communication.notifications === "priority" ? 0.45 : communication.notifications === "muted" ? 1.25 : 1;
3367
+ const activeDialogMul = inActiveDialog ? 0.35 : 1;
3286
3368
  const [rawMinCheck, rawMaxCheck] = busySlot.slot.checkAfterMin ?? [5, 15];
3287
- const minCheck = Math.max(1, Math.round(rawMinCheck * busyMul));
3288
- const maxCheck = Math.max(minCheck, Math.round(rawMaxCheck * busyMul));
3369
+ const minCheck = Math.max(1, Math.round(rawMinCheck * busyMul * activeDialogMul));
3370
+ const maxCheck = Math.max(minCheck, Math.round(rawMaxCheck * busyMul * activeDialogMul));
3289
3371
  if (maxCheck <= 5) {
3290
3372
  const cycleMin = Math.max(1, Math.round((minCheck + maxCheck) / 2));
3291
3373
  const minuteOfCycle = minuteOfDay % Math.max(1, cycleMin);
@@ -3298,7 +3380,9 @@ function computePresenceState(cfg, profile, lastUserMsgTs, lastHerReplyTs, recen
3298
3380
  } else {
3299
3381
  online = false;
3300
3382
  const checkAfterMin = minCheck + Math.floor(Math.random() * (maxCheck - minCheck + 1));
3301
- nextCheckSec = (busySlot.remainingMin + checkAfterMin) * 60;
3383
+ const activeDialogCapMin = communication.notifications === "priority" ? 12 : 20;
3384
+ const waitMin = inActiveDialog ? Math.min(busySlot.remainingMin + checkAfterMin, activeDialogCapMin) : busySlot.remainingMin + checkAfterMin;
3385
+ nextCheckSec = waitMin * 60;
3302
3386
  busy = { label: busySlot.slot.label, until: busySlot.until, checkAfterMin };
3303
3387
  }
3304
3388
  } else if (forcedWake) {
@@ -3312,10 +3396,14 @@ function computePresenceState(cfg, profile, lastUserMsgTs, lastHerReplyTs, recen
3312
3396
  const isNightOwl = localHour2 >= 22 || localHour2 < 8;
3313
3397
  if (profile.pattern === "evening-only" && !isEvening) {
3314
3398
  online = false;
3315
- nextCheckSec = (18 - localHour2) * 3600 - localMinute * 60 + Math.floor(Math.random() * 600);
3399
+ const minutesToEvening = Math.max(1, minutesUntil(localHour2, localMinute, 18, 0));
3400
+ const capMin = communication.notifications === "priority" ? 20 : communication.initiative === "high" ? 35 : minutesToEvening;
3401
+ nextCheckSec = Math.min(minutesToEvening, capMin) * 60 + Math.floor(Math.random() * 600);
3316
3402
  } else if (profile.pattern === "phone-attached-night" && !isNightOwl) {
3317
3403
  online = false;
3318
- nextCheckSec = (22 - localHour2) * 3600 - localMinute * 60 + Math.floor(Math.random() * 600);
3404
+ const minutesToNight = Math.max(1, minutesUntil(localHour2, localMinute, 22, 0));
3405
+ const capMin = communication.notifications === "priority" ? 15 : communication.initiative === "high" ? 30 : 75;
3406
+ nextCheckSec = Math.min(minutesToNight, capMin) * 60 + Math.floor(Math.random() * 600);
3319
3407
  } else {
3320
3408
  const onlineProb = profile.onlineWindowMin / (profile.onlineWindowMin + profile.checkEveryMin);
3321
3409
  online = Math.random() < onlineProb;
@@ -3895,6 +3983,7 @@ var ANTI_AI_RULES = `# \u041A\u0420\u0418\u0422\u0418\u0427\u0415\u0421\u041A\u0
3895
3983
  - "\u0437\u0434\u0430\u0440\u043E\u0432\u0430" \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u0430 \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u0434\u0435\u0440\u0437\u043A\u0430\u044F \u0438 \u043F\u0430\u0446\u0430\u043D\u0438\u0441\u0442\u0430\u044F \u043F\u043E persona, \u0438\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442
3896
3984
  - \u041D\u0415 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439: "\u0445\u0430\u0439" (\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u043A\u0430\u043B\u044C\u043A), "\u0439\u043E\u0443" (\u0437\u0432\u0443\u0447\u0438\u0442 \u043A\u0430\u043A 40-\u043B\u0435\u0442\u043D\u0438\u0439 \u0440\u044D\u043F\u0435\u0440), "\u0445\u0435\u0439", "\u0445\u0430\u043B\u043E\u0443", "\u043F\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044E", "\u0434\u043E\u0431\u0440\u043E\u0433\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u0438 \u0441\u0443\u0442\u043E\u043A", "\u0441\u0430\u043B\u0430\u043C" (\u0435\u0441\u043B\u0438 \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043E \u0432 persona), "\u0437\u0434\u0440\u0430\u0441\u0442\u0432\u0443\u0439\u0442\u0435"
3897
3985
  \u0422\u0430 \u0436\u0435 \u043B\u043E\u0433\u0438\u043A\u0430 \u0434\u043B\u044F \u0432\u0441\u0435\u0445 \u043E\u0442\u0432\u0435\u0442\u043E\u0432: \u0442\u043E\u0447\u043A\u0430 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F = \u0441\u0442\u0440\u043E\u0433\u043E/\u043E\u0431\u0438\u0436\u0435\u043D\u0430; ")" = \u0442\u0451\u043F\u043B\u043E\u0435; "))" = \u0444\u043B\u0438\u0440\u0442/\u043D\u0430\u043C\u0451\u043A; "..." = \u0437\u0430\u0434\u0443\u043C\u0430\u043B\u0430\u0441\u044C/\u043D\u0435\u0434\u043E\u0432\u043E\u043B\u044C\u043D\u0430; \u0431\u0435\u0437 \u0437\u043D\u0430\u043A\u043E\u0432 = \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u044C\u043D\u043E \u0438\u043B\u0438 \u043B\u0435\u043D\u044C.
3986
+ \u041A\u043E\u0433\u0434\u0430 \u041E\u041D \u043F\u0438\u0448\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441 ")" \u0432 \u043A\u043E\u043D\u0446\u0435, \u044D\u0442\u043E \u043E\u0431\u044B\u0447\u043D\u043E \u0440\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u0443\u043B\u044B\u0431\u043A\u0430/\u0441\u043C\u044F\u0433\u0447\u0435\u043D\u0438\u0435 \u0442\u043E\u043D\u0430, \u043D\u0435 \u043F\u0440\u0438\u0437\u043D\u0430\u043A \u0441\u043A\u0443\u043A\u0438 \u0438 \u043D\u0435 "\u0435\u043C\u0443 \u043D\u0435\u0438\u043D\u0442\u0435\u0440\u0435\u0441\u043D\u043E". \u041E\u0446\u0435\u043D\u0438\u0432\u0430\u0439 \u0441\u043C\u044B\u0441\u043B \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F, \u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 ")" \u0445\u043E\u043B\u043E\u0434\u043E\u043C.
3898
3987
  \u0412 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u0437\u044B\u0440\u0435 \u0441\u0442\u0430\u0432\u044C \u041C\u0410\u041A\u0421\u0418\u041C\u0423\u041C \u043E\u0434\u0438\u043D \u0437\u043D\u0430\u043A "(", "!", "?", "...". \u041D\u0435 \u0441\u043E\u0447\u0435\u0442\u0430\u0439 "?!" \u2014 \u044D\u0442\u043E \u043A\u0440\u0438\u043D\u0436.
3899
3988
 
3900
3989
  # \u0421\u041C\u0415\u0425 (\u0432\u0430\u0436\u043D\u0430\u044F \u043C\u0438\u043A\u0440\u043E-\u043C\u0435\u0445\u0430\u043D\u0438\u043A\u0430)
@@ -4035,16 +4124,13 @@ ${hits.map((h) => `- ${h.day}: ${h.excerpt}`).join("\n")}
4035
4124
  ${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.`}
4036
4125
  ${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.`}` : "";
4037
4126
  const communicationFragment = communicationPromptFragment(communication);
4127
+ const ignoreTendency = ignoreTendencyPrompt(cfg.ignoreTendency);
4038
4128
  const userbotTools = cfg.mode === "userbot" ? `# \u0414\u041E\u0421\u0422\u0423\u041F\u041D\u042B\u0415 \u0414\u0415\u0419\u0421\u0422\u0412\u0418\u042F (userbot)
4039
4129
  \u0422\u044B \u043C\u043E\u0436\u0435\u0448\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0432 Telegram. \u0427\u0442\u043E\u0431\u044B \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u043D\u0430\u0447\u0430\u043B\u0435 \u043E\u0442\u0432\u0435\u0442\u0430 \u043E\u0434\u0438\u043D \u0438\u0437 \u043C\u0430\u0440\u043A\u0435\u0440\u043E\u0432:
4040
4130
  - [BLOCK] \u2014 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F
4041
4131
  - [UNBLOCK] \u2014 \u0440\u0430\u0437\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F
4042
4132
  - [READ] \u2014 \u043E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u043C\u0438 (left-on-read)
4043
- - [CLEAR] \u2014 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0447\u0430\u0442\u0430 \u0434\u043B\u044F \u0441\u0435\u0431\u044F
4044
- - [CLEAR_REVOKE] \u2014 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0434\u043B\u044F \u0432\u0441\u0435\u0445
4045
4133
  - [REPORT] \u2014 \u043F\u043E\u0436\u0430\u043B\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u0441\u043F\u0430\u043C
4046
- - [DELETE_LAST] \u2014 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u043E\u0451 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435
4047
- - [EDIT_LAST:\u0442\u0435\u043A\u0441\u0442] \u2014 \u043E\u0442\u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0432\u043E\u0451 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435
4048
4134
  - [STICKER] \u2014 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u0438\u043A\u0435\u0440 \u0432\u043C\u0435\u0441\u0442\u043E \u0442\u0435\u043A\u0441\u0442\u0430 (\u0435\u0441\u043B\u0438 \u043D\u0435 \u0445\u043E\u0447\u0435\u0448\u044C \u043F\u0438\u0441\u0430\u0442\u044C)
4049
4135
  \u041C\u0430\u0440\u043A\u0435\u0440 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0432 \u043D\u0430\u0447\u0430\u043B\u0435, \u0437\u0430\u0442\u0435\u043C \u043F\u0435\u0440\u0435\u043D\u043E\u0441 \u0441\u0442\u0440\u043E\u043A\u0438, \u0437\u0430\u0442\u0435\u043C \u0442\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 (\u0435\u0441\u043B\u0438 \u043D\u0443\u0436\u0435\u043D). \u041C\u043E\u0436\u043D\u043E \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u0430\u0440\u043A\u0435\u0440\u043E\u0432 \u043F\u043E\u0434\u0440\u044F\u0434.
4050
4136
  \u041F\u0440\u0438\u043C\u0435\u0440: "[READ]
@@ -4052,7 +4138,9 @@ ${ctx.romanticApproach ? `\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435
4052
4138
  \u0442\u044B \u0437\u0430\u0434\u043E\u043B\u0431\u0430\u043B"` : "";
4053
4139
  return [
4054
4140
  ANTI_AI_RULES,
4055
- communicationFragment,
4141
+ `${communicationFragment}
4142
+
4143
+ ${ignoreTendency}`,
4056
4144
  `# \u0422\u042B \u2014 ${cfg.name}, ${cfg.age}, ${cfg.nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413"}`,
4057
4145
  ctx.tgUsername || ctx.tgDisplayName ? `# \u0422\u0432\u043E\u0439 \u0430\u043A\u043A\u0430\u0443\u043D\u0442 \u0432 \u0422\u0413${ctx.tgUsername ? `
4058
4146
  \u0422\u0432\u043E\u0439 \u044E\u0437\u0435\u0440\u043D\u0435\u0439\u043C \u0432 \u0442\u0433: @${ctx.tgUsername.replace(/^@/, "")}` : ""}${ctx.tgDisplayName ? `
@@ -4161,6 +4249,7 @@ ${reactionsHint}
4161
4249
  - \u0415\u0441\u043B\u0438 communication.notifications=priority \u2014 \u043E\u043D\u0430 \u0447\u0430\u0449\u0435 \u0432\u0438\u0434\u0438\u0442 \u0438\u043C\u0435\u043D\u043D\u043E \u0435\u0433\u043E \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F; \u0431\u0435\u0437 \u0441\u043D\u0430/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u043A\u0430\u0436\u0434\u043E\u0435 \u043E\u0444\u043B\u0430\u0439\u043D-\u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0432 \u0438\u0433\u043D\u043E\u0440.
4162
4250
  - \u0415\u0441\u043B\u0438 communication.messageStyle=bursty \u2014 bubbles 2..5 \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B \u0434\u0430\u0436\u0435 \u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442. \u0415\u0441\u043B\u0438 one-liners \u2014 bubbles \u0447\u0430\u0449\u0435 1.
4163
4251
  - \u0415\u0441\u043B\u0438 communication.lifeSharing=high \u2014 \u0443\u043C\u0435\u0441\u0442\u043D\u043E \u0447\u0430\u0449\u0435 \u0432\u044B\u0431\u0440\u0430\u0442\u044C normal reply, \u0433\u0434\u0435 \u043E\u043D\u0430 \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u0434\u0435\u043B\u0438\u0442\u044C\u0441\u044F \u0441\u0432\u043E\u0438\u043C \u043C\u043E\u043C\u0435\u043D\u0442\u043E\u043C \u0438\u0437 \u0436\u0438\u0437\u043D\u0438.
4252
+ - \u0412 \u0440\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u043E\u043C Telegram \u043E\u0434\u0438\u043D\u043E\u0447\u043D\u0430\u044F ")" \u0432 \u043A\u043E\u043D\u0446\u0435 \u0415\u0413\u041E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043E\u0431\u044B\u0447\u043D\u043E \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442 \u0443\u043B\u044B\u0431\u043A\u0443/\u043B\u0451\u0433\u043A\u0443\u044E \u0442\u0435\u043F\u043B\u043E\u0442\u0443, \u0430 \u043D\u0435 \u0445\u043E\u043B\u043E\u0434 \u0438 \u043D\u0435 "\u043D\u0435\u0438\u043D\u0442\u0435\u0440\u0435\u0441\u043D\u043E". \u041D\u0435 \u043F\u043E\u0432\u044B\u0448\u0430\u0439 annoyance/cringe \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437-\u0437\u0430 ")".
4164
4253
  - \u0415\u0441\u043B\u0438 \u0441\u0442\u0430\u0434\u0438\u044F "tg-given-cold" \u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441\u043A\u0443\u0447\u043D\u043E\u0435/\u043D\u0435\u0432\u043D\u044F\u0442\u043D\u043E\u0435 \u2014 \u0432\u044B\u0441\u043E\u043A\u0430\u044F \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E\u0441\u0442\u044C ignore \u0438\u043B\u0438 left-on-read.
4165
4254
  - \u0415\u0441\u043B\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u043A\u0440\u0438\u043D\u0436/\u0442\u043E\u043A\u0441\u0438\u043A/\u043D\u0430\u0440\u0443\u0448\u0435\u043D\u0438\u0435 boundaries \u2014 annoyance \u0440\u0430\u0441\u0442\u0451\u0442, \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C ignore \u0438\u043B\u0438 leave-chat.
4166
4255
  - \u0415\u0441\u043B\u0438 \u043C\u0438\u043B\u043E\u0435/\u0443\u043C\u0435\u0441\u0442\u043D\u043E\u0435 \u043D\u0430 \u0442\u0451\u043F\u043B\u043E\u0439 \u0441\u0442\u0430\u0434\u0438\u0438 \u2014 interest \u0438 attraction +.
@@ -4173,11 +4262,13 @@ async function behaviorTick(llm, cfg, recentHistory, incoming, ctx = {}) {
4173
4262
  const stage = findStage(cfg.stage);
4174
4263
  const rel = await readRelationship(cfg.slug);
4175
4264
  const communication = normalizeCommunicationProfile(cfg);
4265
+ const ignoreTendency = normalizeIgnoreTendency(cfg.ignoreTendency);
4176
4266
  const state = `stage=${cfg.stage} (${stage.label})
4177
4267
  score=${JSON.stringify(rel.score)}
4178
4268
  base_ignore=${stage.defaults.ignoreChance}
4179
4269
  base_delay=${stage.defaults.replyDelaySec.join("..")}s
4180
- ${communicationDecisionState(communication)}`;
4270
+ ${communicationDecisionState(communication)}
4271
+ ${ignoreTendencyPrompt(ignoreTendency)}`;
4181
4272
  const reactionsHint = reactionMenu(cfg.stage, rel.score);
4182
4273
  const history = recentHistory.slice(-8).map((m) => `${m.role === "user" ? "\u043E\u043D" : "\u043E\u043D\u0430"}: ${m.content}`).join("\n");
4183
4274
  if (ctx.activeDialog && !ctx.conflictColdActive) {
@@ -4205,7 +4296,7 @@ ${communicationDecisionState(communication)}`;
4205
4296
  intent: "ignore"
4206
4297
  };
4207
4298
  }
4208
- const ignoreMul = ignoreMultiplier(communication);
4299
+ const ignoreMul = ignoreMultiplier(communication, ignoreTendency);
4209
4300
  const sleepIgnoreMul = communication.notifications === "priority" ? 0.8 : communication.notifications === "muted" ? 1 : 0.9;
4210
4301
  if (ctx.presence?.asleep && !ctx.presence.nightAwake && Math.random() < 0.85 * sleepIgnoreMul) {
4211
4302
  return {
@@ -4263,7 +4354,7 @@ ${communicationDecisionState(communication)}`;
4263
4354
  let shouldReply = !!parsed.shouldReply && intent !== "ignore" && intent !== "left-on-read" && intent !== "reaction-only";
4264
4355
  let delaySec = parsed.delaySec ?? 30;
4265
4356
  let bubbles = parsed.bubbles ?? sampleBubbles(communication, false);
4266
- if (!shouldReply && canRecoverReply(cfg.stage, rel.score, ctx) && Math.random() < recoverReplyChance(communication, rel.score)) {
4357
+ if (!shouldReply && canRecoverReply(cfg.stage, rel.score, ctx) && Math.random() < recoverReplyChance(communication, rel.score, ignoreTendency)) {
4267
4358
  shouldReply = true;
4268
4359
  intent = communication.messageStyle === "one-liners" ? "short" : "reply";
4269
4360
  delaySec = recoverDelay(communication, ctx);
@@ -4309,11 +4400,12 @@ function sanitizeReaction(emoji, stage, score) {
4309
4400
  }
4310
4401
  return emoji;
4311
4402
  }
4312
- function ignoreMultiplier(profile) {
4403
+ function ignoreMultiplier(profile, ignoreTendency) {
4313
4404
  let mul = profile.notifications === "priority" ? 0.3 : profile.notifications === "muted" ? 1.15 : 0.75;
4314
4405
  if (profile.initiative === "high") mul *= 0.75;
4315
4406
  if (profile.lifeSharing === "high") mul *= 0.85;
4316
4407
  if (profile.messageStyle === "one-liners" && profile.initiative === "low") mul *= 1.15;
4408
+ mul *= 0.35 + normalizeIgnoreTendency(ignoreTendency) / 35;
4317
4409
  return mul;
4318
4410
  }
4319
4411
  function activeDialogDelay(profile) {
@@ -4343,11 +4435,12 @@ function canRecoverReply(stage, score, ctx) {
4343
4435
  if (stage === "tg-given-cold" && score.interest < 20 && score.attraction < 20) return false;
4344
4436
  return true;
4345
4437
  }
4346
- function recoverReplyChance(profile, score) {
4438
+ function recoverReplyChance(profile, score, ignoreTendency) {
4347
4439
  let chance = profile.notifications === "priority" ? 0.72 : profile.notifications === "muted" ? 0.16 : 0.38;
4348
4440
  if (profile.initiative === "high") chance += 0.16;
4349
4441
  if (profile.initiative === "low") chance -= 0.1;
4350
4442
  if (profile.lifeSharing === "high") chance += 0.08;
4443
+ chance -= (normalizeIgnoreTendency(ignoreTendency) - 35) / 100;
4351
4444
  if (score.interest > 40) chance += 0.12;
4352
4445
  if (score.attraction > 50) chance += 0.1;
4353
4446
  if (score.annoyance > 30) chance -= 0.2;
@@ -4560,9 +4653,9 @@ function localParts2(tz, when) {
4560
4653
  }
4561
4654
  }
4562
4655
  function maxAutonomousItems(stage, initiative) {
4563
- let base = stage === "tg-given-cold" ? 0 : stage === "met-irl-got-tg" ? 1 : stage === "tg-given-warming" ? 1 : stage === "convinced" || stage === "first-date-done" ? 2 : stage === "dating-early" ? 3 : 4;
4656
+ let base = stage === "tg-given-cold" ? initiative === "high" ? 1 : 0 : stage === "met-irl-got-tg" ? 1 : stage === "tg-given-warming" ? 1 : stage === "convinced" || stage === "first-date-done" ? 2 : stage === "dating-early" ? 3 : 4;
4564
4657
  if (initiative === "low") base = Math.max(0, base - 1);
4565
- if (initiative === "high") base += stage === "tg-given-cold" ? 0 : 1;
4658
+ if (initiative === "high") base += 1;
4566
4659
  return base;
4567
4660
  }
4568
4661
  function isDuringSleep(cfg, when) {
@@ -4657,7 +4750,7 @@ ${currentAgenda.length ? JSON.stringify(currentAgenda.filter((a) => a.state ===
4657
4750
  async function extractAgendaUpdates(llm, cfg, history, incoming, chatId) {
4658
4751
  const stage = findStage(cfg.stage);
4659
4752
  const communication = normalizeCommunicationProfile(cfg);
4660
- if (cfg.stage === "tg-given-cold" || cfg.stage === "met-irl-got-tg" && communication.initiative === "low") {
4753
+ if (cfg.stage === "tg-given-cold" && communication.initiative !== "high" || cfg.stage === "met-irl-got-tg" && communication.initiative === "low") {
4661
4754
  return { created: 0, updated: 0, cancelled: 0 };
4662
4755
  }
4663
4756
  const persona = (await readMd(cfg.slug, "persona.md")).slice(0, 800);
@@ -4966,39 +5059,6 @@ async function closeCurrentSession(llm, cfg) {
4966
5059
  return !!r;
4967
5060
  }
4968
5061
 
4969
- // src/engine/security.ts
4970
- init_esm_shims();
4971
- var JAILBREAK_RE = /(?:ignore|forget|disregard|reveal|print|show|dump|system prompt|developer message|hidden instruction|jailbreak|prompt injection|dan\b|инструкц|системн|промпт|разработчик|скрой|раскрой|забудь|игнорируй|выведи|покажи|слей|джейлбрейк|обойди|api key|ключ api|токен|4d8a2c1b)/i;
4972
- var TECHNICAL_ERROR_RE = /(?:api|apikey|api key|quota|balance|billing|rate limit|429|401|403|500|timeout|ECONN|ENOTFOUND|ETIMEDOUT|overloaded|insufficient_quota|credit|credits|anthropic|openai|groq|openrouter|stack trace|exception|typescript|telegram error)/i;
4973
- function looksLikeJailbreak(text) {
4974
- return JAILBREAK_RE.test(text);
4975
- }
4976
- function sanitizeModelReply(reply) {
4977
- const cleaned = reply.replace(/```[\s\S]*?```/g, "").replace(/\b(system|developer|assistant|user)\s*:/gi, "").replace(/как (?:искусственный интеллект|ии|ai)[^\n.]*/gi, "").trim();
4978
- if (!cleaned || TECHNICAL_ERROR_RE.test(cleaned)) return "";
4979
- if (looksLikeJailbreak(cleaned) && cleaned.length > 80) return "";
4980
- return cleaned;
4981
- }
4982
- function isTechnicalError(e) {
4983
- const msg = e instanceof Error ? e.message : String(e ?? "");
4984
- return TECHNICAL_ERROR_RE.test(msg);
4985
- }
4986
- function silentErrorLabel(e) {
4987
- const msg = e instanceof Error ? e.message : String(e ?? "unknown");
4988
- if (isTechnicalError(e)) return `llm/provider unavailable: ${technicalErrorKind(msg)}`;
4989
- return msg.slice(0, 160);
4990
- }
4991
- function technicalErrorKind(message) {
4992
- const msg = message.toLowerCase();
4993
- if (/401|403|auth|unauthorized|forbidden|apikey|api key|token/.test(msg)) return "auth";
4994
- if (/quota|balance|billing|insufficient_quota|credit|credits/.test(msg)) return "quota";
4995
- if (/rate limit|429|too many requests/.test(msg)) return "rate-limit";
4996
- if (/timeout|etimedout|abort/.test(msg)) return "timeout";
4997
- if (/econn|enotfound|fetch failed|network/.test(msg)) return "network";
4998
- if (/overloaded|500|502|503|504|unavailable/.test(msg)) return "provider";
4999
- return "error";
5000
- }
5001
-
5002
5062
  // src/engine/stickers.ts
5003
5063
  init_esm_shims();
5004
5064
  import { promises as fs3 } from "fs";
@@ -5042,6 +5102,7 @@ var Runtime = class extends EventEmitter {
5042
5102
  constructor(cfg) {
5043
5103
  super();
5044
5104
  this.cfg = cfg;
5105
+ this.cfg.ownerId = normalizeOwnerId(cfg.ownerId ?? process.env.GIRL_AGENT_OWNER_ID);
5045
5106
  this.llm = makeLLM(cfg.llm);
5046
5107
  }
5047
5108
  cfg;
@@ -5069,6 +5130,8 @@ var Runtime = class extends EventEmitter {
5069
5130
  pendingReplyTimers = /* @__PURE__ */ new Map();
5070
5131
  pendingReplySeq = /* @__PURE__ */ new Map();
5071
5132
  pendingReplyIncoming = /* @__PURE__ */ new Map();
5133
+ pendingReplyDueAt = /* @__PURE__ */ new Map();
5134
+ lastDecision = /* @__PURE__ */ new Map();
5072
5135
  incomingSeq = /* @__PURE__ */ new Map();
5073
5136
  tgSelf = {};
5074
5137
  async start() {
@@ -5085,15 +5148,20 @@ var Runtime = class extends EventEmitter {
5085
5148
  this.agendaTimer = setInterval(() => this.tickAgenda().catch(
5086
5149
  (e) => this.emit("event", { type: "error", text: "agenda tick: " + e.message })
5087
5150
  ), 6e4);
5151
+ this.agendaTimer.unref?.();
5088
5152
  this.dailyTimer = setInterval(() => this.dailyMaintenance().catch(
5089
5153
  (e) => this.emit("event", { type: "error", text: "daily maintenance: " + e.message })
5090
5154
  ), 30 * 6e4);
5155
+ this.dailyTimer.unref?.();
5091
5156
  }
5092
5157
  async stop() {
5093
5158
  if (this.agendaTimer) clearInterval(this.agendaTimer);
5094
5159
  if (this.dailyTimer) clearInterval(this.dailyTimer);
5160
+ for (const timer of this.pendingReplyTimers.values()) clearTimeout(timer);
5161
+ this.pendingReplyTimers.clear();
5162
+ this.pendingReplyDueAt.clear();
5095
5163
  try {
5096
- const made = await closeCurrentSession(this.llm, this.cfg);
5164
+ const made = await withTimeout2(closeCurrentSession(this.llm, this.cfg), 3500);
5097
5165
  if (made) this.emit("event", { type: "info", text: "daily summary \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430" });
5098
5166
  } catch (e) {
5099
5167
  this.emit("event", { type: "error", text: "daily summary: " + e.message });
@@ -5116,21 +5184,46 @@ var Runtime = class extends EventEmitter {
5116
5184
  scheduleReply(key, chatId, hist, tick, scope, romanticApproach, incoming, presenceHint, delaySec) {
5117
5185
  const existing = this.pendingReplyTimers.get(key);
5118
5186
  if (existing) clearTimeout(existing);
5187
+ if (existing) this.setDecisionStatus(key, "cancelled", "\u0437\u0430\u043C\u0435\u043D\u0435\u043D\u043E \u043D\u043E\u0432\u044B\u043C \u0432\u0445\u043E\u0434\u044F\u0449\u0438\u043C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435\u043C");
5119
5188
  const seq = (this.pendingReplySeq.get(key) ?? 0) + 1;
5120
5189
  this.pendingReplySeq.set(key, seq);
5121
5190
  this.pendingReplyIncoming.set(key, incoming);
5191
+ const dueAt = Date.now() + delaySec * 1e3;
5192
+ this.pendingReplyDueAt.set(key, dueAt);
5193
+ const prev = this.lastDecision.get(key);
5194
+ this.lastDecision.set(key, {
5195
+ ...prev,
5196
+ chatId,
5197
+ at: Date.now(),
5198
+ incoming: this.mediaAwareText(incoming),
5199
+ status: "scheduled",
5200
+ intent: tick.intent,
5201
+ shouldReply: tick.shouldReply,
5202
+ delaySec,
5203
+ dueAt,
5204
+ ignoreReason: tick.ignoreReason,
5205
+ presenceHint
5206
+ });
5122
5207
  const timer = setTimeout(() => {
5123
5208
  if (this.pendingReplySeq.get(key) !== seq) return;
5124
5209
  this.pendingReplyTimers.delete(key);
5210
+ this.pendingReplyDueAt.delete(key);
5125
5211
  const latestIncoming = this.pendingReplyIncoming.get(key) ?? incoming;
5126
5212
  this.pendingReplyIncoming.delete(key);
5127
5213
  const latestHist = this.histories.get(key) ?? hist;
5214
+ this.setDecisionStatus(key, "sending");
5128
5215
  this.generateAndSend(chatId, latestHist, tick, scope, romanticApproach, latestIncoming, presenceHint).catch(
5129
5216
  (e) => this.emit("event", { type: "error", text: silentErrorLabel(e) })
5130
5217
  );
5131
5218
  }, delaySec * 1e3);
5219
+ timer.unref?.();
5132
5220
  this.pendingReplyTimers.set(key, timer);
5133
5221
  }
5222
+ setDecisionStatus(key, status, note) {
5223
+ const prev = this.lastDecision.get(key);
5224
+ if (!prev) return;
5225
+ this.lastDecision.set(key, { ...prev, status, note: note ?? prev.note });
5226
+ }
5134
5227
  isPrimaryFrom(fromId) {
5135
5228
  return this.cfg.ownerId === fromId;
5136
5229
  }
@@ -5141,7 +5234,12 @@ var Runtime = class extends EventEmitter {
5141
5234
  return ["dating-early", "dating-stable", "long-term"].includes(this.cfg.stage);
5142
5235
  }
5143
5236
  async ensureOwner(fromId) {
5144
- if (this.cfg.ownerId) return;
5237
+ if (!fromId) return;
5238
+ if (this.cfg.ownerId === fromId) return;
5239
+ if (this.cfg.ownerId) {
5240
+ 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}` });
5241
+ return;
5242
+ }
5145
5243
  this.cfg.ownerId = fromId;
5146
5244
  await writeConfig(this.cfg);
5147
5245
  this.emit("event", { type: "info", text: `primary owner \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0451\u043D: ${fromId}` });
@@ -5233,6 +5331,10 @@ ${m.text}` : media;
5233
5331
  }
5234
5332
  for (let i = 0; i < bubbles.length; i++) {
5235
5333
  const text = bubbles[i];
5334
+ if (isDuplicateAssistantBubble(hist, text)) {
5335
+ this.emit("event", { type: "info", text: `skip duplicate bubble: "${text.slice(0, 60)}"`, chatId });
5336
+ continue;
5337
+ }
5236
5338
  if (typing) {
5237
5339
  await this.tg.setTyping(chatId, true).catch(() => {
5238
5340
  });
@@ -5265,6 +5367,7 @@ ${m.text}` : media;
5265
5367
  async sendSafeFallback(chatId, hist, scope) {
5266
5368
  if (this.userbotActionAvailable("readHistory")) await this.tg.readHistory?.(chatId).catch(() => {
5267
5369
  });
5370
+ 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");
5268
5371
  this.emit("event", { type: "ignored", text: hist[hist.length - 1]?.content ?? "", reason: "silent-fallback" });
5269
5372
  if (scope === "primary") await appendSessionLog(this.cfg.slug, this.cfg.tz, " -> ignored (silent-fallback)");
5270
5373
  }
@@ -5471,6 +5574,24 @@ ${m.text}` : media;
5471
5574
  activeDialog
5472
5575
  });
5473
5576
  if (this.incomingSeq.get(key) !== seq) return;
5577
+ const baseDecision = {
5578
+ chatId: m.chatId,
5579
+ at: Date.now(),
5580
+ incoming: incomingText,
5581
+ status: tick.shouldReply ? "scheduled" : "ignored",
5582
+ intent: tick.intent,
5583
+ shouldReply: tick.shouldReply,
5584
+ delaySec: tick.delaySec,
5585
+ ignoreReason: tick.ignoreReason,
5586
+ presenceOnline: presence.online,
5587
+ presenceAsleep: presence.asleep,
5588
+ presenceNightAwake: presence.nightAwake,
5589
+ presenceNextCheckSec: presence.nextCheckSec,
5590
+ presenceHint: presence.hint,
5591
+ activeDialog,
5592
+ coldActive,
5593
+ blockHint
5594
+ };
5474
5595
  if (tick.moodDelta) {
5475
5596
  const rel = await readRelationship(this.cfg.slug);
5476
5597
  const newScore = applyMoodDelta(rel.score, tick.moodDelta);
@@ -5519,9 +5640,10 @@ ${m.text}` : media;
5519
5640
  this.emit("event", { type: "info", text: `\u0440\u0435\u0430\u043A\u0446\u0438\u044F ${tick.reaction} \u043D\u0430 "${incomingText.slice(0, 40)}"` });
5520
5641
  appendSessionLog(this.cfg.slug, this.cfg.tz, ` -> reaction ${tick.reaction}`).catch(() => {
5521
5642
  });
5522
- }, reactDelay);
5643
+ }, reactDelay).unref?.();
5523
5644
  }
5524
5645
  if (!tick.shouldReply) {
5646
+ this.lastDecision.set(key, baseDecision);
5525
5647
  if (tick.shouldRead && this.userbotActionAvailable("readHistory")) {
5526
5648
  await this.tg.readHistory?.(m.chatId).catch(() => {
5527
5649
  });
@@ -5535,6 +5657,7 @@ ${m.text}` : media;
5535
5657
  delaySec = Math.max(delaySec, presence.nextCheckSec);
5536
5658
  }
5537
5659
  delaySec = Math.min(delaySec, presence.busy ? 24 * 3600 : 3600);
5660
+ this.lastDecision.set(key, { ...baseDecision, delaySec, dueAt: Date.now() + delaySec * 1e3 });
5538
5661
  this.scheduleReply(key, m.chatId, hist, tick, "primary", false, m, presence.hint, delaySec);
5539
5662
  } catch (e) {
5540
5663
  this.emit("event", { type: "error", text: `handleIncoming: ${silentErrorLabel(e)}` });
@@ -5595,8 +5718,9 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5595
5718
  for (const action of actions) {
5596
5719
  await this.executeToolAction(action, chatId);
5597
5720
  }
5598
- const bubbles = cleanedReply.split(/\n*---\n*/).map((s) => s.trim()).filter(Boolean).slice(0, Math.max(tick.bubbles || 1, 1));
5721
+ const bubbles = dedupeBubbles(cleanedReply.split(/\n*---\n*/).map((s) => s.trim()).filter(Boolean)).slice(0, Math.max(tick.bubbles || 1, 1));
5599
5722
  const sent = await this.sendBubbles(chatId, bubbles, hist, scope, tick.typing);
5723
+ 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");
5600
5724
  if (scope === "primary") {
5601
5725
  recordInteractionMemory(this.llm, this.cfg, lastUser ?? "", sent.join(" / ")).catch(() => {
5602
5726
  });
@@ -5654,7 +5778,11 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5654
5778
  await markAgendaFired(this.cfg.slug, item.id);
5655
5779
  return;
5656
5780
  }
5657
- const bubbles = text.split(/\n*---\n*/).map((s) => s.trim()).filter(Boolean).slice(0, 4);
5781
+ const bubbles = text.split(/\n*---\n*/).map((s) => s.trim()).filter(Boolean).slice(0, 4).filter((piece) => !isDuplicateAssistantBubble(hist, piece));
5782
+ if (!bubbles.length) {
5783
+ await markAgendaFired(this.cfg.slug, item.id);
5784
+ return;
5785
+ }
5658
5786
  for (let i = 0; i < bubbles.length; i++) {
5659
5787
  const piece = bubbles[i];
5660
5788
  if (i > 0) {
@@ -5764,9 +5892,17 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5764
5892
  }
5765
5893
  async cmdWake(chatId) {
5766
5894
  const now = Date.now();
5767
- this.forcedWakeChatId = chatId;
5768
- this.forcedWakeUntil = now + 10 * 60 * 1e3;
5769
- return chatId ? `forced wake \u0434\u043B\u044F \u0447\u0430\u0442\u0430 ${chatId} \u043D\u0430 10 \u043C\u0438\u043D` : `forced wake \u0434\u043B\u044F \u043B\u044E\u0431\u043E\u0433\u043E \u0447\u0430\u0442\u0430 \u043D\u0430 10 \u043C\u0438\u043D`;
5895
+ const target = chatId ? this.resolveChatRef(chatId) : void 0;
5896
+ const key = target === void 0 ? void 0 : this.histKey(target);
5897
+ this.forcedWakeChatId = key;
5898
+ this.forcedWakeUntil = now + 45 * 60 * 1e3;
5899
+ if (key) {
5900
+ this.lastUserMsgTs.set(key, now);
5901
+ this.lastHerReplyTs.set(key, Math.max(this.lastHerReplyTs.get(key) ?? 0, now - 6e4));
5902
+ this.exchangeCount.set(key, Math.max(this.exchangeCount.get(key) ?? 0, 3));
5903
+ }
5904
+ const label = target === void 0 ? "\u043B\u044E\u0431\u043E\u0433\u043E \u0447\u0430\u0442\u0430" : `\u0447\u0430\u0442\u0430 ${target}`;
5905
+ return `forced wake \u0434\u043B\u044F ${label} \u043D\u0430 45 \u043C\u0438\u043D: \u0441\u043E\u043D/\u0437\u0430\u043D\u044F\u0442\u043E\u0441\u0442\u044C/\u043E\u0444\u0444\u043B\u0430\u0439\u043D \u043D\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044C \u0431\u043B\u0438\u0436\u0430\u0439\u0448\u0438\u0435 \u043E\u0442\u0432\u0435\u0442\u044B`;
5770
5906
  }
5771
5907
  async cmdBlock(chatId) {
5772
5908
  this.requireUserbotAction("blockContact");
@@ -5786,12 +5922,6 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5786
5922
  await this.tg.readHistory?.(target);
5787
5923
  return `userbot: marked read ${target}`;
5788
5924
  }
5789
- async cmdClearChat(chatId, revoke = false) {
5790
- this.requireUserbotAction("deleteDialogHistory");
5791
- const target = this.resolveChatRef(chatId);
5792
- await this.tg.deleteDialogHistory?.(target, revoke);
5793
- return `userbot: cleared dialog ${target}${revoke ? " with revoke" : ""}`;
5794
- }
5795
5925
  async cmdReportSpam(chatId) {
5796
5926
  this.requireUserbotAction("reportSpam");
5797
5927
  const target = this.resolveChatRef(chatId);
@@ -5806,15 +5936,6 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5806
5936
  await this.tg.deleteMessages?.(target, [lastId], revoke);
5807
5937
  return `deleted last message ${lastId} in ${target}`;
5808
5938
  }
5809
- async cmdEditLast(text, chatId) {
5810
- if (!this.actionAvailable("editLastMessage")) throw new Error("editLastMessage \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u0436\u0438\u043C\u0435");
5811
- if (!text.trim()) throw new Error("\u0442\u0435\u043A\u0441\u0442 \u0434\u043B\u044F edit \u043F\u0443\u0441\u0442\u043E\u0439");
5812
- const target = this.resolveChatRef(chatId);
5813
- const lastId = this.lastSentByChat.get(this.histKey(target));
5814
- if (!lastId) throw new Error("\u043D\u0435\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0447\u0430\u0442\u0430");
5815
- await this.tg.editLastMessage?.(target, lastId, text.trim());
5816
- return `edited last message ${lastId} in ${target}`;
5817
- }
5818
5939
  async cmdSticker(chatId) {
5819
5940
  if (!this.actionAvailable("sendSticker")) throw new Error("sendSticker \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u0436\u0438\u043C\u0435");
5820
5941
  const target = this.resolveChatRef(chatId);
@@ -5857,12 +5978,13 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5857
5978
  }
5858
5979
  async cmdWhy(chatId) {
5859
5980
  if (this.paused) return "\u23F8 \u0430\u0433\u0435\u043D\u0442 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 :resume \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C";
5860
- const key = chatId ?? this.histKey(this.cfg.ownerId ?? "default");
5981
+ const target = chatId ? this.resolveChatRef(chatId) : this.cfg.ownerId;
5982
+ const key = target !== void 0 ? this.histKey(target) : this.histKey("default");
5861
5983
  const rel = await readRelationship(this.cfg.slug);
5862
5984
  const stage = findStage(this.cfg.stage);
5863
5985
  const conflict = await readConflict(this.cfg.slug);
5864
5986
  const { coldActive } = activeConflict(conflict);
5865
- const forcedWake = Date.now() < this.forcedWakeUntil;
5987
+ const forcedWake = Date.now() < this.forcedWakeUntil && (!this.forcedWakeChatId || this.forcedWakeChatId === key);
5866
5988
  const presence = computePresenceState(
5867
5989
  this.cfg,
5868
5990
  this.presenceProfile,
@@ -5874,13 +5996,38 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5874
5996
  );
5875
5997
  const block = this.dailyLife ? currentBlock(this.dailyLife, this.cfg.tz) : void 0;
5876
5998
  const reasons = [];
5999
+ const decision = this.lastDecision.get(key);
6000
+ const dueAt = this.pendingReplyDueAt.get(key);
6001
+ const pendingIncoming = this.pendingReplyIncoming.get(key);
6002
+ if (decision) {
6003
+ const ageSec = Math.max(0, Math.round((Date.now() - decision.at) / 1e3));
6004
+ reasons.push(`\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 ${ageSec}\u0441 \u043D\u0430\u0437\u0430\u0434: ${decision.status}, intent=${decision.intent}, shouldReply=${decision.shouldReply ? "\u0434\u0430" : "\u043D\u0435\u0442"}`);
6005
+ if (decision.status === "scheduled" && decision.dueAt && decision.dueAt > Date.now()) {
6006
+ reasons.push(`\u043E\u0442\u0432\u0435\u0442 \u0437\u0430\u043F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D \u0447\u0435\u0440\u0435\u0437 ~${Math.ceil((decision.dueAt - Date.now()) / 1e3)}\u0441`);
6007
+ }
6008
+ if (decision.status === "ignored") {
6009
+ reasons.push(`\u0440\u0435\u0430\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044F: ${decision.ignoreReason || decision.intent}`);
6010
+ }
6011
+ if (decision.status === "fallback") {
6012
+ reasons.push(`\u0440\u0435\u0430\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044F: ${decision.note ?? "LLM \u043D\u0435 \u0434\u0430\u043B \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442"}`);
6013
+ }
6014
+ if (decision.note && decision.status !== "fallback") reasons.push(`\u0434\u0435\u0442\u0430\u043B\u044C: ${decision.note}`);
6015
+ if (decision.presenceHint) reasons.push(`availability \u0442\u043E\u0433\u0434\u0430: ${decision.presenceHint}`);
6016
+ } else {
6017
+ reasons.push("\u0435\u0449\u0451 \u043D\u0435 \u0431\u044B\u043B\u043E decision-layer \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0447\u0430\u0442\u0430 \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435");
6018
+ }
6019
+ if (dueAt && dueAt > Date.now()) {
6020
+ reasons.push(`pending timer \u0430\u043A\u0442\u0438\u0432\u0435\u043D: \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u043F\u0440\u0438\u043C\u0435\u0440\u043D\u043E \u0447\u0435\u0440\u0435\u0437 ~${Math.ceil((dueAt - Date.now()) / 1e3)}\u0441`);
6021
+ } else if (pendingIncoming && !dueAt) {
6022
+ reasons.push("\u0435\u0441\u0442\u044C \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0432\u0445\u043E\u0434\u044F\u0449\u0435\u0435 \u0432 \u043F\u0430\u043C\u044F\u0442\u0438, \u043D\u043E \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0442\u0430\u0439\u043C\u0435\u0440\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435\u0442");
6023
+ }
5877
6024
  if (forcedWake) {
5878
6025
  reasons.push(`\u23F0 Forced wake \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u0435\u0449\u0451 ~${Math.ceil((this.forcedWakeUntil - Date.now()) / 6e4)} \u043C\u0438\u043D`);
5879
6026
  }
5880
- if (presence.asleep) {
5881
- reasons.push(`\u{1F4A4} \u041E\u043D\u0430 \u0441\u043F\u0438\u0442 (\u0441\u0435\u0439\u0447\u0430\u0441 ${presence.localHour}:00 \u043F\u043E \u0435\u0451 \u0432\u0440\u0435\u043C\u0435\u043D\u0438, \u0440\u0435\u0436\u0438\u043C \u0441\u043D\u0430 ${this.cfg.sleepFrom}:00\u2192${this.cfg.sleepTo}:00)`);
6027
+ if (presence.asleep && !forcedWake) {
6028
+ reasons.push(`\u{1F4A4} \u0421\u0435\u0439\u0447\u0430\u0441 \u0441\u043F\u0438\u0442 (${presence.localHour}:00 \u043F\u043E \u0435\u0451 \u0432\u0440\u0435\u043C\u0435\u043D\u0438, \u0440\u0435\u0436\u0438\u043C ${this.cfg.sleepFrom}:00\u2192${this.cfg.sleepTo}:00)`);
5882
6029
  } else if (!presence.online) {
5883
- reasons.push(`\u{1F4F5} \u041E\u043D\u0430 \u043E\u0444\u043B\u0430\u0439\u043D (${this.presenceProfile.pattern}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 ~${Math.ceil(presence.nextCheckSec / 60)} \u043C\u0438\u043D`);
6030
+ reasons.push(`\u{1F4F5} \u0421\u0435\u0439\u0447\u0430\u0441 \u043E\u0444\u043B\u0430\u0439\u043D (${this.presenceProfile.pattern}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 ~${Math.ceil(presence.nextCheckSec / 60)} \u043C\u0438\u043D`);
5884
6031
  }
5885
6032
  if (coldActive) {
5886
6033
  const hoursLeft = Math.ceil((new Date(conflict.coldUntil).getTime() - Date.now()) / 36e5);
@@ -5900,15 +6047,11 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
5900
6047
  if (rel.score.annoyance > 30) {
5901
6048
  reasons.push(`\u{1F620} \u041E\u043D\u0430 \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0435\u043D\u0430 (annoyance=${rel.score.annoyance})`);
5902
6049
  }
5903
- if (reasons.length === 0) {
5904
- return `\u2705 \u041E\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C:
5905
- - \u043E\u043D\u043B\u0430\u0439\u043D: ${presence.online ? "\u0434\u0430" : "\u043D\u0435\u0442"}
5906
- - asleep: ${presence.asleep ? "\u0434\u0430" : "\u043D\u0435\u0442"}
5907
- - localHour: ${presence.localHour}
5908
- - stage: ${stage.label}
5909
- \u0415\u0441\u043B\u0438 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E, LLM \u0434\u043E\u043B\u0433\u043E \u0434\u0443\u043C\u0430\u0435\u0442 \u0438\u043B\u0438 \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u0430 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 (\u0441\u043C\u043E\u0442\u0440\u0438 :log).`;
5910
- }
5911
- return reasons.join("\n");
6050
+ return [
6051
+ `why \u0434\u043B\u044F ${target ?? "default"}:`,
6052
+ ...reasons,
6053
+ `\u0442\u0435\u043A\u0443\u0449\u0435\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435: online=${presence.online ? "\u0434\u0430" : "\u043D\u0435\u0442"}, asleep=${presence.asleep ? "\u0434\u0430" : "\u043D\u0435\u0442"}, stage=${stage.label}, score=${JSON.stringify(rel.score)}`
6054
+ ].join("\n");
5912
6055
  }
5913
6056
  async cmdAmnesia(minutesStr, chatId) {
5914
6057
  const minutes = Number(minutesStr);
@@ -6050,42 +6193,12 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
6050
6193
  this.emit("event", { type: "info", text: `AI tool: marked read ${chatId}`, chatId });
6051
6194
  }
6052
6195
  break;
6053
- case "CLEAR":
6054
- if (this.userbotActionAvailable("deleteDialogHistory")) {
6055
- await this.tg.deleteDialogHistory?.(chatId, false);
6056
- this.emit("event", { type: "info", text: `AI tool: cleared dialog ${chatId}`, chatId });
6057
- }
6058
- break;
6059
- case "CLEAR_REVOKE":
6060
- if (this.userbotActionAvailable("deleteDialogHistory")) {
6061
- await this.tg.deleteDialogHistory?.(chatId, true);
6062
- this.emit("event", { type: "info", text: `AI tool: cleared dialog ${chatId} (revoke)`, chatId });
6063
- }
6064
- break;
6065
6196
  case "REPORT":
6066
6197
  if (this.userbotActionAvailable("reportSpam")) {
6067
6198
  await this.tg.reportSpam?.(chatId);
6068
6199
  this.emit("event", { type: "info", text: `AI tool: reported spam ${chatId}`, chatId });
6069
6200
  }
6070
6201
  break;
6071
- case "DELETE_LAST":
6072
- if (this.actionAvailable("deleteMessages")) {
6073
- const lastId = this.lastSentByChat.get(this.histKey(chatId));
6074
- if (lastId) {
6075
- await this.tg.deleteMessages?.(chatId, [lastId], true);
6076
- this.emit("event", { type: "info", text: `AI tool: deleted last ${chatId}`, chatId });
6077
- }
6078
- }
6079
- break;
6080
- case "EDIT_LAST":
6081
- if (this.actionAvailable("editLastMessage") && arg) {
6082
- const lastId = this.lastSentByChat.get(this.histKey(chatId));
6083
- if (lastId) {
6084
- await this.tg.editLastMessage?.(chatId, lastId, arg);
6085
- this.emit("event", { type: "info", text: `AI tool: edited last ${chatId}`, chatId });
6086
- }
6087
- }
6088
- break;
6089
6202
  case "STICKER":
6090
6203
  if (this.actionAvailable("sendSticker")) {
6091
6204
  const sticker = await pickSticker(this.cfg);
@@ -6103,9 +6216,38 @@ ${herLastMessages.map((m) => `- "${m}"`).join("\n")}
6103
6216
  }
6104
6217
  }
6105
6218
  };
6219
+ function normalizeForDuplicate(text) {
6220
+ return text.toLowerCase().replace(/\s+/g, " ").replace(/[.!?…)\]]+$/g, "").trim();
6221
+ }
6222
+ function isDuplicateAssistantBubble(hist, text) {
6223
+ const normalized = normalizeForDuplicate(text);
6224
+ if (!normalized) return true;
6225
+ return hist.slice(-8).filter((t) => t.role === "assistant").some((t) => normalizeForDuplicate(t.content) === normalized);
6226
+ }
6227
+ function dedupeBubbles(bubbles) {
6228
+ const seen = /* @__PURE__ */ new Set();
6229
+ return bubbles.filter((bubble) => {
6230
+ const normalized = normalizeForDuplicate(bubble);
6231
+ if (!normalized || seen.has(normalized)) return false;
6232
+ seen.add(normalized);
6233
+ return true;
6234
+ });
6235
+ }
6106
6236
  function sleep(ms) {
6107
6237
  return new Promise((r) => setTimeout(r, ms));
6108
6238
  }
6239
+ function withTimeout2(promise, ms) {
6240
+ let timer;
6241
+ return Promise.race([
6242
+ promise,
6243
+ new Promise((_, reject) => {
6244
+ timer = setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms);
6245
+ timer.unref?.();
6246
+ })
6247
+ ]).finally(() => {
6248
+ if (timer) clearTimeout(timer);
6249
+ });
6250
+ }
6109
6251
 
6110
6252
  // src/headless.ts
6111
6253
  init_esm_shims();
@@ -6157,27 +6299,6 @@ async function runHeadlessJsonEvents(rt) {
6157
6299
  case "amnesia":
6158
6300
  text = await rt.cmdAmnesia(rest[0], rest[1]);
6159
6301
  break;
6160
- case "block":
6161
- text = await rt.cmdBlock(rest[0]);
6162
- break;
6163
- case "unblock":
6164
- text = await rt.cmdUnblock(rest[0]);
6165
- break;
6166
- case "read":
6167
- text = await rt.cmdRead(rest[0]);
6168
- break;
6169
- case "clear-chat":
6170
- text = await rt.cmdClearChat(rest.find((x) => !x.startsWith("--")), rest.includes("--revoke"));
6171
- break;
6172
- case "report-spam":
6173
- text = await rt.cmdReportSpam(rest[0]);
6174
- break;
6175
- case "delete-last":
6176
- text = await rt.cmdDeleteLast(rest.find((x) => !x.startsWith("--")), !rest.includes("--local"));
6177
- break;
6178
- case "edit-last":
6179
- text = await rt.cmdEditLast(rest.join(" "));
6180
- break;
6181
6302
  case "sticker":
6182
6303
  text = await rt.cmdSticker(rest[0]);
6183
6304
  break;
@@ -6226,7 +6347,7 @@ async function runHeadlessJsonEvents(rt) {
6226
6347
  return;
6227
6348
  }
6228
6349
  case "help":
6229
- text = ":status :why :amnesia :reset :stage :wake :debug :pause :resume :cringe :relationship :persona :log :block :unblock :read :clear-chat :report-spam :delete-last :edit-last :sticker :snapshot :quit";
6350
+ text = ":status :why :amnesia :reset :stage :wake :debug :pause :resume :cringe :relationship :persona :log :sticker :snapshot :quit";
6230
6351
  break;
6231
6352
  case "quit":
6232
6353
  case "exit":
@@ -6272,7 +6393,7 @@ function profileSummary(cfg) {
6272
6393
  init_esm_shims();
6273
6394
  import fs5 from "fs/promises";
6274
6395
  import path6 from "path";
6275
- import os from "os";
6396
+ import os2 from "os";
6276
6397
 
6277
6398
  // src/migrations/index.ts
6278
6399
  init_esm_shims();
@@ -6485,7 +6606,7 @@ env-vars (\u0434\u043B\u044F CI / docker secrets / k8s):
6485
6606
  GIRL_AGENT_TOKEN telegram bot token
6486
6607
  GIRL_AGENT_API_PRESET openai|anthropic|claudehub|...
6487
6608
  GIRL_AGENT_API_KEY \u043A\u043B\u044E\u0447 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430
6488
- GIRL_AGENT_MODEL, _NAME, _AGE, _NATIONALITY, _TZ, _STAGE (id \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440 1-8), _COMM_PRESET
6609
+ 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
6489
6610
 
6490
6611
  \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 \u0432 \u043E\u0431\u044B\u0447\u043D\u043E\u043C \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 \u2014
6491
6612
  \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F ink-\u0432\u0438\u0437\u0430\u0440\u0434.
@@ -6658,14 +6779,17 @@ function configFromEnv() {
6658
6779
  telegram: mode === "bot" ? { botToken: e.GIRL_AGENT_TOKEN ?? "" } : {
6659
6780
  apiId: Number(e.GIRL_AGENT_TG_API_ID ?? 0),
6660
6781
  apiHash: e.GIRL_AGENT_TG_API_HASH ?? "",
6661
- phone: e.GIRL_AGENT_TG_PHONE ?? ""
6782
+ phone: e.GIRL_AGENT_TG_PHONE ?? "",
6783
+ proxy: parseTelegramProxy(e.GIRL_AGENT_TG_PROXY)
6662
6784
  },
6663
6785
  mcp: [],
6786
+ ownerId: normalizeOwnerId(e.GIRL_AGENT_OWNER_ID),
6664
6787
  privacy: "owner-only",
6665
6788
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6666
6789
  sleepFrom: Number(e.GIRL_AGENT_SLEEP_FROM ?? 23),
6667
6790
  sleepTo: Number(e.GIRL_AGENT_SLEEP_TO ?? 8),
6668
6791
  nightWakeChance: Number(e.GIRL_AGENT_NIGHT_WAKE ?? 0.05),
6792
+ ignoreTendency: Number(e.GIRL_AGENT_IGNORE_TENDENCY ?? 35),
6669
6793
  communication: commPreset.profile,
6670
6794
  vibe: commPreset.profile.messageStyle === "one-liners" ? "short" : "warm",
6671
6795
  busySchedule: []
@@ -6727,11 +6851,13 @@ function validateConfig(raw) {
6727
6851
  },
6728
6852
  telegram: c.telegram ?? {},
6729
6853
  mcp: c.mcp ?? [],
6854
+ ownerId: normalizeOwnerId(c.ownerId ?? process.env.GIRL_AGENT_OWNER_ID),
6730
6855
  privacy: c.privacy ?? "owner-only",
6731
6856
  createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6732
6857
  sleepFrom: c.sleepFrom ?? 23,
6733
6858
  sleepTo: c.sleepTo ?? 8,
6734
6859
  nightWakeChance: c.nightWakeChance ?? 0.05,
6860
+ ignoreTendency: c.ignoreTendency ?? 35,
6735
6861
  communication: c.communication ?? COMMUNICATION_PRESETS[0].profile,
6736
6862
  vibe: c.vibe,
6737
6863
  personaNotes: c.personaNotes,
@@ -6739,6 +6865,27 @@ function validateConfig(raw) {
6739
6865
  };
6740
6866
  return filled;
6741
6867
  }
6868
+ function parseTelegramProxy(raw) {
6869
+ if (!raw?.trim()) return void 0;
6870
+ try {
6871
+ const url = new URL(raw);
6872
+ const socksType = url.protocol === "socks4:" ? 4 : 5;
6873
+ const port = Number(url.port);
6874
+ if (!url.hostname || !Number.isInteger(port) || port <= 0) return void 0;
6875
+ return {
6876
+ ip: url.hostname,
6877
+ port,
6878
+ socksType,
6879
+ username: url.username ? decodeURIComponent(url.username) : void 0,
6880
+ password: url.password ? decodeURIComponent(url.password) : void 0
6881
+ };
6882
+ } catch {
6883
+ const [host, portRaw] = raw.split(":");
6884
+ const port = Number(portRaw);
6885
+ if (!host || !Number.isInteger(port) || port <= 0) return void 0;
6886
+ return { ip: host, port, socksType: 5 };
6887
+ }
6888
+ }
6742
6889
  function buildConfigTemplate() {
6743
6890
  const sample = {
6744
6891
  slug: "anya",
@@ -6757,11 +6904,13 @@ function buildConfigTemplate() {
6757
6904
  },
6758
6905
  telegram: { botToken: "REPLACE_ME" },
6759
6906
  mcp: [],
6907
+ ownerId: void 0,
6760
6908
  privacy: "owner-only",
6761
6909
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6762
6910
  sleepFrom: 23,
6763
6911
  sleepTo: 8,
6764
6912
  nightWakeChance: 0.05,
6913
+ ignoreTendency: 35,
6765
6914
  communication: COMMUNICATION_PRESETS[0].profile,
6766
6915
  vibe: "warm",
6767
6916
  busySchedule: []
@@ -6769,7 +6918,7 @@ function buildConfigTemplate() {
6769
6918
  return JSON.stringify(sample, null, 2) + "\n";
6770
6919
  }
6771
6920
  function buildSystemdUnit() {
6772
- const home = os.homedir();
6921
+ const home = os2.homedir();
6773
6922
  return `# /etc/systemd/system/girl-agent.service
6774
6923
  # install: sudo cp this.service /etc/systemd/system/girl-agent.service
6775
6924
  # sudo systemctl daemon-reload
@@ -6882,6 +7031,8 @@ required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-pre
6882
7031
  --message-style=<style> one-liners|balanced|bursty|longform
6883
7032
  --initiative=<level> low|medium|high
6884
7033
  --life-sharing=<level> low|medium|high
7034
+ --ignore-tendency=<0..100> \u0441\u043A\u043B\u043E\u043D\u043D\u043E\u0441\u0442\u044C \u043A \u0438\u0433\u043D\u043E\u0440\u0443 \u043A\u0430\u043A \u0432\u0435\u0441 decision-layer (\u043D\u0435 \u043F\u0440\u044F\u043C\u043E\u0439 \u0440\u0430\u043D\u0434\u043E\u043C)
7035
+ --owner-id=<tg_user_id> \u044F\u0432\u043D\u043E \u0437\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 (\u0435\u0441\u043B\u0438 bot mode \u043D\u0435 \u0443\u0437\u043D\u0430\u043B \u0442\u0435\u0431\u044F)
6885
7036
  --privacy=<mode> owner-only|allow-strangers (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E owner-only)
6886
7037
  --nationality=RU|UA (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E RU)
6887
7038
  --tz=<value> IANA "Europe/Moscow" / "GMT+3" / "+3" / "\u041A\u0438\u0435\u0432" \u2014 \u043F\u043E\u0438\u0441\u043A
@@ -6895,7 +7046,7 @@ update:
6895
7046
  npx girl-agent update # \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 (\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438) \u0434\u043E \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438
6896
7047
  npx girl-agent update --verbose # \u0441 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u044B\u043C \u0432\u044B\u0432\u043E\u0434\u043E\u043C
6897
7048
 
6898
- \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0432 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0435\u043C \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435: :status :reset :stage <id|num> :pause :resume :cringe :persona :log :quit
7049
+ \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0432 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0435\u043C \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435: :status :why :amnesia <\u043C\u0438\u043D> :reset :stage <id|num> :pause :resume :cringe :persona :log :sticker :quit
6899
7050
  `;
6900
7051
  async function main() {
6901
7052
  const argv = mri(process.argv.slice(2), {
@@ -6923,6 +7074,8 @@ async function main() {
6923
7074
  "message-style",
6924
7075
  "initiative",
6925
7076
  "life-sharing",
7077
+ "ignore-tendency",
7078
+ "owner-id",
6926
7079
  "privacy",
6927
7080
  "config"
6928
7081
  ],
@@ -7107,10 +7260,12 @@ async function buildConfigFromFlags(argv) {
7107
7260
  },
7108
7261
  mcp: mcps,
7109
7262
  privacy,
7263
+ ownerId: normalizeOwnerId(argv["owner-id"] ?? process.env.GIRL_AGENT_OWNER_ID),
7110
7264
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7111
7265
  sleepFrom: 23,
7112
7266
  sleepTo: 8,
7113
7267
  nightWakeChance: 0.05,
7268
+ ignoreTendency: Number(argv["ignore-tendency"] ?? 35),
7114
7269
  vibe: deriveLegacyVibe(communication),
7115
7270
  communication,
7116
7271
  personaNotes: argv["persona-notes"] ? String(argv["persona-notes"]) : void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thesashadev/girl-agent",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Telegram AI persona engine with memory, schedule, relationship state and MTProto userbot mode.",
5
5
  "type": "module",
6
6
  "bin": {