noormme 1.2.0 ā 1.2.1
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/README.md +60 -6
- package/dist/cjs/agentic/ActionJournal.d.ts +5 -2
- package/dist/cjs/agentic/ActionJournal.js +13 -5
- package/dist/cjs/agentic/CapabilityManager.d.ts +7 -0
- package/dist/cjs/agentic/CapabilityManager.js +84 -7
- package/dist/cjs/agentic/CognitiveRepository.js +3 -6
- package/dist/cjs/agentic/Cortex.d.ts +4 -0
- package/dist/cjs/agentic/Cortex.js +38 -17
- package/dist/cjs/agentic/EpisodicMemory.d.ts +5 -1
- package/dist/cjs/agentic/EpisodicMemory.js +11 -4
- package/dist/cjs/agentic/PersonaManager.js +37 -31
- package/dist/cjs/agentic/PolicyEnforcer.d.ts +6 -1
- package/dist/cjs/agentic/PolicyEnforcer.js +74 -17
- package/dist/cjs/agentic/ResourceMonitor.d.ts +9 -0
- package/dist/cjs/agentic/ResourceMonitor.js +36 -2
- package/dist/cjs/agentic/SessionManager.js +24 -17
- package/dist/cjs/agentic/VectorIndexer.d.ts +1 -0
- package/dist/cjs/agentic/VectorIndexer.js +26 -17
- package/dist/cjs/agentic/improvement/AblationEngine.d.ts +4 -6
- package/dist/cjs/agentic/improvement/AblationEngine.js +57 -37
- package/dist/cjs/agentic/improvement/ActionRefiner.js +30 -14
- package/dist/cjs/agentic/improvement/ConflictResolver.d.ts +3 -1
- package/dist/cjs/agentic/improvement/ConflictResolver.js +59 -47
- package/dist/cjs/agentic/improvement/CortexJanitor.js +11 -0
- package/dist/cjs/agentic/improvement/CuriosityEngine.d.ts +1 -1
- package/dist/cjs/agentic/improvement/CuriosityEngine.js +48 -21
- package/dist/cjs/agentic/improvement/EvolutionRitual.js +26 -14
- package/dist/cjs/agentic/improvement/EvolutionaryPilot.js +16 -4
- package/dist/cjs/agentic/improvement/GoalArchitect.d.ts +6 -2
- package/dist/cjs/agentic/improvement/GoalArchitect.js +72 -34
- package/dist/cjs/agentic/improvement/GovernanceManager.d.ts +9 -3
- package/dist/cjs/agentic/improvement/GovernanceManager.js +232 -92
- package/dist/cjs/agentic/improvement/HiveLink.d.ts +7 -3
- package/dist/cjs/agentic/improvement/HiveLink.js +135 -113
- package/dist/cjs/agentic/improvement/KnowledgeDistiller.js +43 -35
- package/dist/cjs/agentic/improvement/QuotaManager.d.ts +41 -0
- package/dist/cjs/agentic/improvement/QuotaManager.js +185 -0
- package/dist/cjs/agentic/improvement/RecursiveReasoner.js +50 -25
- package/dist/cjs/agentic/improvement/ReflectionEngine.d.ts +4 -1
- package/dist/cjs/agentic/improvement/ReflectionEngine.js +3 -1
- package/dist/cjs/agentic/improvement/RitualOrchestrator.js +27 -16
- package/dist/cjs/agentic/improvement/RuleEngine.d.ts +1 -1
- package/dist/cjs/agentic/improvement/RuleEngine.js +10 -4
- package/dist/cjs/agentic/improvement/SelfEvolution.js +21 -17
- package/dist/cjs/agentic/improvement/SelfTestRegistry.d.ts +5 -0
- package/dist/cjs/agentic/improvement/SelfTestRegistry.js +129 -109
- package/dist/cjs/agentic/improvement/SkillSynthesizer.d.ts +1 -0
- package/dist/cjs/agentic/improvement/SkillSynthesizer.js +22 -13
- package/dist/cjs/agentic/improvement/StrategicPlanner.d.ts +1 -0
- package/dist/cjs/agentic/improvement/StrategicPlanner.js +26 -19
- package/dist/cjs/agentic/telemetry/CognitiveSynthesizer.d.ts +5 -0
- package/dist/cjs/agentic/telemetry/CognitiveSynthesizer.js +54 -12
- package/dist/cjs/agentic/telemetry/EventHarvester.d.ts +1 -1
- package/dist/cjs/agentic/telemetry/EventHarvester.js +10 -3
- package/dist/cjs/agentic/telemetry/ResearchAlchemist.d.ts +7 -2
- package/dist/cjs/agentic/telemetry/ResearchAlchemist.js +49 -8
- package/dist/cjs/agentic/telemetry/TelemetryOrchestrator.d.ts +4 -1
- package/dist/cjs/agentic/telemetry/TelemetryOrchestrator.js +38 -11
- package/dist/cjs/cli/commands/inspect.js +40 -1
- package/dist/cjs/cli/commands/watch.js +31 -25
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +15 -5
- package/dist/cjs/helpers/agent-schema.js +1 -0
- package/dist/cjs/migration/data_migrator.js +4 -4
- package/dist/cjs/migration/schema_differ.js +37 -15
- package/dist/cjs/types/index.d.ts +12 -0
- package/dist/cjs/util/safe-sql-helpers.js +7 -10
- package/dist/esm/agentic/ActionJournal.d.ts +5 -2
- package/dist/esm/agentic/ActionJournal.js +13 -5
- package/dist/esm/agentic/CapabilityManager.d.ts +7 -0
- package/dist/esm/agentic/CapabilityManager.js +84 -7
- package/dist/esm/agentic/CognitiveRepository.js +3 -6
- package/dist/esm/agentic/Cortex.d.ts +4 -0
- package/dist/esm/agentic/Cortex.js +38 -17
- package/dist/esm/agentic/EpisodicMemory.d.ts +5 -1
- package/dist/esm/agentic/EpisodicMemory.js +11 -4
- package/dist/esm/agentic/PersonaManager.js +37 -31
- package/dist/esm/agentic/PolicyEnforcer.d.ts +6 -1
- package/dist/esm/agentic/PolicyEnforcer.js +74 -17
- package/dist/esm/agentic/ResourceMonitor.d.ts +9 -0
- package/dist/esm/agentic/ResourceMonitor.js +36 -2
- package/dist/esm/agentic/SessionManager.js +24 -17
- package/dist/esm/agentic/VectorIndexer.d.ts +1 -0
- package/dist/esm/agentic/VectorIndexer.js +26 -17
- package/dist/esm/agentic/improvement/AblationEngine.d.ts +4 -6
- package/dist/esm/agentic/improvement/AblationEngine.js +57 -37
- package/dist/esm/agentic/improvement/ActionRefiner.js +30 -14
- package/dist/esm/agentic/improvement/ConflictResolver.d.ts +3 -1
- package/dist/esm/agentic/improvement/ConflictResolver.js +59 -47
- package/dist/esm/agentic/improvement/CortexJanitor.js +11 -0
- package/dist/esm/agentic/improvement/CuriosityEngine.d.ts +1 -1
- package/dist/esm/agentic/improvement/CuriosityEngine.js +48 -21
- package/dist/esm/agentic/improvement/EvolutionRitual.js +26 -14
- package/dist/esm/agentic/improvement/EvolutionaryPilot.js +16 -4
- package/dist/esm/agentic/improvement/GoalArchitect.d.ts +6 -2
- package/dist/esm/agentic/improvement/GoalArchitect.js +72 -34
- package/dist/esm/agentic/improvement/GovernanceManager.d.ts +9 -3
- package/dist/esm/agentic/improvement/GovernanceManager.js +232 -92
- package/dist/esm/agentic/improvement/HiveLink.d.ts +7 -3
- package/dist/esm/agentic/improvement/HiveLink.js +135 -113
- package/dist/esm/agentic/improvement/KnowledgeDistiller.js +43 -35
- package/dist/esm/agentic/improvement/QuotaManager.d.ts +41 -0
- package/dist/esm/agentic/improvement/QuotaManager.js +182 -0
- package/dist/esm/agentic/improvement/RecursiveReasoner.js +50 -25
- package/dist/esm/agentic/improvement/ReflectionEngine.d.ts +4 -1
- package/dist/esm/agentic/improvement/ReflectionEngine.js +3 -1
- package/dist/esm/agentic/improvement/RitualOrchestrator.js +27 -16
- package/dist/esm/agentic/improvement/RuleEngine.d.ts +1 -1
- package/dist/esm/agentic/improvement/RuleEngine.js +10 -4
- package/dist/esm/agentic/improvement/SelfEvolution.js +21 -17
- package/dist/esm/agentic/improvement/SelfTestRegistry.d.ts +5 -0
- package/dist/esm/agentic/improvement/SelfTestRegistry.js +129 -109
- package/dist/esm/agentic/improvement/SkillSynthesizer.d.ts +1 -0
- package/dist/esm/agentic/improvement/SkillSynthesizer.js +22 -13
- package/dist/esm/agentic/improvement/StrategicPlanner.d.ts +1 -0
- package/dist/esm/agentic/improvement/StrategicPlanner.js +26 -19
- package/dist/esm/agentic/telemetry/CognitiveSynthesizer.d.ts +5 -0
- package/dist/esm/agentic/telemetry/CognitiveSynthesizer.js +54 -12
- package/dist/esm/agentic/telemetry/EventHarvester.d.ts +1 -1
- package/dist/esm/agentic/telemetry/EventHarvester.js +10 -3
- package/dist/esm/agentic/telemetry/ResearchAlchemist.d.ts +7 -2
- package/dist/esm/agentic/telemetry/ResearchAlchemist.js +49 -8
- package/dist/esm/agentic/telemetry/TelemetryOrchestrator.d.ts +4 -1
- package/dist/esm/agentic/telemetry/TelemetryOrchestrator.js +38 -11
- package/dist/esm/cli/commands/inspect.js +40 -1
- package/dist/esm/cli/commands/watch.js +31 -25
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +15 -5
- package/dist/esm/helpers/agent-schema.js +1 -0
- package/dist/esm/migration/data_migrator.js +4 -4
- package/dist/esm/migration/schema_differ.js +37 -15
- package/dist/esm/types/index.d.ts +12 -0
- package/dist/esm/util/safe-sql-helpers.js +7 -10
- package/package.json +1 -1
|
@@ -36,11 +36,14 @@ export class StrategicPlanner {
|
|
|
36
36
|
.selectFrom(this.personasTable)
|
|
37
37
|
.selectAll()
|
|
38
38
|
.execute();
|
|
39
|
+
const allParsedPersonas = personas.map((p) => this.parsePersona(p));
|
|
40
|
+
const globalBlacklistDuration = this.config.strategy?.globalBlacklistDuration || 3600000; // 1 hour
|
|
41
|
+
const localBlacklistDuration = this.config.strategy?.localBlacklistDuration || 86400000; // 24 hours
|
|
39
42
|
for (const p of personas) {
|
|
40
43
|
const persona = this.parsePersona(p);
|
|
41
44
|
// 1. Verification Monitor
|
|
42
45
|
if (persona.metadata?.evolution_status === 'verifying') {
|
|
43
|
-
const result = await this.verifyEvolution(persona);
|
|
46
|
+
const result = await this.verifyEvolution(persona, allParsedPersonas);
|
|
44
47
|
if (result)
|
|
45
48
|
mutations.push(result);
|
|
46
49
|
continue;
|
|
@@ -50,20 +53,15 @@ export class StrategicPlanner {
|
|
|
50
53
|
const report = await this.analyzePersona(persona.id);
|
|
51
54
|
// 3. Blacklist Check (Local & Global Phase 5)
|
|
52
55
|
const lastMutation = persona.metadata?.last_failed_mutation;
|
|
53
|
-
const
|
|
54
|
-
.selectFrom(this.personasTable)
|
|
55
|
-
.selectAll()
|
|
56
|
-
.execute();
|
|
57
|
-
const isGloballyBlacklisted = allPersonas.some((p) => {
|
|
58
|
-
const mp = this.parsePersona(p);
|
|
56
|
+
const isGloballyBlacklisted = allParsedPersonas.some((mp) => {
|
|
59
57
|
return (mp.metadata?.last_failed_mutation?.type === report.recommendation &&
|
|
60
58
|
Date.now() - (mp.metadata?.last_failed_mutation?.timestamp || 0) <
|
|
61
|
-
|
|
59
|
+
globalBlacklistDuration);
|
|
62
60
|
});
|
|
63
61
|
if (isGloballyBlacklisted ||
|
|
64
62
|
(lastMutation &&
|
|
65
63
|
report.recommendation === lastMutation.type &&
|
|
66
|
-
Date.now() - lastMutation.timestamp <
|
|
64
|
+
Date.now() - lastMutation.timestamp < localBlacklistDuration)) {
|
|
67
65
|
console.log(`[StrategicPlanner] Skipping blacklisted mutation ${report.recommendation} for persona ${persona.id} (Global=${isGloballyBlacklisted})`);
|
|
68
66
|
continue;
|
|
69
67
|
}
|
|
@@ -109,15 +107,19 @@ export class StrategicPlanner {
|
|
|
109
107
|
.filter((p) => (p.metadata?.evolution_status === 'stable' ||
|
|
110
108
|
!p.metadata?.evolution_status) &&
|
|
111
109
|
p.metadata?.mutation_reason?.includes(report.recommendation));
|
|
112
|
-
if (winningMutations.length > 0
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
if (winningMutations.length > 0) {
|
|
111
|
+
// PRODUCTION HARDENING: Deterministic Alpha Selection
|
|
112
|
+
// Instead of Math.random(), pick the variant with the highest anchored reliability
|
|
113
|
+
const sorted = winningMutations.sort((a, b) => (b.metadata?.anchored_reliability || 0) - (a.metadata?.anchored_reliability || 0));
|
|
114
|
+
const alphaMatch = sorted[0];
|
|
115
|
+
console.log(`[StrategicPlanner] Cross-Pollinating success from Alpha Persona ${alphaMatch.id} (Reliability: ${alphaMatch.metadata?.anchored_reliability || 0})`);
|
|
116
|
+
updates = { role: this.sanitizeRole(alphaMatch.role || persona.role || 'Agent') };
|
|
115
117
|
}
|
|
116
118
|
else {
|
|
117
119
|
switch (report.recommendation) {
|
|
118
120
|
case 'optimize_accuracy':
|
|
119
121
|
updates = {
|
|
120
|
-
role: `${persona.role || ''} (Focus strictly on accuracy and detailed verification)
|
|
122
|
+
role: this.sanitizeRole(`${persona.role || ''} (Focus strictly on accuracy and detailed verification)`),
|
|
121
123
|
};
|
|
122
124
|
break;
|
|
123
125
|
case 'optimize_efficiency':
|
|
@@ -137,6 +139,9 @@ export class StrategicPlanner {
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
if (updates.role) {
|
|
143
|
+
updates.role = this.sanitizeRole(updates.role);
|
|
144
|
+
}
|
|
140
145
|
// 1. Predictive Conflict Detection (Pre-flight)
|
|
141
146
|
const proposedState = { ...persona, ...updates };
|
|
142
147
|
const contradictions = await this.cortex.reasoner.detectContradictions();
|
|
@@ -196,18 +201,13 @@ export class StrategicPlanner {
|
|
|
196
201
|
* Check if a persona in verification should be stabilized or rolled back.
|
|
197
202
|
* Uses dynamic statistical variance and adaptive meta-tuning.
|
|
198
203
|
*/
|
|
199
|
-
async verifyEvolution(persona) {
|
|
204
|
+
async verifyEvolution(persona, allPersonas = []) {
|
|
200
205
|
const report = await this.analyzePersona(persona.id);
|
|
201
206
|
// Adaptive Meta-Tuning: Increase window based on rollback history (Phase 4)
|
|
202
207
|
const rollbackHistory = persona.metadata?.rollbackHistory || [];
|
|
203
208
|
const recentRollbacks = rollbackHistory.filter((ts) => Date.now() - ts < 604800000).length;
|
|
204
209
|
// Hive-Mind Verification Speedups (Phase 5)
|
|
205
|
-
const allPersonas = await this.typedDb
|
|
206
|
-
.selectFrom(this.personasTable)
|
|
207
|
-
.selectAll()
|
|
208
|
-
.execute();
|
|
209
210
|
const hiveTrusted = allPersonas
|
|
210
|
-
.map((p) => this.parsePersona(p))
|
|
211
211
|
.filter((p) => p.metadata?.evolution_status === 'stable' &&
|
|
212
212
|
p.metadata?.mutation_reason === persona.metadata?.mutation_reason).length;
|
|
213
213
|
let sampleSizeThreshold = 10 + recentRollbacks * 10;
|
|
@@ -430,6 +430,13 @@ export class StrategicPlanner {
|
|
|
430
430
|
return `Rolled back mutation ${lastMutation.id} for persona ${id}`;
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
|
+
sanitizeRole(role) {
|
|
434
|
+
// Audit Phase 7: Semantic Security
|
|
435
|
+
// Truncate to prevent context-window exhaustion/bloat
|
|
436
|
+
const truncated = role.slice(0, 500).trim();
|
|
437
|
+
// Sanitize: remove potentially dangerous prompt-injection markers or control chars
|
|
438
|
+
return truncated.replace(/[\u0000-\u001F\u007F-\u009F]/g, '').replace(/<\|.*?\|>/g, '');
|
|
439
|
+
}
|
|
433
440
|
parsePersona(p) {
|
|
434
441
|
return {
|
|
435
442
|
id: p.id,
|
|
@@ -22,5 +22,10 @@ export declare class CognitiveSynthesizer {
|
|
|
22
22
|
private inferGoalFromContent;
|
|
23
23
|
private detectStrategy;
|
|
24
24
|
private calculateAutonomy;
|
|
25
|
+
/**
|
|
26
|
+
* Detect "Sentiment Drift" or Cognitive Friction in the input stream.
|
|
27
|
+
* Mirrors the agent's internal "frustration" or "flow" state.
|
|
28
|
+
*/
|
|
29
|
+
private detectSentimentDrift;
|
|
25
30
|
private parsePath;
|
|
26
31
|
}
|
|
@@ -19,8 +19,15 @@ export class CognitiveSynthesizer {
|
|
|
19
19
|
* Uses robust transaction logic and behavioral pathing.
|
|
20
20
|
*/
|
|
21
21
|
async synthesize(sessionId, input, pattern) {
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// PRODUCTION HARDENING: Reasoning-Driven Synthesis
|
|
23
|
+
// Instead of regex heuristics, we leverage the RecursiveReasoner for high-fidelity goal extraction
|
|
24
|
+
const cortex = this.config.cortex;
|
|
25
|
+
let goalInferred = this.inferGoalFromContent(input);
|
|
26
|
+
if (cortex?.reasoner) {
|
|
27
|
+
const reasoning = await cortex.reasoner.analyzeIntent(input);
|
|
28
|
+
if (reasoning.goal)
|
|
29
|
+
goalInferred = reasoning.goal;
|
|
30
|
+
}
|
|
24
31
|
const strategy = pattern || this.detectStrategy(input);
|
|
25
32
|
try {
|
|
26
33
|
await this.db.transaction().execute(async (trx) => {
|
|
@@ -31,14 +38,17 @@ export class CognitiveSynthesizer {
|
|
|
31
38
|
.executeTakeFirst();
|
|
32
39
|
if (existing) {
|
|
33
40
|
const currentPath = this.parsePath(existing.evolution_path);
|
|
34
|
-
// Only append to path if goal or
|
|
41
|
+
// Only append to path if goal, strategy, or sentiment shifted significantly
|
|
42
|
+
const sentiment = this.detectSentimentDrift(input);
|
|
35
43
|
if (existing.inferred_goal !== goalInferred ||
|
|
36
|
-
existing.strategy !== strategy
|
|
44
|
+
existing.strategy !== strategy ||
|
|
45
|
+
(existing.metadata && JSON.parse(existing.metadata).lastSentiment !== sentiment)) {
|
|
37
46
|
currentPath.push({
|
|
38
47
|
timestamp: new Date().toISOString(),
|
|
39
48
|
previousGoal: existing.inferred_goal,
|
|
40
49
|
newGoal: goalInferred,
|
|
41
50
|
strategyShift: strategy,
|
|
51
|
+
sentiment,
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
54
|
await trx
|
|
@@ -48,6 +58,11 @@ export class CognitiveSynthesizer {
|
|
|
48
58
|
strategy,
|
|
49
59
|
evolution_path: JSON.stringify(currentPath),
|
|
50
60
|
status: 'active',
|
|
61
|
+
metadata: JSON.stringify({
|
|
62
|
+
...JSON.parse(existing.metadata || '{}'),
|
|
63
|
+
lastSentiment: sentiment,
|
|
64
|
+
pivots: currentPath.length,
|
|
65
|
+
}),
|
|
51
66
|
autonomy_level: this.calculateAutonomy(currentPath.length, existing.autonomy_level || 1),
|
|
52
67
|
updated_at: new Date(),
|
|
53
68
|
})
|
|
@@ -122,9 +137,13 @@ export class CognitiveSynthesizer {
|
|
|
122
137
|
inferGoalFromContent(content) {
|
|
123
138
|
// Real-world simulation: extract the first imperative sentence or key noun phrases
|
|
124
139
|
const clean = content.trim().replace(/\n/g, ' ');
|
|
140
|
+
// Hardened heuristic: prioritize imperative verbs and specific NOORMME intents
|
|
141
|
+
const imperativeMatch = clean.match(/(?:please |can you |let's )?(implement|fix|refactor|add|search|analyze|delete|evolve) .+/i);
|
|
142
|
+
if (imperativeMatch) {
|
|
143
|
+
return imperativeMatch[0].substring(0, 100) + (imperativeMatch[0].length > 100 ? '...' : '');
|
|
144
|
+
}
|
|
125
145
|
if (clean.length < 100)
|
|
126
146
|
return clean;
|
|
127
|
-
// Simple heuristic: look for "need", "want", "please", "can you"
|
|
128
147
|
const keywords = [
|
|
129
148
|
'need',
|
|
130
149
|
'want',
|
|
@@ -144,21 +163,44 @@ export class CognitiveSynthesizer {
|
|
|
144
163
|
}
|
|
145
164
|
detectStrategy(content) {
|
|
146
165
|
const c = content.toLowerCase();
|
|
147
|
-
|
|
166
|
+
// Hardened strategy detection
|
|
167
|
+
if (c.includes('debug') || c.includes('fix') || c.includes('error') || c.includes('broken'))
|
|
148
168
|
return 'Diagnostic Repair';
|
|
149
|
-
if (c.includes('create') || c.includes('build') || c.includes('implement'))
|
|
169
|
+
if (c.includes('create') || c.includes('build') || c.includes('implement') || c.includes('new file'))
|
|
150
170
|
return 'Generative Construction';
|
|
151
|
-
if (c.includes('research') || c.includes('explain') || c.includes('how'))
|
|
171
|
+
if (c.includes('research') || c.includes('explain') || c.includes('how') || c.includes('investigate'))
|
|
152
172
|
return 'Knowledge Acquisition';
|
|
173
|
+
if (c.includes('evolve') || c.includes('mutate') || c.includes('dna'))
|
|
174
|
+
return 'Self-Evolutionary';
|
|
153
175
|
return 'Adaptive Exploration';
|
|
154
176
|
}
|
|
155
177
|
calculateAutonomy(pivots, currentLevel) {
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
if (pivots >
|
|
159
|
-
return Math.
|
|
178
|
+
// Enhanced Autonomy: Lower weight on raw pivots, more on ratio
|
|
179
|
+
// If pivots are moderate but yield successful sub-goals, autonomy holds.
|
|
180
|
+
if (pivots > 10)
|
|
181
|
+
return Math.max(1, currentLevel - 1);
|
|
182
|
+
if (pivots > 5 && currentLevel > 2)
|
|
183
|
+
return currentLevel;
|
|
184
|
+
if (pivots === 0)
|
|
185
|
+
return Math.min(5, currentLevel + 1);
|
|
160
186
|
return currentLevel;
|
|
161
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Detect "Sentiment Drift" or Cognitive Friction in the input stream.
|
|
190
|
+
* Mirrors the agent's internal "frustration" or "flow" state.
|
|
191
|
+
*/
|
|
192
|
+
detectSentimentDrift(content) {
|
|
193
|
+
const c = content.toLowerCase();
|
|
194
|
+
const negativeTerms = ['slow', 'wrong', 'bad', 'error', 'failed', 'cannot', 'stuck'];
|
|
195
|
+
const postiveTerms = ['great', 'correct', 'good', 'success', 'works', 'yes'];
|
|
196
|
+
const negCount = negativeTerms.filter(t => c.includes(t)).length;
|
|
197
|
+
const posCount = postiveTerms.filter(t => c.includes(t)).length;
|
|
198
|
+
if (negCount > posCount + 1)
|
|
199
|
+
return 'frustration';
|
|
200
|
+
if (posCount > negCount)
|
|
201
|
+
return 'flow';
|
|
202
|
+
return 'neutral';
|
|
203
|
+
}
|
|
162
204
|
parsePath(pathData) {
|
|
163
205
|
if (!pathData)
|
|
164
206
|
return [];
|
|
@@ -6,7 +6,7 @@ export declare class EventHarvester {
|
|
|
6
6
|
private telemetryTable;
|
|
7
7
|
constructor(db: Kysely<any>, config?: AgenticConfig);
|
|
8
8
|
/**
|
|
9
|
-
* Persist a raw telemetry event
|
|
9
|
+
* Persist a raw telemetry event with safety valves for scale.
|
|
10
10
|
*/
|
|
11
11
|
harvest(sessionId: string | number, type: TelemetryEvent['type'], content: string, metadata?: Record<string, any>): Promise<void>;
|
|
12
12
|
}
|
|
@@ -10,17 +10,24 @@ export class EventHarvester {
|
|
|
10
10
|
config.telemetryEventsTable || 'agent_telemetry_events';
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Persist a raw telemetry event
|
|
13
|
+
* Persist a raw telemetry event with safety valves for scale.
|
|
14
14
|
*/
|
|
15
15
|
async harvest(sessionId, type, content, metadata) {
|
|
16
|
+
// 1. Safety Valve: Drop excessively large payloads (> 100KB)
|
|
17
|
+
if (content.length > 102400) {
|
|
18
|
+
console.warn(`[EventHarvester] DROPPING EVENT: Content exceeds 100KB safety limit (${content.length} bytes)`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// 2. Metadata Integrity: Ensure metadata is a valid object
|
|
22
|
+
const finalMetadata = metadata && typeof metadata === 'object' ? metadata : {};
|
|
16
23
|
try {
|
|
17
24
|
await this.db
|
|
18
25
|
.insertInto(this.telemetryTable)
|
|
19
26
|
.values({
|
|
20
27
|
session_id: sessionId,
|
|
21
28
|
type,
|
|
22
|
-
content,
|
|
23
|
-
metadata:
|
|
29
|
+
content: content.substring(0, 50000), // Hard truncated for storage safety
|
|
30
|
+
metadata: JSON.stringify(finalMetadata),
|
|
24
31
|
created_at: new Date(),
|
|
25
32
|
})
|
|
26
33
|
.execute();
|
|
@@ -6,11 +6,16 @@ export declare class ResearchAlchemist {
|
|
|
6
6
|
private metricsTable;
|
|
7
7
|
constructor(db: Kysely<any>, config?: AgenticConfig);
|
|
8
8
|
/**
|
|
9
|
-
* Transmute raw events into research metrics
|
|
9
|
+
* Transmute raw events into research metrics with statistical noise filtering.
|
|
10
10
|
*/
|
|
11
11
|
transmute(sessionId: string | number, metricName: ResearchMetric['metricName'], value: number, metadata?: Record<string, any>): Promise<void>;
|
|
12
12
|
/**
|
|
13
|
-
* Calculate novelty
|
|
13
|
+
* Calculate discovery velocity: rate of novelty acquisition in the last 10 minutes.
|
|
14
|
+
*/
|
|
15
|
+
private calculateDiscoveryVelocity;
|
|
16
|
+
/**
|
|
17
|
+
* Calculate novelty or Discovery Index.
|
|
18
|
+
* Uses cross-session frequency analysis to determine actual novelty.
|
|
14
19
|
*/
|
|
15
20
|
trackDiscovery(sessionId: string | number, taskType: string): Promise<void>;
|
|
16
21
|
/**
|
|
@@ -9,9 +9,15 @@ export class ResearchAlchemist {
|
|
|
9
9
|
this.metricsTable = config.researchMetricsTable || 'agent_research_metrics';
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Transmute raw events into research metrics
|
|
12
|
+
* Transmute raw events into research metrics with statistical noise filtering.
|
|
13
13
|
*/
|
|
14
14
|
async transmute(sessionId, metricName, value, metadata) {
|
|
15
|
+
// Hardening: Prevent extreme outliers from skewing discovery data
|
|
16
|
+
const magicCeiling = this.config.research?.magicCeiling || 100;
|
|
17
|
+
if (metricName === 'time_to_magic' && (value < 0 || value > magicCeiling)) {
|
|
18
|
+
console.warn(`[ResearchAlchemist] Filtering outlier magic score: ${value}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
15
21
|
try {
|
|
16
22
|
await this.db
|
|
17
23
|
.insertInto(this.metricsTable)
|
|
@@ -23,26 +29,61 @@ export class ResearchAlchemist {
|
|
|
23
29
|
created_at: new Date(),
|
|
24
30
|
})
|
|
25
31
|
.execute();
|
|
32
|
+
// Secondary Transmutation: Calculate discovery velocity
|
|
33
|
+
if (metricName === 'discovery_index') {
|
|
34
|
+
await this.calculateDiscoveryVelocity(sessionId);
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
catch (e) {
|
|
28
38
|
console.warn(`[ResearchAlchemist] Failed to transmute metric ${metricName}: ${e}`);
|
|
29
39
|
}
|
|
30
40
|
}
|
|
31
41
|
/**
|
|
32
|
-
* Calculate novelty
|
|
42
|
+
* Calculate discovery velocity: rate of novelty acquisition in the last 10 minutes.
|
|
43
|
+
*/
|
|
44
|
+
async calculateDiscoveryVelocity(sessionId) {
|
|
45
|
+
const windowMs = this.config.research?.velocityWindowMinutes * 60 * 1000 || 10 * 60 * 1000;
|
|
46
|
+
const recent = await this.db
|
|
47
|
+
.selectFrom(this.metricsTable)
|
|
48
|
+
.select((eb) => eb.fn.count('id').as('count'))
|
|
49
|
+
.where('session_id', '=', sessionId)
|
|
50
|
+
.where('metric_name', '=', 'discovery_index')
|
|
51
|
+
.where('created_at', '>', new Date(Date.now() - windowMs))
|
|
52
|
+
.executeTakeFirst();
|
|
53
|
+
const count = Number(recent?.count || 0);
|
|
54
|
+
const velocity = count / 10; // novelty per minute
|
|
55
|
+
// Persist velocity as its own research metric
|
|
56
|
+
await this.db
|
|
57
|
+
.insertInto(this.metricsTable)
|
|
58
|
+
.values({
|
|
59
|
+
session_id: sessionId,
|
|
60
|
+
metric_name: 'discovery_velocity',
|
|
61
|
+
value: velocity,
|
|
62
|
+
created_at: new Date(),
|
|
63
|
+
})
|
|
64
|
+
.execute();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Calculate novelty or Discovery Index.
|
|
68
|
+
* Uses cross-session frequency analysis to determine actual novelty.
|
|
33
69
|
*/
|
|
34
70
|
async trackDiscovery(sessionId, taskType) {
|
|
35
|
-
//
|
|
36
|
-
const
|
|
71
|
+
// 1. Check frequency across all recent sessions (Last 500 metrics)
|
|
72
|
+
const frequency = await this.db
|
|
37
73
|
.selectFrom(this.metricsTable)
|
|
38
|
-
.select('id')
|
|
74
|
+
.select((eb) => eb.fn.count('id').as('count'))
|
|
39
75
|
.where('metric_name', '=', 'discovery_index')
|
|
40
|
-
.where('metadata', 'like', `%${taskType}%`)
|
|
76
|
+
.where('metadata', 'like', `%${taskType.replace(/[%_]/g, '')}%`)
|
|
41
77
|
.executeTakeFirst();
|
|
42
|
-
const
|
|
78
|
+
const count = Number(frequency?.count || 0);
|
|
79
|
+
// Discovery Value: Inverse frequency (Inverted Logarithmic Scale)
|
|
80
|
+
// 1.0 = Brand new, 0.0 = Commonplace
|
|
81
|
+
const discoveryValue = Math.max(0, 1.0 - Math.log10(count + 1));
|
|
43
82
|
await this.transmute(sessionId, 'discovery_index', discoveryValue, {
|
|
44
83
|
taskType,
|
|
45
|
-
|
|
84
|
+
occurrenceCount: count,
|
|
85
|
+
isInitialDiscovery: count === 0,
|
|
86
|
+
timestamp: Date.now()
|
|
46
87
|
});
|
|
47
88
|
}
|
|
48
89
|
/**
|
|
@@ -9,9 +9,12 @@ export declare class TelemetryOrchestrator {
|
|
|
9
9
|
harvester: EventHarvester;
|
|
10
10
|
synthesizer: CognitiveSynthesizer;
|
|
11
11
|
alchemist: ResearchAlchemist;
|
|
12
|
+
private circuitBreakerActive;
|
|
13
|
+
private failureCount;
|
|
14
|
+
private lastFailureTime;
|
|
12
15
|
constructor(db: Kysely<any>, config?: AgenticConfig);
|
|
13
16
|
/**
|
|
14
|
-
* Track a raw event and trigger synthesis
|
|
17
|
+
* Track a raw event and trigger synthesis with Circuit Breaking safety.
|
|
15
18
|
*/
|
|
16
19
|
track(sessionId: string | number, type: TelemetryEvent['type'], content: string, metadata?: Record<string, any>): Promise<void>;
|
|
17
20
|
/**
|
|
@@ -8,6 +8,9 @@ export class TelemetryOrchestrator {
|
|
|
8
8
|
harvester;
|
|
9
9
|
synthesizer;
|
|
10
10
|
alchemist;
|
|
11
|
+
circuitBreakerActive = false;
|
|
12
|
+
failureCount = 0;
|
|
13
|
+
lastFailureTime = 0;
|
|
11
14
|
constructor(db, config = {}) {
|
|
12
15
|
this.db = db;
|
|
13
16
|
this.config = config;
|
|
@@ -16,21 +19,45 @@ export class TelemetryOrchestrator {
|
|
|
16
19
|
this.alchemist = new ResearchAlchemist(db, config);
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
|
-
* Track a raw event and trigger synthesis
|
|
22
|
+
* Track a raw event and trigger synthesis with Circuit Breaking safety.
|
|
20
23
|
*/
|
|
21
24
|
async track(sessionId, type, content, metadata) {
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// 1. Circuit Breaker Check
|
|
26
|
+
if (this.circuitBreakerActive) {
|
|
27
|
+
if (Date.now() - this.lastFailureTime > 60000) {
|
|
28
|
+
console.log('[TelemetryOrchestrator] Attempting circuit recovery...');
|
|
29
|
+
this.circuitBreakerActive = false;
|
|
30
|
+
this.failureCount = 0;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Drop non-critical events during outage
|
|
34
|
+
if (type !== 'error' && type !== 'magic')
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
await this.
|
|
38
|
+
try {
|
|
39
|
+
// Layer A: Harvest
|
|
40
|
+
await this.harvester.harvest(sessionId, type, content, metadata);
|
|
41
|
+
// Layer B: Synthesize
|
|
42
|
+
if (type === 'prompt' || type === 'action') {
|
|
43
|
+
await this.synthesizer.synthesize(sessionId, content);
|
|
44
|
+
}
|
|
45
|
+
// Layer C: Research triggers
|
|
46
|
+
if (type === 'magic') {
|
|
47
|
+
await this.alchemist.recordMagic(sessionId, metadata?.surpriseScore || 1.0);
|
|
48
|
+
}
|
|
49
|
+
if (type === 'pivot' || type === 'error') {
|
|
50
|
+
await this.synthesizer.trackShift(sessionId, type === 'pivot' ? 'pivot' : 'abandonment');
|
|
51
|
+
}
|
|
31
52
|
}
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
catch (e) {
|
|
54
|
+
this.failureCount++;
|
|
55
|
+
this.lastFailureTime = Date.now();
|
|
56
|
+
console.error(`[TelemetryOrchestrator] SENSOR FAILURE (${this.failureCount}): ${e}`);
|
|
57
|
+
if (this.failureCount > 5) {
|
|
58
|
+
console.error('[TelemetryOrchestrator] CIRCUIT BREAKER TRIPPED. Entering degraded mode.');
|
|
59
|
+
this.circuitBreakerActive = true;
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
62
|
}
|
|
36
63
|
/**
|
|
@@ -75,8 +75,12 @@ export async function inspect(tableName, options = {}) {
|
|
|
75
75
|
await showPerformanceOverview(db);
|
|
76
76
|
}
|
|
77
77
|
// Show automation recommendations
|
|
78
|
-
console.log('\n' + chalk.blue.bold('š” Automation Recommendations:\n'));
|
|
78
|
+
console.log('\n' + chalk.blue.bold('š” Automation Recommendations & Semantic Insights:\n'));
|
|
79
79
|
await showAutomationRecommendations(schemaInfo, db);
|
|
80
|
+
// PRODUCTION HARDENING: Soft Relationship Discovery
|
|
81
|
+
if (options.relationships) {
|
|
82
|
+
await showSoftRelationshipInsights(schemaInfo, db);
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
85
|
await db.close();
|
|
82
86
|
}
|
|
@@ -470,6 +474,41 @@ function showTableDetails(table, relationships, db, rowCount = 0) {
|
|
|
470
474
|
}
|
|
471
475
|
console.log(chalk.gray('```'));
|
|
472
476
|
}
|
|
477
|
+
async function showSoftRelationshipInsights(schemaInfo, db) {
|
|
478
|
+
const reasoner = db.agent?.cortex?.reasoner;
|
|
479
|
+
if (!reasoner)
|
|
480
|
+
return;
|
|
481
|
+
try {
|
|
482
|
+
const spinner = new AgenticSpinner();
|
|
483
|
+
spinner.start('Analyzing semantic link matrix...');
|
|
484
|
+
const insights = [];
|
|
485
|
+
for (const table of schemaInfo.tables) {
|
|
486
|
+
// Only analyze tables with potential soft links (id-like columns without FKs)
|
|
487
|
+
const targets = table.columns.filter((c) => c.name.includes('_id') &&
|
|
488
|
+
!table.foreignKeys.some((fk) => fk.column === c.name));
|
|
489
|
+
if (targets.length > 0) {
|
|
490
|
+
const analysis = await reasoner.analyzeSoftLinks(table.name, targets.map((t) => t.name));
|
|
491
|
+
if (analysis && analysis.links.length > 0) {
|
|
492
|
+
insights.push(...analysis.links);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
spinner.stop();
|
|
497
|
+
if (insights.length > 0) {
|
|
498
|
+
console.log(chalk.blue.bold('\nš§ AI-Inferred Soft Relationships:'));
|
|
499
|
+
insights.forEach(link => {
|
|
500
|
+
console.log(chalk.gray(` ⢠${link.sourceTable}.${link.sourceColumn} `) +
|
|
501
|
+
chalk.yellow('āāā') +
|
|
502
|
+
chalk.gray(` ${link.targetTable}.${link.targetColumn} `) +
|
|
503
|
+
chalk.cyan(`(Confidence: ${Math.round(link.confidence * 100)}%)`));
|
|
504
|
+
console.log(chalk.italic.gray(` Reason: ${link.reason}`));
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
// Fail silently for discovery
|
|
510
|
+
}
|
|
511
|
+
}
|
|
473
512
|
function showRelationships(relationships) {
|
|
474
513
|
if (relationships.length === 0) {
|
|
475
514
|
console.log(chalk.gray('No relationships found.'));
|
|
@@ -84,46 +84,52 @@ export async function watch(options) {
|
|
|
84
84
|
pollInterval: intervalMs,
|
|
85
85
|
enabled: true,
|
|
86
86
|
});
|
|
87
|
-
//
|
|
87
|
+
// PRODUCTION HARDENING: Resilient Polling Loop
|
|
88
|
+
let retryCount = 0;
|
|
89
|
+
const maxRetries = 10;
|
|
90
|
+
let currentInterval = intervalMs;
|
|
88
91
|
const periodicTaskInterval = setInterval(async () => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
try {
|
|
93
|
+
// Reset interval on success
|
|
94
|
+
if (retryCount > 0) {
|
|
95
|
+
console.log(chalk.green('ā
Connection restored.'));
|
|
96
|
+
retryCount = 0;
|
|
97
|
+
currentInterval = intervalMs;
|
|
98
|
+
}
|
|
99
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
100
|
+
const schemaInfo = await db.getSchemaInfo();
|
|
101
|
+
// Show periodic status
|
|
102
|
+
if (Date.now() % (intervalMs * 10) < intervalMs) {
|
|
103
|
+
console.log(chalk.gray(`[${timestamp}] Monitoring active - ${schemaInfo.tables.length} tables`));
|
|
104
|
+
}
|
|
105
|
+
// Periodic optimization if auto-optimize is enabled
|
|
106
|
+
if (autoOptimize &&
|
|
107
|
+
Date.now() - lastOptimizationTime > optimizationInterval) {
|
|
108
|
+
console.log(chalk.blue(`\nš Periodic optimization check at ${timestamp}`));
|
|
94
109
|
const metrics = await db.getSQLitePerformanceMetrics();
|
|
95
110
|
const indexRecs = await db.getSQLiteIndexRecommendations();
|
|
96
111
|
if (indexRecs.recommendations.length > 0) {
|
|
97
112
|
console.log(chalk.yellow(`š” Found ${indexRecs.recommendations.length} new index recommendations`));
|
|
98
|
-
if (autoIndex) {
|
|
99
|
-
console.log(chalk.gray('Run optimize command to apply index recommendations'));
|
|
100
|
-
}
|
|
101
113
|
}
|
|
102
|
-
// Check if optimization is needed
|
|
103
114
|
if (metrics.cacheHitRate < 0.8 || metrics.averageQueryTime > 100) {
|
|
104
115
|
console.log(chalk.yellow('ā ļø Performance degradation detected, running optimization...'));
|
|
105
116
|
const result = await db.getSQLiteOptimizations();
|
|
106
117
|
console.log(chalk.green(`ā
Generated ${result.appliedOptimizations.length} performance optimization recommendations`));
|
|
107
118
|
}
|
|
108
|
-
else {
|
|
109
|
-
console.log(chalk.green('ā
Performance metrics look good'));
|
|
110
|
-
}
|
|
111
119
|
lastOptimizationTime = Date.now();
|
|
112
120
|
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error(chalk.red('ā Periodic optimization failed:'), error instanceof Error ? error.message : error);
|
|
115
|
-
}
|
|
116
121
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.log(chalk.gray(`[${timestamp}] Monitoring active`));
|
|
122
|
+
catch (error) {
|
|
123
|
+
retryCount++;
|
|
124
|
+
const backoff = Math.min(30000, intervalMs * Math.pow(2, retryCount));
|
|
125
|
+
console.error(chalk.red(`\nā Watcher interrupted (${retryCount}/${maxRetries}):`), error instanceof Error ? error.message : String(error));
|
|
126
|
+
if (retryCount >= maxRetries) {
|
|
127
|
+
console.error(chalk.red.bold('CRITICAL: Max retries exceeded. Schema watcher terminating.'));
|
|
128
|
+
shutdown();
|
|
129
|
+
return;
|
|
126
130
|
}
|
|
131
|
+
console.log(chalk.yellow(`ā³ Retrying in ${backoff}ms...`));
|
|
132
|
+
// Note: interval remains same but we skip logic via retryCount logic or we could re-init
|
|
127
133
|
}
|
|
128
134
|
}, intervalMs);
|
|
129
135
|
// Handle graceful shutdown
|
|
@@ -38,16 +38,26 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
38
38
|
}
|
|
39
39
|
async #getTableMetadata(options) {
|
|
40
40
|
const tablesResult = await this.#tablesQuery(this.#db, options).execute();
|
|
41
|
-
//
|
|
41
|
+
// Unified Discovery: Get columns, indexes, and foreign keys for each table
|
|
42
42
|
const columnsByTable = {};
|
|
43
|
+
const indexesByTable = {};
|
|
44
|
+
const fksByTable = {};
|
|
43
45
|
for (const table of tablesResult) {
|
|
44
46
|
try {
|
|
45
|
-
const columns = await
|
|
47
|
+
const [columns, indexes, fks] = await Promise.all([
|
|
48
|
+
sql `PRAGMA table_info(${sql.lit(table.name)})`.execute(this.#db),
|
|
49
|
+
this.getIndexes(table.name),
|
|
50
|
+
this.getForeignKeys(table.name),
|
|
51
|
+
]);
|
|
46
52
|
columnsByTable[table.name] = columns.rows;
|
|
53
|
+
indexesByTable[table.name] = indexes;
|
|
54
|
+
fksByTable[table.name] = fks;
|
|
47
55
|
}
|
|
48
56
|
catch (error) {
|
|
49
|
-
console.warn(`Failed to
|
|
57
|
+
console.warn(`Failed to introspect table ${table.name}:`, error);
|
|
50
58
|
columnsByTable[table.name] = [];
|
|
59
|
+
indexesByTable[table.name] = [];
|
|
60
|
+
fksByTable[table.name] = [];
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
return tablesResult.map(({ name, sql, type }) => {
|
|
@@ -66,8 +76,8 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
66
76
|
defaultValue: col.dflt_value,
|
|
67
77
|
isPrimaryKey: col.pk > 0,
|
|
68
78
|
})),
|
|
69
|
-
indexes: [],
|
|
70
|
-
foreignKeys: [],
|
|
79
|
+
indexes: indexesByTable[name] || [],
|
|
80
|
+
foreignKeys: fksByTable[name] || [],
|
|
71
81
|
};
|
|
72
82
|
});
|
|
73
83
|
}
|
|
@@ -196,6 +196,7 @@ export class AgentSchemaHelper {
|
|
|
196
196
|
.addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
|
|
197
197
|
.addColumn('name', 'text', (col) => col.notNull().unique())
|
|
198
198
|
.addColumn('role', 'text')
|
|
199
|
+
.addColumn('status', 'text', (col) => col.notNull().defaultTo('active'))
|
|
199
200
|
.addColumn('capabilities', 'text')
|
|
200
201
|
.addColumn('policies', 'text')
|
|
201
202
|
.addColumn('metadata', 'text')
|