nexo-brain 5.3.19 → 5.3.21

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/bin/nexo-brain.js +52 -10
  3. package/package.json +1 -1
  4. package/src/auto_update.py +11 -8
  5. package/src/dashboard/static/favicon 2.svg +32 -0
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +40 -0
  8. package/src/dashboard/static/style 2.css +2458 -0
  9. package/src/dashboard/templates/adaptive 2.html +118 -0
  10. package/src/dashboard/templates/artifacts 2.html +133 -0
  11. package/src/dashboard/templates/backups 2.html +136 -0
  12. package/src/dashboard/templates/base 2.html +417 -0
  13. package/src/dashboard/templates/calendar 2.html +591 -0
  14. package/src/dashboard/templates/chat 2.html +356 -0
  15. package/src/dashboard/templates/claims 2.html +259 -0
  16. package/src/dashboard/templates/cortex 2.html +321 -0
  17. package/src/dashboard/templates/credentials 2.html +128 -0
  18. package/src/dashboard/templates/crons 2.html +370 -0
  19. package/src/dashboard/templates/dashboard 2.html +494 -0
  20. package/src/dashboard/templates/dreams 2.html +252 -0
  21. package/src/dashboard/templates/email 2.html +160 -0
  22. package/src/dashboard/templates/evolution 2.html +189 -0
  23. package/src/dashboard/templates/feed 2.html +249 -0
  24. package/src/dashboard/templates/followup_health 2.html +170 -0
  25. package/src/dashboard/templates/graph 2.html +201 -0
  26. package/src/dashboard/templates/guard 2.html +259 -0
  27. package/src/dashboard/templates/inbox 2.html +251 -0
  28. package/src/dashboard/templates/memory 2.html +420 -0
  29. package/src/dashboard/templates/operations 2.html +608 -0
  30. package/src/dashboard/templates/plugins 2.html +185 -0
  31. package/src/dashboard/templates/protocol 2.html +199 -0
  32. package/src/dashboard/templates/rules 2.html +246 -0
  33. package/src/dashboard/templates/sentiment 2.html +247 -0
  34. package/src/dashboard/templates/sessions 2.html +218 -0
  35. package/src/dashboard/templates/skills 2.html +329 -0
  36. package/src/dashboard/templates/somatic 2.html +73 -0
  37. package/src/dashboard/templates/triggers 2.html +133 -0
  38. package/src/dashboard/templates/trust 2.html +360 -0
  39. package/src/db/__init__ 2.py +259 -0
  40. package/src/db/_core 2.py +437 -0
  41. package/src/db/_credentials 2.py +124 -0
  42. package/src/db/_episodic 2.py +762 -0
  43. package/src/db/_evolution 2.py +54 -0
  44. package/src/db/_fts 2.py +406 -0
  45. package/src/db/_goal_profiles 2.py +376 -0
  46. package/src/db/_hot_context 2.py +660 -0
  47. package/src/db/_outcomes 2.py +800 -0
  48. package/src/db/_personal_scripts 2.py +582 -0
  49. package/src/db/_sessions 2.py +330 -0
  50. package/src/db/_tasks 2.py +91 -0
  51. package/src/db/_watchers 2.py +173 -0
  52. package/src/doctor/formatters 2.py +52 -0
  53. package/src/doctor/models 2.py +69 -0
  54. package/src/doctor/planes 2.py +87 -0
  55. package/src/doctor/providers/__init__ 2.py +1 -0
  56. package/src/doctor/providers/deep 2.py +367 -0
  57. package/src/evolution_cycle 2.py +519 -0
  58. package/src/hooks/auto_capture 2.py +208 -0
  59. package/src/hooks/caffeinate-guard 2.sh +8 -0
  60. package/src/hooks/capture-session 2.sh +21 -0
  61. package/src/hooks/capture-tool-logs 2.sh +158 -0
  62. package/src/hooks/daily-briefing-check 2.sh +33 -0
  63. package/src/hooks/heartbeat-enforcement 2.py +90 -0
  64. package/src/hooks/heartbeat-posttool 2.sh +18 -0
  65. package/src/hooks/inbox-hook 2.sh +76 -0
  66. package/src/hooks/post-compact 2.sh +152 -0
  67. package/src/hooks/pre-compact 2.sh +169 -0
  68. package/src/hooks/protocol-guardrail 2.sh +10 -0
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
  70. package/src/hooks/session-stop 2.sh +52 -0
  71. package/src/kg_populate 2.py +292 -0
  72. package/src/maintenance 2.py +53 -0
  73. package/src/memory_backends 2.py +71 -0
  74. package/src/migrate_embeddings 2.py +124 -0
  75. package/src/nexo_sdk 2.py +103 -0
  76. package/src/observability 2.py +199 -0
  77. package/src/plugin_loader 2.py +217 -0
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +450 -0
  80. package/src/plugins/backup 2.py +127 -0
  81. package/src/plugins/claims_tools 2.py +119 -0
  82. package/src/plugins/cognitive_memory 2.py +609 -0
  83. package/src/plugins/core_rules 2.py +252 -0
  84. package/src/plugins/cortex 2.py +1155 -0
  85. package/src/plugins/entities 2.py +67 -0
  86. package/src/plugins/episodic_memory 2.py +560 -0
  87. package/src/plugins/evolution 2.py +167 -0
  88. package/src/plugins/goal_engine 2.py +142 -0
  89. package/src/plugins/guard 2.py +862 -0
  90. package/src/plugins/impact 2.py +29 -0
  91. package/src/plugins/knowledge_graph_tools 2.py +137 -0
  92. package/src/plugins/media_memory_tools 2.py +98 -0
  93. package/src/plugins/memory_export 2.py +196 -0
  94. package/src/plugins/outcomes 2.py +130 -0
  95. package/src/plugins/personal_scripts 2.py +117 -0
  96. package/src/plugins/preferences 2.py +47 -0
  97. package/src/plugins/protocol 2.py +1449 -0
  98. package/src/plugins/simple_api 2.py +106 -0
  99. package/src/plugins/skills 2.py +341 -0
  100. package/src/plugins/state_watchers 2.py +79 -0
  101. package/src/plugins/update 2.py +986 -0
  102. package/src/plugins/user_state_tools 2.py +43 -0
  103. package/src/plugins/workflow 2.py +588 -0
  104. package/src/protocol_settings 2.py +59 -0
  105. package/src/public_contribution 2.py +466 -0
  106. package/src/public_evolution_queue 2.py +241 -0
  107. package/src/requirements 2.txt +14 -0
  108. package/src/retroactive_learnings 2.py +373 -0
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +331 -0
  111. package/src/rules/migrate 2.py +207 -0
  112. package/src/runtime_power 2.py +874 -0
  113. package/src/script_registry 2.py +1559 -0
  114. package/src/scripts/check-context 2.py +272 -0
  115. package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
  116. package/src/scripts/deep-sleep/collect 2.py +928 -0
  117. package/src/scripts/deep-sleep/extract 2.py +330 -0
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
  119. package/src/scripts/deep-sleep/synthesize 2.py +312 -0
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
  121. package/src/scripts/nexo-agent-run 2.py +75 -0
  122. package/src/scripts/nexo-auto-update 2.py +6 -0
  123. package/src/scripts/nexo-backup 2.sh +25 -0
  124. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  125. package/src/scripts/nexo-catchup 2.py +300 -0
  126. package/src/scripts/nexo-cognitive-decay 2.py +257 -0
  127. package/src/scripts/nexo-cortex-cycle 2.py +293 -0
  128. package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
  129. package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
  130. package/src/scripts/nexo-dashboard 2.sh +29 -0
  131. package/src/scripts/nexo-deep-sleep 2.sh +86 -0
  132. package/src/scripts/nexo-evolution-run 2.py +1664 -0
  133. package/src/scripts/nexo-followup-hygiene 2.py +139 -0
  134. package/src/scripts/nexo-hook-record 2.py +42 -0
  135. package/src/scripts/nexo-immune 2.py +936 -0
  136. package/src/scripts/nexo-impact-scorer 2.py +117 -0
  137. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  138. package/src/scripts/nexo-install 2.py +6 -0
  139. package/src/scripts/nexo-learning-housekeep 2.py +401 -0
  140. package/src/scripts/nexo-learning-validator 2.py +266 -0
  141. package/src/scripts/nexo-migrate 2.py +260 -0
  142. package/src/scripts/nexo-outcome-checker 2.py +127 -0
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
  144. package/src/scripts/nexo-pre-commit 2.py +120 -0
  145. package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
  146. package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
  147. package/src/scripts/nexo-reflection 2.py +256 -0
  148. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  149. package/src/scripts/nexo-sleep 2.py +631 -0
  150. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  151. package/src/scripts/nexo-sync-clients 2.py +16 -0
  152. package/src/scripts/nexo-synthesis 2.py +475 -0
  153. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  154. package/src/scripts/nexo-update 2.sh +306 -0
  155. package/src/scripts/nexo-watchdog 2.sh +1207 -0
  156. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
  158. package/src/server 2.py +1296 -0
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
  164. package/src/skills/run-release-final-audit/guide 2.md +16 -0
  165. package/src/skills/run-release-final-audit/script 2.py +259 -0
  166. package/src/skills/run-release-final-audit/skill 2.json +77 -0
  167. package/src/skills/run-runtime-doctor/guide 2.md +12 -0
  168. package/src/skills/run-runtime-doctor/script 2.py +21 -0
  169. package/src/skills/run-runtime-doctor/skill 2.json +25 -0
  170. package/src/skills_runtime 2.py +932 -0
  171. package/src/state_watchers_runtime 2.py +475 -0
  172. package/src/storage_router 2.py +32 -0
  173. package/src/system_catalog 2.py +786 -0
  174. package/src/tools_coordination 2.py +103 -0
  175. package/src/tools_credentials 2.py +68 -0
  176. package/src/tools_drive 2.py +487 -0
  177. package/src/tools_hot_context 2.py +163 -0
  178. package/src/tools_learnings 2.py +612 -0
  179. package/src/tools_menu 2.py +229 -0
  180. package/src/tools_reminders 2.py +88 -0
  181. package/src/tools_reminders_crud 2.py +363 -0
  182. package/src/tools_sessions 2.py +1054 -0
  183. package/src/tools_system_catalog 2.py +19 -0
  184. package/src/tools_task_history 2.py +57 -0
  185. package/src/tools_transcripts 2.py +98 -0
  186. package/src/transcript_utils 2.py +412 -0
  187. package/src/user_context 2.py +46 -0
  188. package/src/user_data_portability 2.py +328 -0
  189. package/src/user_state_model 2.py +170 -0
  190. package/templates/CLAUDE.md 2.template +108 -0
  191. package/templates/CODEX.AGENTS.md 2.template +66 -0
  192. package/templates/launchagents/README 2.md +132 -0
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
  194. package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
  198. package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
  200. package/templates/launchagents/com.nexo.immune 2.plist +41 -0
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
  205. package/templates/nexo_helper 2.py +301 -0
  206. package/templates/openclaw 2.json +13 -0
  207. package/templates/plugin-template 2.py +40 -0
  208. package/templates/script-template 2.py +59 -0
  209. package/templates/script-template 2.sh +13 -0
  210. package/templates/skill-script-template 2.py +48 -0
  211. package/templates/skill-template 2.md +33 -0
