tuna-agent 0.1.22 → 0.1.24

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.
@@ -83,6 +83,7 @@ export class ClaudeCodeAdapter {
83
83
  console.warn(`[Mem0 Recall] Failed:`, err instanceof Error ? err.message : err);
84
84
  }
85
85
  }
86
+ let lastTaskOutput = ''; // Track last output for reflection
86
87
  try {
87
88
  for (let round = 0; round < MAX_ROUNDS; round++) {
88
89
  let streamMsgId = `team-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -203,6 +204,8 @@ export class ClaudeCodeAdapter {
203
204
  this.runReflection(task, result.result, 'failed', cwd).catch(() => { });
204
205
  return;
205
206
  }
207
+ // Track last output for reflection
208
+ lastTaskOutput = turnAccumulatedText.trim();
206
209
  // Send finalized message for the last turn's remaining text
207
210
  if (turnAccumulatedText.trim()) {
208
211
  ws.sendPMMessage(task.id, {
@@ -248,7 +251,8 @@ export class ClaudeCodeAdapter {
248
251
  if (err instanceof Error && err.message === '__FOLLOW_UP_TIMEOUT__') {
249
252
  console.log(`[ClaudeCode] No follow-up after ${FOLLOW_UP_TIMEOUT_MS / 1000}s — closing task`);
250
253
  pendingInputResolvers.delete(task.id);
251
- this.runReflection(task, 'Agent Team task completed (no follow-up)', 'done', task.repoPath).catch(() => { });
254
+ const timeoutOutput = lastTaskOutput || 'Task completed (no follow-up)';
255
+ this.runReflection(task, timeoutOutput, 'done', task.repoPath).catch(() => { });
252
256
  return;
253
257
  }
254
258
  throw err;
@@ -279,8 +283,8 @@ export class ClaudeCodeAdapter {
279
283
  await new Promise(resolve => setTimeout(resolve, 150));
280
284
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
281
285
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
282
- // Post-task reflection (non-blocking — runs after task is marked done)
283
- this.runReflection(task, 'Agent Team task completed', 'done', task.repoPath).catch(() => { });
286
+ // Post-task reflection with actual output (non-blocking)
287
+ this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath).catch(() => { });
284
288
  }
285
289
  finally {
286
290
  cleanupAttachments(task.id);
@@ -671,29 +675,33 @@ export class ClaudeCodeAdapter {
671
675
  const MIN_DESCRIPTION_LENGTH = 20;
672
676
  if (task.description.length < MIN_DESCRIPTION_LENGTH)
673
677
  return;
674
- const taskContext = task.description.substring(0, 150);
675
- const resultContext = resultSummary.substring(0, 200);
676
- const statusLabel = status === 'done' ? 'COMPLETED' : 'FAILED';
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('. ');
689
678
  try {
690
- console.log(`[Reflection] Storing reflection for task ${task.id} (${status})`);
691
- const { callMem0AddMemory } = await import('../mcp/setup.js');
692
- await callMem0AddMemory(memoryText, this.agentConfig.name);
693
- console.log(`[Reflection] Reflection stored for task ${task.id}`);
679
+ // Step 1: Generate AI-powered reflection via Ollama
680
+ console.log(`[Reflection] Generating AI reflection for task ${task.id} (${status})`);
681
+ const { callMem0Reflect, callMem0AddMemory } = await import('../mcp/setup.js');
682
+ const aiReflection = await callMem0Reflect(task.description, resultSummary, status);
683
+ if (!aiReflection) {
684
+ console.log(`[Reflection] No meaningful lesson — skipping storage for task ${task.id}`);
685
+ return;
686
+ }
687
+ // Step 2: Store the AI-generated reflection in Mem0
688
+ console.log(`[Reflection] Storing: "${aiReflection.substring(0, 100)}..."`);
689
+ await callMem0AddMemory(aiReflection, this.agentConfig.name);
690
+ console.log(`[Reflection] Stored for task ${task.id}`);
694
691
  }
695
692
  catch (err) {
696
- console.warn(`[Reflection] Failed for task ${task.id}:`, err instanceof Error ? err.message : err);
693
+ // Fallback: store basic reflection if AI reflection fails
694
+ console.warn(`[Reflection] AI reflection failed, using fallback for task ${task.id}:`, err instanceof Error ? err.message : err);
695
+ try {
696
+ const fallback = status === 'failed'
697
+ ? `Task failed: "${task.description.substring(0, 150)}". Error: ${resultSummary.substring(0, 200)}`
698
+ : `Task completed: "${task.description.substring(0, 150)}". Result: ${resultSummary.substring(0, 200)}`;
699
+ const { callMem0AddMemory } = await import('../mcp/setup.js');
700
+ await callMem0AddMemory(fallback, this.agentConfig.name);
701
+ }
702
+ catch {
703
+ // Both AI and fallback failed — give up silently
704
+ }
697
705
  }
698
706
  }
699
707
  /**
@@ -9,6 +9,12 @@ export declare function callMem0AddMemory(text: string, agentName: string): Prom
9
9
  * Returns array of memory texts sorted by relevance.
10
10
  */
11
11
  export declare function callMem0SearchMemory(query: string, agentName: string, limit?: number): Promise<string[]>;
12
+ /**
13
+ * Generate AI-powered reflection from task results using Ollama.
14
+ * Calls mem0-reflect script on relabs01 via SSH.
15
+ * Returns a concise lesson learned, or empty string if nothing meaningful.
16
+ */
17
+ export declare function callMem0Reflect(taskDesc: string, resultSummary: string, status: 'done' | 'failed'): Promise<string>;
12
18
  /**
13
19
  * Generate MCP server config file for Claude Code.
14
20
  * This file is auto-detected by runClaude and passed via --mcp-config.
package/dist/mcp/setup.js CHANGED
@@ -124,6 +124,67 @@ export async function callMem0SearchMemory(query, agentName, limit = 5) {
124
124
  });
125
125
  });
126
126
  }
127
+ /**
128
+ * Generate AI-powered reflection from task results using Ollama.
129
+ * Calls mem0-reflect script on relabs01 via SSH.
130
+ * Returns a concise lesson learned, or empty string if nothing meaningful.
131
+ */
132
+ export async function callMem0Reflect(taskDesc, resultSummary, status) {
133
+ if (!MEM0_SSH_HOST)
134
+ return '';
135
+ const { execFile } = await import('child_process');
136
+ const input = JSON.stringify({
137
+ task: taskDesc.substring(0, 300),
138
+ result: resultSummary.substring(0, 500),
139
+ status,
140
+ });
141
+ return new Promise((resolve) => {
142
+ let cmd;
143
+ let args;
144
+ let options = {};
145
+ if (MEM0_SSH_HOST === 'local') {
146
+ cmd = 'mem0-reflect';
147
+ args = [];
148
+ options.env = { ...process.env };
149
+ }
150
+ else {
151
+ cmd = 'ssh';
152
+ args = ['-p', MEM0_SSH_PORT, '-o', 'StrictHostKeyChecking=no'];
153
+ if (MEM0_SSH_KEY)
154
+ args.push('-i', MEM0_SSH_KEY);
155
+ args.push(MEM0_SSH_HOST, 'mem0-reflect');
156
+ }
157
+ const child = execFile(cmd, args, { ...options, timeout: 90000 }, (err, stdout) => {
158
+ if (err) {
159
+ console.warn(`[Mem0 Reflect] Failed: ${err.message}`);
160
+ resolve('');
161
+ return;
162
+ }
163
+ try {
164
+ const data = JSON.parse(stdout.trim());
165
+ if (data.error) {
166
+ console.warn(`[Mem0 Reflect] Error: ${data.error}`);
167
+ resolve('');
168
+ return;
169
+ }
170
+ if (data.skipped) {
171
+ console.log(`[Mem0 Reflect] Skipped — nothing meaningful to learn`);
172
+ resolve('');
173
+ return;
174
+ }
175
+ // Clean up: remove trailing SKIP if LLM appended it
176
+ let reflection = (data.reflection || '').replace(/\s*SKIP\s*$/i, '').trim();
177
+ resolve(reflection);
178
+ }
179
+ catch {
180
+ resolve('');
181
+ }
182
+ });
183
+ // Send input via stdin
184
+ child.stdin?.write(input);
185
+ child.stdin?.end();
186
+ });
187
+ }
127
188
  /**
128
189
  * Build Mem0 MCP server config for an agent.
129
190
  * - MEM0_SSH_HOST="local": run mem0-mcp directly (Mem0 infra on same machine)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"