nexo-brain 3.1.8 → 3.2.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 +21 -0
- package/package.json +1 -1
- package/src/auto_update.py +27 -30
- package/src/scripts/deep-sleep/collect.py +6 -200
- package/src/server.py +41 -0
- package/src/system_catalog.py +419 -0
- package/src/tools_system_catalog.py +19 -0
- package/src/tools_transcripts.py +98 -0
- package/src/transcript_utils.py +412 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
Start here:
|
|
22
22
|
- [5-minute quickstart](docs/quickstart-5-minutes.md)
|
|
23
23
|
- [Workflow quickstart](docs/workflows-quickstart.md)
|
|
24
|
+
- [Recent memory fallbacks + live system catalog](docs/recent-memory-fallbacks-and-system-catalog.md)
|
|
24
25
|
- [Supported client guides](docs/integrations/cursor.md)
|
|
25
26
|
- [Docker setup](docs/docker-setup.md)
|
|
26
27
|
- [Architecture visuals](docs/architecture-visuals.md)
|
|
@@ -79,6 +80,13 @@ Versions `3.0.0` and `3.0.1` close the next execution gap:
|
|
|
79
80
|
- `cost_per_solved_task`
|
|
80
81
|
- SDK/API/quickstart surface
|
|
81
82
|
|
|
83
|
+
Versions `3.1.7` through `3.2.0` close the recent-memory gap:
|
|
84
|
+
|
|
85
|
+
- recent operational continuity is now first-class through `hot context` and `recent events`
|
|
86
|
+
- the runtime can build a reusable pre-action bundle instead of reconstructing the last few hours from diaries and durable recall only
|
|
87
|
+
- when even that misses, NEXO now exposes raw transcript fallback tools for Claude Code and Codex session stores
|
|
88
|
+
- NEXO can now inspect itself through a live system catalog derived from canonical sources instead of relying only on stale docs or operator memory
|
|
89
|
+
|
|
82
90
|
### Client Capability Matrix
|
|
83
91
|
|
|
84
92
|
| Capability | Claude Code | Codex | Claude Desktop |
|
|
@@ -340,6 +348,15 @@ NEXO Brain provides **150+ MCP tools** across 23 categories. These features impl
|
|
|
340
348
|
| **Auto-Merge Duplicates** | Batch cosine deduplication during the 03:00 sleep cycle. Respects sibling discrimination — similar memories about different contexts are kept separate. |
|
|
341
349
|
| **Memory Dreaming** | Discovers hidden connections between recent memories during the 03:00 sleep cycle and now feeds a 60-day long-horizon Deep Sleep blend, so older patterns can reappear when they become relevant again. |
|
|
342
350
|
|
|
351
|
+
### Operational Continuity
|
|
352
|
+
|
|
353
|
+
| Feature | What It Does |
|
|
354
|
+
|---------|-------------|
|
|
355
|
+
| **Hot Context 24h** | Keeps active topics, blockers, and waiting states fresh across sessions, clients, cron ticks, and channel changes. This is the shared recent-memory substrate for operational continuity. |
|
|
356
|
+
| **Pre-Action Context Bundle** | Loads recent contexts, recent events, related reminders, and related followups before acting, so continuity is explicit instead of prompt-only. |
|
|
357
|
+
| **Transcript Fallback** | When recent-memory capture is thin or missing, NEXO can now search and read recent Claude Code / Codex transcripts directly through MCP instead of pretending the conversation is lost. |
|
|
358
|
+
| **Live System Catalog** | NEXO can now inspect its own current surface — core tools, plugin tools, skills, scripts, crons, projects, and artifacts — through a live catalog derived from canonical sources at read time. |
|
|
359
|
+
|
|
343
360
|
### Retrieval
|
|
344
361
|
|
|
345
362
|
| Feature | What It Does |
|
|
@@ -724,10 +741,14 @@ Public entry points for the mental model now stay intentionally small:
|
|
|
724
741
|
- `nexo_memory_recall`
|
|
725
742
|
- `nexo_consolidate`
|
|
726
743
|
- `nexo_run_workflow`
|
|
744
|
+
- `nexo_pre_action_context`
|
|
745
|
+
- `nexo_transcript_search`
|
|
746
|
+
- `nexo_system_catalog`
|
|
727
747
|
|
|
728
748
|
If you want the shell or Python wrappers instead of raw MCP tools:
|
|
729
749
|
- [docs/quickstart-5-minutes.md](docs/quickstart-5-minutes.md)
|
|
730
750
|
- [docs/memory-classes.md](docs/memory-classes.md)
|
|
751
|
+
- [docs/recent-memory-fallbacks-and-system-catalog.md](docs/recent-memory-fallbacks-and-system-catalog.md)
|
|
731
752
|
- [docs/sdk-python.md](docs/sdk-python.md)
|
|
732
753
|
- [docs/reference-verticals.md](docs/reference-verticals.md)
|
|
733
754
|
- [compare/README.md](compare/README.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/auto_update.py
CHANGED
|
@@ -1186,27 +1186,38 @@ def _source_repo_status(repo_dir: Path) -> dict:
|
|
|
1186
1186
|
}
|
|
1187
1187
|
|
|
1188
1188
|
|
|
1189
|
+
def _discover_runtime_root_python_modules(base_dir: Path) -> list[str]:
|
|
1190
|
+
"""Return every top-level runtime `.py` module in the source/runtime root."""
|
|
1191
|
+
if not base_dir.is_dir():
|
|
1192
|
+
return []
|
|
1193
|
+
modules: list[str] = []
|
|
1194
|
+
for item in sorted(base_dir.iterdir(), key=lambda path: path.name):
|
|
1195
|
+
if not item.is_file() or item.suffix != ".py":
|
|
1196
|
+
continue
|
|
1197
|
+
if item.name.startswith(".") or item.name == "__init__.py":
|
|
1198
|
+
continue
|
|
1199
|
+
modules.append(item.name)
|
|
1200
|
+
return modules
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def _runtime_flat_files(base_dir: Path) -> list[str]:
|
|
1204
|
+
ordered: list[str] = []
|
|
1205
|
+
seen: set[str] = set()
|
|
1206
|
+
for name in _discover_runtime_root_python_modules(base_dir) + ["requirements.txt", "package.json", "version.json"]:
|
|
1207
|
+
if name in seen:
|
|
1208
|
+
continue
|
|
1209
|
+
seen.add(name)
|
|
1210
|
+
ordered.append(name)
|
|
1211
|
+
return ordered
|
|
1212
|
+
|
|
1213
|
+
|
|
1189
1214
|
def _backup_runtime_tree(dest: Path = NEXO_HOME) -> str:
|
|
1190
1215
|
timestamp = time.strftime("%Y-%m-%d-%H%M%S")
|
|
1191
1216
|
backup_dir = NEXO_HOME / "backups" / f"runtime-tree-{timestamp}"
|
|
1192
1217
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
1193
1218
|
|
|
1194
1219
|
code_dirs = ["hooks", "plugins", "db", "cognitive", "dashboard", "rules", "crons", "scripts", "doctor", "skills-core"]
|
|
1195
|
-
flat_files =
|
|
1196
|
-
"server.py", "plugin_loader.py", "knowledge_graph.py", "kg_populate.py",
|
|
1197
|
-
"maintenance.py", "storage_router.py", "claim_graph.py", "hnsw_index.py",
|
|
1198
|
-
"evolution_cycle.py", "migrate_embeddings.py", "auto_close_sessions.py",
|
|
1199
|
-
"client_sync.py",
|
|
1200
|
-
"client_preferences.py", "agent_runner.py", "bootstrap_docs.py",
|
|
1201
|
-
"hook_guardrails.py", "protocol_settings.py", "public_evolution_queue.py",
|
|
1202
|
-
"auto_update.py", "tools_sessions.py", "tools_coordination.py",
|
|
1203
|
-
"tools_hot_context.py",
|
|
1204
|
-
"tools_reminders.py", "tools_reminders_crud.py", "tools_learnings.py",
|
|
1205
|
-
"tools_credentials.py", "tools_task_history.py", "tools_menu.py",
|
|
1206
|
-
"cli.py", "script_registry.py", "skills_runtime.py", "user_context.py",
|
|
1207
|
-
"public_contribution.py",
|
|
1208
|
-
"cron_recovery.py", "runtime_power.py", "requirements.txt", "package.json", "version.json",
|
|
1209
|
-
]
|
|
1220
|
+
flat_files = _runtime_flat_files(dest)
|
|
1210
1221
|
for name in code_dirs:
|
|
1211
1222
|
src = dest / name
|
|
1212
1223
|
if src.is_dir():
|
|
@@ -1244,21 +1255,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
|
|
|
1244
1255
|
import shutil
|
|
1245
1256
|
|
|
1246
1257
|
packages = ["db", "cognitive", "doctor", "dashboard", "rules", "crons", "hooks"]
|
|
1247
|
-
flat_files =
|
|
1248
|
-
"server.py", "plugin_loader.py", "knowledge_graph.py", "kg_populate.py",
|
|
1249
|
-
"maintenance.py", "storage_router.py", "claim_graph.py", "hnsw_index.py",
|
|
1250
|
-
"evolution_cycle.py", "migrate_embeddings.py", "auto_close_sessions.py",
|
|
1251
|
-
"client_sync.py",
|
|
1252
|
-
"client_preferences.py", "agent_runner.py", "bootstrap_docs.py",
|
|
1253
|
-
"hook_guardrails.py", "protocol_settings.py", "public_evolution_queue.py",
|
|
1254
|
-
"auto_update.py", "tools_sessions.py", "tools_coordination.py",
|
|
1255
|
-
"tools_hot_context.py",
|
|
1256
|
-
"tools_reminders.py", "tools_reminders_crud.py", "tools_learnings.py",
|
|
1257
|
-
"tools_credentials.py", "tools_task_history.py", "tools_menu.py",
|
|
1258
|
-
"cli.py", "script_registry.py", "skills_runtime.py", "user_context.py",
|
|
1259
|
-
"public_contribution.py",
|
|
1260
|
-
"cron_recovery.py", "runtime_power.py", "requirements.txt",
|
|
1261
|
-
]
|
|
1258
|
+
flat_files = _runtime_flat_files(src_dir)
|
|
1262
1259
|
copied_packages = 0
|
|
1263
1260
|
copied_files = 0
|
|
1264
1261
|
|
|
@@ -19,6 +19,7 @@ import sys
|
|
|
19
19
|
from collections import Counter
|
|
20
20
|
from datetime import datetime, timedelta
|
|
21
21
|
from pathlib import Path
|
|
22
|
+
import transcript_utils as _transcripts
|
|
22
23
|
|
|
23
24
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
24
25
|
NEXO_CODE = Path(os.environ.get("NEXO_CODE", ""))
|
|
@@ -64,196 +65,22 @@ def _session_identifier(client: str, session_file: str) -> str:
|
|
|
64
65
|
|
|
65
66
|
def find_claude_session_files() -> list[Path]:
|
|
66
67
|
"""Find Claude Code session JSONL files under ~/.claude/projects."""
|
|
67
|
-
|
|
68
|
-
if not claude_dir.exists():
|
|
69
|
-
return []
|
|
70
|
-
return sorted(claude_dir.rglob("*.jsonl"))
|
|
68
|
+
return _transcripts.find_claude_session_files()
|
|
71
69
|
|
|
72
70
|
|
|
73
71
|
def find_codex_session_files() -> list[Path]:
|
|
74
72
|
"""Find Codex session JSONL files under ~/.codex/sessions and archived_sessions."""
|
|
75
|
-
|
|
76
|
-
Path.home() / ".codex" / "sessions",
|
|
77
|
-
Path.home() / ".codex" / "archived_sessions",
|
|
78
|
-
]
|
|
79
|
-
files: list[Path] = []
|
|
80
|
-
seen: set[str] = set()
|
|
81
|
-
for root in roots:
|
|
82
|
-
if not root.exists():
|
|
83
|
-
continue
|
|
84
|
-
for jsonl in sorted(root.rglob("*.jsonl")):
|
|
85
|
-
key = jsonl.name
|
|
86
|
-
if key in seen:
|
|
87
|
-
continue
|
|
88
|
-
seen.add(key)
|
|
89
|
-
files.append(jsonl)
|
|
90
|
-
return files
|
|
73
|
+
return _transcripts.find_codex_session_files()
|
|
91
74
|
|
|
92
75
|
|
|
93
76
|
def extract_claude_session(jsonl_path: Path) -> dict | None:
|
|
94
77
|
"""Extract clean transcript from a Claude Code JSONL session."""
|
|
95
|
-
|
|
96
|
-
tool_uses = []
|
|
97
|
-
user_msg_count = 0
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
with open(jsonl_path, "r") as f:
|
|
101
|
-
for line_no, line in enumerate(f, 1):
|
|
102
|
-
line = line.strip()
|
|
103
|
-
if not line:
|
|
104
|
-
continue
|
|
105
|
-
try:
|
|
106
|
-
d = json.loads(line)
|
|
107
|
-
except json.JSONDecodeError:
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
msg_type = d.get("type")
|
|
111
|
-
|
|
112
|
-
# User messages
|
|
113
|
-
if msg_type == "user":
|
|
114
|
-
content = d.get("message", {}).get("content", "")
|
|
115
|
-
if isinstance(content, str) and content.strip():
|
|
116
|
-
if content.startswith("<system-reminder>"):
|
|
117
|
-
continue
|
|
118
|
-
messages.append({
|
|
119
|
-
"role": "user",
|
|
120
|
-
"index": line_no,
|
|
121
|
-
"text": _redact_sensitive(content[:5000]),
|
|
122
|
-
"uuid": d.get("uuid", "")
|
|
123
|
-
})
|
|
124
|
-
user_msg_count += 1
|
|
125
|
-
|
|
126
|
-
# Assistant messages
|
|
127
|
-
elif msg_type in ("message", "assistant"):
|
|
128
|
-
msg = d.get("message", {})
|
|
129
|
-
content_blocks = msg.get("content", [])
|
|
130
|
-
text_parts = []
|
|
131
|
-
for block in content_blocks:
|
|
132
|
-
if isinstance(block, dict):
|
|
133
|
-
if block.get("type") == "text":
|
|
134
|
-
text_parts.append(block.get("text", ""))
|
|
135
|
-
elif block.get("type") == "tool_use":
|
|
136
|
-
tool_input = block.get("input", {})
|
|
137
|
-
raw_file = (
|
|
138
|
-
tool_input.get("file_path", "")
|
|
139
|
-
or str(tool_input.get("command", ""))[:100]
|
|
140
|
-
) if isinstance(tool_input, dict) else ""
|
|
141
|
-
tool_uses.append({
|
|
142
|
-
"tool": block.get("name", ""),
|
|
143
|
-
"input_keys": list(tool_input.keys()) if isinstance(tool_input, dict) else [],
|
|
144
|
-
"file": _redact_sensitive(raw_file)
|
|
145
|
-
})
|
|
146
|
-
if text_parts:
|
|
147
|
-
combined = "\n".join(text_parts)[:5000]
|
|
148
|
-
combined = _redact_sensitive(combined)
|
|
149
|
-
messages.append({
|
|
150
|
-
"role": "assistant",
|
|
151
|
-
"index": line_no,
|
|
152
|
-
"text": combined
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
except Exception as e:
|
|
156
|
-
print(f" [collect] Error reading {jsonl_path}: {e}", file=sys.stderr)
|
|
157
|
-
return None
|
|
158
|
-
|
|
159
|
-
if user_msg_count < MIN_USER_MESSAGES:
|
|
160
|
-
return None
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
"client": "claude_code",
|
|
164
|
-
"session_file": _session_identifier("claude_code", jsonl_path.name),
|
|
165
|
-
"display_name": jsonl_path.name,
|
|
166
|
-
"session_path": str(jsonl_path),
|
|
167
|
-
"message_count": len(messages),
|
|
168
|
-
"user_message_count": user_msg_count,
|
|
169
|
-
"tool_use_count": len(tool_uses),
|
|
170
|
-
"messages": messages,
|
|
171
|
-
"tool_uses": tool_uses,
|
|
172
|
-
"source": "claude_projects",
|
|
173
|
-
}
|
|
78
|
+
return _transcripts.extract_claude_session(jsonl_path)
|
|
174
79
|
|
|
175
80
|
|
|
176
81
|
def extract_codex_session(jsonl_path: Path) -> dict | None:
|
|
177
82
|
"""Extract clean transcript from a Codex JSONL session."""
|
|
178
|
-
|
|
179
|
-
tool_uses = []
|
|
180
|
-
user_msg_count = 0
|
|
181
|
-
session_meta: dict = {}
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
with open(jsonl_path, "r") as f:
|
|
185
|
-
for line_no, line in enumerate(f, 1):
|
|
186
|
-
line = line.strip()
|
|
187
|
-
if not line:
|
|
188
|
-
continue
|
|
189
|
-
try:
|
|
190
|
-
d = json.loads(line)
|
|
191
|
-
except json.JSONDecodeError:
|
|
192
|
-
continue
|
|
193
|
-
|
|
194
|
-
item_type = d.get("type")
|
|
195
|
-
payload = d.get("payload", {})
|
|
196
|
-
|
|
197
|
-
if item_type == "session_meta" and isinstance(payload, dict):
|
|
198
|
-
session_meta = payload
|
|
199
|
-
continue
|
|
200
|
-
|
|
201
|
-
if item_type == "event_msg" and isinstance(payload, dict) and payload.get("type") == "user_message":
|
|
202
|
-
content = str(payload.get("message", "") or "").strip()
|
|
203
|
-
if not content or content.startswith("<environment_context>"):
|
|
204
|
-
continue
|
|
205
|
-
messages.append({
|
|
206
|
-
"role": "user",
|
|
207
|
-
"index": line_no,
|
|
208
|
-
"text": _redact_sensitive(content[:5000]),
|
|
209
|
-
})
|
|
210
|
-
user_msg_count += 1
|
|
211
|
-
continue
|
|
212
|
-
|
|
213
|
-
if item_type == "response_item" and isinstance(payload, dict):
|
|
214
|
-
response_type = payload.get("type")
|
|
215
|
-
role = payload.get("role")
|
|
216
|
-
if response_type == "message" and role == "assistant":
|
|
217
|
-
text_parts = []
|
|
218
|
-
for block in payload.get("content", []) or []:
|
|
219
|
-
if isinstance(block, dict) and block.get("type") == "output_text":
|
|
220
|
-
text_parts.append(str(block.get("text", "")))
|
|
221
|
-
combined = "\n".join(part for part in text_parts if part).strip()
|
|
222
|
-
if combined:
|
|
223
|
-
messages.append({
|
|
224
|
-
"role": "assistant",
|
|
225
|
-
"index": line_no,
|
|
226
|
-
"text": _redact_sensitive(combined[:5000]),
|
|
227
|
-
})
|
|
228
|
-
elif response_type == "function_call":
|
|
229
|
-
tool_uses.append({
|
|
230
|
-
"tool": payload.get("name", ""),
|
|
231
|
-
"input_keys": [],
|
|
232
|
-
"file": _redact_sensitive(str(payload.get("arguments", ""))[:100]),
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
except Exception as e:
|
|
236
|
-
print(f" [collect] Error reading {jsonl_path}: {e}", file=sys.stderr)
|
|
237
|
-
return None
|
|
238
|
-
|
|
239
|
-
if user_msg_count < MIN_USER_MESSAGES:
|
|
240
|
-
return None
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
"client": "codex",
|
|
244
|
-
"session_file": _session_identifier("codex", jsonl_path.name),
|
|
245
|
-
"display_name": jsonl_path.name,
|
|
246
|
-
"session_path": str(jsonl_path),
|
|
247
|
-
"message_count": len(messages),
|
|
248
|
-
"user_message_count": user_msg_count,
|
|
249
|
-
"tool_use_count": len(tool_uses),
|
|
250
|
-
"messages": messages,
|
|
251
|
-
"tool_uses": tool_uses,
|
|
252
|
-
"source": session_meta.get("source", "codex"),
|
|
253
|
-
"cwd": session_meta.get("cwd", ""),
|
|
254
|
-
"originator": session_meta.get("originator", ""),
|
|
255
|
-
"session_uid": session_meta.get("id", ""),
|
|
256
|
-
}
|
|
83
|
+
return _transcripts.extract_codex_session(jsonl_path)
|
|
257
84
|
|
|
258
85
|
|
|
259
86
|
def collect_transcripts_since(since_iso: str, until_iso: str = "") -> list[dict]:
|
|
@@ -262,28 +89,7 @@ def collect_transcripts_since(since_iso: str, until_iso: str = "") -> list[dict]
|
|
|
262
89
|
Uses a watermark approach: deep sleep tracks the last processed timestamp
|
|
263
90
|
so nothing is missed regardless of when sessions happen (day, night, etc.).
|
|
264
91
|
"""
|
|
265
|
-
|
|
266
|
-
until_dt = datetime.fromisoformat(until_iso) if until_iso else datetime.now()
|
|
267
|
-
|
|
268
|
-
sessions = []
|
|
269
|
-
transcript_files: list[tuple[str, Path]] = [
|
|
270
|
-
("claude_code", path) for path in find_claude_session_files()
|
|
271
|
-
] + [
|
|
272
|
-
("codex", path) for path in find_codex_session_files()
|
|
273
|
-
]
|
|
274
|
-
for client, session_file in transcript_files:
|
|
275
|
-
try:
|
|
276
|
-
mtime = datetime.fromtimestamp(session_file.stat().st_mtime)
|
|
277
|
-
except OSError:
|
|
278
|
-
continue
|
|
279
|
-
if not (since_dt < mtime <= until_dt):
|
|
280
|
-
continue
|
|
281
|
-
session = extract_codex_session(session_file) if client == "codex" else extract_claude_session(session_file)
|
|
282
|
-
if session:
|
|
283
|
-
session["modified"] = mtime.isoformat()
|
|
284
|
-
sessions.append(session)
|
|
285
|
-
sessions.sort(key=lambda s: s["modified"])
|
|
286
|
-
return sessions
|
|
92
|
+
return _transcripts.collect_transcripts_since(since_iso, until_iso)
|
|
287
93
|
|
|
288
94
|
|
|
289
95
|
# ── Database queries ──────────────────────────────────────────────────────
|
package/src/server.py
CHANGED
|
@@ -23,6 +23,15 @@ from tools_hot_context import (
|
|
|
23
23
|
handle_recent_context_resolve,
|
|
24
24
|
handle_hot_context_list,
|
|
25
25
|
)
|
|
26
|
+
from tools_transcripts import (
|
|
27
|
+
handle_transcript_recent,
|
|
28
|
+
handle_transcript_search,
|
|
29
|
+
handle_transcript_read,
|
|
30
|
+
)
|
|
31
|
+
from tools_system_catalog import (
|
|
32
|
+
handle_system_catalog,
|
|
33
|
+
handle_tool_explain,
|
|
34
|
+
)
|
|
26
35
|
from user_context import get_context as _get_ctx
|
|
27
36
|
from tools_coordination import (
|
|
28
37
|
handle_track, handle_untrack, handle_files,
|
|
@@ -209,6 +218,8 @@ mcp = FastMCP(
|
|
|
209
218
|
"- **Delegate:** prefer direct. If needed: `nexo_context_packet(area)` + guard + 'if unsure STOP'\n"
|
|
210
219
|
"- **Memory:** `nexo_recall` searches all. For fresh 24h continuity use `nexo_pre_action_context(query='...')` before acting and "
|
|
211
220
|
"`nexo_recent_context_capture(...)` / `nexo_recent_context_resolve(...)` for important ongoing threads. "
|
|
221
|
+
"If that is not enough, use `nexo_transcript_search(...)` / `nexo_transcript_read(...)` as the raw fallback to full conversations. "
|
|
222
|
+
"Use `nexo_system_catalog(...)` / `nexo_tool_explain(...)` when you need the live map of NEXO itself. "
|
|
212
223
|
"Capture: errors→`nexo_learning_add`, prefs, entities, decisions\n"
|
|
213
224
|
"- **Change log:** `nexo_task_close` should be the default closure path. If you bypass it, call `nexo_change_log(...)` after production edits. NOT for config dir\n"
|
|
214
225
|
"- **Diary:** When user signals end of session (any language, any style — 'bye', 'done', 'cierro', etc.), "
|
|
@@ -380,6 +391,36 @@ def nexo_hot_context_list(hours: int = 24, limit: int = 10, state: str = "") ->
|
|
|
380
391
|
return handle_hot_context_list(hours, limit, state)
|
|
381
392
|
|
|
382
393
|
|
|
394
|
+
@mcp.tool
|
|
395
|
+
def nexo_transcript_recent(hours: int = 24, client: str = "", limit: int = 10) -> str:
|
|
396
|
+
"""List recent Claude Code / Codex transcripts visible to NEXO."""
|
|
397
|
+
return handle_transcript_recent(hours, client, limit)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@mcp.tool
|
|
401
|
+
def nexo_transcript_search(query: str = "", hours: int = 24, client: str = "", limit: int = 10) -> str:
|
|
402
|
+
"""Search recent transcripts directly when recall/hot-context are not enough."""
|
|
403
|
+
return handle_transcript_search(query, hours, client, limit)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@mcp.tool
|
|
407
|
+
def nexo_transcript_read(session_ref: str = "", transcript_path: str = "", client: str = "", max_messages: int = 80) -> str:
|
|
408
|
+
"""Read a full transcript fallback by session id, transcript display name, session_uid, or exact path."""
|
|
409
|
+
return handle_transcript_read(session_ref, transcript_path, client, max_messages)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@mcp.tool
|
|
413
|
+
def nexo_system_catalog(section: str = "", query: str = "", limit: int = 20) -> str:
|
|
414
|
+
"""Read NEXO's live system catalog built from core tools, plugins, skills, scripts, crons, projects, and artifacts."""
|
|
415
|
+
return handle_system_catalog(section, query, limit)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@mcp.tool
|
|
419
|
+
def nexo_tool_explain(name: str) -> str:
|
|
420
|
+
"""Explain a live NEXO tool/capability from the generated system catalog."""
|
|
421
|
+
return handle_tool_explain(name)
|
|
422
|
+
|
|
423
|
+
|
|
383
424
|
@mcp.tool
|
|
384
425
|
def nexo_smart_startup() -> str:
|
|
385
426
|
"""Pre-load relevant cognitive memories based on pending followups, due reminders, and last session topics.
|