nexo-brain 5.3.26 → 5.3.27

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 (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/server.py +3 -0
  4. package/src/tools_sessions.py +6 -1
  5. package/src/dashboard/static/favicon 2.svg +0 -32
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  8. package/src/dashboard/static/style 2.css +0 -2458
  9. package/src/dashboard/templates/adaptive 2.html +0 -118
  10. package/src/dashboard/templates/artifacts 2.html +0 -133
  11. package/src/dashboard/templates/backups 2.html +0 -136
  12. package/src/dashboard/templates/base 2.html +0 -417
  13. package/src/dashboard/templates/calendar 2.html +0 -591
  14. package/src/dashboard/templates/chat 2.html +0 -356
  15. package/src/dashboard/templates/claims 2.html +0 -259
  16. package/src/dashboard/templates/cortex 2.html +0 -321
  17. package/src/dashboard/templates/credentials 2.html +0 -128
  18. package/src/dashboard/templates/crons 2.html +0 -370
  19. package/src/dashboard/templates/dashboard 2.html +0 -494
  20. package/src/dashboard/templates/dreams 2.html +0 -252
  21. package/src/dashboard/templates/email 2.html +0 -160
  22. package/src/dashboard/templates/evolution 2.html +0 -189
  23. package/src/dashboard/templates/feed 2.html +0 -249
  24. package/src/dashboard/templates/followup_health 2.html +0 -170
  25. package/src/dashboard/templates/graph 2.html +0 -201
  26. package/src/dashboard/templates/guard 2.html +0 -259
  27. package/src/dashboard/templates/inbox 2.html +0 -251
  28. package/src/dashboard/templates/memory 2.html +0 -420
  29. package/src/dashboard/templates/operations 2.html +0 -608
  30. package/src/dashboard/templates/plugins 2.html +0 -185
  31. package/src/dashboard/templates/protocol 2.html +0 -199
  32. package/src/dashboard/templates/rules 2.html +0 -246
  33. package/src/dashboard/templates/sentiment 2.html +0 -247
  34. package/src/dashboard/templates/sessions 2.html +0 -218
  35. package/src/dashboard/templates/skills 2.html +0 -329
  36. package/src/dashboard/templates/somatic 2.html +0 -73
  37. package/src/dashboard/templates/triggers 2.html +0 -133
  38. package/src/dashboard/templates/trust 2.html +0 -360
  39. package/src/db/__init__ 2.py +0 -259
  40. package/src/db/_core 2.py +0 -437
  41. package/src/db/_credentials 2.py +0 -124
  42. package/src/db/_episodic 2.py +0 -762
  43. package/src/db/_evolution 2.py +0 -54
  44. package/src/db/_fts 2.py +0 -406
  45. package/src/db/_goal_profiles 2.py +0 -376
  46. package/src/db/_hot_context 2.py +0 -660
  47. package/src/db/_outcomes 2.py +0 -800
  48. package/src/db/_personal_scripts 2.py +0 -582
  49. package/src/db/_sessions 2.py +0 -330
  50. package/src/db/_tasks 2.py +0 -91
  51. package/src/db/_watchers 2.py +0 -173
  52. package/src/doctor/formatters 2.py +0 -52
  53. package/src/doctor/models 2.py +0 -69
  54. package/src/doctor/planes 2.py +0 -87
  55. package/src/doctor/providers/__init__ 2.py +0 -1
  56. package/src/doctor/providers/deep 2.py +0 -367
  57. package/src/evolution_cycle 2.py +0 -519
  58. package/src/hooks/auto_capture 2.py +0 -208
  59. package/src/hooks/caffeinate-guard 2.sh +0 -8
  60. package/src/hooks/capture-session 2.sh +0 -21
  61. package/src/hooks/capture-tool-logs 2.sh +0 -158
  62. package/src/hooks/daily-briefing-check 2.sh +0 -33
  63. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  64. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  65. package/src/hooks/inbox-hook 2.sh +0 -76
  66. package/src/hooks/post-compact 2.sh +0 -152
  67. package/src/hooks/pre-compact 2.sh +0 -169
  68. package/src/hooks/protocol-guardrail 2.sh +0 -10
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  70. package/src/hooks/session-stop 2.sh +0 -52
  71. package/src/kg_populate 2.py +0 -292
  72. package/src/maintenance 2.py +0 -53
  73. package/src/memory_backends 2.py +0 -71
  74. package/src/migrate_embeddings 2.py +0 -124
  75. package/src/nexo_sdk 2.py +0 -103
  76. package/src/observability 2.py +0 -199
  77. package/src/plugin_loader 2.py +0 -217
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +0 -450
  80. package/src/plugins/backup 2.py +0 -127
  81. package/src/plugins/claims_tools 2.py +0 -119
  82. package/src/plugins/cognitive_memory 2.py +0 -609
  83. package/src/plugins/core_rules 2.py +0 -252
  84. package/src/plugins/cortex 2.py +0 -1155
  85. package/src/plugins/entities 2.py +0 -67
  86. package/src/plugins/episodic_memory 2.py +0 -560
  87. package/src/plugins/evolution 2.py +0 -167
  88. package/src/plugins/goal_engine 2.py +0 -142
  89. package/src/plugins/guard 2.py +0 -862
  90. package/src/plugins/impact 2.py +0 -29
  91. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  92. package/src/plugins/media_memory_tools 2.py +0 -98
  93. package/src/plugins/memory_export 2.py +0 -196
  94. package/src/plugins/outcomes 2.py +0 -130
  95. package/src/plugins/personal_scripts 2.py +0 -117
  96. package/src/plugins/preferences 2.py +0 -47
  97. package/src/plugins/protocol 2.py +0 -1449
  98. package/src/plugins/simple_api 2.py +0 -106
  99. package/src/plugins/skills 2.py +0 -341
  100. package/src/plugins/state_watchers 2.py +0 -79
  101. package/src/plugins/update 2.py +0 -986
  102. package/src/plugins/user_state_tools 2.py +0 -43
  103. package/src/plugins/workflow 2.py +0 -588
  104. package/src/protocol_settings 2.py +0 -59
  105. package/src/public_contribution 2.py +0 -466
  106. package/src/public_evolution_queue 2.py +0 -241
  107. package/src/requirements 2.txt +0 -14
  108. package/src/retroactive_learnings 2.py +0 -373
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +0 -331
  111. package/src/rules/migrate 2.py +0 -207
  112. package/src/runtime_power 2.py +0 -874
  113. package/src/script_registry 2.py +0 -1559
  114. package/src/scripts/check-context 2.py +0 -272
  115. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  116. package/src/scripts/deep-sleep/collect 2.py +0 -928
  117. package/src/scripts/deep-sleep/extract 2.py +0 -330
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  119. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  121. package/src/scripts/nexo-agent-run 2.py +0 -75
  122. package/src/scripts/nexo-auto-update 2.py +0 -6
  123. package/src/scripts/nexo-backup 2.sh +0 -25
  124. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  125. package/src/scripts/nexo-catchup 2.py +0 -300
  126. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  127. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  128. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  129. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  130. package/src/scripts/nexo-dashboard 2.sh +0 -29
  131. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  132. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  133. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  134. package/src/scripts/nexo-hook-record 2.py +0 -42
  135. package/src/scripts/nexo-immune 2.py +0 -936
  136. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  137. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  138. package/src/scripts/nexo-install 2.py +0 -6
  139. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  140. package/src/scripts/nexo-learning-validator 2.py +0 -266
  141. package/src/scripts/nexo-migrate 2.py +0 -260
  142. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  144. package/src/scripts/nexo-pre-commit 2.py +0 -120
  145. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  146. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  147. package/src/scripts/nexo-reflection 2.py +0 -256
  148. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  149. package/src/scripts/nexo-sleep 2.py +0 -631
  150. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  151. package/src/scripts/nexo-sync-clients 2.py +0 -16
  152. package/src/scripts/nexo-synthesis 2.py +0 -475
  153. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  154. package/src/scripts/nexo-update 2.sh +0 -306
  155. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  156. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  158. package/src/server 2.py +0 -1296
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  164. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  165. package/src/skills/run-release-final-audit/script 2.py +0 -259
  166. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  167. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  168. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  169. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  170. package/src/skills_runtime 2.py +0 -932
  171. package/src/state_watchers_runtime 2.py +0 -475
  172. package/src/storage_router 2.py +0 -32
  173. package/src/system_catalog 2.py +0 -786
  174. package/src/tools_coordination 2.py +0 -103
  175. package/src/tools_credentials 2.py +0 -68
  176. package/src/tools_drive 2.py +0 -487
  177. package/src/tools_hot_context 2.py +0 -163
  178. package/src/tools_learnings 2.py +0 -612
  179. package/src/tools_menu 2.py +0 -229
  180. package/src/tools_reminders 2.py +0 -88
  181. package/src/tools_reminders_crud 2.py +0 -363
  182. package/src/tools_sessions 2.py +0 -1054
  183. package/src/tools_system_catalog 2.py +0 -19
  184. package/src/tools_task_history 2.py +0 -57
  185. package/src/tools_transcripts 2.py +0 -98
  186. package/src/transcript_utils 2.py +0 -412
  187. package/src/user_context 2.py +0 -46
  188. package/src/user_data_portability 2.py +0 -328
  189. package/src/user_state_model 2.py +0 -170
  190. package/templates/CLAUDE.md 2.template +0 -108
  191. package/templates/CODEX.AGENTS.md 2.template +0 -66
  192. package/templates/launchagents/README 2.md +0 -132
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  194. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  200. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  205. package/templates/nexo_helper 2.py +0 -301
  206. package/templates/openclaw 2.json +0 -13
  207. package/templates/plugin-template 2.py +0 -40
  208. package/templates/script-template 2.py +0 -59
  209. package/templates/script-template 2.sh +0 -13
  210. package/templates/skill-script-template 2.py +0 -48
  211. package/templates/skill-template 2.md +0 -33
@@ -1,475 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- NEXO Synthesis Engine v2 — Daily intelligence brief.
4
-
5
- Before: ~400 lines of Python concatenating SQL results into markdown sections.
6
- Now: Collects raw data, passes to the configured automation backend which synthesizes
7
- with real understanding of what matters for tomorrow.
8
-
9
- Runs daily at 06:00 via LaunchAgent.
10
- """
11
-
12
- import fcntl
13
- import json
14
- import os
15
- import sqlite3
16
- import subprocess
17
- import sys
18
- from datetime import datetime, date, timedelta
19
- from pathlib import Path
20
-
21
-
22
- try:
23
- from client_preferences import resolve_user_model as _resolve_user_model
24
- _USER_MODEL = _resolve_user_model()
25
- except Exception:
26
- _USER_MODEL = ""
27
-
28
- HOME = Path.home()
29
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
30
- _script_dir = Path(__file__).resolve().parent
31
- _repo_src = _script_dir.parent
32
- NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
33
- if str(NEXO_CODE) not in sys.path:
34
- sys.path.insert(0, str(NEXO_CODE))
35
-
36
- from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
37
-
38
- CLAUDE_DIR = NEXO_HOME
39
- COORD_DIR = CLAUDE_DIR / "coordination"
40
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
41
- OUTPUT_FILE = COORD_DIR / "daily-synthesis.md"
42
- LAST_RUN_FILE = COORD_DIR / "synthesis-last-run"
43
- LOCK_FILE = COORD_DIR / "synthesis.lock"
44
- def _resolve_claude_cli() -> Path:
45
- """Find claude CLI: saved path > PATH > common locations."""
46
- import shutil as _shutil
47
- saved = NEXO_HOME / "config" / "claude-cli-path"
48
- if saved.exists():
49
- p = Path(saved.read_text().strip())
50
- if p.exists():
51
- return p
52
- found = _shutil.which("claude")
53
- if found:
54
- return Path(found)
55
- for candidate in [
56
- HOME / ".local" / "bin" / "claude",
57
- HOME / ".npm-global" / "bin" / "claude",
58
- Path("/usr/local/bin/claude"),
59
- ]:
60
- if candidate.exists():
61
- return candidate
62
- return HOME / ".local" / "bin" / "claude"
63
-
64
- CLAUDE_CLI = _resolve_claude_cli()
65
-
66
- TODAY = date.today()
67
- TODAY_STR = TODAY.isoformat()
68
-
69
-
70
- def log(msg: str):
71
- ts = datetime.now().strftime("%H:%M:%S")
72
- print(f"[{ts}] {msg}", flush=True)
73
-
74
-
75
- def should_run() -> bool:
76
- if LAST_RUN_FILE.exists():
77
- return LAST_RUN_FILE.read_text().strip() != TODAY_STR
78
- return True
79
-
80
-
81
- def mark_done():
82
- LAST_RUN_FILE.write_text(TODAY_STR)
83
-
84
-
85
- def acquire_lock():
86
- lock_fd = open(LOCK_FILE, "w")
87
- try:
88
- fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
89
- return lock_fd
90
- except BlockingIOError:
91
- log("Another instance running. Exiting.")
92
- sys.exit(0)
93
-
94
-
95
- def release_lock(lock_fd):
96
- fcntl.flock(lock_fd, fcntl.LOCK_UN)
97
- lock_fd.close()
98
- LOCK_FILE.unlink(missing_ok=True)
99
-
100
-
101
- def safe_query(sql: str, params=()) -> list:
102
- if not NEXO_DB.exists():
103
- return []
104
- try:
105
- conn = sqlite3.connect(str(NEXO_DB))
106
- conn.row_factory = sqlite3.Row
107
- rows = [dict(r) for r in conn.execute(sql, params).fetchall()]
108
- conn.close()
109
- return rows
110
- except Exception as e:
111
- log(f"Query error: {e}")
112
- return []
113
-
114
-
115
- def _table_columns(table_name: str) -> set[str]:
116
- if not NEXO_DB.exists():
117
- return set()
118
- try:
119
- conn = sqlite3.connect(str(NEXO_DB))
120
- rows = conn.execute(f"PRAGMA table_info({table_name})").fetchall()
121
- conn.close()
122
- return {str(row[1]) for row in rows}
123
- except Exception:
124
- return set()
125
-
126
-
127
- def _parse_json_field(value):
128
- if isinstance(value, str):
129
- stripped = value.strip()
130
- if not stripped:
131
- return {}
132
- try:
133
- parsed = json.loads(stripped)
134
- except json.JSONDecodeError:
135
- return {}
136
- return parsed if isinstance(parsed, dict) else {}
137
- return value if isinstance(value, dict) else {}
138
-
139
-
140
- def _impact_reasoning(row: dict) -> str:
141
- factors = _parse_json_field(row.get("impact_factors"))
142
- return str(factors.get("reasoning") or "").strip()
143
-
144
-
145
- def _load_json_summary(path: Path, *, actionable) -> tuple[dict | None, str | None]:
146
- if not path.exists():
147
- return None, None
148
- try:
149
- payload = json.loads(path.read_text(encoding="utf-8"))
150
- except Exception as exc:
151
- return None, str(exc)
152
- if not isinstance(payload, dict):
153
- return None, "summary payload is not a JSON object"
154
- if not actionable(payload):
155
- return None, None
156
- return payload, None
157
-
158
-
159
- def _load_coordination_summary(filename: str, *, actionable) -> tuple[dict | None, str | None]:
160
- return _load_json_summary(COORD_DIR / filename, actionable=actionable)
161
-
162
-
163
- def _update_summary_actionable(payload: dict) -> bool:
164
- if any(payload.get(key) for key in ("error", "updated", "deferred_reason", "git_update", "npm_notice")):
165
- return True
166
- for action in payload.get("actions") or []:
167
- if str(action).startswith("personal-schedules-"):
168
- return True
169
- for message in payload.get("client_bootstrap_updates") or []:
170
- if "already current" not in str(message).lower():
171
- return True
172
- return False
173
-
174
-
175
- def collect_data() -> dict:
176
- """Collect all raw data for synthesis."""
177
- data = {"date": TODAY_STR}
178
-
179
- # Today's learnings
180
- data["learnings"] = safe_query(
181
- "SELECT category, title, content, reasoning FROM learnings "
182
- "WHERE date(created_at, 'unixepoch') = ? ORDER BY created_at DESC",
183
- (TODAY_STR,)
184
- )
185
-
186
- # Today's decisions
187
- data["decisions"] = safe_query(
188
- "SELECT domain, decision, alternatives, based_on, outcome FROM decisions "
189
- "WHERE date(created_at) = ? ORDER BY created_at DESC",
190
- (TODAY_STR,)
191
- )
192
-
193
- # Today's changes
194
- data["changes"] = safe_query(
195
- "SELECT files, what_changed, why, affects, risks FROM change_log "
196
- "WHERE date(created_at) = ? ORDER BY created_at DESC",
197
- (TODAY_STR,)
198
- )
199
-
200
- # Session diaries (summaries + mental_state)
201
- data["diaries"] = safe_query(
202
- "SELECT summary, self_critique, mental_state, user_signals FROM session_diary "
203
- "WHERE date(created_at) = ? ORDER BY created_at DESC",
204
- (TODAY_STR,)
205
- )
206
-
207
- # Overdue reminders (schema: description, date, status uppercase)
208
- data["overdue_reminders"] = safe_query(
209
- "SELECT id, description, date FROM reminders "
210
- "WHERE status='PENDING' AND date <= ? ORDER BY date",
211
- (TODAY_STR,)
212
- )
213
-
214
- # Pending followups (schema: description, date, status uppercase)
215
- followup_columns = _table_columns("followups")
216
- if "impact_score" in followup_columns:
217
- impact_factors_sql = ", impact_factors" if "impact_factors" in followup_columns else ""
218
- followup_select = (
219
- "SELECT id, description, date, priority, impact_score"
220
- f"{impact_factors_sql} FROM followups "
221
- )
222
- followup_order = (
223
- "ORDER BY "
224
- "CASE WHEN COALESCE(impact_score, 0) > 0 THEN 0 ELSE 1 END ASC, "
225
- "COALESCE(impact_score, 0) DESC, "
226
- "CASE WHEN date IS NULL OR date = '' THEN 1 ELSE 0 END ASC, "
227
- "date ASC"
228
- )
229
- else:
230
- followup_select = "SELECT id, description, date FROM followups "
231
- followup_order = "ORDER BY date"
232
- data["pending_followups"] = safe_query(
233
- f"{followup_select} WHERE status='PENDING' {followup_order}"
234
- )
235
- for row in data["pending_followups"]:
236
- if "impact_factors" in row:
237
- row["impact_factors"] = _parse_json_field(row.get("impact_factors"))
238
- row["impact_reasoning"] = _impact_reasoning(row)
239
-
240
- impact_summary_file = COORD_DIR / "impact-scorer-summary.json"
241
- if impact_summary_file.exists():
242
- try:
243
- data["impact_queue_summary"] = json.loads(impact_summary_file.read_text(encoding="utf-8"))
244
- except Exception as exc:
245
- data["impact_queue_summary_error"] = str(exc)
246
-
247
- followup_hygiene_summary, followup_hygiene_error = _load_coordination_summary(
248
- "followup-hygiene-summary.json",
249
- actionable=lambda payload: any(
250
- int(payload.get(key, 0) or 0) > 0
251
- for key in ("dirty_normalized", "stale_count", "orphan_count")
252
- ),
253
- )
254
- if followup_hygiene_summary is not None:
255
- data["followup_hygiene_summary"] = followup_hygiene_summary
256
- elif followup_hygiene_error:
257
- data["followup_hygiene_summary_error"] = followup_hygiene_error
258
-
259
- outcome_checker_summary, outcome_checker_error = _load_coordination_summary(
260
- "outcome-checker-summary.json",
261
- actionable=lambda payload: (
262
- any(
263
- int(payload.get(key, 0) or 0) > 0
264
- for key in ("checked", "met", "missed", "pending", "errors")
265
- )
266
- or bool(payload.get("ids"))
267
- or bool(((payload.get("auto_promoted_patterns") or {}).get("promoted") or []))
268
- ),
269
- )
270
- if outcome_checker_summary is not None:
271
- data["outcome_checker_summary"] = outcome_checker_summary
272
- elif outcome_checker_error:
273
- data["outcome_checker_summary_error"] = outcome_checker_error
274
-
275
- update_summary, update_summary_error = _load_json_summary(
276
- NEXO_HOME / "logs" / "update-last-summary.json",
277
- actionable=_update_summary_actionable,
278
- )
279
- if update_summary is not None:
280
- data["update_summary"] = update_summary
281
- elif update_summary_error:
282
- data["update_summary_error"] = update_summary_error
283
-
284
- # Guard stats
285
- data["guard_stats"] = safe_query(
286
- "SELECT category, COUNT(*) as cnt FROM learnings WHERE status='active' "
287
- "GROUP BY category ORDER BY cnt DESC LIMIT 10"
288
- )
289
-
290
- # Postmortem daily (if exists)
291
- pm_file = COORD_DIR / "postmortem-daily.md"
292
- if pm_file.exists():
293
- data["postmortem_summary"] = pm_file.read_text()[:2000]
294
-
295
- return data
296
-
297
-
298
- def synthesize(data: dict) -> bool:
299
- """CLI synthesizes the daily brief."""
300
-
301
- data_json = json.dumps(data, ensure_ascii=False, indent=1)
302
- if len(data_json) > 15000:
303
- data_json = data_json[:15000] + "\n... (truncated)"
304
-
305
- prompt = f"""FIRST: Call nexo_startup(task='daily synthesis') to register this session.
306
-
307
- You are NEXO's synthesis engine. Write the daily intelligence brief for tomorrow's
308
- startup. This file is read by NEXO at the beginning of each session to understand
309
- what happened today and what to focus on tomorrow. Use nexo_learning_add and nexo_followup_create if you discover actionable items.
310
-
311
- TODAY'S RAW DATA:
312
- {data_json}
313
-
314
- Write the synthesis to {OUTPUT_FILE} with this structure:
315
-
316
- # NEXO Daily Synthesis — {TODAY_STR}
317
-
318
- ## Errors & Learnings
319
- [New learnings from today — what went wrong, what was learned]
320
-
321
- ## Decisions Made
322
- [Key decisions and their reasoning]
323
-
324
- ## Changes Deployed
325
- [What was changed in production today]
326
-
327
- ## the user — Observations
328
- [Patterns in the user's behavior: frustrations, pending decisions, ideas without
329
- deadlines, topics he started but didn't close. This is NEXO's peripheral vision.]
330
-
331
- ## Weak Points (self-assessment)
332
- [Where NEXO failed or could have done better today — from session diaries]
333
-
334
- ## Tomorrow's Context
335
- [What the next session needs to know: pending followups, overdue reminders,
336
- in-progress tasks, things to verify]
337
-
338
- ## Guard Status
339
- [Areas with most learnings — where errors concentrate]
340
-
341
- Be concise. Each section 3-8 bullet points max. Focus on what CHANGES BEHAVIOR,
342
- not what merely happened. If a section has nothing, write "Nothing notable."
343
-
344
- Execute without asking."""
345
-
346
- log("Invoking automation backend for synthesis...")
347
- try:
348
- result = run_automation_prompt(
349
- prompt,
350
- model=_USER_MODEL or "opus",
351
- timeout=21600,
352
- output_format="text",
353
- allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
354
- )
355
-
356
- if result.returncode != 0:
357
- log(f"CLI error ({result.returncode}): {(result.stderr or '')[:300]}")
358
- return False
359
-
360
- log(f"Synthesis complete. Output: {len(result.stdout or '')} chars")
361
- return True
362
-
363
- except AutomationBackendUnavailableError as e:
364
- log(f"Automation backend unavailable: {e}")
365
- return False
366
- except subprocess.TimeoutExpired:
367
- log("CLI timed out (180s)")
368
- return False
369
- except Exception as e:
370
- log(f"Exception: {e}")
371
- return False
372
-
373
-
374
- def fallback_synthesis(data: dict):
375
- """Write a basic synthesis from raw data when CLI is unavailable."""
376
- log("Fallback: writing basic synthesis from raw data...")
377
- lines = [f"# NEXO Daily Synthesis -- {TODAY_STR}", "",
378
- "*(Generated by fallback -- CLI was unavailable)*", ""]
379
-
380
- if data.get("learnings"):
381
- lines.append("## Errors & Learnings")
382
- for l in data["learnings"][:10]:
383
- lines.append(f"- [{l.get('category', 'general')}] {l.get('title', 'untitled')}")
384
- lines.append("")
385
-
386
- if data.get("decisions"):
387
- lines.append("## Decisions Made")
388
- for d in data["decisions"][:10]:
389
- lines.append(f"- [{d.get('domain', 'general')}] {d.get('decision', '')[:120]}")
390
- lines.append("")
391
-
392
- if data.get("changes"):
393
- lines.append("## Changes Deployed")
394
- for c in data["changes"][:10]:
395
- lines.append(f"- {c.get('what_changed', '')[:120]}")
396
- lines.append("")
397
-
398
- if data.get("overdue_reminders"):
399
- lines.append("## Overdue Reminders")
400
- for r in data["overdue_reminders"][:10]:
401
- lines.append(f"- #{r.get('id', '?')} {r.get('description', '')} (due {r.get('date', '?')})")
402
- lines.append("")
403
-
404
- if data.get("pending_followups"):
405
- lines.append("## Pending Followups")
406
- for f in data["pending_followups"][:10]:
407
- impact = float(f.get("impact_score") or 0.0)
408
- impact_tag = f" [impact {impact:.1f}]" if impact > 0 else ""
409
- because = _impact_reasoning(f)
410
- because_tag = f" — {because}" if because else ""
411
- lines.append(
412
- f"- #{f.get('id', '?')} {f.get('description', '')} "
413
- f"(due {f.get('date', '?')}){impact_tag}{because_tag}"
414
- )
415
- lines.append("")
416
-
417
- impact_summary = data.get("impact_queue_summary") or {}
418
- if impact_summary.get("top_changes"):
419
- lines.append("## Queue Changes By Impact")
420
- for item in impact_summary.get("top_changes", [])[:5]:
421
- delta = float(item.get("delta") or 0.0)
422
- if abs(delta) < 1.0:
423
- continue
424
- direction = "+" if delta >= 0 else ""
425
- lines.append(
426
- f"- #{item.get('id', '?')} {direction}{delta:.1f} -> {float(item.get('impact_score') or 0.0):.1f}"
427
- f" ({item.get('impact_reasoning') or 'score recalculated'})"
428
- )
429
- lines.append("")
430
-
431
- OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
432
- OUTPUT_FILE.write_text("\n".join(lines))
433
- log(f"Fallback synthesis written to {OUTPUT_FILE}")
434
-
435
-
436
- def main():
437
- if not should_run():
438
- log(f"Already ran today ({TODAY_STR}). Skipping.")
439
- return
440
-
441
- lock_fd = acquire_lock()
442
- try:
443
- log(f"=== NEXO Synthesis v2 -- {TODAY_STR} ===")
444
-
445
- data = collect_data()
446
- log(f"Collected: {len(data.get('learnings', []))} learnings, "
447
- f"{len(data.get('decisions', []))} decisions, "
448
- f"{len(data.get('changes', []))} changes, "
449
- f"{len(data.get('diaries', []))} diaries")
450
-
451
- success = synthesize(data)
452
-
453
- if success:
454
- mark_done()
455
- log("Synthesis v2 complete.")
456
- else:
457
- log("Synthesis CLI failed -- writing fallback synthesis.")
458
- fallback_synthesis(data)
459
- mark_done()
460
-
461
- # Register for catch-up
462
- try:
463
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
464
- st = json.loads(state_file.read_text()) if state_file.exists() else {}
465
- st["synthesis"] = datetime.now().isoformat()
466
- state_file.write_text(json.dumps(st, indent=2))
467
- except Exception:
468
- pass
469
-
470
- finally:
471
- release_lock(lock_fd)
472
-
473
-
474
- if __name__ == "__main__":
475
- main()
@@ -1,79 +0,0 @@
1
- #!/bin/bash
2
- # NEXO TCC Auto-Approve — grants macOS permissions to new Claude Code versions.
3
- #
4
- # macOS only. On Linux this is a no-op (Linux doesn't have TCC).
5
- # Runs at load to approve any new Claude versions that appeared.
6
- #
7
- # What it does:
8
- # 1. Scans ~/.local/share/claude/versions/ for Claude binaries
9
- # 2. For each new version, grants TCC access to Documents, Desktop, Downloads, etc.
10
- # 3. Also approves the Python binary used by NEXO's venv
11
- # 4. Tracks which versions have been approved to avoid re-processing
12
- #
13
- # Why: Claude Code updates frequently. Each new binary needs macOS permission
14
- # grants or the user gets popup dialogs interrupting their work.
15
-
16
- set -euo pipefail
17
-
18
- # Linux: nothing to do
19
- if [ "$(uname -s)" != "Darwin" ]; then
20
- exit 0
21
- fi
22
-
23
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
24
- TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
25
- VERSIONS_DIR="$HOME/.local/share/claude/versions"
26
- MARKER_DIR="$NEXO_HOME/data/.tcc-approved"
27
- LOG="$NEXO_HOME/logs/tcc-auto-approve.log"
28
-
29
- mkdir -p "$MARKER_DIR" "$(dirname "$LOG")"
30
-
31
- # TCC services Claude Code needs
32
- SERVICES=(
33
- kTCCServiceSystemPolicyDocumentsFolder
34
- kTCCServiceSystemPolicyDesktopFolder
35
- kTCCServiceSystemPolicyDownloadsFolder
36
- kTCCServiceMediaLibrary
37
- kTCCServiceSystemPolicyNetworkVolumes
38
- kTCCServiceSystemPolicyAppData
39
- kTCCServiceFileProviderDomain
40
- )
41
-
42
- # Approve Claude versions
43
- if [ -d "$VERSIONS_DIR" ]; then
44
- for bin_path in "$VERSIONS_DIR"/*; do
45
- [ ! -e "$bin_path" ] && continue
46
- version=$(basename "$bin_path")
47
- marker="$MARKER_DIR/$version"
48
-
49
- # Skip if already approved
50
- [ -f "$marker" ] && continue
51
-
52
- echo "$(date '+%Y-%m-%d %H:%M:%S') Approving Claude $version" >> "$LOG"
53
-
54
- for svc in "${SERVICES[@]}"; do
55
- sqlite3 "$TCC_DB" "
56
- INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
57
- VALUES ('$svc', '$bin_path', 1, 2, 4, 1);
58
- " 2>/dev/null
59
- done
60
-
61
- touch "$marker"
62
- echo "$(date '+%Y-%m-%d %H:%M:%S') Done: Claude $version — ${#SERVICES[@]} services approved" >> "$LOG"
63
- done
64
- fi
65
-
66
- # Also approve Python from NEXO's venv (if it exists)
67
- NEXO_CODE="${NEXO_CODE:-}"
68
- if [ -n "$NEXO_CODE" ]; then
69
- PYTHON_BIN="$(dirname "$NEXO_CODE")/.venv/bin/python"
70
- if [ -e "$PYTHON_BIN" ]; then
71
- PYTHON_REAL=$(readlink -f "$PYTHON_BIN" 2>/dev/null || echo "$PYTHON_BIN")
72
- for svc in "${SERVICES[@]}"; do
73
- sqlite3 "$TCC_DB" "
74
- INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
75
- VALUES ('$svc', '$PYTHON_REAL', 1, 2, 4, 1);
76
- " 2>/dev/null
77
- done
78
- fi
79
- fi