nucleus-mcp 1.0.7 → 1.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/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Runtime - Depth Tracking Operations
|
|
3
|
-
===========================================
|
|
4
|
-
Core logic for depth tracking (Rabbit hole protection).
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import time
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Dict, Any
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
# Relative imports assuming this is in mcp_server_nucleus.runtime
|
|
14
|
-
from .common import get_brain_path
|
|
15
|
-
from .event_ops import _emit_event
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger("nucleus.depth_ops")
|
|
18
|
-
|
|
19
|
-
def _get_depth_path() -> Path:
|
|
20
|
-
"""Get the path to the depth tracking file."""
|
|
21
|
-
brain = get_brain_path()
|
|
22
|
-
session_dir = brain / "session"
|
|
23
|
-
session_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
-
return session_dir / "depth.json"
|
|
25
|
-
|
|
26
|
-
def _get_depth_state() -> Dict[str, Any]:
|
|
27
|
-
"""Get current depth tracking state."""
|
|
28
|
-
try:
|
|
29
|
-
depth_path = _get_depth_path()
|
|
30
|
-
|
|
31
|
-
if not depth_path.exists():
|
|
32
|
-
# Initialize with default state
|
|
33
|
-
default_state = {
|
|
34
|
-
"session_id": f"session-{time.strftime('%Y%m%d')}",
|
|
35
|
-
"current_depth": 0,
|
|
36
|
-
"max_safe_depth": 5,
|
|
37
|
-
"levels": [],
|
|
38
|
-
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
39
|
-
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
40
|
-
}
|
|
41
|
-
with open(depth_path, "w") as f:
|
|
42
|
-
json.dump(default_state, f, indent=2)
|
|
43
|
-
return default_state
|
|
44
|
-
|
|
45
|
-
with open(depth_path, "r") as f:
|
|
46
|
-
return json.load(f)
|
|
47
|
-
except Exception as e:
|
|
48
|
-
logger.error(f"Error getting depth state: {e}")
|
|
49
|
-
return {"current_depth": 0, "levels": [], "max_safe_depth": 5}
|
|
50
|
-
|
|
51
|
-
def _save_depth_state(state: Dict[str, Any]) -> str:
|
|
52
|
-
"""Save depth tracking state."""
|
|
53
|
-
try:
|
|
54
|
-
depth_path = _get_depth_path()
|
|
55
|
-
state["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
56
|
-
with open(depth_path, "w") as f:
|
|
57
|
-
json.dump(state, f, indent=2)
|
|
58
|
-
return "Depth state saved"
|
|
59
|
-
except Exception as e:
|
|
60
|
-
return f"Error saving depth state: {str(e)}"
|
|
61
|
-
|
|
62
|
-
def _depth_push(topic: str) -> Dict[str, Any]:
|
|
63
|
-
"""Go deeper into a subtopic. Returns current state with warnings."""
|
|
64
|
-
try:
|
|
65
|
-
state = _get_depth_state()
|
|
66
|
-
|
|
67
|
-
new_depth = state.get("current_depth", 0) + 1
|
|
68
|
-
max_safe = state.get("max_safe_depth", 5)
|
|
69
|
-
|
|
70
|
-
# Create new level entry
|
|
71
|
-
new_level = {
|
|
72
|
-
"depth": new_depth,
|
|
73
|
-
"topic": topic,
|
|
74
|
-
"started_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
75
|
-
"status": "current"
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
# Update previous level status
|
|
79
|
-
levels = state.get("levels", [])
|
|
80
|
-
for level in levels:
|
|
81
|
-
if level.get("status") == "current":
|
|
82
|
-
level["status"] = "active"
|
|
83
|
-
|
|
84
|
-
levels.append(new_level)
|
|
85
|
-
|
|
86
|
-
state["current_depth"] = new_depth
|
|
87
|
-
state["levels"] = levels
|
|
88
|
-
_save_depth_state(state)
|
|
89
|
-
|
|
90
|
-
# Generate warning based on depth
|
|
91
|
-
warning = None
|
|
92
|
-
warning_level = "safe"
|
|
93
|
-
|
|
94
|
-
if new_depth >= max_safe:
|
|
95
|
-
warning = f"🔴🔴 RABBIT HOLE! You're at level {new_depth}/{max_safe}. Consider resurfacing."
|
|
96
|
-
warning_level = "danger"
|
|
97
|
-
elif new_depth >= max_safe - 1:
|
|
98
|
-
warning = f"🔴 DEEP DIVE ALERT! Level {new_depth}/{max_safe}. You're in the danger zone."
|
|
99
|
-
warning_level = "danger"
|
|
100
|
-
elif new_depth >= 3:
|
|
101
|
-
warning = f"🟡 CAUTION: Level {new_depth}/{max_safe}. Getting deep - just so you know."
|
|
102
|
-
warning_level = "caution"
|
|
103
|
-
|
|
104
|
-
# Build breadcrumb path
|
|
105
|
-
breadcrumbs = " → ".join([lv["topic"] for lv in levels])
|
|
106
|
-
|
|
107
|
-
# Emit event
|
|
108
|
-
_emit_event("depth_increased", "depth_tracker", {
|
|
109
|
-
"new_depth": new_depth,
|
|
110
|
-
"topic": topic,
|
|
111
|
-
"warning_level": warning_level
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
"current_depth": new_depth,
|
|
116
|
-
"max_safe_depth": max_safe,
|
|
117
|
-
"topic": topic,
|
|
118
|
-
"breadcrumbs": breadcrumbs,
|
|
119
|
-
"warning": warning,
|
|
120
|
-
"warning_level": warning_level,
|
|
121
|
-
"indicator": _format_depth_indicator(new_depth, max_safe)
|
|
122
|
-
}
|
|
123
|
-
except Exception as e:
|
|
124
|
-
return {"error": str(e)}
|
|
125
|
-
|
|
126
|
-
def _depth_pop() -> Dict[str, Any]:
|
|
127
|
-
"""Come back up one level. Returns new state."""
|
|
128
|
-
try:
|
|
129
|
-
state = _get_depth_state()
|
|
130
|
-
levels = state.get("levels", [])
|
|
131
|
-
|
|
132
|
-
if not levels:
|
|
133
|
-
return {
|
|
134
|
-
"current_depth": 0,
|
|
135
|
-
"message": "Already at root level (depth 0). Nothing to pop.",
|
|
136
|
-
"indicator": _format_depth_indicator(0, state.get("max_safe_depth", 5))
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# Pop the current level
|
|
140
|
-
popped = levels.pop()
|
|
141
|
-
|
|
142
|
-
# Set new current
|
|
143
|
-
if levels:
|
|
144
|
-
levels[-1]["status"] = "current"
|
|
145
|
-
|
|
146
|
-
new_depth = len(levels)
|
|
147
|
-
state["current_depth"] = new_depth
|
|
148
|
-
state["levels"] = levels
|
|
149
|
-
_save_depth_state(state)
|
|
150
|
-
|
|
151
|
-
# Build breadcrumb path
|
|
152
|
-
breadcrumbs = " → ".join([lv["topic"] for lv in levels]) if levels else "(root)"
|
|
153
|
-
returned_to = levels[-1]["topic"] if levels else "root"
|
|
154
|
-
|
|
155
|
-
# Emit event
|
|
156
|
-
_emit_event("depth_decreased", "depth_tracker", {
|
|
157
|
-
"new_depth": new_depth,
|
|
158
|
-
"returned_to": returned_to,
|
|
159
|
-
"popped_topic": popped["topic"]
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
"current_depth": new_depth,
|
|
164
|
-
"returned_to": returned_to,
|
|
165
|
-
"popped_topic": popped["topic"],
|
|
166
|
-
"breadcrumbs": breadcrumbs,
|
|
167
|
-
"message": f"✅ Resurfaced! Now at level {new_depth}: {returned_to}",
|
|
168
|
-
"indicator": _format_depth_indicator(new_depth, state.get("max_safe_depth", 5))
|
|
169
|
-
}
|
|
170
|
-
except Exception as e:
|
|
171
|
-
return {"error": str(e)}
|
|
172
|
-
|
|
173
|
-
def _depth_show() -> Dict[str, Any]:
|
|
174
|
-
"""Show current depth state with visual indicator."""
|
|
175
|
-
try:
|
|
176
|
-
state = _get_depth_state()
|
|
177
|
-
current_depth = state.get("current_depth", 0)
|
|
178
|
-
max_safe = state.get("max_safe_depth", 5)
|
|
179
|
-
levels = state.get("levels", [])
|
|
180
|
-
|
|
181
|
-
# Build breadcrumb path
|
|
182
|
-
breadcrumbs = " → ".join([lv["topic"] for lv in levels]) if levels else "(root)"
|
|
183
|
-
|
|
184
|
-
# Build tree visualization
|
|
185
|
-
tree_lines = []
|
|
186
|
-
for i, level in enumerate(levels):
|
|
187
|
-
indent = " " * i
|
|
188
|
-
prefix = "└─ " if i > 0 else ""
|
|
189
|
-
marker = " ← YOU ARE HERE" if level.get("status") == "current" else ""
|
|
190
|
-
tree_lines.append(f"{indent}{prefix}{i}: {level['topic']}{marker}")
|
|
191
|
-
|
|
192
|
-
tree = "\n".join(tree_lines) if tree_lines else "(At root level)"
|
|
193
|
-
|
|
194
|
-
# Generate status
|
|
195
|
-
if current_depth >= max_safe:
|
|
196
|
-
status = "🔴 RABBIT HOLE"
|
|
197
|
-
elif current_depth >= max_safe - 1:
|
|
198
|
-
status = "🔴 DANGER ZONE"
|
|
199
|
-
elif current_depth >= 3:
|
|
200
|
-
status = "🟡 CAUTION"
|
|
201
|
-
else:
|
|
202
|
-
status = "🟢 SAFE"
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
"current_depth": current_depth,
|
|
206
|
-
"max_safe_depth": max_safe,
|
|
207
|
-
"status": status,
|
|
208
|
-
"breadcrumbs": breadcrumbs,
|
|
209
|
-
"tree": tree,
|
|
210
|
-
"indicator": _format_depth_indicator(current_depth, max_safe),
|
|
211
|
-
"levels": levels,
|
|
212
|
-
"session_id": state.get("session_id"),
|
|
213
|
-
"help": "Commands: brain_depth_push(topic), brain_depth_pop(), brain_depth_reset(), brain_depth_set_max(n)"
|
|
214
|
-
}
|
|
215
|
-
except Exception as e:
|
|
216
|
-
return {"error": str(e)}
|
|
217
|
-
|
|
218
|
-
def _depth_reset() -> Dict[str, Any]:
|
|
219
|
-
"""Reset depth to 0 (root level). Clears all levels."""
|
|
220
|
-
try:
|
|
221
|
-
state = _get_depth_state()
|
|
222
|
-
state["current_depth"] = 0
|
|
223
|
-
state["levels"] = []
|
|
224
|
-
_save_depth_state(state)
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
"current_depth": 0,
|
|
228
|
-
"message": "✅ Depth reset to root level.",
|
|
229
|
-
"indicator": _format_depth_indicator(0, state.get("max_safe_depth", 5))
|
|
230
|
-
}
|
|
231
|
-
except Exception as e:
|
|
232
|
-
return {"error": str(e)}
|
|
233
|
-
|
|
234
|
-
def _depth_set_max(max_depth: int) -> Dict[str, Any]:
|
|
235
|
-
"""Set the maximum safe depth threshold."""
|
|
236
|
-
try:
|
|
237
|
-
# Validate range
|
|
238
|
-
if max_depth < 1 or max_depth > 10:
|
|
239
|
-
return {
|
|
240
|
-
"error": "max_depth must be between 1 and 10",
|
|
241
|
-
"current_max": None
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
state = _get_depth_state()
|
|
245
|
-
old_max = state.get("max_safe_depth", 5)
|
|
246
|
-
state["max_safe_depth"] = max_depth
|
|
247
|
-
_save_depth_state(state)
|
|
248
|
-
|
|
249
|
-
current = state.get("current_depth", 0)
|
|
250
|
-
return {
|
|
251
|
-
"old_max": old_max,
|
|
252
|
-
"new_max": max_depth,
|
|
253
|
-
"current_depth": current,
|
|
254
|
-
"indicator": _format_depth_indicator(current, max_depth),
|
|
255
|
-
"message": f"✅ Max depth updated: {old_max} → {max_depth}"
|
|
256
|
-
}
|
|
257
|
-
except Exception as e:
|
|
258
|
-
return {"error": str(e)}
|
|
259
|
-
|
|
260
|
-
def _format_depth_indicator(current: int, max_safe: int) -> str:
|
|
261
|
-
"""Returns a visual progress-style indicator of depth."""
|
|
262
|
-
indicator = "["
|
|
263
|
-
for i in range(max_safe):
|
|
264
|
-
if i < current:
|
|
265
|
-
indicator += "█"
|
|
266
|
-
else:
|
|
267
|
-
indicator += "░"
|
|
268
|
-
indicator += "]"
|
|
269
|
-
|
|
270
|
-
if current >= max_safe:
|
|
271
|
-
indicator += " ⚠️"
|
|
272
|
-
|
|
273
|
-
return indicator
|
|
274
|
-
|
|
275
|
-
def _generate_depth_map() -> Dict[str, Any]:
|
|
276
|
-
"""Generate a Mermaid diagram of the current exploration path."""
|
|
277
|
-
try:
|
|
278
|
-
state = _get_depth_state()
|
|
279
|
-
levels = state.get("levels", [])
|
|
280
|
-
max_safe = state.get("max_safe_depth", 5)
|
|
281
|
-
|
|
282
|
-
if not levels:
|
|
283
|
-
return {
|
|
284
|
-
"mermaid": "```mermaid\ngraph TD\n ROOT((🏠 START))\n style ROOT fill:#ccffcc,stroke:#0a0\n```",
|
|
285
|
-
"message": "You're at the root level. No exploration path yet.",
|
|
286
|
-
"node_count": 0
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
# Build Mermaid graph
|
|
290
|
-
lines = ["graph TD"]
|
|
291
|
-
lines.append(" ROOT((🏠 START))")
|
|
292
|
-
|
|
293
|
-
prev_id = "ROOT"
|
|
294
|
-
for i, level in enumerate(levels):
|
|
295
|
-
node_id = f"L{i}"
|
|
296
|
-
topic = level.get("topic", f"Level {i+1}")
|
|
297
|
-
# Escape quotes and special chars
|
|
298
|
-
topic_safe = topic.replace('"', "'").replace("[", "(").replace("]", ")")
|
|
299
|
-
|
|
300
|
-
# Determine node style based on depth
|
|
301
|
-
depth = i + 1
|
|
302
|
-
if depth >= max_safe:
|
|
303
|
-
style = "fill:#ffcccc,stroke:#f00" # Red - rabbit hole
|
|
304
|
-
node_shape = f'{node_id}[["🔴 {topic_safe}"]]'
|
|
305
|
-
elif depth >= max_safe - 1:
|
|
306
|
-
style = "fill:#ffddcc,stroke:#f60" # Orange - danger
|
|
307
|
-
node_shape = f'{node_id}[["🔴 {topic_safe}"]]'
|
|
308
|
-
elif depth >= 3:
|
|
309
|
-
style = "fill:#ffffcc,stroke:#cc0" # Yellow - caution
|
|
310
|
-
node_shape = f'{node_id}["🟡 {topic_safe}"]'
|
|
311
|
-
else:
|
|
312
|
-
style = "fill:#ccffcc,stroke:#0a0" # Green - safe
|
|
313
|
-
node_shape = f'{node_id}["🟢 {topic_safe}"]'
|
|
314
|
-
|
|
315
|
-
lines.append(f" {prev_id} --> {node_shape}")
|
|
316
|
-
lines.append(f" style {node_id} {style}")
|
|
317
|
-
prev_id = node_id
|
|
318
|
-
|
|
319
|
-
# Mark the last node as current
|
|
320
|
-
if levels:
|
|
321
|
-
lines.append(f" style {prev_id} stroke-width:3px")
|
|
322
|
-
|
|
323
|
-
# Wrap in code block
|
|
324
|
-
mermaid_code = "```mermaid\n" + "\n".join(lines) + "\n```"
|
|
325
|
-
|
|
326
|
-
# Build path summary
|
|
327
|
-
path = " → ".join([lv.get("topic", "?") for lv in levels])
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
"mermaid": mermaid_code,
|
|
331
|
-
"path": f"🏠 → {path}",
|
|
332
|
-
"current_depth": len(levels),
|
|
333
|
-
"max_safe_depth": max_safe,
|
|
334
|
-
"node_count": len(levels),
|
|
335
|
-
"message": f"Exploration map with {len(levels)} nodes. Current depth: {len(levels)}/{max_safe}"
|
|
336
|
-
}
|
|
337
|
-
except Exception as e:
|
|
338
|
-
return {"error": str(e)}
|
|
339
|
-
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Runtime - Event Operations
|
|
3
|
-
==================================
|
|
4
|
-
Core logic for event stream management.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import time
|
|
9
|
-
import uuid
|
|
10
|
-
from typing import Dict, Any, List
|
|
11
|
-
from datetime import datetime, timezone
|
|
12
|
-
|
|
13
|
-
from .common import get_brain_path, logger
|
|
14
|
-
|
|
15
|
-
def _log_interaction(emitter: str, event_type: str, data: Dict[str, Any]) -> None:
|
|
16
|
-
"""Log a cryptographic hash of the interaction for user trust (V9 Security)."""
|
|
17
|
-
try:
|
|
18
|
-
import hashlib
|
|
19
|
-
brain = get_brain_path()
|
|
20
|
-
log_path = brain / "ledger" / "interaction_log.jsonl"
|
|
21
|
-
|
|
22
|
-
# Create a stable string representation for hashing
|
|
23
|
-
payload = json.dumps({"type": event_type, "emitter": emitter, "data": data}, sort_keys=True)
|
|
24
|
-
h = hashlib.sha256(payload.encode()).hexdigest()
|
|
25
|
-
|
|
26
|
-
entry = {
|
|
27
|
-
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
28
|
-
"emitter": emitter,
|
|
29
|
-
"type": event_type,
|
|
30
|
-
"hash": h,
|
|
31
|
-
"alg": "sha256"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
with open(log_path, "a") as f:
|
|
35
|
-
f.write(json.dumps(entry) + "\n")
|
|
36
|
-
except Exception as e:
|
|
37
|
-
logger.warning(f"Failed to log interaction trust signal: {e}")
|
|
38
|
-
|
|
39
|
-
def _emit_event(event_type: str, emitter: str, data: Dict[str, Any], description: str = "") -> str:
|
|
40
|
-
"""Core logic for emitting an event."""
|
|
41
|
-
try:
|
|
42
|
-
brain = get_brain_path()
|
|
43
|
-
events_path = brain / "ledger" / "events.jsonl"
|
|
44
|
-
|
|
45
|
-
event_id = f"evt-{int(time.time())}-{str(uuid.uuid4())[:8]}"
|
|
46
|
-
timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
47
|
-
|
|
48
|
-
event = {
|
|
49
|
-
"event_id": event_id,
|
|
50
|
-
"timestamp": timestamp,
|
|
51
|
-
"type": event_type,
|
|
52
|
-
"emitter": emitter,
|
|
53
|
-
"data": data,
|
|
54
|
-
"description": description
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
with open(events_path, "a") as f:
|
|
58
|
-
f.write(json.dumps(event) + "\n")
|
|
59
|
-
|
|
60
|
-
# Log interaction for security audit (Trust Signal)
|
|
61
|
-
_log_interaction(emitter, event_type, data)
|
|
62
|
-
|
|
63
|
-
# Update activity summary for fast satellite view (Tier 2 precomputation)
|
|
64
|
-
try:
|
|
65
|
-
summary_path = brain / "ledger" / "activity_summary.json"
|
|
66
|
-
summary = {}
|
|
67
|
-
if summary_path.exists():
|
|
68
|
-
with open(summary_path, "r") as f:
|
|
69
|
-
summary = json.load(f)
|
|
70
|
-
|
|
71
|
-
summary["last_event"] = event
|
|
72
|
-
summary["updated_at"] = timestamp
|
|
73
|
-
summary["event_count"] = summary.get("event_count", 0) + 1
|
|
74
|
-
|
|
75
|
-
with open(summary_path, "w") as f:
|
|
76
|
-
json.dump(summary, f, indent=2)
|
|
77
|
-
except Exception:
|
|
78
|
-
pass # Don't fail event emit if summary update fails
|
|
79
|
-
|
|
80
|
-
return event_id
|
|
81
|
-
except Exception as e:
|
|
82
|
-
return f"Error emitting event: {str(e)}"
|
|
83
|
-
|
|
84
|
-
def _read_events(limit: int = 10) -> List[Dict[str, Any]]:
|
|
85
|
-
"""Core logic for reading events."""
|
|
86
|
-
try:
|
|
87
|
-
brain = get_brain_path()
|
|
88
|
-
events_path = brain / "ledger" / "events.jsonl"
|
|
89
|
-
|
|
90
|
-
if not events_path.exists():
|
|
91
|
-
return []
|
|
92
|
-
|
|
93
|
-
events = []
|
|
94
|
-
with open(events_path, "r") as f:
|
|
95
|
-
for line in f:
|
|
96
|
-
if line.strip():
|
|
97
|
-
events.append(json.loads(line))
|
|
98
|
-
|
|
99
|
-
return events[-limit:]
|
|
100
|
-
except Exception as e:
|
|
101
|
-
# Avoid logger dependency here if possible, or print
|
|
102
|
-
print(f"Error reading events: {e}")
|
|
103
|
-
return []
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Agent Runtime - Event Stream
|
|
3
|
-
=====================================
|
|
4
|
-
The nervous system of the Agent Control Plane.
|
|
5
|
-
Events flow through here, triggering agent activation.
|
|
6
|
-
|
|
7
|
-
Location: mcp_server_nucleus/runtime/event_stream.py
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
from datetime import datetime, timezone
|
|
12
|
-
from enum import Enum
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Dict, List, Any, Optional
|
|
15
|
-
import uuid
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class EventSeverity(Enum):
|
|
19
|
-
"""Event severity levels per synthesizer.md protocol"""
|
|
20
|
-
ROUTINE = "ROUTINE" # Auto-approve, log only
|
|
21
|
-
NOTABLE = "NOTABLE" # Include in Daily Digest
|
|
22
|
-
CRITICAL = "CRITICAL" # IMMEDIATE escalation to founder
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def get_events_path(brain_path: Path) -> Path:
|
|
26
|
-
"""Get path to event stream"""
|
|
27
|
-
return brain_path / "ledger" / "events.jsonl"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def read_events(brain_path: Path, limit: int = 50) -> List[Dict]:
|
|
31
|
-
"""
|
|
32
|
-
Read the last N events from the stream.
|
|
33
|
-
Returns events in reverse chronological order (newest first).
|
|
34
|
-
"""
|
|
35
|
-
events_path = get_events_path(brain_path)
|
|
36
|
-
|
|
37
|
-
if not events_path.exists():
|
|
38
|
-
return []
|
|
39
|
-
|
|
40
|
-
# Read all events
|
|
41
|
-
events = []
|
|
42
|
-
with open(events_path, 'r') as f:
|
|
43
|
-
for line in f:
|
|
44
|
-
line = line.strip()
|
|
45
|
-
if line:
|
|
46
|
-
try:
|
|
47
|
-
events.append(json.loads(line))
|
|
48
|
-
except json.JSONDecodeError:
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
# Return last N, reversed (newest first)
|
|
52
|
-
return events[-limit:][::-1]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def emit_event(
|
|
56
|
-
brain_path: Path,
|
|
57
|
-
event_type: str,
|
|
58
|
-
emitter: str,
|
|
59
|
-
payload: Dict[str, Any],
|
|
60
|
-
severity: EventSeverity = EventSeverity.ROUTINE,
|
|
61
|
-
metadata: Optional[Dict] = None
|
|
62
|
-
) -> Dict:
|
|
63
|
-
"""
|
|
64
|
-
Emit an event to the stream.
|
|
65
|
-
Returns the created event.
|
|
66
|
-
"""
|
|
67
|
-
events_path = get_events_path(brain_path)
|
|
68
|
-
events_path.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
-
|
|
70
|
-
event = {
|
|
71
|
-
"event_id": f"{emitter[:3]}-{uuid.uuid4().hex[:8]}",
|
|
72
|
-
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
73
|
-
"emitter": emitter,
|
|
74
|
-
"event_type": event_type,
|
|
75
|
-
"severity": severity.value,
|
|
76
|
-
"payload": payload,
|
|
77
|
-
"metadata": metadata or {}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Append to stream
|
|
81
|
-
with open(events_path, 'a') as f:
|
|
82
|
-
f.write(json.dumps(event) + '\n')
|
|
83
|
-
|
|
84
|
-
# Bridge to Cloud (Fire & Forget)
|
|
85
|
-
try:
|
|
86
|
-
from mcp_server_nucleus.runtime.firestore_bridge import get_bridge
|
|
87
|
-
get_bridge().push_event(event)
|
|
88
|
-
except Exception:
|
|
89
|
-
# Never block local op on cloud fail
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
return event
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def get_unprocessed_events(
|
|
96
|
-
brain_path: Path,
|
|
97
|
-
last_processed_id: Optional[str] = None,
|
|
98
|
-
severity_filter: Optional[EventSeverity] = None
|
|
99
|
-
) -> List[Dict]:
|
|
100
|
-
"""
|
|
101
|
-
Get events that haven't been processed yet.
|
|
102
|
-
Optionally filter by severity.
|
|
103
|
-
"""
|
|
104
|
-
events = read_events(brain_path, limit=100)
|
|
105
|
-
|
|
106
|
-
# If we have a last processed ID, skip events until we find it
|
|
107
|
-
if last_processed_id:
|
|
108
|
-
found = False
|
|
109
|
-
filtered = []
|
|
110
|
-
for event in events:
|
|
111
|
-
if found:
|
|
112
|
-
filtered.append(event)
|
|
113
|
-
elif event.get('event_id') == last_processed_id:
|
|
114
|
-
found = True
|
|
115
|
-
events = filtered if found else events
|
|
116
|
-
|
|
117
|
-
# Filter by severity if specified
|
|
118
|
-
if severity_filter:
|
|
119
|
-
events = [e for e in events if e.get('severity') == severity_filter.value]
|
|
120
|
-
|
|
121
|
-
return events
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def get_critical_events(brain_path: Path, limit: int = 10) -> List[Dict]:
|
|
125
|
-
"""Get CRITICAL events that need immediate attention"""
|
|
126
|
-
events = read_events(brain_path, limit=limit * 5) # Read more to find criticals
|
|
127
|
-
return [e for e in events if e.get('severity') == 'CRITICAL'][:limit]
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def rotate_events(brain_path: Path, keep_count: int = 1000) -> int:
|
|
131
|
-
"""
|
|
132
|
-
Rotate event stream to prevent unbounded growth.
|
|
133
|
-
Keeps the last N events, archives the rest.
|
|
134
|
-
Returns count of archived events.
|
|
135
|
-
"""
|
|
136
|
-
events_path = get_events_path(brain_path)
|
|
137
|
-
archive_path = brain_path / "ledger" / "archive" / f"events_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
|
|
138
|
-
|
|
139
|
-
if not events_path.exists():
|
|
140
|
-
return 0
|
|
141
|
-
|
|
142
|
-
# Read all events
|
|
143
|
-
events = []
|
|
144
|
-
with open(events_path, 'r') as f:
|
|
145
|
-
for line in f:
|
|
146
|
-
line = line.strip()
|
|
147
|
-
if line:
|
|
148
|
-
events.append(line)
|
|
149
|
-
|
|
150
|
-
if len(events) <= keep_count:
|
|
151
|
-
return 0
|
|
152
|
-
|
|
153
|
-
# Split into keep and archive
|
|
154
|
-
to_archive = events[:-keep_count]
|
|
155
|
-
to_keep = events[-keep_count:]
|
|
156
|
-
|
|
157
|
-
# Write archive
|
|
158
|
-
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
-
with open(archive_path, 'w') as f:
|
|
160
|
-
f.write('\n'.join(to_archive) + '\n')
|
|
161
|
-
|
|
162
|
-
# Overwrite main file with kept events
|
|
163
|
-
with open(events_path, 'w') as f:
|
|
164
|
-
f.write('\n'.join(to_keep) + '\n')
|
|
165
|
-
|
|
166
|
-
return len(to_archive)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# ============================================================
|
|
170
|
-
# COMMON EVENT TYPES (for type safety)
|
|
171
|
-
# ============================================================
|
|
172
|
-
|
|
173
|
-
class EventTypes:
|
|
174
|
-
"""Standard event types in the system"""
|
|
175
|
-
# Task lifecycle
|
|
176
|
-
TASK_ASSIGNED = "task_assigned"
|
|
177
|
-
TASK_COMPLETED = "task_completed"
|
|
178
|
-
TASK_FAILED = "task_failed"
|
|
179
|
-
|
|
180
|
-
# Sprint lifecycle
|
|
181
|
-
SPRINT_STARTED = "sprint_started"
|
|
182
|
-
SPRINT_COMPLETED = "sprint_completed"
|
|
183
|
-
|
|
184
|
-
# Agent lifecycle
|
|
185
|
-
AGENT_ACTIVATED = "agent_activated"
|
|
186
|
-
AGENT_TERMINATED = "agent_terminated"
|
|
187
|
-
|
|
188
|
-
# Review lifecycle
|
|
189
|
-
IMPLEMENTATION_COMPLETE = "implementation_complete"
|
|
190
|
-
REVIEW_APPROVED = "review_approved"
|
|
191
|
-
REVIEW_BLOCKED = "review_blocked"
|
|
192
|
-
|
|
193
|
-
# Escalation
|
|
194
|
-
FOUNDER_DECISION_NEEDED = "founder_decision_needed"
|
|
195
|
-
|
|
196
|
-
# System
|
|
197
|
-
BRAIN_INITIALIZED = "brain_initialized"
|
|
198
|
-
DAILY_DIGEST_GENERATED = "daily_digest_generated"
|
|
199
|
-
META_OPTIMIZATION_COMPLETE = "meta_optimization_complete"
|
|
200
|
-
|
|
201
|
-
# v0.6.0 DSoR - Federation Events
|
|
202
|
-
FEDERATION_PEER_JOINED = "federation_peer_joined"
|
|
203
|
-
FEDERATION_PEER_LEFT = "federation_peer_left"
|
|
204
|
-
FEDERATION_PEER_SUSPECT = "federation_peer_suspect"
|
|
205
|
-
FEDERATION_LEADER_ELECTED = "federation_leader_elected"
|
|
206
|
-
FEDERATION_TASK_ROUTED = "federation_task_routed"
|
|
207
|
-
FEDERATION_STATE_SYNCED = "federation_state_synced"
|
|
208
|
-
|
|
209
|
-
# v0.6.0 DSoR - Decision Provenance
|
|
210
|
-
DECISION_MADE = "decision_made"
|
|
211
|
-
CONTEXT_SNAPSHOT = "context_snapshot"
|
|
212
|
-
IPC_TOKEN_ISSUED = "ipc_token_issued"
|
|
213
|
-
IPC_TOKEN_CONSUMED = "ipc_token_consumed"
|