@@ -0,0 +1,106 @@
1
+ """Minimal public API wrappers for the core NEXO mental model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+
8
+ import cognitive
9
+
10
+ from plugins.episodic_memory import handle_recall
11
+ from plugins.workflow import handle_workflow_open
12
+
13
+
14
+ def handle_remember(
15
+ content: str,
16
+ title: str = "",
17
+ domain: str = "",
18
+ source_type: str = "note",
19
+ tags: str = "",
20
+ bypass_gate: bool = True,
21
+ ) -> str:
22
+ """Store one durable memory item with a single high-level call."""
23
+ clean_content = (content or "").strip()
24
+ if not clean_content:
25
+ return json.dumps({"ok": False, "error": "content is required"}, ensure_ascii=False, indent=2)
26
+
27
+ clean_title = (title or "").strip()[:120]
28
+ # Content fingerprint for deterministic dedup id — not security-sensitive.
29
+ source_id = hashlib.sha1(
30
+ f"{clean_title}|{clean_content}".encode("utf-8"), usedforsecurity=False
31
+ ).hexdigest()[:12]
32
+ memory_id = cognitive.ingest_to_ltm(
33
+ clean_content,
34
+ source_type=(source_type or "note").strip()[:40],
35
+ source_id=source_id,
36
+ source_title=clean_title or clean_content[:80],
37
+ domain=(domain or "").strip()[:120],
38
+ tags=(tags or "").strip()[:200],
39
+ bypass_gate=bool(bypass_gate),
40
+ )
41
+ return json.dumps(
42
+ {
43
+ "ok": bool(memory_id),
44
+ "memory_id": int(memory_id or 0),
45
+ "source_type": (source_type or "note").strip()[:40],
46
+ "title": clean_title or clean_content[:80],
47
+ "domain": (domain or "").strip()[:120],
48
+ },
49
+ ensure_ascii=False,
50
+ indent=2,
51
+ )
52
+
53
+
54
+ def handle_memory_recall(query: str, days: int = 30) -> str:
55
+ """High-level memory lookup wrapper around nexo_recall."""
56
+ return handle_recall((query or "").strip(), days=max(1, int(days or 30)))
57
+
58
+
59
+ def handle_consolidate(
60
+ max_insights: int = 12,
61
+ threshold: float = 0.9,
62
+ dry_run: bool = False,
63
+ ) -> str:
64
+ """Run the core memory consolidation cycle explicitly."""
65
+ promoted = cognitive.promote_stm_to_ltm()
66
+ quarantine = cognitive.process_quarantine()
67
+ dreamed = cognitive.dream_cycle(max_insights=max(1, int(max_insights or 12)))
68
+ semantic = cognitive.consolidate_semantic(threshold=float(threshold or 0.9), dry_run=bool(dry_run))
69
+ payload = {
70
+ "ok": True,
71
+ "promoted_to_ltm": int(promoted or 0),
72
+ "quarantine": quarantine,
73
+ "dream_cycle": dreamed,
74
+ "semantic_consolidation": semantic,
75
+ "dry_run": bool(dry_run),
76
+ }
77
+ return json.dumps(payload, ensure_ascii=False, indent=2)
78
+
79
+
80
+ def handle_run_workflow(
81
+ sid: str,
82
+ goal: str,
83
+ steps: str = "[]",
84
+ goal_id: str = "",
85
+ shared_state: str = "{}",
86
+ owner: str = "",
87
+ idempotency_key: str = "",
88
+ ) -> str:
89
+ """Open a durable workflow with the public mental-model naming."""
90
+ return handle_workflow_open(
91
+ sid=sid,
92
+ goal=goal,
93
+ steps=steps,
94
+ goal_id=goal_id,
95
+ shared_state=shared_state,
96
+ owner=owner,
97
+ idempotency_key=idempotency_key,
98
+ )
99
+
100
+
101
+ TOOLS = [
102
+ (handle_remember, "nexo_remember", "High-level memory write: store one durable memory item."),
103
+ (handle_memory_recall, "nexo_memory_recall", "High-level memory lookup wrapper around nexo_recall."),
104
+ (handle_consolidate, "nexo_consolidate", "Run NEXO memory consolidation explicitly: promote, process quarantine, dream, consolidate."),
105
+ (handle_run_workflow, "nexo_run_workflow", "High-level durable workflow entry point for the public API surface."),
106
+ ]
@@ -0,0 +1,341 @@
1
+ """Skills plugin — reusable procedures, executable skills, and feedback loops."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from db import (
8
+ create_skill,
9
+ delete_skill,
10
+ get_skill,
11
+ get_skill_stats,
12
+ list_skills,
13
+ match_skills,
14
+ merge_skills,
15
+ record_skill_usage,
16
+ search_skills,
17
+ update_skill,
18
+ )
19
+ from skills_runtime import (
20
+ apply_skill,
21
+ approve_skill_execution,
22
+ compose_skills,
23
+ detect_skill_coactivation_candidates,
24
+ get_featured_skill_summaries,
25
+ list_evolution_candidates,
26
+ materialize_outcome_pattern_skill,
27
+ promote_skill,
28
+ review_skill_outcomes,
29
+ retire_skill,
30
+ sync_skills,
31
+ test_skill,
32
+ )
33
+
34
+
35
+ def handle_skill_create(
36
+ id: str,
37
+ name: str,
38
+ description: str = "",
39
+ level: str = "draft",
40
+ tags: str = "[]",
41
+ trigger_patterns: str = "[]",
42
+ source_sessions: str = "[]",
43
+ linked_learnings: str = "[]",
44
+ file_path: str = "",
45
+ mode: str = "",
46
+ source_kind: str = "personal",
47
+ execution_level: str = "none",
48
+ approval_required: bool = False,
49
+ params_schema: str = "{}",
50
+ command_template: str = "{}",
51
+ executable_entry: str = "",
52
+ ) -> str:
53
+ if not id.startswith("SK-"):
54
+ return "ERROR: Skill ID must start with 'SK-' (e.g., SK-DEPLOY-CHROME-EXT)"
55
+ if get_skill(id):
56
+ return f"ERROR: Skill {id} already exists. Use nexo_skill_update to modify."
57
+
58
+ result = create_skill(
59
+ skill_id=id,
60
+ name=name,
61
+ description=description,
62
+ level=level,
63
+ tags=tags,
64
+ trigger_patterns=trigger_patterns,
65
+ source_sessions=source_sessions,
66
+ linked_learnings=linked_learnings,
67
+ file_path=file_path,
68
+ mode=mode,
69
+ source_kind=source_kind,
70
+ execution_level=execution_level,
71
+ approval_required=approval_required,
72
+ params_schema=params_schema,
73
+ command_template=command_template,
74
+ executable_entry=executable_entry,
75
+ )
76
+ if "error" in result:
77
+ return f"ERROR: {result['error']}"
78
+
79
+ return (
80
+ f"Skill {id} created ({result['level']}, {result.get('mode', 'guide')}, trust={result.get('trust_score', 50)}).\n"
81
+ f" Name: {name}\n"
82
+ f" Source: {result.get('source_kind', source_kind)}\n"
83
+ f" Execution: {result.get('execution_level', execution_level)}"
84
+ )
85
+
86
+
87
+ def handle_skill_match(task: str, level: str = "") -> str:
88
+ matches = match_skills(task, level=level)
89
+ if not matches:
90
+ return f"No skills found for: '{task}'"
91
+
92
+ lines = [f"SKILLS MATCHED ({len(matches)}) for '{task}':"]
93
+ for match in matches:
94
+ match_method = match.pop("_match", "unknown")
95
+ lines.append(
96
+ f" [{match['id']}] {match['name']} ({match['level']}, {match.get('mode', 'guide')}, "
97
+ f"{match.get('source_kind', 'personal')}, trust={match['trust_score']}, used={match['use_count']}x) "
98
+ f"via {match_method}"
99
+ )
100
+ lines.append(f" {match['description'][:140]}")
101
+ return "\n".join(lines)
102
+
103
+
104
+ def handle_skill_get(id: str) -> str:
105
+ skill = get_skill(id)
106
+ if not skill:
107
+ return f"ERROR: Skill {id} not found."
108
+
109
+ lines = [
110
+ f"SKILL: {skill['id']}",
111
+ f" Name: {skill['name']}",
112
+ f" Description: {skill['description']}",
113
+ f" Level: {skill['level']}",
114
+ f" Mode: {skill.get('mode', 'guide')}",
115
+ f" Source: {skill.get('source_kind', 'personal')}",
116
+ f" Trust: {skill['trust_score']}",
117
+ f" Execution level: {skill.get('execution_level', 'none')}",
118
+ f" Approval required: {bool(skill.get('approval_required', 0))}",
119
+ f" Approved at: {skill.get('approved_at') or 'no'}",
120
+ f" Definition: {skill.get('definition_path') or '(none)'}",
121
+ f" File: {skill.get('file_path') or '(none)'}",
122
+ f" Params schema: {skill.get('params_schema', '{}')}",
123
+ f" Triggers: {skill['trigger_patterns']}",
124
+ f" Stats: {skill['use_count']} uses, {skill['success_count']} success, {skill['fail_count']} fail",
125
+ ]
126
+ return "\n".join(lines)
127
+
128
+
129
+ def handle_skill_result(id: str, success: bool = True, context: str = "", notes: str = "") -> str:
130
+ result = record_skill_usage(skill_id=id, success=success, context=context, notes=notes)
131
+ if "error" in result:
132
+ return f"ERROR: {result['error']}"
133
+
134
+ promotion = result.get("_promotion")
135
+ msg = f"Skill {id} usage recorded: {'SUCCESS' if success else 'FAILURE'} (trust={result['trust_score']})"
136
+ if promotion:
137
+ msg += f"\n ⚡ PROMOTION: {promotion}"
138
+ return msg
139
+
140
+
141
+ def handle_skill_list(level: str = "", tag: str = "", source_kind: str = "") -> str:
142
+ skills = list_skills(level=level, tag=tag, source_kind=source_kind)
143
+ if not skills:
144
+ return "No skills found."
145
+
146
+ lines = [f"SKILLS ({len(skills)}):"]
147
+ for skill in skills:
148
+ lines.append(
149
+ f" [{skill['id']}] {skill['name']} ({skill['level']}, {skill.get('mode', 'guide')}, "
150
+ f"{skill.get('source_kind', 'personal')}, trust={skill['trust_score']}, used={skill['use_count']}x)"
151
+ )
152
+ return "\n".join(lines)
153
+
154
+
155
+ def handle_skill_merge(id1: str, id2: str, keep_id: str = "") -> str:
156
+ result = merge_skills(id1, id2, keep_id=keep_id)
157
+ if "error" in result:
158
+ return f"ERROR: {result['error']}"
159
+ return (
160
+ f"Skills merged. Kept {result['id']}, deleted {result['_merged_from']}.\n"
161
+ f" Trust: {result['trust_score']}, Uses: {result['use_count']}"
162
+ )
163
+
164
+
165
+ def handle_skill_stats() -> str:
166
+ stats = get_skill_stats()
167
+ return (
168
+ "SKILL STATS:\n"
169
+ f" Total: {stats['total']}\n"
170
+ f" By level: {', '.join(f'{k}={v}' for k, v in sorted(stats['by_level'].items()))}\n"
171
+ f" Avg trust: {stats['avg_trust']}\n"
172
+ f" Total uses: {stats['total_uses']} (success rate: {stats['success_rate']}%)\n"
173
+ f" Uses last 7d: {stats['uses_last_7d']}\n"
174
+ f" Reuse rate: {stats['skill_reuse_rate']}\n"
175
+ f" Outcome-backed skills: {stats['outcome_backed_skills']} (avg success: {stats['outcome_backed_success_rate']}%)\n"
176
+ f" Promoted from evidence: {stats['promoted_from_evidence_count']}\n"
177
+ f" Retired for poor outcomes: {stats['retired_for_poor_outcomes_count']}"
178
+ )
179
+
180
+
181
+ def handle_skill_apply(id: str, params: str = "{}", mode: str = "auto", dry_run: bool = False, context: str = "") -> str:
182
+ return json.dumps(apply_skill(id, params=params, mode=mode, dry_run=dry_run, context=context), ensure_ascii=False)
183
+
184
+
185
+ def handle_skill_test(id: str, params: str = "{}", mode: str = "auto", context: str = "") -> str:
186
+ return json.dumps(test_skill(id, params=params, mode=mode, context=context), ensure_ascii=False)
187
+
188
+
189
+ def handle_skill_approve(id: str, execution_level: str = "", approved_by: str = "") -> str:
190
+ result = approve_skill_execution(id, execution_level=execution_level, approved_by=approved_by)
191
+ if "error" in result:
192
+ return f"ERROR: {result['error']}"
193
+ return (
194
+ f"Skill {id} approved.\n"
195
+ f" Execution level: {result.get('execution_level', 'none')}\n"
196
+ f" Approved at: {result.get('approved_at', '')}\n"
197
+ f" Approved by: {result.get('approved_by', '')}"
198
+ )
199
+
200
+
201
+ def handle_skill_sync() -> str:
202
+ result = sync_skills()
203
+ return json.dumps(result, ensure_ascii=False)
204
+
205
+
206
+ def handle_skill_featured(limit: int = 5) -> str:
207
+ return json.dumps(get_featured_skill_summaries(limit=limit), ensure_ascii=False)
208
+
209
+
210
+ def handle_skill_evolution_candidates() -> str:
211
+ return json.dumps(list_evolution_candidates(), ensure_ascii=False)
212
+
213
+
214
+ def handle_skill_compose_candidates(
215
+ min_co_occurrence: int = 3,
216
+ min_success_rate: float = 0.6,
217
+ limit: int = 20,
218
+ ) -> str:
219
+ """List Voyager-style composition candidates from skill_usage co-activation.
220
+
221
+ Closes Fase 5 item 5. Detects pairs of skills that fire together
222
+ in the same session repeatedly and could become a single composite.
223
+ Returns the candidates sorted by co-occurrence count, with a
224
+ suggested deterministic skill_id (SK-COMPOSE-<a>+<b>) that
225
+ nexo_skill_compose can take as input.
226
+
227
+ Args:
228
+ min_co_occurrence: minimum sessions where both skills fired (default 3).
229
+ min_success_rate: minimum joint success rate (default 0.6).
230
+ limit: max candidates to return (default 20).
231
+ """
232
+ return json.dumps(
233
+ detect_skill_coactivation_candidates(
234
+ min_co_occurrence=int(min_co_occurrence),
235
+ min_success_rate=float(min_success_rate),
236
+ limit=int(limit),
237
+ ),
238
+ ensure_ascii=False,
239
+ indent=2,
240
+ )
241
+
242
+
243
+ def handle_skill_seed_from_outcome_pattern(pattern_key: str) -> str:
244
+ return json.dumps(materialize_outcome_pattern_skill(pattern_key), ensure_ascii=False)
245
+
246
+
247
+ def handle_skill_outcome_review(id: str, auto_apply: bool = False) -> str:
248
+ return json.dumps(review_skill_outcomes(id, auto_apply=auto_apply), ensure_ascii=False)
249
+
250
+
251
+ def handle_skill_promote(id: str, target_level: str = "published", reason: str = "") -> str:
252
+ return json.dumps(promote_skill(id, target_level=target_level, reason=reason), ensure_ascii=False)
253
+
254
+
255
+ def handle_skill_retire(id: str, replacement_id: str = "", reason: str = "") -> str:
256
+ return json.dumps(retire_skill(id, replacement_id=replacement_id, reason=reason), ensure_ascii=False)
257
+
258
+
259
+ def handle_skill_compose(
260
+ new_id: str,
261
+ name: str,
262
+ component_ids: str = "[]",
263
+ description: str = "",
264
+ level: str = "draft",
265
+ mode: str = "guide",
266
+ tags: str = "[]",
267
+ trigger_patterns: str = "[]",
268
+ ) -> str:
269
+ try:
270
+ component_list = json.loads(component_ids) if str(component_ids or "").strip().startswith("[") else [
271
+ item.strip() for item in str(component_ids or "").split(",") if item.strip()
272
+ ]
273
+ except json.JSONDecodeError:
274
+ component_list = []
275
+ try:
276
+ tags_list = json.loads(tags) if str(tags or "").strip().startswith("[") else [
277
+ item.strip() for item in str(tags or "").split(",") if item.strip()
278
+ ]
279
+ except json.JSONDecodeError:
280
+ tags_list = []
281
+ try:
282
+ trigger_list = json.loads(trigger_patterns) if str(trigger_patterns or "").strip().startswith("[") else [
283
+ item.strip() for item in str(trigger_patterns or "").split(",") if item.strip()
284
+ ]
285
+ except json.JSONDecodeError:
286
+ trigger_list = []
287
+ return json.dumps(
288
+ compose_skills(
289
+ new_skill_id=new_id,
290
+ name=name,
291
+ component_ids=component_list,
292
+ description=description,
293
+ level=level,
294
+ mode=mode,
295
+ tags=tags_list,
296
+ trigger_patterns=trigger_list,
297
+ ),
298
+ ensure_ascii=False,
299
+ )
300
+
301
+
302
+ TOOLS = [
303
+ (handle_skill_create, "nexo_skill_create",
304
+ "Create a new skill with guide/execute/hybrid metadata, triggers, params schema, and execution level."),
305
+ (handle_skill_match, "nexo_skill_match",
306
+ "Find skills matching a task description. Call before multi-step tasks."),
307
+ (handle_skill_get, "nexo_skill_get",
308
+ "Get a skill's full details, including execution metadata and approval state."),
309
+ (handle_skill_result, "nexo_skill_result",
310
+ "Record the result of using a skill. Updates trust and promotions."),
311
+ (handle_skill_list, "nexo_skill_list",
312
+ "List skills, optionally filtered by level, tag, or source kind."),
313
+ (handle_skill_merge, "nexo_skill_merge",
314
+ "Merge two similar skills into one."),
315
+ (handle_skill_stats, "nexo_skill_stats",
316
+ "Show aggregate skill statistics."),
317
+ (handle_skill_apply, "nexo_skill_apply",
318
+ "Apply a skill in guide, execute, or hybrid mode. Execution goes through the stable nexo scripts runtime."),
319
+ (handle_skill_test, "nexo_skill_test",
320
+ "Test a skill through the canonical runtime in dry-run mode before wider use."),
321
+ (handle_skill_approve, "nexo_skill_approve",
322
+ "Approve a local/remote executable skill so it can run."),
323
+ (handle_skill_sync, "nexo_skill_sync",
324
+ "Sync filesystem skill definitions from personal/core/community directories into SQLite."),
325
+ (handle_skill_featured, "nexo_skill_featured",
326
+ "Return featured published/stable skills for startup discovery."),
327
+ (handle_skill_evolution_candidates, "nexo_skill_evolution_candidates",
328
+ "Return candidates for skill improvement or text-to-script evolution."),
329
+ (handle_skill_seed_from_outcome_pattern, "nexo_skill_seed_from_outcome_pattern",
330
+ "Materialize a draft skill candidate from a repeated successful outcome pattern with linked evidence."),
331
+ (handle_skill_outcome_review, "nexo_skill_outcome_review",
332
+ "Review whether a skill should be promoted, deprioritized, or retired based on sustained outcome evidence."),
333
+ (handle_skill_promote, "nexo_skill_promote",
334
+ "Promote a skill to a stronger published/stable lifecycle stage."),
335
+ (handle_skill_retire, "nexo_skill_retire",
336
+ "Retire a skill cleanly so it leaves the active lifecycle."),
337
+ (handle_skill_compose, "nexo_skill_compose",
338
+ "Compose multiple existing skills into one higher-level reusable skill."),
339
+ (handle_skill_compose_candidates, "nexo_skill_compose_candidates",
340
+ "Voyager-style detection: list skill pairs that fired together in 3+ sessions with 60%+ joint success and could become a single composite (closes Fase 5 item 5)."),
341
+ ]
@@ -0,0 +1,79 @@
1
+ """State watchers plugin — persistent drift/health/expiry watchers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from db import create_state_watcher, list_state_watchers, update_state_watcher
8
+ from state_watchers_runtime import run_state_watchers
9
+
10
+
11
+ def handle_state_watcher_create(
12
+ watcher_type: str,
13
+ title: str,
14
+ target: str = "",
15
+ severity: str = "warn",
16
+ status: str = "active",
17
+ config: str = "{}",
18
+ ) -> str:
19
+ """Create a persistent state watcher for drift, health, or expiry."""
20
+ try:
21
+ watcher = create_state_watcher(
22
+ watcher_type,
23
+ title,
24
+ target=target,
25
+ severity=severity,
26
+ status=status,
27
+ config=json.loads(config) if str(config).strip() else {},
28
+ )
29
+ except (ValueError, json.JSONDecodeError) as exc:
30
+ return json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False, indent=2)
31
+ return json.dumps({"ok": True, "watcher": watcher}, ensure_ascii=False, indent=2)
32
+
33
+
34
+ def handle_state_watcher_update(
35
+ watcher_id: str,
36
+ title: str = "",
37
+ target: str = "",
38
+ severity: str = "",
39
+ status: str = "",
40
+ config: str = "",
41
+ ) -> str:
42
+ """Update an existing state watcher."""
43
+ payload = None
44
+ if str(config).strip():
45
+ try:
46
+ payload = json.loads(config)
47
+ except json.JSONDecodeError as exc:
48
+ return json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False, indent=2)
49
+ watcher = update_state_watcher(
50
+ watcher_id,
51
+ title=(title or None),
52
+ target=(target or None),
53
+ severity=(severity or None),
54
+ status=(status or None),
55
+ config=payload,
56
+ )
57
+ if not watcher:
58
+ return json.dumps({"ok": False, "error": f"Unknown watcher_id: {watcher_id}"}, ensure_ascii=False, indent=2)
59
+ return json.dumps({"ok": True, "watcher": watcher}, ensure_ascii=False, indent=2)
60
+
61
+
62
+ def handle_state_watcher_list(status: str = "", watcher_type: str = "", limit: int = 50) -> str:
63
+ """List configured state watchers."""
64
+ watchers = list_state_watchers(status=status, watcher_type=watcher_type, limit=max(1, int(limit or 50)))
65
+ return json.dumps({"ok": True, "count": len(watchers), "watchers": watchers}, ensure_ascii=False, indent=2)
66
+
67
+
68
+ def handle_state_watcher_run(status: str = "active", persist: bool = True) -> str:
69
+ """Run active state watchers and return their current health."""
70
+ summary = run_state_watchers(status=status or "active", persist=bool(persist))
71
+ return json.dumps({"ok": True, **summary}, ensure_ascii=False, indent=2)
72
+
73
+
74
+ TOOLS = [
75
+ (handle_state_watcher_create, "nexo_state_watcher_create", "Create a persistent state watcher for repo drift, cron drift, API health, environment drift, or expiry."),
76
+ (handle_state_watcher_update, "nexo_state_watcher_update", "Update an existing persistent state watcher."),
77
+ (handle_state_watcher_list, "nexo_state_watcher_list", "List persistent state watchers."),
78
+ (handle_state_watcher_run, "nexo_state_watcher_run", "Run persistent state watchers and return their current health summary."),
79
+ ]