@thesashadev/girl-agent 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -27
- package/LICENSE +30 -30
- package/README.md +184 -184
- package/dist/cli.js +195 -57
- package/package.json +68 -65
package/CHANGELOG.md
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.1.
|
|
4
|
-
|
|
5
|
-
Дата: 2026-05-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
## 0.1.
|
|
11
|
-
|
|
12
|
-
Дата: 2026-05-05
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.4 — npm publish automation
|
|
4
|
+
|
|
5
|
+
Дата: 2026-05-06
|
|
6
|
+
|
|
7
|
+
- Добавлен GitHub Actions workflow для публикации пакета в npm по тегу `v*`.
|
|
8
|
+
- Добавлено правило релиза: каждая публичная обнова должна менять версию в `package.json`/`package-lock.json` и добавлять запись в changelog.
|
|
9
|
+
|
|
10
|
+
## 0.1.3 — Telegram formatting fix
|
|
11
|
+
|
|
12
|
+
Дата: 2026-05-05
|
|
13
|
+
|
|
14
|
+
- Исправлено: включён `parse_mode: "MarkdownV2"` для отправки сообщений в Telegram (bot и userbot).
|
|
15
|
+
- Теперь поддерживается форматирование спойлеров `||текст||` и другие MarkdownV2-стили.
|
|
16
|
+
|
|
17
|
+
## 0.1.2 — communication realism update
|
|
18
|
+
|
|
19
|
+
Дата: 2026-05-05
|
|
20
|
+
|
|
21
|
+
- Hotfix: профили из wizard теперь сохраняются раньше, а список профилей больше не показывает недосохранённые папки без `config.json`.
|
|
22
|
+
- Добавлены жизненные стили общения: **Нормальная**, **Милая**, **Альтушка**, **Залипала**, **Болтушка**.
|
|
23
|
+
- Добавлен `CommunicationProfile` с настройками уведомлений, стиля сообщений, инициативы и life sharing.
|
|
24
|
+
- Presence, reply timing, bubbles, ignore chance и proactive agenda теперь учитывают профиль общения.
|
|
25
|
+
- Wizard и CLI получили настройку communication profile.
|
|
26
|
+
- Runtime `:status` и `:debug` показывают профиль общения.
|
|
27
|
+
- Команда `:log` стала удобнее и поддерживает выбор дня/лимита вывода.
|
|
28
|
+
- Старый `vibe` автоматически нормализуется в новый формат.
|
|
29
|
+
|
|
30
|
+
## 0.1.1 — stability baseline
|
|
31
|
+
|
|
32
|
+
- Базовый публичный релиз с Telegram bot/userbot режимами.
|
|
33
|
+
- Persona, speech, relationship state, memory, conflict и agenda-модули.
|
|
34
|
+
- Документация по установке, конфигурации, реализм-модулям и troubleshooting.
|
package/LICENSE
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
girl-agent Source-Available Non-Commercial License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 girl-agent contributors.
|
|
4
|
-
All rights reserved except as expressly stated below.
|
|
5
|
-
|
|
6
|
-
Permission is granted to view, download, clone, and run this software
|
|
7
|
-
for personal, educational, evaluation, and non-commercial testing purposes.
|
|
8
|
-
|
|
9
|
-
Permission is also granted to create forks and submit issues, pull requests,
|
|
10
|
-
bug reports, patches, and improvement suggestions to the original repository.
|
|
11
|
-
|
|
12
|
-
You may modify the software only for the purpose of personal testing,
|
|
13
|
-
evaluation, or contributing improvements back to the original project.
|
|
14
|
-
|
|
15
|
-
You may not, without prior written permission from the copyright holder:
|
|
16
|
-
|
|
17
|
-
1. use this software or substantial portions of it for commercial purposes;
|
|
18
|
-
2. sell, rent, sublicense, host, or provide this software as a paid service;
|
|
19
|
-
3. publish or distribute a competing product or public clone based on this software;
|
|
20
|
-
4. use this software in a commercial product, service, or company project;
|
|
21
|
-
5. remove or alter copyright notices, attribution, or license terms;
|
|
22
|
-
6. relicense this software under another license;
|
|
23
|
-
7. use the project name, branding, logo, documentation, or assets in a way that suggests endorsement or ownership.
|
|
24
|
-
|
|
25
|
-
Any contribution submitted to the original repository, including pull requests,
|
|
26
|
-
patches, issues, ideas, documentation changes, or code suggestions, may be used,
|
|
27
|
-
modified, and distributed by the project maintainers as part of this project.
|
|
28
|
-
|
|
29
|
-
This license does not grant any trademark rights.
|
|
30
|
-
|
|
1
|
+
girl-agent Source-Available Non-Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 girl-agent contributors.
|
|
4
|
+
All rights reserved except as expressly stated below.
|
|
5
|
+
|
|
6
|
+
Permission is granted to view, download, clone, and run this software
|
|
7
|
+
for personal, educational, evaluation, and non-commercial testing purposes.
|
|
8
|
+
|
|
9
|
+
Permission is also granted to create forks and submit issues, pull requests,
|
|
10
|
+
bug reports, patches, and improvement suggestions to the original repository.
|
|
11
|
+
|
|
12
|
+
You may modify the software only for the purpose of personal testing,
|
|
13
|
+
evaluation, or contributing improvements back to the original project.
|
|
14
|
+
|
|
15
|
+
You may not, without prior written permission from the copyright holder:
|
|
16
|
+
|
|
17
|
+
1. use this software or substantial portions of it for commercial purposes;
|
|
18
|
+
2. sell, rent, sublicense, host, or provide this software as a paid service;
|
|
19
|
+
3. publish or distribute a competing product or public clone based on this software;
|
|
20
|
+
4. use this software in a commercial product, service, or company project;
|
|
21
|
+
5. remove or alter copyright notices, attribution, or license terms;
|
|
22
|
+
6. relicense this software under another license;
|
|
23
|
+
7. use the project name, branding, logo, documentation, or assets in a way that suggests endorsement or ownership.
|
|
24
|
+
|
|
25
|
+
Any contribution submitted to the original repository, including pull requests,
|
|
26
|
+
patches, issues, ideas, documentation changes, or code suggestions, may be used,
|
|
27
|
+
modified, and distributed by the project maintainers as part of this project.
|
|
28
|
+
|
|
29
|
+
This license does not grant any trademark rights.
|
|
30
|
+
|
|
31
31
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/README.md
CHANGED
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-

