@innvisor/conny-ai 9.7.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 (175) hide show
  1. package/.env.example +68 -0
  2. package/CHANGELOG.md +54 -0
  3. package/LICENSE +21 -0
  4. package/README.md +369 -0
  5. package/brand-assets/A_dark_luxury_web_background_202605210700.jpeg +0 -0
  6. package/brand-assets/Conny.web.logo.png +0 -0
  7. package/brand-assets/Logo_Conny_Petalo_Claro.png +0 -0
  8. package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +22 -0
  9. package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +11 -0
  10. package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +11 -0
  11. package/brand-assets/cl-nica-las-am-ricas/manifest.json +22 -0
  12. package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +11 -0
  13. package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +11 -0
  14. package/brand-assets/conny-demo/manifest.json +22 -0
  15. package/brand-assets/conny-demo/processed/business-identity.txt +7 -0
  16. package/brand-assets/conny-demo/raw/business-identity.txt +7 -0
  17. package/brand-assets/conny-logo.png +0 -0
  18. package/brand-assets/web.background.png +0 -0
  19. package/brand_assets.py +323 -0
  20. package/conny +28 -0
  21. package/conny-chat.py +579 -0
  22. package/conny-omni.py +3843 -0
  23. package/conny.py +113 -0
  24. package/conny_agents/__init__.py +1 -0
  25. package/conny_agents/agenda.py +1 -0
  26. package/conny_agents/captacion.py +1 -0
  27. package/conny_agents/conocimiento.py +1 -0
  28. package/conny_agents/escalacion.py +1 -0
  29. package/conny_agents/objeciones.py +1 -0
  30. package/conny_agents/seguimiento.py +1 -0
  31. package/conny_app.py +287 -0
  32. package/conny_audio.py +350 -0
  33. package/conny_audio_learn.py +84 -0
  34. package/conny_brain_v10.py +804 -0
  35. package/conny_bridge.py +656 -0
  36. package/conny_calendar.py +169 -0
  37. package/conny_cli.py +11784 -0
  38. package/conny_cli_bb.py +437 -0
  39. package/conny_commands.py +243 -0
  40. package/conny_config.py +215 -0
  41. package/conny_core/__init__.py +3 -0
  42. package/conny_core/conversation_engine.py +446 -0
  43. package/conny_core/first_turn_ops.py +287 -0
  44. package/conny_core/persona_registry.py +157 -0
  45. package/conny_core/prompt_ops.py +561 -0
  46. package/conny_cron.py +72 -0
  47. package/conny_demo_v2.py +209 -0
  48. package/conny_demo_voice.py +134 -0
  49. package/conny_design.py +43 -0
  50. package/conny_doctor.py +319 -0
  51. package/conny_domino.py +696 -0
  52. package/conny_generator.py +447 -0
  53. package/conny_google_auth.py +159 -0
  54. package/conny_i18n.py +619 -0
  55. package/conny_init.py +509 -0
  56. package/conny_integrations/__init__.py +4 -0
  57. package/conny_integrations/llm.py +1 -0
  58. package/conny_integrations/vault.py +77 -0
  59. package/conny_integrations/whatsapp.py +1 -0
  60. package/conny_intelligence.py +65 -0
  61. package/conny_learning.py +154 -0
  62. package/conny_memory.py +243 -0
  63. package/conny_memory_engine.py +292 -0
  64. package/conny_nova_proxy.py +170 -0
  65. package/conny_nuke_robot_phrases.py +493 -0
  66. package/conny_pairing.py +253 -0
  67. package/conny_patch.py +291 -0
  68. package/conny_persona_cli.py +150 -0
  69. package/conny_router.py +308 -0
  70. package/conny_runtime_ops.py +271 -0
  71. package/conny_session.py +516 -0
  72. package/conny_skills/__init__.py +1 -0
  73. package/conny_skills/demo_mode.py +35 -0
  74. package/conny_skills/text_processing.py +1 -0
  75. package/conny_skills/tone_detection.py +1 -0
  76. package/conny_smart_features.py +333 -0
  77. package/conny_studio.py +161 -0
  78. package/conny_sync_fix.py +306 -0
  79. package/conny_tui.py +512 -0
  80. package/conny_tui_select.py +202 -0
  81. package/conny_ultra_config.py +411 -0
  82. package/conny_uncertainty.py +174 -0
  83. package/conny_utils.py +87 -0
  84. package/conny_voice.py +156 -0
  85. package/conny_voice_engine.py +124 -0
  86. package/conny_web_search.py +66 -0
  87. package/conny_weekly_report.py +85 -0
  88. package/conny_worm.py +88 -0
  89. package/core/__init__.py +25 -0
  90. package/ecosystem.config.js +24 -0
  91. package/fix_init.py +27 -0
  92. package/install.sh +78 -0
  93. package/knowledge_base.py +330 -0
  94. package/nova/rules/default.yaml +37 -0
  95. package/nova_bridge.py +509 -0
  96. package/npm/conny.js +471 -0
  97. package/package.json +102 -0
  98. package/personas/conny/base/default.yaml +35 -0
  99. package/personas/conny/base/estetica_whatsapp.yaml +36 -0
  100. package/requirements.txt +14 -0
  101. package/run.sh +47 -0
  102. package/search.py +465 -0
  103. package/smart_handoff.py +1150 -0
  104. package/src/__init__.py +0 -0
  105. package/src/conny/__init__.py +0 -0
  106. package/src/conny/admin/__init__.py +0 -0
  107. package/src/conny/admin/api.py +234 -0
  108. package/src/conny/admin/dashboard.py +772 -0
  109. package/src/conny/api/__init__.py +0 -0
  110. package/src/conny/api/routes.py +8851 -0
  111. package/src/conny/brain/__init__.py +15 -0
  112. package/src/conny/brain/engine.py +804 -0
  113. package/src/conny/brain/learning.py +154 -0
  114. package/src/conny/brain/memory.py +324 -0
  115. package/src/conny/brain/smart_features.py +333 -0
  116. package/src/conny/brain/uncertainty.py +167 -0
  117. package/src/conny/channels/__init__.py +0 -0
  118. package/src/conny/channels/audio.py +316 -0
  119. package/src/conny/channels/cli.py +11795 -0
  120. package/src/conny/channels/logo_art.py +11 -0
  121. package/src/conny/channels/voice.py +156 -0
  122. package/src/conny/core/__init__.py +0 -0
  123. package/src/conny/core/config.py +215 -0
  124. package/src/conny/core/cron.py +72 -0
  125. package/src/conny/core/messenger.py +563 -0
  126. package/src/conny/core/router.py +297 -0
  127. package/src/conny/core/session.py +312 -0
  128. package/src/conny/demo/__init__.py +0 -0
  129. package/src/conny/demo/handler.py +3110 -0
  130. package/src/conny/integrations/__init__.py +19 -0
  131. package/src/conny/integrations/calendar.py +169 -0
  132. package/src/conny/integrations/knowledge.py +312 -0
  133. package/src/conny/integrations/search.py +66 -0
  134. package/src/conny/personas/__init__.py +0 -0
  135. package/src/conny/personas/generator.py +447 -0
  136. package/src/conny/production/__init__.py +0 -0
  137. package/src/conny/production/domino.py +696 -0
  138. package/src/conny/production/guard.py +550 -0
  139. package/src/conny/production/handoff.py +1150 -0
  140. package/src/conny/production/monitor.py +353 -0
  141. package/src/conny/utils/__init__.py +2 -0
  142. package/src/conny/utils/helpers.py +75 -0
  143. package/src/conny/utils/i18n.py +619 -0
  144. package/src/core/admin_engines.py +772 -0
  145. package/src/core/globals.py +11845 -0
  146. package/src/core/orchestrator.py +273 -0
  147. package/src/core/production_monitor.py +353 -0
  148. package/src/core/runtime.py +5487 -0
  149. package/src/domain/onboarding_flow.py +230 -0
  150. package/src/domain/prompts/__init__.py +1 -0
  151. package/src/domain/prompts/prospect_pitch.py +282 -0
  152. package/src/domain/send_guard.py +636 -0
  153. package/src/domain/swarm/queen.py +96 -0
  154. package/src/infrastructure/llm_providers/engine.py +487 -0
  155. package/src/interfaces/mcp_server.py +73 -0
  156. package/src/interfaces/nova_bridge.py +58 -0
  157. package/src/interfaces/web/admin_api.py +1379 -0
  158. package/src/interfaces/web/app.py +9408 -0
  159. package/src/interfaces/web/demo_handler.py +3450 -0
  160. package/src/interfaces/web/static/generate_avatars.py +46 -0
  161. package/v7/__init__.py +46 -0
  162. package/v7/agents/__init__.py +46 -0
  163. package/v7/agents/agenda.py +77 -0
  164. package/v7/agents/base.py +216 -0
  165. package/v7/agents/captacion.py +60 -0
  166. package/v7/agents/conocimiento.py +69 -0
  167. package/v7/agents/escalacion.py +83 -0
  168. package/v7/agents/objeciones.py +109 -0
  169. package/v7/agents/seguimiento.py +71 -0
  170. package/v7/memory/__init__.py +46 -0
  171. package/v7/memory/patient_profile.py +200 -0
  172. package/v7/orchestrator.py +275 -0
  173. package/v7/postprocess.py +127 -0
  174. package/v7/router.py +239 -0
  175. package/verify_conversation_impl.py +48 -0
