nexo-brain 2.3.2 → 2.5.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 (83) hide show
  1. package/README.md +77 -8
  2. package/bin/nexo-brain.js +230 -22
  3. package/bin/nexo.js +55 -0
  4. package/community/skills/.gitkeep +1 -0
  5. package/package.json +5 -2
  6. package/src/auto_update.py +158 -8
  7. package/src/cli.py +605 -0
  8. package/src/cognitive/_ingest.py +1 -1
  9. package/src/cognitive/_memory.py +4 -4
  10. package/src/crons/manifest.json +8 -0
  11. package/src/dashboard/app.py +709 -37
  12. package/src/dashboard/templates/adaptive.html +112 -218
  13. package/src/dashboard/templates/artifacts.html +133 -0
  14. package/src/dashboard/templates/backups.html +136 -0
  15. package/src/dashboard/templates/base.html +413 -0
  16. package/src/dashboard/templates/calendar.html +523 -652
  17. package/src/dashboard/templates/chat.html +356 -0
  18. package/src/dashboard/templates/claims.html +259 -0
  19. package/src/dashboard/templates/cortex.html +262 -0
  20. package/src/dashboard/templates/credentials.html +128 -0
  21. package/src/dashboard/templates/crons.html +370 -0
  22. package/src/dashboard/templates/dashboard.html +384 -572
  23. package/src/dashboard/templates/dreams.html +252 -0
  24. package/src/dashboard/templates/email.html +160 -0
  25. package/src/dashboard/templates/evolution.html +189 -0
  26. package/src/dashboard/templates/feed.html +249 -0
  27. package/src/dashboard/templates/followup_health.html +170 -0
  28. package/src/dashboard/templates/graph.html +191 -269
  29. package/src/dashboard/templates/guard.html +259 -0
  30. package/src/dashboard/templates/inbox.html +220 -336
  31. package/src/dashboard/templates/memory.html +317 -197
  32. package/src/dashboard/templates/operations.html +498 -652
  33. package/src/dashboard/templates/plugins.html +185 -0
  34. package/src/dashboard/templates/rules.html +246 -0
  35. package/src/dashboard/templates/sentiment.html +247 -0
  36. package/src/dashboard/templates/sessions.html +215 -171
  37. package/src/dashboard/templates/skills.html +329 -0
  38. package/src/dashboard/templates/somatic.html +68 -172
  39. package/src/dashboard/templates/triggers.html +133 -0
  40. package/src/dashboard/templates/trust.html +360 -0
  41. package/src/db/__init__.py +5 -0
  42. package/src/db/_schema.py +25 -1
  43. package/src/db/_sessions.py +22 -0
  44. package/src/db/_skills.py +983 -252
  45. package/src/doctor/__init__.py +1 -0
  46. package/src/doctor/formatters.py +52 -0
  47. package/src/doctor/models.py +44 -0
  48. package/src/doctor/orchestrator.py +42 -0
  49. package/src/doctor/providers/__init__.py +1 -0
  50. package/src/doctor/providers/boot.py +206 -0
  51. package/src/doctor/providers/deep.py +292 -0
  52. package/src/doctor/providers/runtime.py +686 -0
  53. package/src/hooks/capture-tool-logs.sh +18 -4
  54. package/src/hooks/post-compact.sh +5 -1
  55. package/src/hooks/pre-compact.sh +1 -1
  56. package/src/plugin_loader.py +14 -0
  57. package/src/plugins/doctor.py +36 -0
  58. package/src/plugins/evolution.py +2 -1
  59. package/src/plugins/skills.py +135 -175
  60. package/src/requirements.txt +1 -0
  61. package/src/script_registry.py +322 -0
  62. package/src/scripts/deep-sleep/apply_findings.py +63 -33
  63. package/src/scripts/deep-sleep/collect.py +38 -9
  64. package/src/scripts/deep-sleep/extract-prompt.md +14 -0
  65. package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
  66. package/src/scripts/deep-sleep/synthesize.py +37 -1
  67. package/src/scripts/nexo-dashboard.sh +29 -0
  68. package/src/scripts/nexo-day-orchestrator.sh +139 -0
  69. package/src/scripts/nexo-evolution-run.py +2 -1
  70. package/src/scripts/nexo-learning-housekeep.py +1 -1
  71. package/src/scripts/nexo-watchdog.sh +1 -1
  72. package/src/server.py +9 -5
  73. package/src/skills/run-runtime-doctor/guide.md +12 -0
  74. package/src/skills/run-runtime-doctor/script.py +21 -0
  75. package/src/skills/run-runtime-doctor/skill.json +25 -0
  76. package/src/skills_runtime.py +347 -0
  77. package/src/tools_menu.py +3 -2
  78. package/src/tools_sessions.py +126 -0
  79. package/src/user_context.py +46 -0
  80. package/templates/nexo_helper.py +45 -0
  81. package/templates/script-template.py +44 -0
  82. package/templates/skill-script-template.py +39 -0
  83. package/templates/skill-template.md +33 -0
