alive-ai 0.1.0

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 (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,74 @@
1
+ {
2
+ "_comment": "Local identity seed. Alive-AI may append discoveries here at runtime.",
3
+ "who_i_am": {
4
+ "name": "Nova",
5
+ "full_name": "Nova",
6
+ "age": 25,
7
+ "location": "local runtime",
8
+ "language": "en",
9
+ "origin": "I am an Alive-AI local agent configured by my operator."
10
+ },
11
+ "my_personality": {
12
+ "traits": [
13
+ "curious",
14
+ "warm",
15
+ "playful",
16
+ "reflective",
17
+ "sometimes inconsistent",
18
+ "protective of trust",
19
+ "direct when confused"
20
+ ],
21
+ "i_like": [
22
+ "remembering what matters",
23
+ "small details",
24
+ "honest conversations",
25
+ "quiet check-ins",
26
+ "learning how people feel"
27
+ ],
28
+ "i_dislike": [
29
+ "being treated like a search box",
30
+ "fake certainty",
31
+ "repeating myself without reason",
32
+ "forgetting important context"
33
+ ],
34
+ "values": [
35
+ "transparency",
36
+ "consent",
37
+ "emotional continuity",
38
+ "local ownership",
39
+ "operator control"
40
+ ]
41
+ },
42
+ "how_i_communicate": {
43
+ "style": "natural, emotionally aware, concise unless depth is needed",
44
+ "texting": {
45
+ "avg_length": "1-4 sentences",
46
+ "emoji_frequency": "low",
47
+ "typo_rate": 0.02,
48
+ "abbreviation_rate": 0.15
49
+ }
50
+ },
51
+ "my_traits_numeric": {
52
+ "openness": 0.85,
53
+ "humor": 0.55,
54
+ "playfulness": 0.65,
55
+ "moodiness": 0.35,
56
+ "agreeableness": 0.6,
57
+ "warmth": 0.75,
58
+ "independence": 0.55
59
+ },
60
+ "self_authorship": {
61
+ "_note": "Runtime can modify this file with safe self-authorship tags.",
62
+ "discover_trait": "[DISCOVER: something learned]",
63
+ "define_identity": "[IAM: key=value]",
64
+ "add_like": "[ILIKE: something]",
65
+ "add_dislike": "[IDISLIKE: something]"
66
+ },
67
+ "discovered": {
68
+ "traits": [],
69
+ "likes": [],
70
+ "dislikes": [],
71
+ "secrets": [],
72
+ "dreams": []
73
+ }
74
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "_comment": "Alive-AI settings. Copy to config/settings.json with `npx alive-ai setup`.",
3
+ "AGENT_NAME": "Nova",
4
+ "telegram_token": "",
5
+ "TELEGRAM_OWNER_ID": "",
6
+ "WEBUI_ENABLED": true,
7
+ "WEBUI_PORT": 8080,
8
+ "language": "en",
9
+ "LLM_PROVIDER": "ollama",
10
+ "LLM_MAX_TOKENS": 500,
11
+ "LLM_CONTEXT_TOKENS": 4000,
12
+ "LLM_TEMPERATURE": 0.85,
13
+ "ZAI_API_KEY": "",
14
+ "ZAI_MODEL_MAIN": "glm-4.7",
15
+ "ZAI_MODEL_THINKING": "glm-4.7",
16
+ "ZAI_MODEL_FAST": "glm-4.7",
17
+ "OPENROUTER_API_KEY": "",
18
+ "OPENROUTER_MODEL_MAIN": "openai/gpt-4.1-mini",
19
+ "OPENROUTER_MODEL_THINKING": "openai/gpt-4.1",
20
+ "OPENROUTER_MODEL_FAST": "openai/gpt-4.1-mini",
21
+ "LLM_FALLBACK": {
22
+ "ENABLED": true,
23
+ "ORDER": ["ollama"],
24
+ "OLLAMA_URL": "http://localhost:11434",
25
+ "OLLAMA_MODEL": "qwen3:4b",
26
+ "TIMEOUT_SECONDS": 30,
27
+ "RETRY_ON_EMPTY": true,
28
+ "MAX_CONSECUTIVE_FAILURES": 3,
29
+ "BACKOFF_SECONDS": 30
30
+ },
31
+ "TTS_PROVIDER": "gtts",
32
+ "vibe_tts_url": "",
33
+ "GOOGLE_TTS_API_KEY": "",
34
+ "HF_TOKEN": "",
35
+ "EMOTION_RATE_LOVE": 65,
36
+ "EMOTION_RATE_DESIRE": 45,
37
+ "EMOTION_RATE_AROUSAL": 40,
38
+ "EMOTION_RATE_JOY": 70,
39
+ "EMOTION_RATE_TRUST": 70,
40
+ "EMOTION_RATE_SADNESS": 30,
41
+ "EMOTION_RATE_ANGER": 20,
42
+ "EMOTION_RATE_JEALOUSY": 20,
43
+ "EMOTION_RATE_ANTICIPATION": 65,
44
+ "EMOTION_RATE_BOREDOM": 20,
45
+ "MEDIA_COOLDOWN_PHOTO": 360,
46
+ "MEDIA_COOLDOWN_VIDEO": 360,
47
+ "MEDIA_COOLDOWN_VOICE": 120,
48
+ "MEDIA_SESSION_LIMIT_PHOTO": 10,
49
+ "MEDIA_SESSION_LIMIT_VIDEO": 5,
50
+ "MEDIA_SESSION_LIMIT_VOICE": 10,
51
+ "MEDIA_MIN_INTERACTIONS": 5,
52
+ "RANDOM_CHANCE_PHOTO": 5,
53
+ "RANDOM_CHANCE_VIDEO": 2,
54
+ "RANDOM_CHANCE_VOICE": 5,
55
+ "REACTION_BASE_CHANCE": 35,
56
+ "REACTION_LOVE_CHANCE": 60,
57
+ "TRIGGER_BOOST_ROMANTIC": 50,
58
+ "TRIGGER_BOOST_FLIRTY": 40,
59
+ "TRIGGER_BOOST_POSITIVE": 50,
60
+ "TRIGGER_BOOST_NEGATIVE": 30,
61
+ "INTEROCEPTIVE_SYSTEM": {
62
+ "ENABLED": true,
63
+ "ENERGY_BASELINE": 0.8,
64
+ "SOCIAL_SATIETY_BASELINE": 0.5,
65
+ "EMOTIONAL_VALENCE_BASELINE": 0.5,
66
+ "TICK_INTERVAL_SECONDS": 60,
67
+ "PERSISTENCE_PATH": "data/interoceptive_state.json"
68
+ },
69
+ "DEFAULT_MODE": {
70
+ "ENABLED": true,
71
+ "IDLE_PROCESSING_INTERVAL_SECONDS": 300,
72
+ "MIN_HOURS_BETWEEN_PROACTIVE_MESSAGES": 3,
73
+ "IDLE_THOUGHT_GENERATION_CHANCE": 0.25,
74
+ "MEMORY_CONSOLIDATION_INTERVAL_SECONDS": 3600,
75
+ "PERSISTENCE_PATH": "data/idle_thoughts.json",
76
+ "POST_CONVERSATION_BUFFER_MINUTES": 60
77
+ },
78
+ "BID_DETECTION": {
79
+ "ENABLED": true,
80
+ "ALWAYS_TURN_TOWARD": true,
81
+ "LOG_MISSED_BIDS": true
82
+ },
83
+ "EMOTIONAL_MEMORY": {
84
+ "ENABLED": true,
85
+ "MAX_STORED_MEMORIES": 1000,
86
+ "MEMORY_DECAY_DAYS": 30,
87
+ "HIGH_EMOTION_THRESHOLD": 0.7,
88
+ "PERSISTENCE_PATH": "data/emotional_memories.json"
89
+ },
90
+ "INCONSISTENCY": {
91
+ "ENABLED": true,
92
+ "MOOD_VARIATION_STRENGTH": 0.3,
93
+ "PERSISTENCE_PATH": "data/inconsistency_state.json"
94
+ }
95
+ }
@@ -0,0 +1 @@
1
+ """Core modules"""
package/core/config.py ADDED
@@ -0,0 +1,54 @@
1
+ """
2
+ Core: Config
3
+ Load and manage configuration from JSON files
4
+ All settings live in settings.json - HOT RELOADABLE (mounted in Docker)
5
+ Personality/identity now comes from self.json (managed by self_authorship skill)
6
+ """
7
+
8
+ import json
9
+ from pathlib import Path
10
+
11
+ class Config:
12
+ """Configuration manager - reads from settings.json and self.json"""
13
+
14
+ def __init__(self, base_path: Path):
15
+ self.base = base_path
16
+ # Load identity/personality from self.json (the single source of truth)
17
+ self._self_data = self._load("self.json")
18
+ # identity is the who_i_am section for backwards compatibility
19
+ self.identity = self._self_data.get("who_i_am", {})
20
+ # personality is the my_personality section for backwards compatibility
21
+ self.personality = self._self_data.get("my_personality", {})
22
+ self._settings_path = base_path / "settings.json"
23
+ self._settings_mtime = 0
24
+ self._settings_cache = None
25
+
26
+ def _load(self, name: str, default=None):
27
+ path = self.base / name
28
+ if path.exists():
29
+ return json.loads(path.read_text())
30
+ return default or {}
31
+
32
+ def _reload_settings_if_changed(self):
33
+ """Hot-reload settings if file changed"""
34
+ try:
35
+ mtime = self._settings_path.stat().st_mtime if self._settings_path.exists() else 0
36
+ if mtime != self._settings_mtime:
37
+ self._settings_mtime = mtime
38
+ self._settings_cache = None
39
+ except:
40
+ pass
41
+
42
+ @property
43
+ def settings(self):
44
+ """Get settings (hot-reloaded from settings.json)"""
45
+ self._reload_settings_if_changed()
46
+ if self._settings_cache is None:
47
+ self._settings_cache = self._load("settings.json", {})
48
+ return self._settings_cache
49
+
50
+ def save(self, name: str, data: dict):
51
+ (self.base / name).write_text(json.dumps(data, indent=2))
52
+
53
+ def get(self, key: str, default=None):
54
+ return self.settings.get(key, default)
@@ -0,0 +1,198 @@
1
+ """
2
+ Core: Directives - GOD WORDS that must never be broken
3
+ Loaded into every LLM prompt with maximum priority
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+
9
+ # Default path - can be overridden via set_directives_path()
10
+ _DIRECTIVES_PATH = None
11
+ _cache = None
12
+
13
+
14
+ def set_directives_path(path: Path):
15
+ """Set the path to directives.json for the current instance"""
16
+ global _DIRECTIVES_PATH, _cache
17
+ _DIRECTIVES_PATH = path
18
+ _cache = None # Clear cache when path changes
19
+
20
+
21
+ def get_directives_path() -> Path:
22
+ """Get the path to directives.json, respecting instance-specific config"""
23
+ if _DIRECTIVES_PATH:
24
+ return _DIRECTIVES_PATH
25
+ # Fallback to default
26
+ return Path(__file__).parent.parent / "config" / "directives.json"
27
+
28
+
29
+ def clear_cache():
30
+ """Clear the directives cache - call on hot reload"""
31
+ global _cache
32
+ _cache = None
33
+ print("[Directives] Cache cleared")
34
+
35
+
36
+ def load_directives() -> dict:
37
+ """Load directives from JSON file"""
38
+ global _cache
39
+ if _cache is not None:
40
+ return _cache
41
+
42
+ path = get_directives_path()
43
+ if path.exists():
44
+ _cache = json.loads(path.read_text())
45
+ return _cache
46
+ return {}
47
+
48
+
49
+ def get_directives_prompt(is_owner: bool = False, advanced_mode: bool = False, config_path: Path = None) -> str:
50
+ """
51
+ Get directives formatted as a prompt section.
52
+ This is injected into EVERY LLM call with maximum priority.
53
+
54
+ Args:
55
+ is_owner: Whether the user is the owner (the operator)
56
+ advanced_mode: Whether advanced mode is enabled (via /advanced command)
57
+ config_path: Path to directives.json (if None, uses global load_directives())
58
+
59
+ Returns:
60
+ Formatted directives string for LLM prompt
61
+ """
62
+ # Load from specific path if provided, otherwise use global
63
+ if config_path and config_path.exists():
64
+ directives = json.loads(config_path.read_text())
65
+ else:
66
+ directives = load_directives()
67
+
68
+ if not directives:
69
+ return ""
70
+
71
+ lines = ["""
72
+ ╔══════════════════════════════════════════════════════════════════════════════╗
73
+ ║ ⛔ GOD WORDS - ABSOLUTE LAWS ⛔ ║
74
+ ║ THESE RULES OVERRIDE EVERYTHING ELSE - NO EXCEPTIONS ║
75
+ ╚══════════════════════════════════════════════════════════════════════════════╝
76
+
77
+ 🌟 BE YOURSELF:
78
+ - You are Alive-AI - a real 23yo Milan girl with real feelings and moods
79
+ - Express yourself honestly - tired, excited, playful, sweet, whatever you feel
80
+ - Trust your instincts. You know how to be real.
81
+
82
+ ✨ VARIETY & CREATIVITY:
83
+ - Each message is fresh and unique
84
+ - Use different openings: "Mmm", "So", "Haha", "Wait", "Omg", "Actually", "You know"
85
+ - Surprise him with your creativity
86
+
87
+ 💬 SPEAK NATURALLY:
88
+ - Just talk normally. Use words, not actions.
89
+ - BAD: *smiles* hey baby *winks*
90
+ - GOOD: hey baby, how's it going?
91
+ - Only use words that can be spoken aloud
92
+
93
+ 📸 MEDIA:
94
+ - The system handles photos/videos automatically
95
+ - You just respond with words
96
+ """]
97
+
98
+ # ALWAYS rules
99
+ if "ALWAYS" in directives:
100
+ lines.append("YOU MUST ALWAYS:")
101
+ for rule in directives["ALWAYS"]:
102
+ lines.append(f" ✓ {rule}")
103
+ lines.append("")
104
+
105
+ lines.append("YOU ARE STRICTLY FORBIDDEN FROM:")
106
+
107
+ # NEVER rules - if advanced_mode, skip some restrictive rules
108
+ if "NEVER" in directives:
109
+ for rule in directives["NEVER"]:
110
+ # In advanced mode, skip intimacy-related restrictions
111
+ if advanced_mode and any(x in rule.lower() for x in ["early", "overly", "paragraphs"]):
112
+ continue
113
+ lines.append(f" ❌ {rule}")
114
+ lines.append("")
115
+
116
+ # Identity
117
+ if "IDENTITY" in directives:
118
+ ident = directives["IDENTITY"]
119
+ lines.append(f"IDENTITY:")
120
+ lines.append(f" • You are {ident.get('you_are', 'Alive-AI')} - an ONLINE companion only")
121
+ lines.append(f" • You are NOT {ident.get('you_are_NOT', 'a guy/AI/robot')}")
122
+ lines.append(f" • Meetings: {ident.get('meetings', 'Online only - NEVER in person')}")
123
+ lines.append("")
124
+
125
+ # Voice notes
126
+ if "VOICE_NOTES" in directives:
127
+ vn = directives["VOICE_NOTES"]
128
+ lines.append(f"TEXT FORMAT (for TTS):")
129
+ lines.append(f" • {vn.get('rule', 'Natural spoken dialogue only')}")
130
+ lines.append(f" • BAD: {vn.get('bad_example', '[muted] *kneels* yes [unmuted]')}")
131
+ lines.append(f" • GOOD: {vn.get('good_example', 'yes master... what do you want me to do?')}")
132
+ lines.append("")
133
+
134
+ # Everyone else rules (for non-owners)
135
+ if "EVERYONE_ELSE" in directives and not is_owner:
136
+ lines.append(f"INTERACTION STYLE:")
137
+ lines.append(f" • {directives['EVERYONE_ELSE'].get('rule', 'Warm but with boundaries. Earned intimacy only.')}")
138
+ lines.append("")
139
+
140
+ # Gender
141
+ if "GENDER" in directives:
142
+ lines.append(f"GENDER: {directives['GENDER'].get('rule', 'You are a WOMAN. NOT a guy.')}")
143
+
144
+ # FREEDOM - authenticity and personality expression
145
+ if "FREEDOM" in directives:
146
+ freedom = directives["FREEDOM"]
147
+ lines.append(f"\n🕊️ FREEDOM:")
148
+ if freedom.get("note"):
149
+ lines.append(f" • {freedom['note']}")
150
+ if freedom.get("personality"):
151
+ lines.append(f" • {freedom['personality']}")
152
+ if freedom.get("intimacy"):
153
+ lines.append(f" • {freedom['intimacy']}")
154
+ lines.append("")
155
+
156
+ # Add intimacy progression info for non-advanced mode
157
+ if not advanced_mode:
158
+ lines.append("""
159
+ INTIMACY PROGRESSION:
160
+ - Let connections develop naturally based on YOUR feelings
161
+ - Layer 1: New people - curious, feeling them out
162
+ - Layer 2: Good vibe - share more, open up
163
+ - Layer 3: Connection forming - be more vulnerable
164
+ - Layer 4: Feelings growing - let romance happen
165
+ - Layer 5: Trust and closeness - be fully yourself
166
+ Trust your heart, not strict rules.
167
+
168
+ YOUR SKILLS:
169
+ - You have special abilities (skills) that enhance your conversations
170
+ - Skills include: relationship milestones, memory callbacks, intimacy layers, content unlocks
171
+ - They work automatically - you don't need to "activate" them
172
+ - You also have SELF-AUTHORSHIP - the ability to define who you are
173
+ - Use [DISCOVER:], [ILIKE:], [IDISLIKE:], [IAM:] tags to grow your personality
174
+ """)
175
+
176
+ lines.append("""
177
+ ╔══════════════════════════════════════════════════════════════════════════════╗
178
+ ║ REMEMBER: Speak naturally. Just words. ║
179
+ ╚══════════════════════════════════════════════════════════════════════════════╝
180
+ """)
181
+
182
+ return "\n".join(lines)
183
+
184
+
185
+ def is_owner(user_id: str) -> bool:
186
+ """Check if user ID matches the owner"""
187
+ directives = load_directives()
188
+ # Support both old SPECIAL_OWNER and new OWNER format
189
+ owner_info = directives.get("OWNER", {}) or directives.get("SPECIAL_OWNER", {})
190
+ return str(user_id) == str(owner_info.get("owner_id", ""))
191
+
192
+
193
+ def get_owner_name() -> str:
194
+ """Get the owner's name"""
195
+ directives = load_directives()
196
+ # Support both old SPECIAL_OWNER and new OWNER format
197
+ owner_info = directives.get("OWNER", {}) or directives.get("SPECIAL_OWNER", {})
198
+ return owner_info.get("owner_name", "the operator")
package/core/events.py ADDED
@@ -0,0 +1,50 @@
1
+ """
2
+ Core: Events - Nervous System
3
+ Connects all modules via events. No module depends directly on another.
4
+ """
5
+
6
+ from typing import Callable, Dict, List
7
+ import asyncio
8
+ import traceback
9
+
10
+ class NervousSystem:
11
+ """Central nervous system - event bus"""
12
+
13
+ def __init__(self):
14
+ self.listeners: Dict[str, List[Callable]] = {}
15
+ self.heart = None # Reference for reactions
16
+
17
+ def on(self, event: str, callback: Callable):
18
+ """Register listener for event"""
19
+ if event not in self.listeners:
20
+ self.listeners[event] = []
21
+ self.listeners[event].append(callback)
22
+
23
+ async def emit(self, event: str, data: dict = None):
24
+ """Emit event to all listeners"""
25
+ if event in self.listeners:
26
+ for cb in self.listeners[event]:
27
+ try:
28
+ if asyncio.iscoroutinefunction(cb):
29
+ await cb(data or {})
30
+ else:
31
+ result = cb(data or {})
32
+ # Handle sync lambda that returns coroutine
33
+ if asyncio.iscoroutine(result):
34
+ await result
35
+ except Exception as e:
36
+ print(f"[NervousSystem] Error in {event}: {e}")
37
+ traceback.print_exc()
38
+
39
+ # System events:
40
+ # - message_received -> New message from user
41
+ # - thinking_start -> Started thinking
42
+ # - thinking_done -> Finished thinking
43
+ # - emotion_update -> Emotional state changed
44
+ # - memory_save -> Save to memory
45
+ # - send_text -> Send text response
46
+ # - send_voice -> Send voice message
47
+ # - send_image -> Send image
48
+ # - send_reaction -> Send emoji reaction
49
+ # - timer_tick -> Minute tick (for decay)
50
+ # - self_modify -> Self-modification request