File without changes
File without changes
File without changes
@@ -0,0 +1,234 @@
1
+ """
2
+ Conny Admin API — Multi-tenant runtime configuration endpoints.
3
+
4
+ Mounted at /admin on the main FastAPI app.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import logging
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+ from typing import List, Optional
13
+
14
+ from fastapi import APIRouter, Header, HTTPException, Request
15
+ from pydantic import BaseModel, Field
16
+
17
+ log = logging.getLogger("conny.admin_api")
18
+
19
+ router = APIRouter(prefix="/admin", tags=["admin"])
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Auth
23
+ # ---------------------------------------------------------------------------
24
+
25
+ def _get_admin_key() -> str:
26
+ return os.environ.get("ADMIN_API_KEY") or os.environ.get("MASTER_API_KEY", "")
27
+
28
+
29
+ def _verify_auth(x_admin_key: Optional[str] = None):
30
+ expected = _get_admin_key()
31
+ if not expected:
32
+ raise HTTPException(status_code=500, detail="ADMIN_API_KEY not configured")
33
+ if x_admin_key != expected:
34
+ raise HTTPException(status_code=401, detail="Invalid admin key")
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Request/Response models
39
+ # ---------------------------------------------------------------------------
40
+
41
+ class PersonaUpdate(BaseModel):
42
+ tone: str = "professional"
43
+ verbosity: str = "concise"
44
+ greeting_style: str = "warm"
45
+ sign_off: str = ""
46
+ forbidden_topics: List[str] = Field(default_factory=list)
47
+ escalation_phrases: List[str] = Field(default_factory=list)
48
+
49
+
50
+ class ModelUpdate(BaseModel):
51
+ provider: str = "anthropic"
52
+ model_id: str = "claude-sonnet-4-20250514"
53
+ temperature: float = 0.7
54
+ max_tokens: int = 1024
55
+ thinking_budget: int = 0
56
+
57
+
58
+ class TeachRequest(BaseModel):
59
+ question: str
60
+ answer: str
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Helpers
65
+ # ---------------------------------------------------------------------------
66
+
67
+ _BASE_DIR = Path(__file__).resolve().parent
68
+ _PERSONAS_DIR = _BASE_DIR / "personas"
69
+ _MODEL_CONFIG_DIR = _BASE_DIR / "model_configs"
70
+ _KNOWLEDGE_GAPS_DIR = _BASE_DIR / "knowledge_gaps"
71
+ _TEACHINGS_DIR = _BASE_DIR / "teachings"
72
+
73
+
74
+ def _read_jsonl(path: Path, limit: int = 100) -> list:
75
+ """Read last N lines from a JSONL file."""
76
+ if not path.exists():
77
+ return []
78
+ lines = path.read_text().strip().splitlines()
79
+ entries = []
80
+ for line in lines[-limit:]:
81
+ try:
82
+ entries.append(json.loads(line))
83
+ except json.JSONDecodeError:
84
+ continue
85
+ return entries
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Endpoints
90
+ # ---------------------------------------------------------------------------
91
+
92
+ @router.post("/{instance_id}/persona")
93
+ async def update_persona(
94
+ instance_id: str,
95
+ body: PersonaUpdate,
96
+ x_admin_key: Optional[str] = Header(None),
97
+ ):
98
+ """Update personality/persona at runtime for a given instance."""
99
+ _verify_auth(x_admin_key)
100
+
101
+ persona_dir = _PERSONAS_DIR / instance_id
102
+ persona_dir.mkdir(parents=True, exist_ok=True)
103
+
104
+ override_path = persona_dir / "runtime_override.json"
105
+ payload = body.model_dump()
106
+ payload["updated_at"] = datetime.now().isoformat()
107
+
108
+ override_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
109
+ log.info(f"[admin] persona updated for {instance_id}")
110
+
111
+ return {"ok": True, "applied": payload}
112
+
113
+
114
+ @router.post("/{instance_id}/model")
115
+ async def update_model(
116
+ instance_id: str,
117
+ body: ModelUpdate,
118
+ x_admin_key: Optional[str] = Header(None),
119
+ ):
120
+ """Change LLM provider/model configuration at runtime."""
121
+ _verify_auth(x_admin_key)
122
+
123
+ _MODEL_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
124
+ config_path = _MODEL_CONFIG_DIR / f"{instance_id}.json"
125
+
126
+ payload = body.model_dump()
127
+ payload["updated_at"] = datetime.now().isoformat()
128
+
129
+ config_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
130
+ log.info(f"[admin] model config updated for {instance_id}: {body.provider}/{body.model_id}")
131
+
132
+ return {"ok": True, "model": payload}
133
+
134
+
135
+ @router.get("/{instance_id}/status")
136
+ async def get_status(
137
+ instance_id: str,
138
+ x_admin_key: Optional[str] = Header(None),
139
+ ):
140
+ """Get current config and recent knowledge gaps for an instance."""
141
+ _verify_auth(x_admin_key)
142
+
143
+ # Read persona override
144
+ persona_path = _PERSONAS_DIR / instance_id / "runtime_override.json"
145
+ persona = {}
146
+ if persona_path.exists():
147
+ try:
148
+ persona = json.loads(persona_path.read_text())
149
+ except json.JSONDecodeError:
150
+ persona = {"error": "corrupt persona file"}
151
+
152
+ # Read model config
153
+ model_path = _MODEL_CONFIG_DIR / f"{instance_id}.json"
154
+ model = {}
155
+ if model_path.exists():
156
+ try:
157
+ model = json.loads(model_path.read_text())
158
+ except json.JSONDecodeError:
159
+ model = {"error": "corrupt model config file"}
160
+
161
+ # Read recent gaps
162
+ today = datetime.now().strftime("%Y-%m-%d")
163
+ gap_file = _KNOWLEDGE_GAPS_DIR / f"{today}.jsonl"
164
+ all_gaps = _read_jsonl(gap_file, limit=200)
165
+ instance_gaps = [g for g in all_gaps if g.get("instance_id") == instance_id][-10:]
166
+
167
+ return {
168
+ "instance_id": instance_id,
169
+ "persona": persona,
170
+ "model": model,
171
+ "recent_gaps": instance_gaps,
172
+ "gaps_today": len(instance_gaps),
173
+ }
174
+
175
+
176
+ @router.get("/{instance_id}/gaps")
177
+ async def get_gaps(
178
+ instance_id: str,
179
+ x_admin_key: Optional[str] = Header(None),
180
+ limit: int = 50,
181
+ ):
182
+ """Get knowledge gaps log for an instance."""
183
+ _verify_auth(x_admin_key)
184
+
185
+ _KNOWLEDGE_GAPS_DIR.mkdir(parents=True, exist_ok=True)
186
+
187
+ # Collect from recent JSONL files
188
+ gap_files = sorted(_KNOWLEDGE_GAPS_DIR.glob("*.jsonl"), reverse=True)[:7]
189
+ all_gaps: list = []
190
+
191
+ for gf in gap_files:
192
+ entries = _read_jsonl(gf, limit=500)
193
+ instance_entries = [e for e in entries if e.get("instance_id") == instance_id]
194
+ all_gaps.extend(instance_entries)
195
+ if len(all_gaps) >= limit:
196
+ break
197
+
198
+ all_gaps = all_gaps[:limit]
199
+
200
+ return {
201
+ "instance_id": instance_id,
202
+ "total": len(all_gaps),
203
+ "gaps": all_gaps,
204
+ }
205
+
206
+
207
+ @router.post("/{instance_id}/teach")
208
+ async def teach_fact(
209
+ instance_id: str,
210
+ body: TeachRequest,
211
+ x_admin_key: Optional[str] = Header(None),
212
+ ):
213
+ """Admin teaches Conny a new fact (question/answer pair)."""
214
+ _verify_auth(x_admin_key)
215
+
216
+ _TEACHINGS_DIR.mkdir(parents=True, exist_ok=True)
217
+ teachings_file = _TEACHINGS_DIR / f"{instance_id}.jsonl"
218
+
219
+ entry = {
220
+ "ts": datetime.now().isoformat(),
221
+ "question": body.question,
222
+ "answer": body.answer,
223
+ }
224
+
225
+ with open(teachings_file, "a") as f:
226
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
227
+
228
+ log.info(f"[admin] new teaching for {instance_id}: Q='{body.question[:60]}'")
229
+
230
+ return {
231
+ "ok": True,
232
+ "instance_id": instance_id,
233
+ "taught": entry,
234
+ }