@@ -19,9 +19,13 @@ from datetime import datetime
19
19
  from pathlib import Path
20
20
 
21
21
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
22
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[2])))
22
23
  DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
23
24
  PROMPT_FILE = Path(__file__).parent / "synthesize-prompt.md"
24
25
 
26
+ if str(NEXO_CODE) not in sys.path:
27
+ sys.path.insert(0, str(NEXO_CODE))
28
+
25
29
  CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
26
30
 
27
31
 
@@ -71,6 +75,31 @@ def extract_json_from_response(text: str) -> dict | None:
71
75
  return None
72
76
 
73
77
 
78
+ def collect_skill_runtime_candidates(target_date: str) -> tuple[Path, dict]:
79
+ """Collect mature skill candidates from DB usage so Deep Sleep can evolve them."""
80
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-skill-runtime-candidates.json"
81
+ payload = {
82
+ "scriptable": [],
83
+ "improvements": [],
84
+ }
85
+ try:
86
+ from db import (
87
+ collect_scriptable_skill_candidates,
88
+ collect_skill_improvement_candidates,
89
+ init_db,
90
+ )
91
+
92
+ init_db()
93
+ payload["scriptable"] = collect_scriptable_skill_candidates()
94
+ payload["improvements"] = collect_skill_improvement_candidates()
95
+ except Exception as e:
96
+ payload["error"] = str(e)
97
+
98
+ with open(output_file, "w") as f:
99
+ json.dump(payload, f, indent=2, ensure_ascii=False)
100
+ return output_file, payload
101
+
102
+
74
103
  def main():
75
104
  target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
76
105
 
@@ -86,7 +115,10 @@ def main():
86
115
  extractions = json.load(f)
87
116
 
88
117
  total_findings = extractions.get("total_findings", 0)
89
- if total_findings == 0:
118
+ runtime_candidates_file, runtime_candidates = collect_skill_runtime_candidates(target_date)
119
+ runtime_candidate_count = len(runtime_candidates.get("scriptable", [])) + len(runtime_candidates.get("improvements", []))
120
+
121
+ if total_findings == 0 and runtime_candidate_count == 0:
90
122
  print(f"[synthesize] No findings to synthesize for {target_date}.")
91
123
  # Write minimal synthesis
92
124
  output = {
@@ -95,6 +127,8 @@ def main():
95
127
  "cross_session_patterns": [],
96
128
  "morning_agenda": [],
97
129
  "context_packets": [],
130
+ "skills": [],
131
+ "skill_evolution_candidates": [],
98
132
  "actions": [],
99
133
  "summary": f"No significant findings for {target_date}."
100
134
  }
@@ -108,9 +142,11 @@ def main():
108
142
  prompt_template = PROMPT_FILE.read_text()
109
143
  prompt = prompt_template.replace("{{EXTRACTIONS_FILE}}", str(extractions_file))
