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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +72 -20
- package/hooks/hooks.json +79 -0
- package/package.json +1 -1
- package/src/agent_runner.py +296 -8
- package/src/cli.py +209 -4
- package/src/client_preferences.py +115 -0
- package/src/client_sync.py +202 -2
- package/src/cognitive/__init__.py +1 -1
- package/src/cognitive/_search.py +39 -19
- package/src/dashboard/app.py +264 -0
- package/src/dashboard/templates/base.html +4 -0
- package/src/dashboard/templates/dashboard.html +59 -1
- package/src/dashboard/templates/protocol.html +199 -0
- package/src/db/__init__.py +23 -1
- package/src/db/_learnings.py +31 -4
- package/src/db/_personal_scripts.py +12 -0
- package/src/db/_protocol.py +303 -0
- package/src/db/_schema.py +248 -0
- package/src/db/_watchers.py +173 -0
- package/src/db/_workflow.py +952 -0
- package/src/doctor/providers/runtime.py +1095 -3
- package/src/evolution_cycle.py +62 -0
- package/src/hook_guardrails.py +308 -0
- package/src/hooks/protocol-guardrail.sh +10 -0
- package/src/nexo_sdk.py +103 -0
- package/src/plugins/cognitive_memory.py +18 -0
- package/src/plugins/cortex.py +55 -35
- package/src/plugins/guard.py +132 -16
- package/src/plugins/protocol.py +911 -0
- package/src/plugins/schedule.py +40 -6
- package/src/plugins/simple_api.py +103 -0
- package/src/plugins/skills.py +67 -0
- package/src/plugins/state_watchers.py +79 -0
- package/src/plugins/workflow.py +588 -0
- package/src/public_contribution.py +86 -12
- package/src/script_registry.py +142 -0
- package/src/scripts/deep-sleep/apply_findings.py +482 -2
- package/src/scripts/deep-sleep/collect.py +49 -4
- package/src/scripts/nexo-agent-run.py +2 -0
- package/src/scripts/nexo-daily-self-audit.py +843 -5
- package/src/scripts/nexo-evolution-run.py +343 -1
- package/src/server.py +92 -6
- package/src/skills_runtime.py +151 -0
- package/src/state_watchers_runtime.py +334 -0
- package/src/tools_learnings.py +345 -7
- package/src/tools_sessions.py +183 -0
- package/templates/CLAUDE.md.template +9 -1
- 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)
|