- Set correct table ID: mx149yctebfwvys (ai_data_Memory) - Updated type column with SingleSelect options - Added all required columns for corrections, preferences, episodes, decisions, validation - Fixed column options for type, severity, status fields
350 lines
9.2 KiB
JavaScript
350 lines
9.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Memory Service - NocoDB Integration
|
|
*
|
|
* Uses ai_data_Memory table (mx149yctebfwvys) in Agent base (pedwxnsn51vxdq2)
|
|
* Single table with 'type' field to distinguish record types
|
|
*/
|
|
|
|
const axios = require('axios');
|
|
|
|
class MemoryService {
|
|
constructor(config = {}) {
|
|
this.nocodbUrl = config.nocodbUrl || 'http://192.168.25.5:8080';
|
|
this.nocodbToken = config.nocodbToken || process.env.NOCODB_TOKEN;
|
|
this.baseId = config.baseId || 'pedwxnsn51vxdq2';
|
|
this.tableId = config.tableId || 'mx149yctebfwvys'; // ai_data_Memory
|
|
}
|
|
|
|
/**
|
|
* Get HTTP client with auth
|
|
*/
|
|
getClient() {
|
|
return axios.create({
|
|
baseURL: `${this.nocodbUrl}/api/v2/tables/${this.tableId}`,
|
|
headers: {
|
|
'xc-token': this.nocodbToken,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============================================
|
|
// CORRECTIONS API
|
|
// ============================================
|
|
|
|
/**
|
|
* Load all active corrections
|
|
*/
|
|
async loadCorrections() {
|
|
const client = this.getClient();
|
|
const response = await client.get('/records', {
|
|
params: {
|
|
where: `(type,eq,correction)~and(blocking,eq,true)`,
|
|
limit: 1000
|
|
}
|
|
});
|
|
return response.data.list.map(this.parseCorrectionRecord);
|
|
}
|
|
|
|
/**
|
|
* Store new correction
|
|
*/
|
|
async storeCorrection(correction) {
|
|
const client = this.getClient();
|
|
|
|
const payload = {
|
|
type: 'correction',
|
|
id: correction.id,
|
|
pattern: correction.pattern,
|
|
exclude_pattern: correction.excludePattern || null,
|
|
severity: correction.severity,
|
|
message: correction.message,
|
|
suggestion: correction.suggestion,
|
|
blocking: correction.blocking !== false,
|
|
auto_detected: correction.autoDetected || true,
|
|
manual_entry: correction.manualEntry || false,
|
|
hit_count: 0,
|
|
context: correction.context || 'unknown',
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
};
|
|
|
|
const response = await client.post('/records', payload);
|
|
return this.parseCorrectionRecord(response.data);
|
|
}
|
|
|
|
/**
|
|
* Increment hit count for correction
|
|
*/
|
|
async incrementCorrectionHit(correctionId) {
|
|
const client = this.getClient();
|
|
|
|
// Get current record
|
|
const current = await client.get(`/records/${correctionId}`);
|
|
const newCount = (current.data.hit_count || 0) + 1;
|
|
|
|
await client.patch(`/records/${correctionId}`, {
|
|
hit_count: newCount,
|
|
last_triggered: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
});
|
|
|
|
return newCount;
|
|
}
|
|
|
|
parseCorrectionRecord(record) {
|
|
return {
|
|
id: record.id,
|
|
pattern: record.pattern,
|
|
excludePattern: record.exclude_pattern,
|
|
severity: record.severity,
|
|
message: record.message,
|
|
suggestion: record.suggestion,
|
|
blocking: record.blocking,
|
|
autoDetected: record.auto_detected,
|
|
manualEntry: record.manual_entry,
|
|
hitCount: record.hit_count,
|
|
lastTriggered: record.last_triggered,
|
|
context: record.context,
|
|
createdAt: record.created_at
|
|
};
|
|
}
|
|
|
|
// ============================================
|
|
// PREFERENCES API
|
|
// ============================================
|
|
|
|
/**
|
|
* Get preferences by category
|
|
*/
|
|
async getPreferences(category, minImportance = 5) {
|
|
const client = this.getClient();
|
|
|
|
const response = await client.get('/records', {
|
|
params: {
|
|
where: `(type,eq,preference)~and(category,eq,${category})~and(importance,gte,${minImportance})`,
|
|
sort: '-importance,-access_count'
|
|
}
|
|
});
|
|
|
|
return response.data.list.map(this.parsePreferenceRecord);
|
|
}
|
|
|
|
/**
|
|
* Store preference
|
|
*/
|
|
async storePreference(pref) {
|
|
const client = this.getClient();
|
|
|
|
const payload = {
|
|
type: 'preference',
|
|
id: pref.id,
|
|
category: pref.category,
|
|
key: pref.key,
|
|
value: pref.value,
|
|
importance: pref.importance || 5,
|
|
confidence: pref.confidence || 1.0,
|
|
tags: JSON.stringify(pref.tags || []),
|
|
confirmed_count: pref.confirmedCount || 0,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
};
|
|
|
|
const response = await client.post('/records', payload);
|
|
return this.parsePreferenceRecord(response.data);
|
|
}
|
|
|
|
parsePreferenceRecord(record) {
|
|
return {
|
|
id: record.id,
|
|
category: record.category,
|
|
key: record.key,
|
|
value: record.value,
|
|
importance: record.importance,
|
|
confidence: record.confidence,
|
|
tags: JSON.parse(record.tags || '[]'),
|
|
accessCount: record.access_count,
|
|
lastAccessed: record.last_accessed,
|
|
confirmedCount: record.confirmed_count,
|
|
createdAt: record.created_at
|
|
};
|
|
}
|
|
|
|
// ============================================
|
|
// EPISODES API
|
|
// ============================================
|
|
|
|
/**
|
|
* Store episodic memory
|
|
*/
|
|
async storeEpisode(episode) {
|
|
const client = this.getClient();
|
|
|
|
const payload = {
|
|
type: 'episode',
|
|
id: episode.id,
|
|
date: episode.date || new Date().toISOString().split('T')[0],
|
|
summary: episode.summary,
|
|
details: episode.details,
|
|
project: episode.project,
|
|
outcomes: JSON.stringify(episode.outcomes || []),
|
|
corrections_triggered: JSON.stringify(episode.correctionsTriggered || []),
|
|
created_at: new Date().toISOString()
|
|
};
|
|
|
|
const response = await client.post('/records', payload);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Get recent episodes
|
|
*/
|
|
async getEpisodes(limit = 10) {
|
|
const client = this.getClient();
|
|
|
|
const response = await client.get('/records', {
|
|
params: {
|
|
where: `(type,eq,episode)`,
|
|
sort: '-date',
|
|
limit
|
|
}
|
|
});
|
|
|
|
return response.data.list;
|
|
}
|
|
|
|
// ============================================
|
|
// DECISIONS API
|
|
// ============================================
|
|
|
|
/**
|
|
* Store decision
|
|
*/
|
|
async storeDecision(decision) {
|
|
const client = this.getClient();
|
|
|
|
const payload = {
|
|
type: 'decision',
|
|
id: decision.id,
|
|
date: decision.date || new Date().toISOString().split('T')[0],
|
|
project: decision.project,
|
|
decision: decision.decision,
|
|
alternatives: JSON.stringify(decision.alternatives || []),
|
|
rationale: decision.rationale,
|
|
status: decision.status || 'active',
|
|
created_at: new Date().toISOString()
|
|
};
|
|
|
|
const response = await client.post('/records', payload);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Get active decisions
|
|
*/
|
|
async getActiveDecisions(project) {
|
|
const client = this.getClient();
|
|
|
|
const params = {
|
|
where: `(type,eq,decision)~and(status,eq,active)`,
|
|
sort: '-date'
|
|
};
|
|
|
|
if (project) {
|
|
params.where += `~and(project,eq,${project})`;
|
|
}
|
|
|
|
const response = await client.get('/records', { params });
|
|
return response.data.list;
|
|
}
|
|
|
|
// ============================================
|
|
// VALIDATION LOGGING
|
|
// ============================================
|
|
|
|
/**
|
|
* Log validation run
|
|
*/
|
|
async logValidation(run) {
|
|
const client = this.getClient();
|
|
|
|
const payload = {
|
|
type: 'validation',
|
|
id: run.id,
|
|
session_id: run.sessionId,
|
|
timestamp: new Date().toISOString(),
|
|
input_length: run.inputLength,
|
|
output_length: run.outputLength,
|
|
violations_found: run.violationsFound || 0,
|
|
new_patterns_detected: run.newPatternsDetected || 0,
|
|
corrections_auto_stored: run.correctionsAutoStored || 0,
|
|
processing_time_ms: run.processingTimeMs,
|
|
blocked: run.blocked || false,
|
|
workflow: run.workflow
|
|
};
|
|
|
|
const response = await client.post('/records', payload);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Get validation statistics
|
|
*/
|
|
async getValidationStats(days = 7) {
|
|
const client = this.getClient();
|
|
|
|
const cutoff = new Date();
|
|
cutoff.setDate(cutoff.getDate() - days);
|
|
const cutoffStr = cutoff.toISOString();
|
|
|
|
const response = await client.get('/records', {
|
|
params: {
|
|
where: `(type,eq,validation)~and(timestamp,gte,${cutoffStr})`,
|
|
fields: 'blocked,violations_found,corrections_auto_stored'
|
|
}
|
|
});
|
|
|
|
const runs = response.data.list;
|
|
|
|
return {
|
|
totalRuns: runs.length,
|
|
blockedCount: runs.filter(r => r.blocked).length,
|
|
totalViolations: runs.reduce((sum, r) => sum + (r.violations_found || 0), 0),
|
|
totalAutoCorrections: runs.reduce((sum, r) => sum + (r.corrections_auto_stored || 0), 0),
|
|
blockRate: runs.length > 0 ? (runs.filter(r => r.blocked).length / runs.length * 100).toFixed(1) : 0
|
|
};
|
|
}
|
|
|
|
// ============================================
|
|
// UTILITY
|
|
// ============================================
|
|
|
|
/**
|
|
* Test connection
|
|
*/
|
|
async testConnection() {
|
|
const client = this.getClient();
|
|
|
|
try {
|
|
const response = await client.get('/records', {
|
|
params: { limit: 1 }
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Connected to ai_data_Memory table',
|
|
recordCount: response.data.pageInfo?.totalRows || 0
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
success: false,
|
|
message: err.response?.data?.error || err.message
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export
|
|
module.exports = MemoryService;
|