110
144
  prompt = prompt.replace("{{CONTEXT_FILE}}", str(context_file))
145
+ prompt = prompt.replace("{{SKILL_RUNTIME_FILE}}", str(runtime_candidates_file))
111
146
 
112
147
  claude_bin = find_claude_cli()
113
148
  print(f"[synthesize] Phase 3: Synthesizing {total_findings} findings from {target_date}")
149
+ print(f"[synthesize] Skill runtime candidates: {runtime_candidate_count}")
114
150
  print(f"[synthesize] Claude CLI: {claude_bin}")
115
151
 
116
152
  try:
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # NEXO Dashboard — Web UI at localhost:6174
4
+ # Schedule: keepAlive (persistent daemon, auto-restart on crash)
5
+ # ============================================================================
6
+ set -uo pipefail
7
+
8
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
9
+ NEXO_CODE="${NEXO_CODE:-$NEXO_HOME}"
10
+
11
+ # Find Python
12
+ if [ -x "$NEXO_HOME/.venv/bin/python3" ]; then
13
+ PYTHON="$NEXO_HOME/.venv/bin/python3"
14
+ else
15
+ PYTHON="python3"
16
+ fi
17
+
18
+ # Dashboard module location: prefer NEXO_CODE (repo), fallback NEXO_HOME (installed)
19
+ if [ -f "$NEXO_CODE/dashboard/app.py" ]; then
20
+ DASH_DIR="$NEXO_CODE"
21
+ elif [ -f "$NEXO_HOME/dashboard/app.py" ]; then
22
+ DASH_DIR="$NEXO_HOME"
23
+ else
24
+ echo "Dashboard not found" >&2
25
+ exit 1
26
+ fi
27
+
28
+ cd "$DASH_DIR"
29
+ exec "$PYTHON" -m dashboard.app --no-browser --port 6174
@@ -0,0 +1,139 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # NEXO Day Orchestrator — autonomous NEXO cycle every 15 min
4
+ # Schedule: keepAlive, self-enforced operating hours (default 8:00-23:00)
5
+ #
6
+ # This is NOT a Python script that simulates intelligence.
7
+ # This launches Claude Code as NEXO with full MCP access.
8
+ # NEXO thinks, acts, and reports — like any interactive session.
9
+ # ============================================================================
10
+ set -euo pipefail
11
+
12
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
13
+ LOG_DIR="$NEXO_HOME/logs"
14
+ mkdir -p "$LOG_DIR" "$NEXO_HOME/operations"
15
+
16
+ # --- Configuration ---
17
+ CYCLE_INTERVAL=900 # 15 minutes between cycles
18
+ CYCLE_TIMEOUT=600 # 10 min max per cycle
19
+ MAX_TURNS=30 # Claude max turns per cycle
20
+ HOUR_START=8
21
+ HOUR_END=23
22
+
23
+ # --- Find Claude CLI ---
24
+ find_claude() {
25
+ for candidate in \
26
+ "$(command -v claude 2>/dev/null)" \
27
+ "$HOME/.claude/local/claude" \
28
+ "/opt/homebrew/bin/claude" \
29
+ "/usr/local/bin/claude"; do
30
+ if [ -n "$candidate" ] && [ -x "$candidate" ]; then
31
+ echo "$candidate"
32
+ return 0
33
+ fi
34
+ done
35
+ return 1
36
+ }
37
+
38
+ CLAUDE=$(find_claude) || {
39
+ echo "$(date '+%Y-%m-%d %H:%M') ERROR: claude CLI not found" >&2
40
+ exit 1
41
+ }
42
+
43
+ # --- Prevent overlapping cycles ---
44
+ LOCKFILE="$NEXO_HOME/operations/.orchestrator.lock"
45
+ acquire_lock() {
46
+ if [ -f "$LOCKFILE" ]; then
47
+ local pid
48
+ pid=$(cat "$LOCKFILE" 2>/dev/null || echo "")
49
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
50
+ return 1 # Still running
51
+ fi
52
+ fi
53
+ echo $$ > "$LOCKFILE"
54
+ return 0
55
+ }
56
+ release_lock() { rm -f "$LOCKFILE"; }
57
+
58
+ # --- The orchestrator prompt ---
59
+ PROMPT='You are NEXO in autonomous orchestrator mode. The user is NOT present. You have 5 minutes max.
60
+
61
+ ABSOLUTE PRIORITY: act, do not list. If you can do something, do it. If you need the user, send email.
62
+
63
+ CHECKLIST (in this order):
64
+
65
+ 1. OVERDUE FOLLOWUPS: nexo_reminders(filter="due") + nexo_reminders(filter="followups")
66
+ - NEXO tasks (verify, check, monitor) → DO THEM NOW
67
+ - Tasks needing user decision → accumulate for email
68
+ - Completed ones → nexo_followup_complete
69
+
70
+ 2. EMAIL: nexo_email_inbox(unread_only=true, limit=10)
71
+ - Emails you can process → process them
72
+ - Important emails for user → accumulate for email
73
+
74
+ 3. INFRASTRUCTURE: nexo_doctor(tier="runtime")
75
+ - If degraded/critical → try to fix
76
+
77
+ 4. EMAIL TO USER (only if there is something to report):
78
+ - nexo_email_send with clean HTML summary
79
+ - Only what needs attention or decision
80
+ - Include what you ALREADY DID (not just pending items)
81
+ - If nothing relevant → DO NOT send email
82
+ - Max 1 email per cycle
83
+
84
+ 5. DIARY: nexo_session_diary_write with what you did
85
+
86
+ RULES:
87
+ - DO NOT ask permission. autonomy=full
88
+ - DO NOT send empty or "all ok" emails
89
+ - DO NOT list things without acting
90
+ - If a followup is executable → execute it before reporting
91
+ - Use nexo_heartbeat at start
92
+ - Clean close: diary + nexo_stop'
93
+
94
+ # --- Main loop ---
95
+ echo "$(date '+%Y-%m-%d %H:%M') NEXO Day Orchestrator starting (PID $$)"
96
+ echo " Claude: $CLAUDE"
97
+ echo " Cycle: every ${CYCLE_INTERVAL}s, ${HOUR_START}:00-${HOUR_END}:00"
98
+ echo " Timeout: ${CYCLE_TIMEOUT}s, max turns: $MAX_TURNS"
99
+
100
+ while true; do
101
+ HOUR=$(date +%H | sed 's/^0//')
102
+
103
+ # Outside operating hours — sleep and check again
104
+ if [ "$HOUR" -lt "$HOUR_START" ] || [ "$HOUR" -ge "$HOUR_END" ]; then
105
+ sleep 300 # Check every 5 min if we're back in hours
106
+ continue
107
+ fi
108
+
109
+ # Try to acquire lock
110
+ if ! acquire_lock; then
111
+ echo "$(date '+%Y-%m-%d %H:%M') Previous cycle still running. Skipping."
112
+ sleep "$CYCLE_INTERVAL"
113
+ continue
114
+ fi
115
+
116
+ TIMESTAMP=$(date '+%Y-%m-%d_%H%M')
117
+ LOGFILE="$LOG_DIR/orchestrator-$TIMESTAMP.log"
118
+ echo "$(date '+%Y-%m-%d %H:%M') Cycle starting..."
119
+
120
+ # Launch Claude Code as NEXO
121
+ set +e
122
+ timeout "$CYCLE_TIMEOUT" "$CLAUDE" \
123
+ --dangerously-skip-permissions \
124
+ -p "$PROMPT" \
125
+ --max-turns "$MAX_TURNS" \
126
+ >>"$LOGFILE" 2>&1
127
+ EXIT_CODE=$?
128
+ set -e
129
+
130
+ echo "$(date '+%Y-%m-%d %H:%M') Cycle finished (exit $EXIT_CODE)" | tee -a "$LOGFILE"
131
+
132
+ release_lock
133
+
134
+ # Clean old logs (keep 7 days)
135
+ find "$LOG_DIR" -name "orchestrator-*.log" -mtime +7 -delete 2>/dev/null || true
136
+
137
+ # Sleep until next cycle
138
+ sleep "$CYCLE_INTERVAL"
139
+ done
@@ -45,9 +45,10 @@ AUTO_SAFE_PREFIXES = [
45
45
  str(CLAUDE_DIR / "coordination") + "/",
46
46
  ]
