Update memory service with working NocoDB configuration

- 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
This commit is contained in:
JC Beasley
2026-07-04 17:02:40 -07:00
parent cf3f1e1c53
commit cb5e26b561
453 changed files with 386793 additions and 64 deletions
+135 -64
View File
@@ -1,11 +1,9 @@
#!/usr/bin/env node
/**
* Memory Service - NocoDB + Critic Integration
* Memory Service - NocoDB Integration
*
* Provides:
* - Structured memory storage (preferences, episodes, decisions)
* - Correction store (self-correcting patterns)
* - Validation logging
* Uses ai_data_Memory table (mx149yctebfwvys) in Agent base (pedwxnsn51vxdq2)
* Single table with 'type' field to distinguish record types
*/
const axios = require('axios');
@@ -14,16 +12,8 @@ class MemoryService {
constructor(config = {}) {
this.nocodbUrl = config.nocodbUrl || 'http://192.168.25.5:8080';
this.nocodbToken = config.nocodbToken || process.env.NOCODB_TOKEN;
this.projectId = config.projectId || 'default';
// Table mappings (NocoDB table IDs)
this.tables = {
corrections: config.correctionsTable || 'corrections',
preferences: config.preferencesTable || 'memory_preferences',
episodes: config.episodesTable || 'memory_episodes',
decisions: config.decisionsTable || 'memory_decisions',
validation: config.validationTable || 'validation_runs'
};
this.baseId = config.baseId || 'pedwxnsn51vxdq2';
this.tableId = config.tableId || 'mx149yctebfwvys'; // ai_data_Memory
}
/**
@@ -31,7 +21,7 @@ class MemoryService {
*/
getClient() {
return axios.create({
baseURL: `${this.nocodbUrl}/api/v2/tables`,
baseURL: `${this.nocodbUrl}/api/v2/tables/${this.tableId}`,
headers: {
'xc-token': this.nocodbToken,
'Content-Type': 'application/json'
@@ -48,13 +38,13 @@ class MemoryService {
*/
async loadCorrections() {
const client = this.getClient();
const response = await client.get(`/${this.tables.corrections}/records`, {
const response = await client.get('/records', {
params: {
where: `(blocking,eq,true)`,
where: `(type,eq,correction)~and(blocking,eq,true)`,
limit: 1000
}
});
return response.data.list;
return response.data.list.map(this.parseCorrectionRecord);
}
/**
@@ -64,6 +54,7 @@ class MemoryService {
const client = this.getClient();
const payload = {
type: 'correction',
id: correction.id,
pattern: correction.pattern,
exclude_pattern: correction.excludePattern || null,
@@ -79,12 +70,8 @@ class MemoryService {
updated_at: new Date().toISOString()
};
const response = await client.post(
`/${this.tables.corrections}/records`,
payload
);
return response.data;
const response = await client.post('/records', payload);
return this.parseCorrectionRecord(response.data);
}
/**
@@ -93,25 +80,37 @@ class MemoryService {
async incrementCorrectionHit(correctionId) {
const client = this.getClient();
// Get current hit count
const current = await client.get(
`/${this.tables.corrections}/records/${correctionId}`
);
// Get current record
const current = await client.get(`/records/${correctionId}`);
const newCount = (current.data.hit_count || 0) + 1;
await client.patch(
`/${this.tables.corrections}/records/${correctionId}`,
{
hit_count: newCount,
last_triggered: new Date().toISOString(),
updated_at: new Date().toISOString()
}
);
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
// ============================================
@@ -122,14 +121,14 @@ class MemoryService {
async getPreferences(category, minImportance = 5) {
const client = this.getClient();
const response = await client.get(`/${this.tables.preferences}/records`, {
const response = await client.get('/records', {
params: {
where: `(category,eq,${category})~and(importance,gte,${minImportance})`,
where: `(type,eq,preference)~and(category,eq,${category})~and(importance,gte,${minImportance})`,
sort: '-importance,-access_count'
}
});
return response.data.list;
return response.data.list.map(this.parsePreferenceRecord);
}
/**
@@ -139,6 +138,7 @@ class MemoryService {
const client = this.getClient();
const payload = {
type: 'preference',
id: pref.id,
category: pref.category,
key: pref.key,
@@ -151,12 +151,24 @@ class MemoryService {
updated_at: new Date().toISOString()
};
const response = await client.post(
`/${this.tables.preferences}/records`,
payload
);
return response.data;
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
};
}
// ============================================
@@ -170,6 +182,7 @@ class MemoryService {
const client = this.getClient();
const payload = {
type: 'episode',
id: episode.id,
date: episode.date || new Date().toISOString().split('T')[0],
summary: episode.summary,
@@ -180,14 +193,27 @@ class MemoryService {
created_at: new Date().toISOString()
};
const response = await client.post(
`/${this.tables.episodes}/records`,
payload
);
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
// ============================================
@@ -199,6 +225,7 @@ class MemoryService {
const client = this.getClient();
const payload = {
type: 'decision',
id: decision.id,
date: decision.date || new Date().toISOString().split('T')[0],
project: decision.project,
@@ -209,14 +236,29 @@ class MemoryService {
created_at: new Date().toISOString()
};
const response = await client.post(
`/${this.tables.decisions}/records`,
payload
);
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
// ============================================
@@ -228,6 +270,7 @@ class MemoryService {
const client = this.getClient();
const payload = {
type: 'validation',
id: run.id,
session_id: run.sessionId,
timestamp: new Date().toISOString(),
@@ -241,11 +284,7 @@ class MemoryService {
workflow: run.workflow
};
const response = await client.post(
`/${this.tables.validation}/records`,
payload
);
const response = await client.post('/records', payload);
return response.data;
}
@@ -255,9 +294,13 @@ class MemoryService {
async getValidationStats(days = 7) {
const client = this.getClient();
const response = await client.get(`/${this.tables.validation}/records`, {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
const cutoffStr = cutoff.toISOString();
const response = await client.get('/records', {
params: {
where: `(timestamp,gte,${days} days ago})`,
where: `(type,eq,validation)~and(timestamp,gte,${cutoffStr})`,
fields: 'blocked,violations_found,corrections_auto_stored'
}
});
@@ -272,6 +315,34 @@ class MemoryService {
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