mirror of
https://github.com/ruvnet/RuView
synced 2026-08-07 20:01:43 +00:00
feat: vendor midstream and sublinear-time-solver libraries (#109)
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/.
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Agentic loop implementation for autonomous decision-making
|
||||
*/
|
||||
|
||||
import {
|
||||
Action,
|
||||
Observation,
|
||||
Plan,
|
||||
Goal,
|
||||
Context,
|
||||
} from './types';
|
||||
|
||||
export class AgenticLoop {
|
||||
private actionHistory: Action[] = [];
|
||||
private totalReward: number = 0;
|
||||
private actionCount: number = 0;
|
||||
|
||||
/**
|
||||
* Plan phase: Generate a plan based on goals and context
|
||||
*/
|
||||
async plan(context: Context, input: string): Promise<Plan> {
|
||||
const goal: Goal = {
|
||||
id: `goal_${this.actionCount}`,
|
||||
description: `Process: ${input}`,
|
||||
priority: 1.0,
|
||||
achieved: false,
|
||||
};
|
||||
|
||||
const actions = await this.generateActionCandidates(input, context);
|
||||
const rankedActions = this.rankActions(actions, context);
|
||||
|
||||
const steps = rankedActions.slice(0, 5).map((action, i) => ({
|
||||
sequence: i,
|
||||
action,
|
||||
preconditions: [],
|
||||
postconditions: [],
|
||||
}));
|
||||
|
||||
return {
|
||||
goal,
|
||||
steps,
|
||||
estimatedReward: rankedActions[0]?.expectedReward || 0,
|
||||
confidence: steps.length > 0 ? 0.8 : 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate candidate actions based on input
|
||||
*/
|
||||
private async generateActionCandidates(
|
||||
input: string,
|
||||
context: Context
|
||||
): Promise<Action[]> {
|
||||
const candidates: Action[] = [];
|
||||
const inputLower = input.toLowerCase();
|
||||
|
||||
if (inputLower.includes('weather')) {
|
||||
candidates.push({
|
||||
actionType: 'get_weather',
|
||||
description: 'Fetch weather information',
|
||||
parameters: { query: input },
|
||||
toolCalls: ['weather_api'],
|
||||
expectedOutcome: 'Weather data',
|
||||
expectedReward: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
if (inputLower.includes('learn') || inputLower.includes('remember')) {
|
||||
candidates.push({
|
||||
actionType: 'update_knowledge',
|
||||
description: 'Update knowledge graph',
|
||||
parameters: { content: input },
|
||||
toolCalls: [],
|
||||
expectedOutcome: 'Knowledge updated',
|
||||
expectedReward: 0.9,
|
||||
});
|
||||
}
|
||||
|
||||
// Default action
|
||||
candidates.push({
|
||||
actionType: 'process_text',
|
||||
description: `Process: ${input}`,
|
||||
parameters: { text: input },
|
||||
toolCalls: [],
|
||||
expectedOutcome: 'Processed text',
|
||||
expectedReward: 0.5,
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rank actions by expected reward
|
||||
*/
|
||||
private rankActions(actions: Action[], context: Context): Action[] {
|
||||
return actions.sort((a, b) => b.expectedReward - a.expectedReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record action execution
|
||||
*/
|
||||
recordAction(action: Action, reward: number): void {
|
||||
this.actionHistory.push(action);
|
||||
this.totalReward += reward;
|
||||
this.actionCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average reward
|
||||
*/
|
||||
getAverageReward(): number {
|
||||
return this.actionCount > 0 ? this.totalReward / this.actionCount : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action count
|
||||
*/
|
||||
getActionCount(): number {
|
||||
return this.actionCount;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Client for interacting with the Lean Agentic Learning System
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import {
|
||||
LeanAgenticConfig,
|
||||
Context,
|
||||
ProcessingResult,
|
||||
SystemStats,
|
||||
Entity,
|
||||
Theorem,
|
||||
LearningStats
|
||||
} from './types';
|
||||
|
||||
export class LeanAgenticClient {
|
||||
private client: AxiosInstance;
|
||||
private config: LeanAgenticConfig;
|
||||
|
||||
constructor(baseURL: string, config: Partial<LeanAgenticConfig> = {}) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.config = {
|
||||
enableFormalVerification: true,
|
||||
learningRate: 0.01,
|
||||
maxPlanningDepth: 5,
|
||||
actionThreshold: 0.7,
|
||||
enableMultiAgent: true,
|
||||
kgUpdateFreq: 100,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a stream chunk through the lean agentic system
|
||||
*/
|
||||
async processChunk(
|
||||
chunk: string,
|
||||
context: Context
|
||||
): Promise<ProcessingResult> {
|
||||
const response = await this.client.post<ProcessingResult>('/process', {
|
||||
chunk,
|
||||
context,
|
||||
config: this.config,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system statistics
|
||||
*/
|
||||
async getStats(): Promise<SystemStats> {
|
||||
const response = await this.client.get<SystemStats>('/stats');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query entities from knowledge graph
|
||||
*/
|
||||
async queryEntities(query: {
|
||||
entityType?: string;
|
||||
searchText?: string;
|
||||
limit?: number;
|
||||
}): Promise<Entity[]> {
|
||||
const response = await this.client.get<Entity[]>('/knowledge/entities', {
|
||||
params: query,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theorems from the formal reasoning system
|
||||
*/
|
||||
async getTheorems(tags?: string[]): Promise<Theorem[]> {
|
||||
const response = await this.client.get<Theorem[]>('/reasoning/theorems', {
|
||||
params: { tags: tags?.join(',') },
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get learning statistics
|
||||
*/
|
||||
async getLearningStats(): Promise<LearningStats> {
|
||||
const response = await this.client.get<LearningStats>('/learning/stats');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system configuration
|
||||
*/
|
||||
async updateConfig(config: Partial<LeanAgenticConfig>): Promise<void> {
|
||||
this.config = { ...this.config, ...config };
|
||||
await this.client.post('/config', this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new context
|
||||
*/
|
||||
createContext(sessionId: string): Context {
|
||||
return {
|
||||
history: [],
|
||||
preferences: {},
|
||||
sessionId,
|
||||
environment: {},
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default LeanAgenticClient;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Lean Agentic Learning System - TypeScript/JavaScript Client
|
||||
*
|
||||
* A revolutionary learning framework combining:
|
||||
* - Formal reasoning (Lean-style theorem proving)
|
||||
* - Agentic AI (autonomous decision-making)
|
||||
* - Stream learning (real-time online adaptation)
|
||||
* - Knowledge evolution (dynamic theorem store)
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export * from './client';
|
||||
export * from './agent';
|
||||
export * from './knowledge';
|
||||
export * from './stream';
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Knowledge graph for dynamic knowledge representation
|
||||
*/
|
||||
|
||||
import { Entity, EntityType, Relation } from './types';
|
||||
|
||||
export class KnowledgeGraph {
|
||||
private entities: Map<string, Entity> = new Map();
|
||||
private relations: Relation[] = [];
|
||||
|
||||
/**
|
||||
* Extract entities from text (simple implementation)
|
||||
*/
|
||||
extractEntities(text: string): Entity[] {
|
||||
const entities: Entity[] = [];
|
||||
const words = text.split(/\s+/);
|
||||
|
||||
words.forEach((word, i) => {
|
||||
// Capitalized words might be entities
|
||||
if (word.length > 0 && word[0] === word[0].toUpperCase()) {
|
||||
entities.push({
|
||||
id: `entity_${i}`,
|
||||
name: word,
|
||||
entityType: EntityType.Unknown,
|
||||
attributes: {},
|
||||
confidence: 0.7,
|
||||
});
|
||||
}
|
||||
|
||||
// Numeric values
|
||||
if (!isNaN(parseFloat(word))) {
|
||||
entities.push({
|
||||
id: `value_${i}`,
|
||||
name: word,
|
||||
entityType: EntityType.Value,
|
||||
attributes: {},
|
||||
confidence: 0.9,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update knowledge graph with new entities
|
||||
*/
|
||||
update(entities: Entity[]): void {
|
||||
entities.forEach(entity => {
|
||||
const existing = this.entities.get(entity.id);
|
||||
|
||||
if (existing) {
|
||||
// Update existing entity
|
||||
existing.confidence = (existing.confidence + entity.confidence) / 2;
|
||||
existing.attributes = { ...existing.attributes, ...entity.attributes };
|
||||
} else {
|
||||
// Add new entity
|
||||
this.entities.set(entity.id, entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relation between entities
|
||||
*/
|
||||
addRelation(relation: Relation): void {
|
||||
this.relations.push(relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query entities by type
|
||||
*/
|
||||
queryEntities(entityType?: EntityType): Entity[] {
|
||||
if (!entityType) {
|
||||
return Array.from(this.entities.values());
|
||||
}
|
||||
|
||||
return Array.from(this.entities.values()).filter(
|
||||
e => e.entityType === entityType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find related entities
|
||||
*/
|
||||
findRelated(entityId: string, maxDepth: number = 2): Set<string> {
|
||||
const related = new Set<string>();
|
||||
const toExplore: Array<[string, number]> = [[entityId, 0]];
|
||||
|
||||
while (toExplore.length > 0) {
|
||||
const [currentId, depth] = toExplore.shift()!;
|
||||
|
||||
if (depth >= maxDepth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.relations.forEach(relation => {
|
||||
if (relation.subject === currentId) {
|
||||
related.add(relation.object);
|
||||
toExplore.push([relation.object, depth + 1]);
|
||||
} else if (relation.object === currentId) {
|
||||
related.add(relation.subject);
|
||||
toExplore.push([relation.subject, depth + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return related;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity count
|
||||
*/
|
||||
entityCount(): number {
|
||||
return this.entities.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relation count
|
||||
*/
|
||||
relationCount(): number {
|
||||
return this.relations.length;
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Stream processing utilities for lean agentic learning
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { ProcessingResult, Context } from './types';
|
||||
import { LeanAgenticClient } from './client';
|
||||
|
||||
export interface StreamChunk {
|
||||
content: string;
|
||||
timestamp: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class StreamProcessor extends EventEmitter {
|
||||
private client: LeanAgenticClient;
|
||||
private context: Context;
|
||||
private chunkBuffer: StreamChunk[] = [];
|
||||
|
||||
constructor(client: LeanAgenticClient, sessionId: string) {
|
||||
super();
|
||||
this.client = client;
|
||||
this.context = client.createContext(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a stream chunk
|
||||
*/
|
||||
async processChunk(chunk: StreamChunk): Promise<ProcessingResult> {
|
||||
this.chunkBuffer.push(chunk);
|
||||
|
||||
// Update context
|
||||
this.context.history.push(chunk.content);
|
||||
this.context.timestamp = chunk.timestamp;
|
||||
|
||||
// Process through lean agentic system
|
||||
const result = await this.client.processChunk(
|
||||
chunk.content,
|
||||
this.context
|
||||
);
|
||||
|
||||
// Emit events
|
||||
this.emit('chunk_processed', { chunk, result });
|
||||
|
||||
if (result.reward > 0.8) {
|
||||
this.emit('high_reward', result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process multiple chunks
|
||||
*/
|
||||
async processStream(chunks: StreamChunk[]): Promise<ProcessingResult[]> {
|
||||
const results: ProcessingResult[] = [];
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const result = await this.processChunk(chunk);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
this.emit('stream_complete', { results });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current context
|
||||
*/
|
||||
getContext(): Context {
|
||||
return { ...this.context };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update context preferences
|
||||
*/
|
||||
updatePreference(key: string, value: number): void {
|
||||
this.context.preferences[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear buffer
|
||||
*/
|
||||
clearBuffer(): void {
|
||||
this.chunkBuffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get buffer size
|
||||
*/
|
||||
getBufferSize(): number {
|
||||
return this.chunkBuffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stream from an async iterator
|
||||
*/
|
||||
export async function* streamFromAsyncIterator<T>(
|
||||
iterator: AsyncIterableIterator<T>
|
||||
): AsyncGenerator<StreamChunk> {
|
||||
for await (const item of iterator) {
|
||||
yield {
|
||||
content: String(item),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a batched stream processor
|
||||
*/
|
||||
export class BatchedStreamProcessor extends StreamProcessor {
|
||||
private batchSize: number;
|
||||
private currentBatch: StreamChunk[] = [];
|
||||
|
||||
constructor(client: LeanAgenticClient, sessionId: string, batchSize: number = 10) {
|
||||
super(client, sessionId);
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
async processChunk(chunk: StreamChunk): Promise<ProcessingResult> {
|
||||
this.currentBatch.push(chunk);
|
||||
|
||||
if (this.currentBatch.length >= this.batchSize) {
|
||||
return this.processBatch();
|
||||
}
|
||||
|
||||
// Return a pending result
|
||||
return {
|
||||
action: {
|
||||
actionType: 'buffer',
|
||||
description: 'Buffering chunk',
|
||||
parameters: {},
|
||||
toolCalls: [],
|
||||
expectedReward: 0,
|
||||
},
|
||||
observation: {
|
||||
success: true,
|
||||
result: 'Buffered',
|
||||
changes: [],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
reward: 0,
|
||||
verified: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async processBatch(): Promise<ProcessingResult> {
|
||||
const combined = this.currentBatch.map(c => c.content).join(' ');
|
||||
const result = await super.processChunk({
|
||||
content: combined,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
this.currentBatch = [];
|
||||
this.emit('batch_processed', { result });
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Core types for the Lean Agentic Learning System
|
||||
*/
|
||||
|
||||
export interface LeanAgenticConfig {
|
||||
/** Enable formal verification of actions */
|
||||
enableFormalVerification: boolean;
|
||||
|
||||
/** Learning rate for online adaptation */
|
||||
learningRate: number;
|
||||
|
||||
/** Maximum planning depth */
|
||||
maxPlanningDepth: number;
|
||||
|
||||
/** Confidence threshold for action execution */
|
||||
actionThreshold: number;
|
||||
|
||||
/** Enable multi-agent collaboration */
|
||||
enableMultiAgent: boolean;
|
||||
|
||||
/** Knowledge graph update frequency */
|
||||
kgUpdateFreq: number;
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
/** Conversation history */
|
||||
history: string[];
|
||||
|
||||
/** User preferences learned over time */
|
||||
preferences: Record<string, number>;
|
||||
|
||||
/** Session identifier */
|
||||
sessionId: string;
|
||||
|
||||
/** Environment state */
|
||||
environment: Record<string, any>;
|
||||
|
||||
/** Timestamp */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
/** Type of action */
|
||||
actionType: string;
|
||||
|
||||
/** Human-readable description */
|
||||
description: string;
|
||||
|
||||
/** Action parameters */
|
||||
parameters: Record<string, string>;
|
||||
|
||||
/** Tool calls required */
|
||||
toolCalls: string[];
|
||||
|
||||
/** Expected outcome */
|
||||
expectedOutcome?: string;
|
||||
|
||||
/** Expected reward */
|
||||
expectedReward: number;
|
||||
}
|
||||
|
||||
export interface Observation {
|
||||
/** Whether action succeeded */
|
||||
success: boolean;
|
||||
|
||||
/** Result of action */
|
||||
result: string;
|
||||
|
||||
/** Changes made */
|
||||
changes: string[];
|
||||
|
||||
/** Timestamp */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Plan {
|
||||
/** Goal being pursued */
|
||||
goal: Goal;
|
||||
|
||||
/** Steps to achieve goal */
|
||||
steps: PlanStep[];
|
||||
|
||||
/** Estimated total reward */
|
||||
estimatedReward: number;
|
||||
|
||||
/** Confidence in plan */
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface PlanStep {
|
||||
/** Step sequence number */
|
||||
sequence: number;
|
||||
|
||||
/** Action to take */
|
||||
action: Action;
|
||||
|
||||
/** Preconditions for this step */
|
||||
preconditions: string[];
|
||||
|
||||
/** Postconditions after this step */
|
||||
postconditions: string[];
|
||||
}
|
||||
|
||||
export interface Goal {
|
||||
/** Goal identifier */
|
||||
id: string;
|
||||
|
||||
/** Description */
|
||||
description: string;
|
||||
|
||||
/** Priority (0-1) */
|
||||
priority: number;
|
||||
|
||||
/** Whether achieved */
|
||||
achieved: boolean;
|
||||
}
|
||||
|
||||
export interface Theorem {
|
||||
/** Theorem identifier */
|
||||
id: string;
|
||||
|
||||
/** Mathematical statement */
|
||||
statement: string;
|
||||
|
||||
/** Proof (if proven) */
|
||||
proof?: Proof;
|
||||
|
||||
/** Confidence score */
|
||||
confidence: number;
|
||||
|
||||
/** Tags for categorization */
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface Proof {
|
||||
/** Proof steps */
|
||||
steps: ProofStep[];
|
||||
|
||||
/** Whether proof is valid */
|
||||
valid: boolean;
|
||||
|
||||
/** Overall confidence */
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface ProofStep {
|
||||
/** Inference rule used */
|
||||
rule: string;
|
||||
|
||||
/** Premises */
|
||||
premises: string[];
|
||||
|
||||
/** Conclusion */
|
||||
conclusion: string;
|
||||
|
||||
/** Step confidence */
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface Entity {
|
||||
/** Entity identifier */
|
||||
id: string;
|
||||
|
||||
/** Entity name */
|
||||
name: string;
|
||||
|
||||
/** Entity type */
|
||||
entityType: EntityType;
|
||||
|
||||
/** Attributes */
|
||||
attributes: Record<string, string>;
|
||||
|
||||
/** Confidence score */
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export enum EntityType {
|
||||
Person = "Person",
|
||||
Place = "Place",
|
||||
Organization = "Organization",
|
||||
Concept = "Concept",
|
||||
Event = "Event",
|
||||
Value = "Value",
|
||||
Unknown = "Unknown"
|
||||
}
|
||||
|
||||
export interface Relation {
|
||||
/** Relation identifier */
|
||||
id: string;
|
||||
|
||||
/** Subject entity */
|
||||
subject: string;
|
||||
|
||||
/** Predicate/relation type */
|
||||
predicate: string;
|
||||
|
||||
/** Object entity */
|
||||
object: string;
|
||||
|
||||
/** Confidence score */
|
||||
confidence: number;
|
||||
|
||||
/** Source of relation */
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ProcessingResult {
|
||||
/** Action taken */
|
||||
action: Action;
|
||||
|
||||
/** Observation received */
|
||||
observation: Observation;
|
||||
|
||||
/** Reward earned */
|
||||
reward: number;
|
||||
|
||||
/** Whether formally verified */
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface SystemStats {
|
||||
/** Total theorems in knowledge base */
|
||||
totalTheorems: number;
|
||||
|
||||
/** Total entities in knowledge graph */
|
||||
totalEntities: number;
|
||||
|
||||
/** Learning iterations completed */
|
||||
learningIterations: number;
|
||||
|
||||
/** Total actions executed */
|
||||
totalActions: number;
|
||||
|
||||
/** Average reward */
|
||||
averageReward: number;
|
||||
}
|
||||
|
||||
export enum AdaptationStrategy {
|
||||
Immediate = "Immediate",
|
||||
Batched = "Batched",
|
||||
ExperienceReplay = "ExperienceReplay"
|
||||
}
|
||||
|
||||
export interface LearningStats {
|
||||
/** Total iterations */
|
||||
iterations: number;
|
||||
|
||||
/** Experience buffer size */
|
||||
bufferSize: number;
|
||||
|
||||
/** Average reward */
|
||||
averageReward: number;
|
||||
|
||||
/** Model parameter count */
|
||||
modelParameters: number;
|
||||
}
|
||||
Reference in New Issue
Block a user