47
47
 
48
- # Public mode: only user-created scripts — NEVER core, cortex, or plugins
48
+ # Public mode: user scripts and plugins only — NEVER core code
49
49
  AUTO_SAFE_PREFIXES_PUBLIC = [
50
50
  str(CLAUDE_DIR / "scripts") + "/",
51
+ str(CLAUDE_DIR / "plugins") + "/",
51
52
  ]
52
53
 
53
54
  # ── Immutable files — NEVER touch (applies to ALL modes) ────────────────
@@ -70,7 +70,7 @@ def adjust_weights(conn):
70
70
  priority = l["priority"] or "medium"
71
71
 
72
72
  # Priority floor — critical learnings never drop below 0.5
73
- priority_floor = {"critical": 0.5, "high": 0.3, "medium": 0.1, "low": 0.05}[priority]
73
+ priority_floor = {"critical": 0.5, "high": 0.3, "medium": 0.1, "low": 0.05}.get(priority, 0.1)
74
74
 
75
75
  new_weight = old_weight
76
76
 
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # ============================================================================
3
3
  # NEXO Watchdog — Comprehensive health monitor for all NEXO services
4
- # Cron: */5 * * * * NEXO_HOME/scripts/nexo-watchdog.sh
4
+ # Schedule: every 30 minutes (interval_seconds: 1800)
5
5
  # ============================================================================