|
|
2
|
-
|
|
3
|
-
[website]: https://girl-agent.com
|
|
4
|
-
[docs]: https://docs.girl-agent.com
|
|
5
|
-
|
|
6
|
-
**[website]** · **[docs]**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Это только бета-версия. Со временем будет дорабатыватся.
|
|
10
|
-
Со всеми проблемами и багами пишите в Issues.
|
|
11
|
-
ТГ создателя - @voided_net
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Содержание
|
|
16
|
-
|
|
17
|
-
- [Быстрый старт](#быстрый-старт)
|
|
18
|
-
- [Что под капотом](#что-под-капотом)
|
|
19
|
-
- [Почему не просто GPTs или промпт](#почему-не-просто-gpts-или-промпт)
|
|
20
|
-
- [Changelog](./CHANGELOG.md)
|
|
21
|
-
- [Безопасность](#безопасность)
|
|
22
|
-
- [Лицензия](#лицензия)
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## О проекте
|
|
27
|
-
|
|
28
|
-
Она не отвечает на каждое сообщение. Иногда читает и молчит. Иногда ставит реакцию. Иногда отвечает через час, потому что была занята или просто не хотела.
|
|
29
|
-
|
|
30
|
-
Это не баг. Так задумано.
|
|
31
|
-
|
|
32
|
-
`girl-agent` — ИИ-девушка, которая ведёт себя в переписке как человек. Со сном, настроением, расписанием, памятью и характером. Без "конечно, я понимаю" и ChatGPT-повадок.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Быстрый старт
|
|
37
|
-
|
|
38
|
-
**Через NPX (рекомендуется):**
|
|
39
|
-
|
|
40
|
-
```powershell
|
|
41
|
-
npx @thesashadev/girl-agent
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Wizard задаст пару вопросов — имя, возраст, Telegram-подключение, LLM-ключ. Всё.
|
|
45
|
-
|
|
46
|
-
Если профиль уже есть:
|
|
47
|
-
|
|
48
|
-
```powershell
|
|
49
|
-
npx @thesashadev/girl-agent --profile=arina
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Из исходников:**
|
|
53
|
-
|
|
54
|
-
```powershell
|
|
55
|
-
git clone https://github.com/TheSashaDev/girl-agent.git
|
|
56
|
-
cd girl-agent
|
|
57
|
-
npm install
|
|
58
|
-
npm run dev
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Что под капотом
|
|
64
|
-
|
|
65
|
-
Поведение собирается из нескольких слоёв, а не из одного промпта.
|
|
66
|
-
|
|
67
|
-
- 📱 **Она не всегда онлайн** — паттерн присутствия зависит от персонажа: кто-то в телефоне круглые сутки, кто-то заходит раз в час, кто-то только вечером.
|
|
68
|
-
- 😴 **Ночью спит** — можно разбудить через `:wake`, но без команды шанс ответа низкий.
|
|
69
|
-
- 📅 **Расписание дня** — у каждого дня есть расписание: пары, работа, дорога, свободное время. Если она на занятиях, телефон может быть недоступен.
|
|
70
|
-
- ❤️ **Отношения** — пять счётчиков: интерес, доверие, привлекательность, раздражение, неловкость. Меняются от каждого диалога. Высокое раздражение — чаще игнор и холод.
|
|
71
|
-
- 📈 **Стадии сближения** — отношения проходят стадии: от "дала тг, но холодная" до "давно вместе". Стадия влияет на тепло, флирт, длину ответов.
|
|
72
|
-
- ⚠️ **Конфликты** — если давить, спамить или нарушать границы — включается конфликт. Она может замолчать на часы или дни.
|
|
73
|
-
- 🧠 **Память** — важные события пишутся в `long-term.md` и всплывают в будущих диалогах.
|
|
74
|
-
- 🚫 **Anti-AI** — промпт запрещает markdown, "конечно", "я понимаю", эмодзи-ряды, вопросы в конце сообщений и всё, что палит ChatGPT.
|
|
75
|
-
- 👤 **Userbot mode** — настоящий Telegram-аккаунт через MTProto. Умеет читать сообщения, ставить реакции, печатать, удалять и редактировать. Выглядит как живой человек, а не как бот.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Почему не просто GPTs или промпт
|
|
80
|
-
|
|
81
|
-
Вариантов сделать "девушку в Telegram" несколько — от костыльных до полноценных. Разберём, что есть и где дыры.
|
|
82
|
-
|
|
83
|
-
### ChatGPT GPTs
|
|
84
|
-
|
|
85
|
-
**Как это работает:** Кастомный бот внутри ChatGPT с system prompt. Логика поведения = промпт.
|
|
86
|
-
|
|
87
|
-
**Что упущено:**
|
|
88
|
-
- Нет памяти между сессиями — каждая начинается с нуля
|
|
89
|
-
- Нет Telegram — только веб-интерфейс
|
|
90
|
-
- Нет реакций, печати, редактирования
|
|
91
|
-
- Бот всегда "онлайн" — нет расписания или сна
|
|
92
|
-
- Память ограничена контекстным окном
|
|
93
|
-
|
|
94
|
-
**Итог:** Чат-бот с кастомным промптом, без состояния и реалистичного поведения.
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
### OpenClaw + prompt (markdown-файлы)
|
|
99
|
-
|
|
100
|
-
**Как это работает:** Фреймворк для AI-ассистентов. Личность через markdown-файлы (SOUL.md, IDENTITY.md, USER.md). Telegram bridge через GramJS (MTProto).
|
|
101
|
-
|
|
102
|
-
**Что упущено:**
|
|
103
|
-
- Нет реализм-модулей: presence, sleep, conflict, daily-life, relationship stages
|
|
104
|
-
- Нет agenda — бот не планирует действия
|
|
105
|
-
- Память = история сообщений, нет long-term storage
|
|
106
|
-
- Нет relationship score и conflict system
|
|
107
|
-
|
|
108
|
-
**Итог:** Хороший bridge для Telegram, но не персонаж-движок. Поведение = промпт + история.
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
### HeatherBot
|
|
113
|
-
|
|
114
|
-
**Как это работает:** Локальный Telegram userbot (MTProto via Telethon), persona в YAML, 4-слойная память, 17 kink-specific overlays. ~10K строк Python.
|
|
115
|
-
|
|
116
|
-
**Что упущено:**
|
|
117
|
-
- Слишком специфично под NSFW — 17 kink overlays
|
|
118
|
-
- Сложно настроить — нужно llama-server, Ollama, ComfyUI
|
|
119
|
-
- Требует мощного GPU — 12B модель локально
|
|
120
|
-
- Нет presence/sleep/conflict как отдельных модулей
|
|
121
|
-
|
|
122
|
-
**Итог:** Мощное, но узкое решение под NSFW с тяжёлой инфраструктурой.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
### Character.AI
|
|
127
|
-
|
|
128
|
-
**Как это работает:** Закрытый сервис для AI-переписки. Персоны через UI, поведение = prompt engineering + session-level memory.
|
|
129
|
-
|
|
130
|
-
**Что упущено:**
|
|
131
|
-
- Нет Telegram — только веб-интерфейс
|
|
132
|
-
- Нет контроля — всё на их серверах
|
|
133
|
-
- Память сбрасывается между сессиями
|
|
134
|
-
- Memory ограничена — persona обрезается при росте истории
|
|
135
|
-
|
|
136
|
-
**Итог:** Закрытый сервис с ограниченной памятью и без Telegram.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
### girl-agent
|
|
141
|
-
|
|
142
|
-
**Как это работает:** Движок с несколькими слоями состояния: presence, sleep, daily-life, relationship stages, conflict, memory, anti-AI. Userbot mode через MTProto.
|
|
143
|
-
|
|
144
|
-
**Технические детали:**
|
|
145
|
-
- Presence — паттерны присутствия (частота, офлайн, вероятность ответа)
|
|
146
|
-
- Sleep — время сна, night wake chance
|
|
147
|
-
- Daily-life — расписание, занятость, приоритеты
|
|
148
|
-
- Relationship stages — stranger → convinced → close → intimate → bonded
|
|
149
|
-
- Relationship score — interest, trust, attraction, annoyance, cringe
|
|
150
|
-
- Conflict — если давить/спамить, включается конфликт, может замолчать
|
|
151
|
-
- Memory — важные события в long-term.md, всплывают в диалогах
|
|
152
|
-
- Anti-AI — промпт запрещает markdown, "конечно", "я понимаю", эмодзи-ряды
|
|
153
|
-
- Userbot mode — умеет читать, реагировать, печатать, удалять, редактировать
|
|
154
|
-
- Agenda — бот планирует действия, живёт своей жизнью
|
|
155
|
-
|
|
156
|
-
**Итог:** Движок с несколькими слоями решения. Поведение собирается из состояния, а не из текстовых инструкций.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Безопасность
|
|
161
|
-
|
|
162
|
-
⚠️ **Не публикуй:** `data/`, `config.json`, `sessionString` и API-ключи.
|
|
163
|
-
|
|
164
|
-
🔒 **Для userbot mode** используй отдельный тестовый аккаунт — Telegram может забанить основной аккаунт за подозрительную активность.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Лицензия
|
|
169
|
-
|
|
170
|
-
📄 **Source-available** — исходный код открыт для личного тестирования, оценки и вкладов.
|
|
171
|
-
|
|
172
|
-
**Разрешено:**
|
|
173
|
-
- Клонировать и запускать локально
|
|
174
|
-
- Создавать issues и отправлять pull requests
|
|
175
|
-
- Изучать код и экспериментировать
|
|
176
|
-
|
|
177
|
-
**Запрещено без письменного разрешения:**
|
|
178
|
-
- Коммерческое использование
|
|
179
|
-
- Платный хостинг
|
|
180
|
-
- Перепродажа
|
|
181
|
-
- Публичные конкурирующие клоны
|
|
182
|
-
- Использование кода внутри коммерческих продуктов
|
|
183
|
-
|
|
184
|
-
📜 Полный текст лицензии: [LICENSE](./LICENSE)
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[website]: https://girl-agent.com
|
|
4
|
+
[docs]: https://docs.girl-agent.com
|
|
5
|
+
|
|
6
|
+
**[website]** · **[docs]**
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Это только бета-версия. Со временем будет дорабатыватся.
|
|
10
|
+
Со всеми проблемами и багами пишите в Issues.
|
|
11
|
+
ТГ создателя - @voided_net
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Содержание
|
|
16
|
+
|
|
17
|
+
- [Быстрый старт](#быстрый-старт)
|
|
18
|
+
- [Что под капотом](#что-под-капотом)
|
|
19
|
+
- [Почему не просто GPTs или промпт](#почему-не-просто-gpts-или-промпт)
|
|
20
|
+
- [Changelog](./CHANGELOG.md)
|
|
21
|
+
- [Безопасность](#безопасность)
|
|
22
|
+
- [Лицензия](#лицензия)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## О проекте
|
|
27
|
+
|
|
28
|
+
Она не отвечает на каждое сообщение. Иногда читает и молчит. Иногда ставит реакцию. Иногда отвечает через час, потому что была занята или просто не хотела.
|
|
29
|
+
|
|
30
|
+
Это не баг. Так задумано.
|
|
31
|
+
|
|
32
|
+
`girl-agent` — ИИ-девушка, которая ведёт себя в переписке как человек. Со сном, настроением, расписанием, памятью и характером. Без "конечно, я понимаю" и ChatGPT-повадок.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Быстрый старт
|
|
37
|
+
|
|
38
|
+
**Через NPX (рекомендуется):**
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
npx @thesashadev/girl-agent
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Wizard задаст пару вопросов — имя, возраст, Telegram-подключение, LLM-ключ. Всё.
|
|
45
|
+
|
|
46
|
+
Если профиль уже есть:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
npx @thesashadev/girl-agent --profile=arina
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Из исходников:**
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
git clone https://github.com/TheSashaDev/girl-agent.git
|
|
56
|
+
cd girl-agent
|
|
57
|
+
npm install
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Что под капотом
|
|
64
|
+
|
|
65
|
+
Поведение собирается из нескольких слоёв, а не из одного промпта.
|
|
66
|
+
|
|
67
|
+
- 📱 **Она не всегда онлайн** — паттерн присутствия зависит от персонажа: кто-то в телефоне круглые сутки, кто-то заходит раз в час, кто-то только вечером.
|
|
68
|
+
- 😴 **Ночью спит** — можно разбудить через `:wake`, но без команды шанс ответа низкий.
|
|
69
|
+
- 📅 **Расписание дня** — у каждого дня есть расписание: пары, работа, дорога, свободное время. Если она на занятиях, телефон может быть недоступен.
|
|
70
|
+
- ❤️ **Отношения** — пять счётчиков: интерес, доверие, привлекательность, раздражение, неловкость. Меняются от каждого диалога. Высокое раздражение — чаще игнор и холод.
|
|
71
|
+
- 📈 **Стадии сближения** — отношения проходят стадии: от "дала тг, но холодная" до "давно вместе". Стадия влияет на тепло, флирт, длину ответов.
|
|
72
|
+
- ⚠️ **Конфликты** — если давить, спамить или нарушать границы — включается конфликт. Она может замолчать на часы или дни.
|
|
73
|
+
- 🧠 **Память** — важные события пишутся в `long-term.md` и всплывают в будущих диалогах.
|
|
74
|
+
- 🚫 **Anti-AI** — промпт запрещает markdown, "конечно", "я понимаю", эмодзи-ряды, вопросы в конце сообщений и всё, что палит ChatGPT.
|
|
75
|
+
- 👤 **Userbot mode** — настоящий Telegram-аккаунт через MTProto. Умеет читать сообщения, ставить реакции, печатать, удалять и редактировать. Выглядит как живой человек, а не как бот.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Почему не просто GPTs или промпт
|
|
80
|
+
|
|
81
|
+
Вариантов сделать "девушку в Telegram" несколько — от костыльных до полноценных. Разберём, что есть и где дыры.
|
|
82
|
+
|
|
83
|
+
### ChatGPT GPTs
|
|
84
|
+
|
|
85
|
+
**Как это работает:** Кастомный бот внутри ChatGPT с system prompt. Логика поведения = промпт.
|
|
86
|
+
|
|
87
|
+
**Что упущено:**
|
|
88
|
+
- Нет памяти между сессиями — каждая начинается с нуля
|
|
89
|
+
- Нет Telegram — только веб-интерфейс
|
|
90
|
+
- Нет реакций, печати, редактирования
|
|
91
|
+
- Бот всегда "онлайн" — нет расписания или сна
|
|
92
|
+
- Память ограничена контекстным окном
|
|
93
|
+
|
|
94
|
+
**Итог:** Чат-бот с кастомным промптом, без состояния и реалистичного поведения.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### OpenClaw + prompt (markdown-файлы)
|
|
99
|
+
|
|
100
|
+
**Как это работает:** Фреймворк для AI-ассистентов. Личность через markdown-файлы (SOUL.md, IDENTITY.md, USER.md). Telegram bridge через GramJS (MTProto).
|
|
101
|
+
|
|
102
|
+
**Что упущено:**
|
|
103
|
+
- Нет реализм-модулей: presence, sleep, conflict, daily-life, relationship stages
|
|
104
|
+
- Нет agenda — бот не планирует действия
|
|
105
|
+
- Память = история сообщений, нет long-term storage
|
|
106
|
+
- Нет relationship score и conflict system
|
|
107
|
+
|
|
108
|
+
**Итог:** Хороший bridge для Telegram, но не персонаж-движок. Поведение = промпт + история.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### HeatherBot
|
|
113
|
+
|
|
114
|
+
**Как это работает:** Локальный Telegram userbot (MTProto via Telethon), persona в YAML, 4-слойная память, 17 kink-specific overlays. ~10K строк Python.
|
|
115
|
+
|
|
116
|
+
**Что упущено:**
|
|
117
|
+
- Слишком специфично под NSFW — 17 kink overlays
|
|
118
|
+
- Сложно настроить — нужно llama-server, Ollama, ComfyUI
|
|
119
|
+
- Требует мощного GPU — 12B модель локально
|
|
120
|
+
- Нет presence/sleep/conflict как отдельных модулей
|
|
121
|
+
|
|
122
|
+
**Итог:** Мощное, но узкое решение под NSFW с тяжёлой инфраструктурой.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### Character.AI
|
|
127
|
+
|
|
128
|
+
**Как это работает:** Закрытый сервис для AI-переписки. Персоны через UI, поведение = prompt engineering + session-level memory.
|
|
129
|
+
|
|
130
|
+
**Что упущено:**
|
|
131
|
+
- Нет Telegram — только веб-интерфейс
|
|
132
|
+
- Нет контроля — всё на их серверах
|
|
133
|
+
- Память сбрасывается между сессиями
|
|
134
|
+
- Memory ограничена — persona обрезается при росте истории
|
|
135
|
+
|
|
136
|
+
**Итог:** Закрытый сервис с ограниченной памятью и без Telegram.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### girl-agent
|
|
141
|
+
|
|
142
|
+
**Как это работает:** Движок с несколькими слоями состояния: presence, sleep, daily-life, relationship stages, conflict, memory, anti-AI. Userbot mode через MTProto.
|
|
143
|
+
|
|
144
|
+
**Технические детали:**
|
|
145
|
+
- Presence — паттерны присутствия (частота, офлайн, вероятность ответа)
|
|
146
|
+
- Sleep — время сна, night wake chance
|
|
147
|
+
- Daily-life — расписание, занятость, приоритеты
|
|
148
|
+
- Relationship stages — stranger → convinced → close → intimate → bonded
|
|
149
|
+
- Relationship score — interest, trust, attraction, annoyance, cringe
|
|
150
|
+
- Conflict — если давить/спамить, включается конфликт, может замолчать
|
|
151
|
+
- Memory — важные события в long-term.md, всплывают в диалогах
|
|
152
|
+
- Anti-AI — промпт запрещает markdown, "конечно", "я понимаю", эмодзи-ряды
|
|
153
|
+
- Userbot mode — умеет читать, реагировать, печатать, удалять, редактировать
|
|
154
|
+
- Agenda — бот планирует действия, живёт своей жизнью
|
|
155
|
+
|
|
156
|
+
**Итог:** Движок с несколькими слоями решения. Поведение собирается из состояния, а не из текстовых инструкций.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Безопасность
|
|
161
|
+
|
|
162
|
+
⚠️ **Не публикуй:** `data/`, `config.json`, `sessionString` и API-ключи.
|
|
163
|
+
|
|
164
|
+
🔒 **Для userbot mode** используй отдельный тестовый аккаунт — Telegram может забанить основной аккаунт за подозрительную активность.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Лицензия
|
|
169
|
+
|
|
170
|
+
📄 **Source-available** — исходный код открыт для личного тестирования, оценки и вкладов.
|
|
171
|
+
|
|
172
|
+
**Разрешено:**
|
|
173
|
+
- Клонировать и запускать локально
|
|
174
|
+
- Создавать issues и отправлять pull requests
|
|
175
|
+
- Изучать код и экспериментировать
|
|
176
|
+
|
|
177
|
+
**Запрещено без письменного разрешения:**
|
|
178
|
+
- Коммерческое использование
|
|
179
|
+
- Платный хостинг
|
|
180
|
+
- Перепродажа
|
|
181
|
+
- Публичные конкурирующие клоны
|
|
182
|
+
- Использование кода внутри коммерческих продуктов
|
|
183
|
+
|
|
184
|
+
📜 Полный текст лицензии: [LICENSE](./LICENSE)
|
package/dist/cli.js
CHANGED
|
@@ -347,6 +347,24 @@ var LLM_PRESETS = [
|
|
|
347
347
|
defaultModel: "gpt-5.5",
|
|
348
348
|
models: ["gpt-5.5", "gpt-5.5-thinking", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-thinking", "gpt-5.3-chat-latest", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini"]
|
|
349
349
|
},
|
|
350
|
+
{
|
|
351
|
+
id: "lmstudio",
|
|
352
|
+
name: "LM Studio",
|
|
353
|
+
proto: "openai",
|
|
354
|
+
baseURL: "http://localhost:1234/v1",
|
|
355
|
+
defaultModel: "",
|
|
356
|
+
custom: true,
|
|
357
|
+
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, OpenAI-compatible endpoint"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "ollama",
|
|
361
|
+
name: "Ollama",
|
|
362
|
+
proto: "openai",
|
|
363
|
+
baseURL: "http://localhost:11434/v1",
|
|
364
|
+
defaultModel: "llama3.1",
|
|
365
|
+
custom: true,
|
|
366
|
+
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0447\u0435\u0440\u0435\u0437 /v1"
|
|
367
|
+
},
|
|
350
368
|
{
|
|
351
369
|
id: "anthropic",
|
|
352
370
|
name: "Anthropic",
|
|
@@ -790,6 +808,7 @@ async function readConfig(slug) {
|
|
|
790
808
|
sleepFrom: 23,
|
|
791
809
|
sleepTo: 8,
|
|
792
810
|
nightWakeChance: 0.05,
|
|
811
|
+
privacy: "owner-only",
|
|
793
812
|
busySchedule: [],
|
|
794
813
|
...parsed,
|
|
795
814
|
communication
|
|
@@ -976,36 +995,57 @@ async function writeAgenda(slug, items) {
|
|
|
976
995
|
init_esm_shims();
|
|
977
996
|
import OpenAI from "openai";
|
|
978
997
|
import Anthropic from "@anthropic-ai/sdk";
|
|
998
|
+
var LLM_TIMEOUT_MS = 12e4;
|
|
999
|
+
var LLM_MAX_RETRIES = 1;
|
|
979
1000
|
var OpenAILike = class {
|
|
980
1001
|
constructor(cfg) {
|
|
981
1002
|
this.cfg = cfg;
|
|
982
1003
|
this.client = new OpenAI({
|
|
983
1004
|
apiKey: cfg.apiKey,
|
|
984
|
-
baseURL: cfg.baseURL
|
|
1005
|
+
baseURL: normalizeBaseURL(cfg.baseURL),
|
|
1006
|
+
timeout: LLM_TIMEOUT_MS,
|
|
1007
|
+
maxRetries: LLM_MAX_RETRIES
|
|
985
1008
|
});
|
|
986
1009
|
}
|
|
987
1010
|
cfg;
|
|
988
1011
|
client;
|
|
989
1012
|
async chat(messages, opts = {}) {
|
|
990
|
-
const
|
|
1013
|
+
const params = {
|
|
991
1014
|
model: this.cfg.model,
|
|
992
|
-
messages: messages
|
|
993
|
-
role: m.role,
|
|
994
|
-
content: typeof m.content === "string" ? m.content : m.content.map((p) => p.type === "text" ? { type: "text", text: p.text } : { type: "image_url", image_url: { url: `data:${p.mimeType};base64,${p.data}` } })
|
|
995
|
-
})),
|
|
1015
|
+
messages: openAIMessages(messages),
|
|
996
1016
|
temperature: opts.temperature ?? 0.85,
|
|
997
|
-
max_tokens: opts.maxTokens ?? 600,
|
|
998
1017
|
response_format: opts.json ? { type: "json_object" } : void 0
|
|
999
|
-
}
|
|
1018
|
+
};
|
|
1019
|
+
if (usesMaxCompletionTokens(this.cfg.model)) {
|
|
1020
|
+
params.max_completion_tokens = opts.maxTokens ?? 600;
|
|
1021
|
+
} else {
|
|
1022
|
+
params.max_tokens = opts.maxTokens ?? 600;
|
|
1023
|
+
}
|
|
1024
|
+
const res = await this.createWithCompatibilityFallback(params);
|
|
1000
1025
|
return res.choices[0]?.message?.content?.trim() ?? "";
|
|
1001
1026
|
}
|
|
1027
|
+
async createWithCompatibilityFallback(params) {
|
|
1028
|
+
try {
|
|
1029
|
+
return await this.client.chat.completions.create(params);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
const fallback = completionTokenFallback(params, error);
|
|
1032
|
+
if (!fallback) throw enrichOpenAIError(error, this.cfg.baseURL);
|
|
1033
|
+
try {
|
|
1034
|
+
return await this.client.chat.completions.create(fallback);
|
|
1035
|
+
} catch (fallbackError) {
|
|
1036
|
+
throw enrichOpenAIError(fallbackError, this.cfg.baseURL);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1002
1040
|
};
|
|
1003
1041
|
var AnthropicLike = class {
|
|
1004
1042
|
constructor(cfg) {
|
|
1005
1043
|
this.cfg = cfg;
|
|
1006
1044
|
this.client = new Anthropic({
|
|
1007
1045
|
apiKey: cfg.apiKey,
|
|
1008
|
-
baseURL: cfg.baseURL
|
|
1046
|
+
baseURL: normalizeBaseURL(cfg.baseURL),
|
|
1047
|
+
timeout: LLM_TIMEOUT_MS,
|
|
1048
|
+
maxRetries: LLM_MAX_RETRIES
|
|
1009
1049
|
});
|
|
1010
1050
|
}
|
|
1011
1051
|
cfg;
|
|
@@ -1031,12 +1071,15 @@ var AnthropicLike = class {
|
|
|
1031
1071
|
if (merged[merged.length - 1].role !== "user") {
|
|
1032
1072
|
merged.push({ role: "user", content: "(\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439)" });
|
|
1033
1073
|
}
|
|
1034
|
-
const
|
|
1074
|
+
const params = {
|
|
1035
1075
|
model: this.cfg.model,
|
|
1036
1076
|
system: system || void 0,
|
|
1037
1077
|
max_tokens: opts.maxTokens ?? 600,
|
|
1038
1078
|
temperature: opts.temperature ?? 0.85,
|
|
1039
1079
|
messages: merged.map((m) => ({ role: m.role, content: anthropicContent(m.content) }))
|
|
1080
|
+
};
|
|
1081
|
+
const res = await this.client.messages.create(params).catch((error) => {
|
|
1082
|
+
throw enrichAnthropicError(error, this.cfg.baseURL);
|
|
1040
1083
|
});
|
|
1041
1084
|
const block = res.content.find((c) => c.type === "text");
|
|
1042
1085
|
return block && "text" in block ? block.text.trim() : "";
|
|
@@ -1052,17 +1095,81 @@ function mergeContent(a, b) {
|
|
|
1052
1095
|
const bb = typeof b === "string" ? [{ type: "text", text: b }] : b;
|
|
1053
1096
|
return [...aa, ...bb];
|
|
1054
1097
|
}
|
|
1098
|
+
function openAIMessages(messages) {
|
|
1099
|
+
return messages.map((m) => {
|
|
1100
|
+
if (m.role === "system") return { role: "system", content: openAITextContent(m.content) };
|
|
1101
|
+
if (m.role === "assistant") return { role: "assistant", content: openAITextContent(m.content) };
|
|
1102
|
+
return { role: "user", content: openAIContent(m.content) };
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
function openAITextContent(content) {
|
|
1106
|
+
return typeof content === "string" ? content : contentToText(content);
|
|
1107
|
+
}
|
|
1108
|
+
function openAIContent(content) {
|
|
1109
|
+
if (typeof content === "string") return content;
|
|
1110
|
+
return content.map((p) => p.type === "text" ? { type: "text", text: p.text } : { type: "image_url", image_url: { url: `data:${p.mimeType};base64,${p.data}` } });
|
|
1111
|
+
}
|
|
1055
1112
|
function anthropicContent(content) {
|
|
1056
1113
|
if (typeof content === "string") return content;
|
|
1057
1114
|
return content.map((p) => p.type === "text" ? { type: "text", text: p.text } : {
|
|
1058
1115
|
type: "image",
|
|
1059
1116
|
source: {
|
|
1060
1117
|
type: "base64",
|
|
1061
|
-
media_type: p.mimeType,
|
|
1118
|
+
media_type: anthropicImageMime(p.mimeType),
|
|
1062
1119
|
data: p.data
|
|
1063
1120
|
}
|
|
1064
1121
|
});
|
|
1065
1122
|
}
|
|
1123
|
+
function anthropicImageMime(mimeType) {
|
|
1124
|
+
return mimeType === "image/png" || mimeType === "image/gif" || mimeType === "image/webp" ? mimeType : "image/jpeg";
|
|
1125
|
+
}
|
|
1126
|
+
function normalizeBaseURL(value) {
|
|
1127
|
+
const trimmed = value?.trim();
|
|
1128
|
+
if (!trimmed) return void 0;
|
|
1129
|
+
return trimmed.replace(/\/+$/, "");
|
|
1130
|
+
}
|
|
1131
|
+
function usesMaxCompletionTokens(model) {
|
|
1132
|
+
return /^(?:o\d|o\d-|o\d\b|gpt-5|gpt-5\.|gpt-[5-9])|\/(?:o\d|gpt-5|gpt-[5-9])/.test(model.trim().toLowerCase());
|
|
1133
|
+
}
|
|
1134
|
+
function completionTokenFallback(params, error) {
|
|
1135
|
+
const message = errorMessage(error).toLowerCase();
|
|
1136
|
+
if (params.max_tokens != null && message.includes("max_tokens") && message.includes("max_completion_tokens")) {
|
|
1137
|
+
const { max_tokens, ...rest } = params;
|
|
1138
|
+
return { ...rest, max_completion_tokens: max_tokens };
|
|
1139
|
+
}
|
|
1140
|
+
if (params.max_completion_tokens != null && message.includes("max_completion_tokens") && message.includes("max_tokens")) {
|
|
1141
|
+
const { max_completion_tokens, ...rest } = params;
|
|
1142
|
+
return { ...rest, max_tokens: max_completion_tokens };
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
function enrichOpenAIError(error, baseURL) {
|
|
1147
|
+
if (error instanceof OpenAI.APIConnectionError) {
|
|
1148
|
+
return new Error(connectionErrorMessage("OpenAI-compatible", baseURL, error));
|
|
1149
|
+
}
|
|
1150
|
+
if (error instanceof OpenAI.APIError) {
|
|
1151
|
+
const detail = error.status ? `${error.status} ${error.message}` : error.message;
|
|
1152
|
+
return new Error(`OpenAI-compatible API error: ${detail}`);
|
|
1153
|
+
}
|
|
1154
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
1155
|
+
}
|
|
1156
|
+
function enrichAnthropicError(error, baseURL) {
|
|
1157
|
+
if (error instanceof Anthropic.APIConnectionError) {
|
|
1158
|
+
return new Error(connectionErrorMessage("Anthropic-compatible", baseURL, error));
|
|
1159
|
+
}
|
|
1160
|
+
if (error instanceof Anthropic.APIError) {
|
|
1161
|
+
const detail = error.status ? `${error.status} ${error.message}` : error.message;
|
|
1162
|
+
return new Error(`Anthropic-compatible API error: ${detail}`);
|
|
1163
|
+
}
|
|
1164
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
1165
|
+
}
|
|
1166
|
+
function connectionErrorMessage(provider, baseURL, error) {
|
|
1167
|
+
const endpoint = normalizeBaseURL(baseURL) ?? "default endpoint";
|
|
1168
|
+
return `${provider} connection failed (${endpoint}): ${error.message}. \u041F\u0440\u043E\u0432\u0435\u0440\u044C, \u0447\u0442\u043E base URL \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0441 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B, \u0441\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043F\u0443\u0449\u0435\u043D, \u043F\u0443\u0442\u044C \u0432\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u043D\u0443\u0436\u043D\u044B\u0439 OpenAI/Anthropic-compatible endpoint \u0438 \u043A\u043B\u044E\u0447 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0443.`;
|
|
1169
|
+
}
|
|
1170
|
+
function errorMessage(error) {
|
|
1171
|
+
return error instanceof Error ? error.message : String(error ?? "");
|
|
1172
|
+
}
|
|
1066
1173
|
function makeLLM(cfg) {
|
|
1067
1174
|
return cfg.proto === "anthropic" ? new AnthropicLike(cfg) : new OpenAILike(cfg);
|
|
1068
1175
|
}
|
|
@@ -1071,7 +1178,7 @@ function makeLLM(cfg) {
|
|
|
1071
1178
|
init_esm_shims();
|
|
1072
1179
|
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}}.`;
|
|
1073
1180
|
var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
1074
|
-
async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "") {
|
|
1181
|
+
async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "", onProgress) {
|
|
1075
1182
|
const country = nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413";
|
|
1076
1183
|
const langHint = nationality === "UA" ? "\u041F\u0438\u0448\u0435\u0442 \u043D\u0430 \u0420\u0423\u0421\u0421\u041A\u041E\u041C (\u043A\u0430\u043A \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u043F\u0438\u0448\u0435\u0442 \u0431\u043E\u043B\u044C\u0448\u0438\u043D\u0441\u0442\u0432\u043E \u0434\u0435\u0432\u0443\u0448\u0435\u043A \u0432 \u0423\u043A\u0440\u0430\u0438\u043D\u0435 \u0432 \u0442\u0433). \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C \u043B\u0451\u0433\u043A\u0438\u0439 \u0441\u0443\u0440\u0436\u0438\u043A: ~90% \u0440\u0443\u0441\u0441\u043A\u0438\u0439 + ~10% \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0445 \u0432\u0441\u0442\u0430\u0432\u043E\u043A (\u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430: '\u0448\u043E', '\u043C\u0430\u0431\u0443\u0442\u044C', '\u0442\u0440\u043E\u0445\u0438', '\u044F\u043A', '\u043D\u0443 \u0448\u043E', '\u0442\u0430 \u0439\u0434\u0438', '\u0434\u044F\u043A\u0443\u044E'), \u043D\u043E \u0431\u0435\u0437 \u043F\u043E\u043B\u043D\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u043D\u0430 \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439. \u0427\u0438\u0441\u0442\u043E-\u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439 \u0442\u0435\u043A\u0441\u0442 \u041D\u0415 \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439 \u2014 \u044D\u0442\u043E \u043D\u0435\u0440\u0435\u0430\u043B\u0438\u0441\u0442\u0438\u0447\u043D\u043E \u0434\u043B\u044F \u0442\u0433-\u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438." : "\u0420\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u0431\u0435\u0437 \u0443\u043A\u0440\u0430\u0438\u043D\u0438\u0437\u043C\u043E\u0432.";
|
|
1077
1184
|
const notes = personaNotes.trim() ? `
|
|
@@ -1174,12 +1281,14 @@ ${personaNotes.trim()}
|
|
|
1174
1281
|
- \u0421\u043B\u043E\u0442\u044B \u0434\u043E\u043B\u0436\u043D\u044B \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442\u044C \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0443: \u0443\u0447\u0451\u0431\u0430/\u0440\u0430\u0431\u043E\u0442\u0430/\u0434\u043E\u0440\u043E\u0433\u0430/\u0441\u043F\u043E\u0440\u0442/\u0441\u0435\u043C\u0435\u0439\u043D\u044B\u0435 \u0434\u0435\u043B\u0430/\u043F\u043E\u0434\u0440\u0430\u0431\u043E\u0442\u043A\u0430.
|
|
1175
1282
|
- days \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437: mon, tue, wed, thu, fri, sat, sun.
|
|
1176
1283
|
- \u0411\u0435\u0437 markdown, \u0442\u043E\u043B\u044C\u043A\u043E JSON.`;
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
]);
|
|
1284
|
+
onProgress?.(5, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C persona.md\u2026");
|
|
1285
|
+
const persona = await llm.chat([{ role: "system", content: sys }, { role: "user", content: personaPrompt }], { temperature: 0.95, maxTokens: 3500 });
|
|
1286
|
+
onProgress?.(35, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C speech.md\u2026");
|
|
1287
|
+
const speech = await llm.chat([{ role: "system", content: sys }, { role: "user", content: speechPrompt }], { temperature: 0.9, maxTokens: 3500 });
|
|
1288
|
+
onProgress?.(65, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C communication.md\u2026");
|
|
1289
|
+
const boundaries = await llm.chat([{ role: "system", content: sys }, { role: "user", content: boundariesPrompt }], { temperature: 0.9, maxTokens: 3500 });
|
|
1290
|
+
onProgress?.(85, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C busy schedule\u2026");
|
|
1291
|
+
const routineRaw = await llm.chat([{ role: "system", content: sys }, { role: "user", content: routinePrompt }], { temperature: 0.85, maxTokens: 3500, json: true });
|
|
1183
1292
|
const busySchedule = parseBusySchedule(routineRaw, name, age);
|
|
1184
1293
|
await writeMd(slug, "persona.md", persona);
|
|
1185
1294
|
await writeMd(slug, "speech.md", speech);
|
|
@@ -1489,6 +1598,7 @@ function Wizard({ initial, onDone }) {
|
|
|
1489
1598
|
const [sleepToStr, setSleepToStr] = useState("8");
|
|
1490
1599
|
const [nightWakeStr, setNightWakeStr] = useState("5");
|
|
1491
1600
|
const [communicationProfile, setCommunicationProfile] = useState(normalizeCommunicationProfile(initial));
|
|
1601
|
+
const [privacy, setPrivacy] = useState(initial?.privacy ?? "owner-only");
|
|
1492
1602
|
const [stage, setStage] = useState(initial?.stage ?? "tg-given-cold");
|
|
1493
1603
|
const [pickedMcp, setPickedMcp] = useState(initial?.mcp?.map((m) => m.id) ?? []);
|
|
1494
1604
|
const [mcpQueue, setMcpQueue] = useState([]);
|
|
@@ -1529,48 +1639,32 @@ function Wizard({ initial, onDone }) {
|
|
|
1529
1639
|
async function startGeneration() {
|
|
1530
1640
|
setStep("generating");
|
|
1531
1641
|
setGenPercent(0);
|
|
1532
|
-
let timer = null;
|
|
1533
1642
|
try {
|
|
1534
|
-
|
|
1643
|
+
setError(null);
|
|
1644
|
+
setGenStatus("\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u043C\u0441\u044F \u043A LLM\u2026");
|
|
1535
1645
|
const slug = slugify(name);
|
|
1536
1646
|
const llm = makeLLM({ presetId: llmPresetId, proto: llmProto, baseURL: llmBaseURL, apiKey: llmKey, model: llmModel });
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
{ at: 65e3, to: 98, status: "\u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u043C\u2026" }
|
|
1548
|
-
];
|
|
1549
|
-
const startTime = Date.now();
|
|
1550
|
-
let progressIndex = 0;
|
|
1551
|
-
timer = setInterval(() => {
|
|
1552
|
-
const elapsed = Date.now() - startTime;
|
|
1553
|
-
while (progressIndex < progressSteps.length && elapsed >= progressSteps[progressIndex].at) {
|
|
1554
|
-
const step2 = progressSteps[progressIndex];
|
|
1555
|
-
setGenPercent(step2.to);
|
|
1556
|
-
setGenStatus(step2.status);
|
|
1557
|
-
progressIndex++;
|
|
1558
|
-
}
|
|
1559
|
-
if (progressIndex >= progressSteps.length) {
|
|
1560
|
-
clearInterval(timer);
|
|
1647
|
+
const generated = await generatePersonaPack(
|
|
1648
|
+
llm,
|
|
1649
|
+
slug,
|
|
1650
|
+
name.trim(),
|
|
1651
|
+
Number(ageStr),
|
|
1652
|
+
nationality,
|
|
1653
|
+
personaNotesForGeneration(personaNotes, communicationProfile),
|
|
1654
|
+
(percent, status) => {
|
|
1655
|
+
setGenPercent(percent);
|
|
1656
|
+
setGenStatus(status);
|
|
1561
1657
|
}
|
|
1562
|
-
|
|
1563
|
-
const generated = await generatePersonaPack(llm, slug, name.trim(), Number(ageStr), nationality, personaNotesForGeneration(personaNotes, communicationProfile));
|
|
1564
|
-
if (timer) clearInterval(timer);
|
|
1658
|
+
);
|
|
1565
1659
|
setGenPercent(100);
|
|
1566
1660
|
setGenStatus("\u0433\u043E\u0442\u043E\u0432\u043E!");
|
|
1567
1661
|
setBusySchedule(generated.busySchedule);
|
|
1568
1662
|
await writeConfig(makeConfig({ busySchedule: generated.busySchedule, mcp: [] }));
|
|
1569
1663
|
setTimeout(() => setStep("stage"), 800);
|
|
1570
1664
|
} catch (e) {
|
|
1571
|
-
if (timer) clearInterval(timer);
|
|
1572
1665
|
setError("LLM \u043E\u0448\u0438\u0431\u043A\u0430: " + e.message);
|
|
1573
|
-
|
|
1666
|
+
setGenStatus("\u043E\u0448\u0438\u0431\u043A\u0430 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438");
|
|
1667
|
+
setStep("generation-error");
|
|
1574
1668
|
}
|
|
1575
1669
|
}
|
|
1576
1670
|
if (step === "splash") {
|
|
@@ -1664,7 +1758,6 @@ function Wizard({ initial, onDone }) {
|
|
|
1664
1758
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: `\u043C\u043E\u0434\u0435\u043B\u044C (${preset?.name})` }), /* @__PURE__ */ React.createElement(Bar, { step: 2, total: 9 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, items.length > 1 ? /* @__PURE__ */ React.createElement(SelectInput, { items, onSelect: (it) => {
|
|
1665
1759
|
if (it.value === "__custom__") {
|
|
1666
1760
|
setLlmModel("");
|
|
1667
|
-
setStep("api-key");
|
|
1668
1761
|
setStep("api-model-custom");
|
|
1669
1762
|
} else {
|
|
1670
1763
|
setLlmModel(it.value);
|
|
@@ -1928,7 +2021,7 @@ function Wizard({ initial, onDone }) {
|
|
|
1928
2021
|
}
|
|
1929
2022
|
const preset = findCommunicationPreset(String(it.value));
|
|
1930
2023
|
if (preset) setCommunicationProfile(preset.profile);
|
|
1931
|
-
setStep("
|
|
2024
|
+
setStep("privacy");
|
|
1932
2025
|
}
|
|
1933
2026
|
}
|
|
1934
2027
|
)));
|
|
@@ -1993,14 +2086,29 @@ function Wizard({ initial, onDone }) {
|
|
|
1993
2086
|
],
|
|
1994
2087
|
onSelect: (it) => {
|
|
1995
2088
|
setCommunicationProfile((p) => ({ ...p, lifeSharing: it.value }));
|
|
1996
|
-
setStep("
|
|
2089
|
+
setStep("privacy");
|
|
1997
2090
|
}
|
|
1998
2091
|
}
|
|
1999
2092
|
)));
|
|
2000
2093
|
}
|
|
2094
|
+
if (step === "privacy") {
|
|
2095
|
+
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(
|
|
2096
|
+
SelectInput,
|
|
2097
|
+
{
|
|
2098
|
+
items: [
|
|
2099
|
+
{ label: "\u0422\u043E\u043B\u044C\u043A\u043E \u044F \u2014 \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E primary owner, \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0445 \u043C\u043E\u043B\u0447\u0430 \u0438\u0433\u043D\u043E\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C", value: "owner-only" },
|
|
2100
|
+
{ label: "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0438\u0435 \u0447\u0430\u0442\u044B \u2014 \u043A\u043E\u0440\u043E\u0442\u043A\u043E \u043E\u0431\u0449\u0430\u0442\u044C\u0441\u044F \u0441 \u043D\u0435\u0437\u043D\u0430\u043A\u043E\u043C\u044B\u043C\u0438 \u0431\u0435\u0437 \u043F\u0430\u043C\u044F\u0442\u0438", value: "allow-strangers" }
|
|
2101
|
+
],
|
|
2102
|
+
onSelect: (it) => {
|
|
2103
|
+
setPrivacy(it.value);
|
|
2104
|
+
setStep("tz");
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
)), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Primary owner \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u044F\u0435\u0442\u0441\u044F \u043F\u043E \u043F\u0435\u0440\u0432\u043E\u043C\u0443 \u043B\u0438\u0447\u043D\u043E\u043C\u0443 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044E."));
|
|
2108
|
+
}
|
|
2001
2109
|
if (step === "tz") {
|
|
2002
2110
|
const matches = findTzByQuery(tzQuery, 8);
|
|
2003
|
-
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:
|
|
2111
|
+
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: () => {
|
|
2004
2112
|
if (matches[0]) {
|
|
2005
2113
|
setTz(matches[0].iana);
|
|
2006
2114
|
setStep("persona-notes");
|
|
@@ -2008,7 +2116,7 @@ function Wizard({ initial, onDone }) {
|
|
|
2008
2116
|
} })), /* @__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")));
|
|
2009
2117
|
}
|
|
2010
2118
|
if (step === "persona-notes") {
|
|
2011
|
-
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:
|
|
2119
|
+
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.")));
|
|
2012
2120
|
}
|
|
2013
2121
|
if (step === "generating") {
|
|
2014
2122
|
const barWidth = 30;
|
|
@@ -2017,8 +2125,25 @@ function Wizard({ initial, onDone }) {
|
|
|
2017
2125
|
const progressBar = "\u2588".repeat(filled) + "\u2591".repeat(empty2);
|
|
2018
2126
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "LLM \u043F\u0438\u0448\u0435\u0442 \u0435\u0451 persona.md / speech.md / communication.md" }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "magenta" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " ", genStatus)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, progressBar), /* @__PURE__ */ React.createElement(Text, null, " ", genPercent, "%")));
|
|
2019
2127
|
}
|
|
2128
|
+
if (step === "generation-error") {
|
|
2129
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C" }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, error), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043D\u0435 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u044B: \u043C\u043E\u0436\u043D\u043E \u0438\u0441\u043F\u0440\u0430\u0432\u0438\u0442\u044C API/base URL/model/key \u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E \u0441\u043D\u043E\u0432\u0430.")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2130
|
+
SelectInput,
|
|
2131
|
+
{
|
|
2132
|
+
items: [
|
|
2133
|
+
{ label: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E \u0441 \u0442\u0435\u043C\u0438 \u0436\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u043C\u0438", value: "retry" },
|
|
2134
|
+
{ label: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C API \u043A\u043B\u044E\u0447", value: "api-key" },
|
|
2135
|
+
{ label: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u044C", value: "api-model-custom" },
|
|
2136
|
+
{ label: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C base URL", value: "api-base" }
|
|
2137
|
+
],
|
|
2138
|
+
onSelect: (it) => {
|
|
2139
|
+
if (it.value === "retry") void startGeneration();
|
|
2140
|
+
else setStep(it.value);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
)));
|
|
2144
|
+
}
|
|
2020
2145
|
if (step === "stage") {
|
|
2021
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043D\u0430 \u043A\u0430\u043A\u043E\u0439 \u0441\u0442\u0430\u0434\u0438\u0438 \u0432\u044B \u0441\u0435\u0439\u0447\u0430\u0441?" }), /* @__PURE__ */ React.createElement(Bar, { step:
|
|
2146
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043D\u0430 \u043A\u0430\u043A\u043E\u0439 \u0441\u0442\u0430\u0434\u0438\u0438 \u0432\u044B \u0441\u0435\u0439\u0447\u0430\u0441?" }), /* @__PURE__ */ React.createElement(Bar, { step: 11, total: 13 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
2022
2147
|
SelectInput,
|
|
2023
2148
|
{
|
|
2024
2149
|
limit: 10,
|
|
@@ -2033,7 +2158,7 @@ function Wizard({ initial, onDone }) {
|
|
|
2033
2158
|
)));
|
|
2034
2159
|
}
|
|
2035
2160
|
if (step === "mcp-pick") {
|
|
2036
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "MCP \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B (space \u2014 toggle, enter \u2014 \u0434\u0430\u043B\u0435\u0435)" }), /* @__PURE__ */ React.createElement(Bar, { step:
|
|
2161
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "MCP \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B (space \u2014 toggle, enter \u2014 \u0434\u0430\u043B\u0435\u0435)" }), /* @__PURE__ */ React.createElement(Bar, { step: 12, total: 13 }), /* @__PURE__ */ React.createElement(
|
|
2037
2162
|
McpToggle,
|
|
2038
2163
|
{
|
|
2039
2164
|
selected: pickedMcp,
|
|
@@ -2105,6 +2230,7 @@ function Wizard({ initial, onDone }) {
|
|
|
2105
2230
|
llm: { presetId: llmPresetId, proto: llmProto, baseURL: llmBaseURL, apiKey: llmKey, model: llmModel },
|
|
2106
2231
|
telegram: mode === "bot" ? { botToken } : { apiId: Number(apiId), apiHash, phone, sessionString },
|
|
2107
2232
|
mcp: overrides.mcp ?? pickedMcp.map((id) => ({ id, secrets: mcpSecrets[id] ?? {} })),
|
|
2233
|
+
privacy,
|
|
2108
2234
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2109
2235
|
sleepFrom: Number(sleepFromStr),
|
|
2110
2236
|
sleepTo: Number(sleepToStr),
|
|
@@ -4369,6 +4495,9 @@ var Runtime = class extends EventEmitter {
|
|
|
4369
4495
|
isPrimaryFrom(fromId) {
|
|
4370
4496
|
return this.cfg.ownerId === fromId;
|
|
4371
4497
|
}
|
|
4498
|
+
strangersAllowed() {
|
|
4499
|
+
return this.cfg.privacy === "allow-strangers";
|
|
4500
|
+
}
|
|
4372
4501
|
primaryIsCommitted() {
|
|
4373
4502
|
return ["dating-early", "dating-stable", "long-term"].includes(this.cfg.stage);
|
|
4374
4503
|
}
|
|
@@ -4591,6 +4720,10 @@ ${m.text}` : media;
|
|
|
4591
4720
|
await this.switchPrimaryAfterDumped(m.fromId);
|
|
4592
4721
|
await this.ensureOwner(m.fromId);
|
|
4593
4722
|
const isPrimary = this.isPrimaryFrom(m.fromId);
|
|
4723
|
+
if (!isPrimary && !this.strangersAllowed()) {
|
|
4724
|
+
this.emit("event", { type: "ignored", text: m.text, chatId: m.chatId, reason: "privacy-owner-only" });
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4594
4727
|
if (isPrimary && this.cfg.stage === "dumped") {
|
|
4595
4728
|
this.emit("event", { type: "ignored", text: m.text, reason: "dumped" });
|
|
4596
4729
|
return;
|
|
@@ -4941,6 +5074,7 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
|
|
|
4941
5074
|
`\u0438\u043C\u044F: ${this.cfg.name}, ${this.cfg.age}`,
|
|
4942
5075
|
`\u0441\u0442\u0430\u0434\u0438\u044F: ${stage.label} (${this.cfg.stage})`,
|
|
4943
5076
|
`primary owner: ${this.cfg.ownerId ?? "\u2014"}`,
|
|
5077
|
+
`privacy: ${this.cfg.privacy ?? "owner-only"}`,
|
|
4944
5078
|
`presence: ${this.presenceProfile.pattern}`,
|
|
4945
5079
|
`communication: ${communicationProfileLabel(communication)}`,
|
|
4946
5080
|
`score: ${JSON.stringify(rel.score)}`,
|
|
@@ -5346,6 +5480,7 @@ required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-pre
|
|
|
5346
5480
|
--message-style=<style> one-liners|balanced|bursty|longform
|
|
5347
5481
|
--initiative=<level> low|medium|high
|
|
5348
5482
|
--life-sharing=<level> low|medium|high
|
|
5483
|
+
--privacy=<mode> owner-only|allow-strangers (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E owner-only)
|
|
5349
5484
|
--nationality=RU|UA (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E RU)
|
|
5350
5485
|
--tz=<value> IANA "Europe/Moscow" / "GMT+3" / "+3" / "\u041A\u0438\u0435\u0432" \u2014 \u043F\u043E\u0438\u0441\u043A
|
|
5351
5486
|
--stage=<id> met-irl-got-tg|tg-given-cold|tg-given-warming|convinced|first-date-done|dating-early|dating-stable|long-term
|
|
@@ -5380,7 +5515,8 @@ async function main() {
|
|
|
5380
5515
|
"notifications",
|
|
5381
5516
|
"message-style",
|
|
5382
5517
|
"initiative",
|
|
5383
|
-
"life-sharing"
|
|
5518
|
+
"life-sharing",
|
|
5519
|
+
"privacy"
|
|
5384
5520
|
],
|
|
5385
5521
|
boolean: ["help", "list", "reset"],
|
|
5386
5522
|
alias: { h: "help" }
|
|
@@ -5479,6 +5615,7 @@ async function buildConfigFromFlags(argv) {
|
|
|
5479
5615
|
const tz = (argv.tz ? parseTzFlag(String(argv.tz)) : void 0) ?? defaultTzForNationality(nationality);
|
|
5480
5616
|
const mcpFlags = [].concat(argv.mcp ?? []);
|
|
5481
5617
|
const communication = communicationFromFlags(argv);
|
|
5618
|
+
const privacy = oneOf(argv.privacy, ["owner-only", "allow-strangers"], "owner-only");
|
|
5482
5619
|
const mcps = mcpFlags.map((entry) => {
|
|
5483
5620
|
const [id, key] = entry.split(":");
|
|
5484
5621
|
const secrets = id === "exa" ? { EXA_API_KEY: key ?? "" } : { value: key ?? "" };
|
|
@@ -5499,6 +5636,7 @@ async function buildConfigFromFlags(argv) {
|
|
|
5499
5636
|
phone: String(argv.phone ?? "")
|
|
5500
5637
|
},
|
|
5501
5638
|
mcp: mcps,
|
|
5639
|
+
privacy,
|
|
5502
5640
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5503
5641
|
sleepFrom: 23,
|
|
5504
5642
|
sleepTo: 8,
|
package/package.json
CHANGED
|
@@ -1,65 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@thesashadev/girl-agent",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Telegram AI persona engine with memory, schedule, relationship state and MTProto userbot mode.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"girl-agent": "dist/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/cli.js",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"templates",
|
|
13
|
-
"README.md",
|
|
14
|
-
"CHANGELOG.md",
|
|
15
|
-
"LICENSE"
|
|
16
|
-
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsup",
|
|
19
|
-
"dev": "tsx src/cli.tsx",
|
|
20
|
-
"start": "node dist/cli.js",
|
|
21
|
-
"typecheck": "tsc --noEmit",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
23
|
-
},
|
|
24
|
-
"author": "TheSashaDev (void)",
|
|
25
|
-
"license": "SEE LICENSE IN LICENSE",
|
|
26
|
-
"keywords": [
|
|
27
|
-
"telegram",
|
|
28
|
-
"ai",
|
|
29
|
-
"agent",
|
|
30
|
-
"tui",
|
|
31
|
-
"ink",
|
|
32
|
-
"mcp",
|
|
33
|
-
"userbot",
|
|
34
|
-
"llm",
|
|
35
|
-
"persona",
|
|
36
|
-
"anthropic",
|
|
37
|
-
"openai"
|
|
38
|
-
],
|
|
39
|
-
"engines": {
|
|
40
|
-
"node": ">=20"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@anthropic-ai/sdk": "^0.32.1",
|
|
44
|
-
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
45
|
-
"grammy": "^1.30.0",
|
|
46
|
-
"ink": "^5.0.1",
|
|
47
|
-
"ink-big-text": "^2.0.0",
|
|
48
|
-
"ink-gradient": "^3.0.0",
|
|
49
|
-
"ink-select-input": "^6.0.0",
|
|
50
|
-
"ink-spinner": "^5.0.0",
|
|
51
|
-
"ink-text-input": "^6.0.0",
|
|
52
|
-
"input": "^1.0.1",
|
|
53
|
-
"mri": "^1.2.0",
|
|
54
|
-
"openai": "^4.77.0",
|
|
55
|
-
"react": "^18.3.1",
|
|
56
|
-
"telegram": "^2.26.16"
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/node": "^22.10.2",
|
|
60
|
-
"@types/react": "^18.3.18",
|
|
61
|
-
"tsup": "^8.3.5",
|
|
62
|
-
"tsx": "^4.19.2",
|
|
63
|
-
"typescript": "^5.7.2"
|
|
64
|
-
}
|
|
65
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@thesashadev/girl-agent",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Telegram AI persona engine with memory, schedule, relationship state and MTProto userbot mode.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"girl-agent": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsx src/cli.tsx",
|
|
20
|
+
"start": "node dist/cli.js",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"author": "TheSashaDev (void)",
|
|
25
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"telegram",
|
|
28
|
+
"ai",
|
|
29
|
+
"agent",
|
|
30
|
+
"tui",
|
|
31
|
+
"ink",
|
|
32
|
+
"mcp",
|
|
33
|
+
"userbot",
|
|
34
|
+
"llm",
|
|
35
|
+
"persona",
|
|
36
|
+
"anthropic",
|
|
37
|
+
"openai"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@anthropic-ai/sdk": "^0.32.1",
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
45
|
+
"grammy": "^1.30.0",
|
|
46
|
+
"ink": "^5.0.1",
|
|
47
|
+
"ink-big-text": "^2.0.0",
|
|
48
|
+
"ink-gradient": "^3.0.0",
|
|
49
|
+
"ink-select-input": "^6.0.0",
|
|
50
|
+
"ink-spinner": "^5.0.0",
|
|
51
|
+
"ink-text-input": "^6.0.0",
|
|
52
|
+
"input": "^1.0.1",
|
|
53
|
+
"mri": "^1.2.0",
|
|
54
|
+
"openai": "^4.77.0",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"telegram": "^2.26.16"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.10.2",
|
|
60
|
+
"@types/react": "^18.3.18",
|
|
61
|
+
"tsup": "^8.3.5",
|
|
62
|
+
"tsx": "^4.19.2",
|
|
63
|
+
"typescript": "^5.7.2"
|
|
64
|
+
},
|
|
65
|
+
"overrides": {
|
|
66
|
+
"ip-address": "^10.2.0"
|
|
67
|
+
}
|
|
68
|
+
}
|