tuna-agent 0.1.19 → 0.1.21
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/dist/agents/claude-code-adapter.js +27 -1
- package/dist/mcp/setup.d.ts +7 -2
- package/dist/mcp/setup.js +82 -70
- package/package.json +1 -1
|
@@ -68,6 +68,21 @@ export class ClaudeCodeAdapter {
|
|
|
68
68
|
console.error(`[ClaudeCode] Failed to download initial attachments:`, err);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
// Pre-task Memory Recall: search Mem0 for relevant past learnings
|
|
72
|
+
if (process.env.MEM0_SSH_HOST && task.description.length >= 20) {
|
|
73
|
+
try {
|
|
74
|
+
const { callMem0SearchMemory } = await import('../mcp/setup.js');
|
|
75
|
+
const memories = await callMem0SearchMemory(task.description, this.agentConfig.name, 5);
|
|
76
|
+
if (memories.length > 0) {
|
|
77
|
+
const memoryContext = memories.map(m => `- ${m}`).join('\n');
|
|
78
|
+
userMessage = `${task.description}\n\n<past_learnings>\nRelevant lessons from previous tasks:\n${memoryContext}\n</past_learnings>`;
|
|
79
|
+
console.log(`[Mem0 Recall] Injected ${memories.length} memories into task prompt`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.warn(`[Mem0 Recall] Failed:`, err instanceof Error ? err.message : err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
71
86
|
try {
|
|
72
87
|
for (let round = 0; round < MAX_ROUNDS; round++) {
|
|
73
88
|
let streamMsgId = `team-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -659,7 +674,18 @@ export class ClaudeCodeAdapter {
|
|
|
659
674
|
const taskContext = task.description.substring(0, 150);
|
|
660
675
|
const resultContext = resultSummary.substring(0, 200);
|
|
661
676
|
const statusLabel = status === 'done' ? 'COMPLETED' : 'FAILED';
|
|
662
|
-
|
|
677
|
+
// Structured reflection: what was asked, what happened, what to remember
|
|
678
|
+
const parts = [];
|
|
679
|
+
if (status === 'failed') {
|
|
680
|
+
parts.push(`[TASK FAILED] "${taskContext}"`);
|
|
681
|
+
parts.push(`Error: ${resultContext}`);
|
|
682
|
+
parts.push(`Lesson: This type of task failed — review approach next time`);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
parts.push(`[TASK ${statusLabel}] "${taskContext}"`);
|
|
686
|
+
parts.push(`Result: ${resultContext}`);
|
|
687
|
+
}
|
|
688
|
+
const memoryText = parts.join('. ');
|
|
663
689
|
try {
|
|
664
690
|
console.log(`[Reflection] Storing reflection for task ${task.id} (${status})`);
|
|
665
691
|
const { callMem0AddMemory } = await import('../mcp/setup.js');
|
package/dist/mcp/setup.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { AgentConfig } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* Call Mem0 add_memory
|
|
4
|
-
*
|
|
3
|
+
* Call Mem0 add_memory via mem0-add script (bypasses OpenMemory API).
|
|
4
|
+
* Calls `mem0-add <text>` directly or via SSH — simple, reliable.
|
|
5
5
|
*/
|
|
6
6
|
export declare function callMem0AddMemory(text: string, agentName: string): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Search Mem0 for relevant memories before task execution.
|
|
9
|
+
* Returns array of memory texts sorted by relevance.
|
|
10
|
+
*/
|
|
11
|
+
export declare function callMem0SearchMemory(query: string, agentName: string, limit?: number): Promise<string[]>;
|
|
7
12
|
/**
|
|
8
13
|
* Generate MCP server config file for Claude Code.
|
|
9
14
|
* This file is auto-detected by runClaude and passed via --mcp-config.
|
package/dist/mcp/setup.js
CHANGED
|
@@ -21,87 +21,99 @@ const MEM0_ENV_VARS = {
|
|
|
21
21
|
MEM0_NEO4J_PASSWORD: 'mem0graph',
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
* Call Mem0 add_memory
|
|
25
|
-
*
|
|
24
|
+
* Call Mem0 add_memory via mem0-add script (bypasses OpenMemory API).
|
|
25
|
+
* Calls `mem0-add <text>` directly or via SSH — simple, reliable.
|
|
26
26
|
*/
|
|
27
27
|
export async function callMem0AddMemory(text, agentName) {
|
|
28
28
|
if (!MEM0_SSH_HOST)
|
|
29
29
|
throw new Error('MEM0_SSH_HOST not configured');
|
|
30
|
-
const {
|
|
30
|
+
const { execFile } = await import('child_process');
|
|
31
31
|
const safeAgentName = agentName.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'agent';
|
|
32
|
-
const envWithUser = { ...MEM0_ENV_VARS, MEM0_USER_ID: safeAgentName };
|
|
33
|
-
let cmd;
|
|
34
|
-
let args;
|
|
35
|
-
let spawnEnv;
|
|
36
|
-
if (MEM0_SSH_HOST === 'local') {
|
|
37
|
-
cmd = 'mem0-mcp';
|
|
38
|
-
args = [];
|
|
39
|
-
spawnEnv = { ...process.env, ...envWithUser };
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
const envString = Object.entries(envWithUser).map(([k, v]) => `${k}=${v}`).join(' ');
|
|
43
|
-
cmd = 'ssh';
|
|
44
|
-
args = ['-p', MEM0_SSH_PORT, '-o', 'StrictHostKeyChecking=no'];
|
|
45
|
-
if (MEM0_SSH_KEY)
|
|
46
|
-
args.push('-i', MEM0_SSH_KEY);
|
|
47
|
-
args.push(MEM0_SSH_HOST, `${envString} mem0-mcp`);
|
|
48
|
-
}
|
|
49
32
|
return new Promise((resolve, reject) => {
|
|
50
|
-
const
|
|
51
|
-
let
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
33
|
+
const timer = setTimeout(() => reject(new Error('Mem0 call timed out')), 30000);
|
|
34
|
+
let cmd;
|
|
35
|
+
let args;
|
|
36
|
+
let options = {};
|
|
37
|
+
if (MEM0_SSH_HOST === 'local') {
|
|
38
|
+
cmd = 'mem0-add';
|
|
39
|
+
args = [text];
|
|
40
|
+
options.env = { ...process.env, MEM0_USER_ID: safeAgentName };
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
cmd = 'ssh';
|
|
44
|
+
args = ['-p', MEM0_SSH_PORT, '-o', 'StrictHostKeyChecking=no'];
|
|
45
|
+
if (MEM0_SSH_KEY)
|
|
46
|
+
args.push('-i', MEM0_SSH_KEY);
|
|
47
|
+
// Shell-escape text with single quotes (escape internal single quotes)
|
|
48
|
+
const escapedText = text.replace(/'/g, "'\\''");
|
|
49
|
+
args.push(MEM0_SSH_HOST, `MEM0_USER_ID=${safeAgentName} mem0-add '${escapedText}'`);
|
|
50
|
+
}
|
|
51
|
+
execFile(cmd, args, { ...options, timeout: 30000 }, (err, stdout, stderr) => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
if (err) {
|
|
54
|
+
reject(new Error(`Mem0 add failed: ${err.message}${stderr ? ` stderr: ${stderr.substring(0, 200)}` : ''}`));
|
|
55
|
+
return;
|
|
70
56
|
}
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const errMatch = stdout.match(/"text"\s*:\s*"([^"]+)"/);
|
|
83
|
-
reject(new Error(`Mem0 add_memory failed: ${errMatch?.[1] || 'unknown error'}`));
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
resolve();
|
|
87
|
-
}
|
|
57
|
+
// Check response for actual results
|
|
58
|
+
try {
|
|
59
|
+
const data = JSON.parse(stdout.trim());
|
|
60
|
+
if (data.error) {
|
|
61
|
+
reject(new Error(`Mem0 add failed: ${data.error}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const results = data.results || [];
|
|
65
|
+
const added = results.filter((r) => r.event === 'ADD' || r.event === 'UPDATE');
|
|
66
|
+
if (added.length === 0 && results.length > 0) {
|
|
67
|
+
console.log(`[Mem0] Memory deduplicated (${results.length} existing matches)`);
|
|
88
68
|
}
|
|
89
69
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
proc.on('close', (code) => {
|
|
94
|
-
if (!resolved) {
|
|
95
|
-
resolved = true;
|
|
96
|
-
clearTimeout(timer);
|
|
97
|
-
reject(new Error(`Mem0 process exited (code ${code}) without completing add_memory. stdout: ${stdout.substring(0, 300)}`));
|
|
70
|
+
catch {
|
|
71
|
+
// Non-JSON output — might be OK if exit code was 0
|
|
72
|
+
console.warn(`[Mem0] Unexpected output: ${stdout.substring(0, 100)}`);
|
|
98
73
|
}
|
|
74
|
+
resolve();
|
|
99
75
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Search Mem0 for relevant memories before task execution.
|
|
80
|
+
* Returns array of memory texts sorted by relevance.
|
|
81
|
+
*/
|
|
82
|
+
export async function callMem0SearchMemory(query, agentName, limit = 5) {
|
|
83
|
+
if (!MEM0_SSH_HOST)
|
|
84
|
+
return [];
|
|
85
|
+
const { execFile } = await import('child_process');
|
|
86
|
+
const safeAgentName = agentName.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'agent';
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let cmd;
|
|
89
|
+
let args;
|
|
90
|
+
let options = {};
|
|
91
|
+
if (MEM0_SSH_HOST === 'local') {
|
|
92
|
+
cmd = 'mem0-search';
|
|
93
|
+
args = [query, String(limit)];
|
|
94
|
+
options.env = { ...process.env, MEM0_USER_ID: safeAgentName };
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
cmd = 'ssh';
|
|
98
|
+
args = ['-p', MEM0_SSH_PORT, '-o', 'StrictHostKeyChecking=no'];
|
|
99
|
+
if (MEM0_SSH_KEY)
|
|
100
|
+
args.push('-i', MEM0_SSH_KEY);
|
|
101
|
+
const escapedQuery = query.replace(/'/g, "'\\''");
|
|
102
|
+
args.push(MEM0_SSH_HOST, `MEM0_USER_ID=${safeAgentName} mem0-search '${escapedQuery}' ${limit}`);
|
|
103
|
+
}
|
|
104
|
+
execFile(cmd, args, { ...options, timeout: 15000 }, (err, stdout) => {
|
|
105
|
+
if (err) {
|
|
106
|
+
console.warn(`[Mem0 Search] Failed: ${err.message}`);
|
|
107
|
+
resolve([]);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const data = JSON.parse(stdout.trim());
|
|
112
|
+
const results = (data.results || []);
|
|
113
|
+
resolve(results.map(r => r.text));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
resolve([]);
|
|
105
117
|
}
|
|
106
118
|
});
|
|
107
119
|
});
|