tuna-agent 0.1.74 → 0.1.76
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.
|
@@ -930,8 +930,45 @@ export class ClaudeCodeAdapter {
|
|
|
930
930
|
}
|
|
931
931
|
}
|
|
932
932
|
}
|
|
933
|
-
// Filter:
|
|
934
|
-
const
|
|
933
|
+
// Filter 1: Quality gate — reject garbage rules
|
|
934
|
+
const MAX_LEARNED_RULES = 20;
|
|
935
|
+
const MIN_CONFIDENCE = 3;
|
|
936
|
+
const qualityPatterns = patterns.filter(p => {
|
|
937
|
+
const r = p.rule.trim();
|
|
938
|
+
// Confidence gate — only persist rules seen 3+ times
|
|
939
|
+
if (p.confidence < MIN_CONFIDENCE) {
|
|
940
|
+
console.log(`[Self-Improve] Skipped (low confidence ${p.confidence}): "${r.substring(0, 80)}"`);
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
// Reject generic platitudes
|
|
944
|
+
if (/^always (prioritize|verify|ensure|keep an eye|strive|validate|check|monitor)/i.test(r)) {
|
|
945
|
+
console.log(`[Self-Improve] Skipped (generic): "${r.substring(0, 80)}"`);
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
if (/^(prior to|when making decisions|before engaging|before using)/i.test(r)) {
|
|
949
|
+
console.log(`[Self-Improve] Skipped (generic): "${r.substring(0, 80)}"`);
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
// Reject rules that look like app descriptions (hallucination from research)
|
|
953
|
+
if (/\b(block all apps|learned to read|language required|learnlock|user education)\b/i.test(r)) {
|
|
954
|
+
console.log(`[Self-Improve] Skipped (hallucinated from app idea): "${r.substring(0, 80)}"`);
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
// Reject too short or too vague rules
|
|
958
|
+
if (r.length < 30) {
|
|
959
|
+
console.log(`[Self-Improve] Skipped (too short): "${r}"`);
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
return true;
|
|
963
|
+
});
|
|
964
|
+
// Cap total rules — if already at max, skip adding more
|
|
965
|
+
if (existingRules.length >= MAX_LEARNED_RULES) {
|
|
966
|
+
console.log(`[Self-Improve] Already at max ${MAX_LEARNED_RULES} rules — skipping`);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const slotsAvailable = MAX_LEARNED_RULES - existingRules.length;
|
|
970
|
+
// Filter 2: skip patterns similar to ANY existing rule
|
|
971
|
+
const newPatterns = qualityPatterns.filter(p => {
|
|
935
972
|
return !existingRules.some(existing => ClaudeCodeAdapter.isSimilarRule(p.rule, existing));
|
|
936
973
|
});
|
|
937
974
|
// Also dedup among new patterns themselves
|
|
@@ -945,8 +982,8 @@ export class ClaudeCodeAdapter {
|
|
|
945
982
|
console.log(`[Self-Improve] ${patterns.length} patterns found but all similar to existing rules`);
|
|
946
983
|
return;
|
|
947
984
|
}
|
|
948
|
-
// Cap at 3 new rules per run
|
|
949
|
-
const toAdd = dedupedPatterns.slice(0, 3);
|
|
985
|
+
// Cap at 3 new rules per run, and respect total max
|
|
986
|
+
const toAdd = dedupedPatterns.slice(0, Math.min(3, slotsAvailable));
|
|
950
987
|
// Append new rules at the END of the Learned Rules section
|
|
951
988
|
const rulesBlock = toAdd.map(p => `- ${p.rule} (confidence: ${p.confidence})`).join('\n');
|
|
952
989
|
if (existingContent.includes(SECTION_HEADER)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
+
import { StringDecoder } from 'string_decoder';
|
|
4
5
|
import { runClaude } from '../utils/claude-cli.js';
|
|
5
6
|
import { validatePath } from '../utils/validate-path.js';
|
|
6
7
|
const NEEDS_INPUT_MARKER = '"status":"NEEDS_INPUT"';
|
|
@@ -82,8 +83,9 @@ export async function runTask(task, onProgress, signal, confirmBeforeEdit) {
|
|
|
82
83
|
let stdout = '';
|
|
83
84
|
let stderr = '';
|
|
84
85
|
let buffer = '';
|
|
86
|
+
const stdoutDecoder = new StringDecoder('utf8');
|
|
85
87
|
proc.stdout.on('data', (chunk) => {
|
|
86
|
-
const text =
|
|
88
|
+
const text = stdoutDecoder.write(chunk);
|
|
87
89
|
stdout += text;
|
|
88
90
|
buffer += text;
|
|
89
91
|
const lines = buffer.split('\n');
|
|
@@ -100,8 +102,9 @@ export async function runTask(task, onProgress, signal, confirmBeforeEdit) {
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
});
|
|
105
|
+
const stderrDecoder = new StringDecoder('utf8');
|
|
103
106
|
proc.stderr.on('data', (chunk) => {
|
|
104
|
-
stderr +=
|
|
107
|
+
stderr += stderrDecoder.write(chunk);
|
|
105
108
|
});
|
|
106
109
|
proc.on('close', (code) => {
|
|
107
110
|
clearTimeout(timeoutTimer);
|
package/dist/mcp/setup.d.ts
CHANGED
|
@@ -5,11 +5,6 @@ import type { AgentConfig } from '../types/index.js';
|
|
|
5
5
|
* Returns 0 if MEM0_HTTP_BASE is not set or request fails.
|
|
6
6
|
*/
|
|
7
7
|
export declare function fetchMem0Count(agentName: string): Promise<number>;
|
|
8
|
-
/**
|
|
9
|
-
* Add memory via OpenMemory HTTP API (POST /api/v1/memories/).
|
|
10
|
-
* Uses MEM0_HTTP_BASE if available, falls back to SSH+mem0-add.
|
|
11
|
-
* OpenMemory API stores in both SQLite (metadata) + Qdrant (vectors) — correct path.
|
|
12
|
-
*/
|
|
13
8
|
export declare function callMem0AddMemory(text: string, agentName: string): Promise<void>;
|
|
14
9
|
/**
|
|
15
10
|
* Search Mem0 for relevant memories before task execution.
|
package/dist/mcp/setup.js
CHANGED
|
@@ -65,9 +65,46 @@ export async function fetchMem0Count(agentName) {
|
|
|
65
65
|
* Uses MEM0_HTTP_BASE if available, falls back to SSH+mem0-add.
|
|
66
66
|
* OpenMemory API stores in both SQLite (metadata) + Qdrant (vectors) — correct path.
|
|
67
67
|
*/
|
|
68
|
+
/**
|
|
69
|
+
* Validate memory text before storing. Returns null if valid, or rejection reason.
|
|
70
|
+
*/
|
|
71
|
+
function validateMemory(text) {
|
|
72
|
+
const t = text.trim();
|
|
73
|
+
// Too short — likely a fragment like "Upvoted 10 posts"
|
|
74
|
+
if (t.length < 60)
|
|
75
|
+
return `too short (${t.length} chars, min 60)`;
|
|
76
|
+
// Generic platitudes that waste memory
|
|
77
|
+
const genericPatterns = [
|
|
78
|
+
/^always (prioritize|verify|ensure|keep an eye|strive|validate|check)/i,
|
|
79
|
+
/^prior to (any|engaging|making|using)/i,
|
|
80
|
+
/^when making decisions/i,
|
|
81
|
+
/^(believe|believes) reddit is/i,
|
|
82
|
+
/^follow (accounts|verified)/i,
|
|
83
|
+
/^before (engaging|using|running)/i,
|
|
84
|
+
];
|
|
85
|
+
if (genericPatterns.some(p => p.test(t)))
|
|
86
|
+
return `generic platitude`;
|
|
87
|
+
// Individual action fragments
|
|
88
|
+
const fragmentPatterns = [
|
|
89
|
+
/^(followed|upvoted|liked|joined|saved|has \d+|observed \d+)\b/i,
|
|
90
|
+
/^top product hunt product:/i,
|
|
91
|
+
/^plan(s)? to /i,
|
|
92
|
+
/^\w+ promoted to watching/i,
|
|
93
|
+
/^\w+ is top watching/i,
|
|
94
|
+
];
|
|
95
|
+
if (fragmentPatterns.some(p => p.test(t)))
|
|
96
|
+
return `action fragment`;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
68
99
|
export async function callMem0AddMemory(text, agentName) {
|
|
69
100
|
if (!MEM0_SSH_HOST && !MEM0_HTTP_BASE)
|
|
70
101
|
throw new Error('Mem0 not configured');
|
|
102
|
+
// Validate memory quality before storing
|
|
103
|
+
const rejection = validateMemory(text);
|
|
104
|
+
if (rejection) {
|
|
105
|
+
console.log(`[Mem0 Filter] Rejected memory (${rejection}): "${text.substring(0, 80)}..."`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
71
108
|
const safeAgentName = agentName.toLowerCase().replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'agent';
|
|
72
109
|
// Try HTTP API first (OpenMemory) — stores in SQLite+Qdrant, shows up in counts
|
|
73
110
|
// Falls through to SSH+curl if HTTP is not reachable (e.g. port not exposed externally)
|