nexo-brain 2.6.21 → 3.0.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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +72 -20
  3. package/hooks/hooks.json +79 -0
  4. package/package.json +1 -1
  5. package/src/agent_runner.py +296 -8
  6. package/src/cli.py +209 -4
  7. package/src/client_preferences.py +115 -0
  8. package/src/client_sync.py +202 -2
  9. package/src/cognitive/__init__.py +1 -1
  10. package/src/cognitive/_search.py +39 -19
  11. package/src/dashboard/app.py +264 -0
  12. package/src/dashboard/templates/base.html +4 -0
  13. package/src/dashboard/templates/dashboard.html +59 -1
  14. package/src/dashboard/templates/protocol.html +199 -0
  15. package/src/db/__init__.py +23 -1
  16. package/src/db/_learnings.py +31 -4
  17. package/src/db/_personal_scripts.py +12 -0
  18. package/src/db/_protocol.py +303 -0
  19. package/src/db/_schema.py +248 -0
  20. package/src/db/_watchers.py +173 -0
  21. package/src/db/_workflow.py +952 -0
  22. package/src/doctor/providers/runtime.py +1095 -3
  23. package/src/evolution_cycle.py +62 -0
  24. package/src/hook_guardrails.py +308 -0
  25. package/src/hooks/protocol-guardrail.sh +10 -0
  26. package/src/nexo_sdk.py +103 -0
  27. package/src/plugins/cognitive_memory.py +18 -0
  28. package/src/plugins/cortex.py +55 -35
  29. package/src/plugins/guard.py +132 -16
  30. package/src/plugins/protocol.py +911 -0
  31. package/src/plugins/schedule.py +40 -6
  32. package/src/plugins/simple_api.py +103 -0
  33. package/src/plugins/skills.py +67 -0
  34. package/src/plugins/state_watchers.py +79 -0
  35. package/src/plugins/workflow.py +588 -0
  36. package/src/public_contribution.py +86 -12
  37. package/src/script_registry.py +142 -0
  38. package/src/scripts/deep-sleep/apply_findings.py +482 -2
  39. package/src/scripts/deep-sleep/collect.py +49 -4
  40. package/src/scripts/nexo-agent-run.py +2 -0
  41. package/src/scripts/nexo-daily-self-audit.py +843 -5
  42. package/src/scripts/nexo-evolution-run.py +343 -1
  43. package/src/server.py +92 -6
  44. package/src/skills_runtime.py +151 -0
  45. package/src/state_watchers_runtime.py +334 -0
  46. package/src/tools_learnings.py +345 -7
  47. package/src/tools_sessions.py +183 -0
  48. package/templates/CLAUDE.md.template +9 -1
  49. package/templates/CODEX.AGENTS.md.template +10 -2
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+ """NEXO DB — state watchers registry."""
3
+
4
+ import json
5
+ import secrets
6
+ import time
7
+
8
+ from db._core import get_db
9
+
10
+ WATCHER_TYPES = {"repo_drift", "cron_drift", "api_health", "environment_drift", "expiry"}
11
+ WATCHER_STATUSES = {"active", "paused", "archived"}
12
+ WATCHER_HEALTH = {"unknown", "healthy", "degraded", "critical"}
13
+
14
+
15
+ def _watcher_id() -> str:
16
+ return f"SW-{int(time.time())}-{secrets.randbelow(100000)}"
17
+
18
+
19
+ def _as_json(value, default):
20
+ if value is None:
21
+ value = default
22
+ if isinstance(value, str):
23
+ return value
24
+ return json.dumps(value, ensure_ascii=False)
25
+
26
+
27
+ def _parse_json(value, default):
28
+ if value in (None, ""):
29
+ return default
30
+ if isinstance(value, (dict, list)):
31
+ return value
32
+ try:
33
+ return json.loads(value)
34
+ except Exception:
35
+ return default
36
+
37
+
38
+ def _row_to_watcher(row) -> dict:
39
+ watcher = dict(row)
40
+ watcher["config"] = _parse_json(watcher.get("config"), {})
41
+ watcher["last_result"] = _parse_json(watcher.get("last_result"), {})
42
+ return watcher
43
+
44
+
45
+ def create_state_watcher(
46
+ watcher_type: str,
47
+ title: str,
48
+ *,
49
+ target: str = "",
50
+ severity: str = "warn",
51
+ status: str = "active",
52
+ config=None,
53
+ ) -> dict:
54
+ clean_type = str(watcher_type or "").strip().lower()
55
+ if clean_type not in WATCHER_TYPES:
56
+ raise ValueError(f"Unsupported watcher_type: {watcher_type}")
57
+ clean_status = str(status or "active").strip().lower()
58
+ if clean_status not in WATCHER_STATUSES:
59
+ clean_status = "active"
60
+ watcher_id = _watcher_id()
61
+ conn = get_db()
62
+ conn.execute(
63
+ """INSERT INTO state_watchers (
64
+ watcher_id, watcher_type, title, target, severity, status, config,
65
+ last_health, last_result, last_checked_at
66
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
67
+ (
68
+ watcher_id,
69
+ clean_type,
70
+ str(title or "").strip(),
71
+ str(target or "").strip(),
72
+ str(severity or "warn").strip().lower() or "warn",
73
+ clean_status,
74
+ _as_json(config, {}),
75
+ "unknown",
76
+ "{}",
77
+ "",
78
+ ),
79
+ )
80
+ conn.commit()
81
+ return get_state_watcher(watcher_id) or {"watcher_id": watcher_id}
82
+
83
+
84
+ def get_state_watcher(watcher_id: str) -> dict | None:
85
+ conn = get_db()
86
+ row = conn.execute(
87
+ "SELECT * FROM state_watchers WHERE watcher_id = ?",
88
+ (str(watcher_id or "").strip(),),
89
+ ).fetchone()
90
+ return _row_to_watcher(row) if row else None
91
+
92
+
93
+ def list_state_watchers(*, status: str = "", watcher_type: str = "", limit: int = 100) -> list[dict]:
94
+ clauses: list[str] = []
95
+ params: list[object] = []
96
+ if status:
97
+ clauses.append("status = ?")
98
+ params.append(str(status).strip().lower())
99
+ if watcher_type:
100
+ clauses.append("watcher_type = ?")
101
+ params.append(str(watcher_type).strip().lower())
102
+ where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
103
+ params.append(max(1, int(limit or 100)))
104
+ conn = get_db()
105
+ rows = conn.execute(
106
+ f"""SELECT *
107
+ FROM state_watchers
108
+ {where}
109
+ ORDER BY updated_at DESC, watcher_id DESC
110
+ LIMIT ?""",
111
+ tuple(params),
112
+ ).fetchall()
113
+ return [_row_to_watcher(row) for row in rows]
114
+
115
+
116
+ def update_state_watcher(
117
+ watcher_id: str,
118
+ *,
119
+ title: str | None = None,
120
+ target: str | None = None,
121
+ severity: str | None = None,
122
+ status: str | None = None,
123
+ config=None,
124
+ ) -> dict | None:
125
+ current = get_state_watcher(watcher_id)
126
+ if not current:
127
+ return None
128
+ updates = {
129
+ "title": current["title"] if title is None else str(title).strip(),
130
+ "target": current["target"] if target is None else str(target).strip(),
131
+ "severity": current["severity"] if severity is None else str(severity).strip().lower(),
132
+ "status": current["status"] if status is None else str(status).strip().lower(),
133
+ "config": _as_json(current["config"] if config is None else config, {}),
134
+ }
135
+ if updates["status"] not in WATCHER_STATUSES:
136
+ updates["status"] = current["status"]
137
+ conn = get_db()
138
+ conn.execute(
139
+ """UPDATE state_watchers
140
+ SET title = ?, target = ?, severity = ?, status = ?, config = ?,
141
+ updated_at = datetime('now')
142
+ WHERE watcher_id = ?""",
143
+ (
144
+ updates["title"],
145
+ updates["target"],
146
+ updates["severity"],
147
+ updates["status"],
148
+ updates["config"],
149
+ str(watcher_id).strip(),
150
+ ),
151
+ )
152
+ conn.commit()
153
+ return get_state_watcher(watcher_id)
154
+
155
+
156
+ def update_state_watcher_result(watcher_id: str, *, health: str, result=None, checked_at: str = "") -> dict | None:
157
+ clean_health = str(health or "unknown").strip().lower()
158
+ if clean_health not in WATCHER_HEALTH:
159
+ clean_health = "unknown"
160
+ conn = get_db()
161
+ conn.execute(
162
+ """UPDATE state_watchers
163
+ SET last_health = ?, last_result = ?, last_checked_at = ?, updated_at = datetime('now')
164
+ WHERE watcher_id = ?""",
165
+ (
166
+ clean_health,
167
+ _as_json(result, {}),
168
+ checked_at.strip(),
169
+ str(watcher_id or "").strip(),
170
+ ),
171
+ )
172
+ conn.commit()
173
+ return get_state_watcher(watcher_id)