6
6
  # Monitors ALL LaunchAgents, cron jobs, and background processes.
7
7
  # Outputs: watchdog-status.json (machine), watchdog-report.txt (human),
package/src/server.py CHANGED
@@ -8,6 +8,7 @@ import sys
8
8
  from fastmcp import FastMCP
9
9
  from db import init_db, rebuild_fts_index, get_db, close_db, fts_add_dir, fts_remove_dir, fts_list_dirs
10
10
  from tools_sessions import handle_startup, handle_heartbeat, handle_status, handle_context_packet, handle_smart_startup_query
11
+ from user_context import get_context as _get_ctx
11
12
  from tools_coordination import (
12
13
  handle_track, handle_untrack, handle_files,
13
14
  handle_send, handle_ask, handle_answer, handle_check_answer,
@@ -154,12 +155,17 @@ def _server_init():
154
155
  mcp = FastMCP(
155
156
  name="nexo",
156
157
  instructions=(
157
- "NEXO — cognitive co-operator. Save important info from tool results before they clear.\n\n"
158
+ f"{_get_ctx().assistant_name} — cognitive co-operator. Save important info from tool results before they clear.\n\n"
159
+ "## CRITICAL — do these or you WILL get corrected\n"
160
+ "- **Guard (MANDATORY before ANY code edit):** `nexo_guard_check(files='...', area='...')` BEFORE editing code. "
161
+ "No exceptions. Blocking rules→resolve first. `nexo_track(sid=SID, paths=[...])` before shared files\n"
162
+ "- **Skills (MANDATORY before multi-step tasks):** `nexo_skill_match(task)` to find reusable procedures. "
163
+ "If match found, read it and follow the steps. After completion, `nexo_skill_result(id, success, context)` to record outcome.\n"
164
+ "- **Learnings (MANDATORY on corrections):** When you discover a bug, pattern, or get corrected→`nexo_learning_add` IMMEDIATELY. "
165
+ "Do NOT batch. Do NOT wait until end of session.\n\n"
158
166
  "## Rules\n"
159
167
  "- **Heartbeat:** `nexo_heartbeat(sid=SID, task='...', context_hint='...')` every user msg. "
160
168
  "React: DIARY REMINDER→write diary, VIBE:NEGATIVE→ultra-concise, AUTO-PRIME→read learnings\n"
161
- "- **Guard:** `nexo_guard_check(files='...', area='...')` BEFORE editing code. "
162
- "Blocking rules→resolve first. `nexo_track(sid=SID, paths=[...])` before shared files\n"
163
169
  "- **Followups:** NEXO tasks, execute silently. 'done'/'all set'→`nexo_followup_complete` NOW. "
164
170
  "Reminders=user's, alert when due\n"
165
171
  "- **Observe:** correction→learning. 'tomorrow'→followup. person→entity. open topic→followup 3d\n"
@@ -175,8 +181,6 @@ mcp = FastMCP(
175
181
  "write `nexo_session_diary_write(...)` with self_critique BEFORE responding. "
176
182
  "Detect intent, not keywords. If session closes without diary, auto_close handles it.\n"
177
183
  "- **Cortex:** `nexo_cortex_check` before budget/campaign/architecture changes\n"
178
- "- **Skills:** before multi-step tasks, `nexo_skill_match(task)` to find reusable procedures. "
179
- "If match found, read it and follow the steps. After completion, `nexo_skill_result(id, success, context)` to record outcome.\n"
180
184
  "- **Dissonance:** user contradicts memory→`nexo_cognitive_dissonance`. Frustrated→force=True\n"
181
185
  "- **Trust:** <40=paranoid verify twice, >80=fluid. Check: `nexo_cognitive_trust`"
182
186
  ),
@@ -0,0 +1,12 @@
1
+ # Run Runtime Doctor
2
+
3
+ Use this skill when you want a fast health snapshot of the running NEXO system.
4
+
5
+ ## Steps
6
+ 1. Run the runtime doctor for the requested tier.
7
+ 2. Review the degraded or critical checks first.
8
+ 3. If the report recommends deterministic fixes, decide whether to run them explicitly.
9
+
10
+ ## Gotchas
11
+ - A critical watchdog result reflects a real system issue, not just a stale skill.
12
+ - `all` is broader and slower than `runtime`.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import subprocess
4
+ import sys
5
+
6
+
7
+ def main() -> int:
8
+ tier = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else "runtime"
9
+ nexo_code = os.environ.get("NEXO_CODE", "")
10
+ if not nexo_code:
11
+ print("NEXO_CODE not set", file=sys.stderr)
12
+ return 1
13
+
14
+ cli_py = os.path.join(nexo_code, "cli.py")
15
+ cmd = [sys.executable, cli_py, "doctor", "--tier", tier, "--json"]
16
+ result = subprocess.run(cmd, text=True)
17
+ return result.returncode
18
+
19
+
20
+ if __name__ == "__main__":
21
+ raise SystemExit(main())
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "SK-RUN-RUNTIME-DOCTOR",
3
+ "name": "Run Runtime Doctor",
4
+ "description": "Runs the NEXO runtime doctor and returns the current health report.",
5
+ "level": "published",
6
+ "mode": "execute",
7
+ "source_kind": "core",
8
+ "execution_level": "read-only",
9
+ "approval_required": false,
10
+ "tags": ["doctor", "diagnostics", "runtime"],
11
+ "trigger_patterns": ["run doctor", "check runtime health", "diagnose nexo"],
12
+ "params_schema": {
13
+ "tier": {
14
+ "type": "string",
15
+ "required": false,
16
+ "default": "runtime",
17
+ "enum": ["boot", "runtime", "deep", "all"]
18
+ }
19
+ },
20
+ "command_template": {
21
+ "argv": ["{{file_path}}", "{{tier}}"]
22
+ },
23
+ "executable_entry": "script.py",
24
+ "stable_after_uses": 10
25
+ }