tuna-agent 0.1.71 → 0.1.73

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.
@@ -79,14 +79,14 @@ export declare class ClaudeCodeAdapter implements AgentAdapter {
79
79
  private static extractKeyPhrases;
80
80
  /** Check if two rules are semantically similar (>40% key phrase overlap). */
81
81
  private static isSimilarRule;
82
- runSelfImprovement(cwd: string): Promise<void>;
82
+ runSelfImprovement(cwd: string, agentId?: string): Promise<void>;
83
83
  /**
84
84
  * Parse "## Learned Rules" section from CLAUDE.md and store in learnedRulesMap.
85
85
  * Rule format: `- Rule text (confidence: 0.7)`
86
86
  */
87
87
  private parseLearnedRules;
88
88
  /** Track task completion metrics (public for daemon resume path). */
89
- trackMetricsPublic(status: 'done' | 'failed', durationMs: number): void;
89
+ trackMetricsPublic(status: 'done' | 'failed', durationMs: number, agentId?: string): void;
90
90
  /** Track task completion metrics. */
91
91
  private trackMetrics;
92
92
  dispose(): Promise<void>;
@@ -149,13 +149,16 @@ export class ClaudeCodeAdapter {
149
149
  : undefined;
150
150
  // Default mode: direct chat with Claude CLI (no PM layer)
151
151
  // Only use PM planning when mode is explicitly 'tuna'
152
- // Set current agent context (metrics + Mem0 identity)
153
- this.currentAgentId = task.agentId || '';
152
+ // Capture agent context as LOCAL variables for parallel-safety
153
+ const localAgentId = task.agentId || '';
154
154
  const defaultWorkspaceEarly = path.join(os.homedir(), 'tuna-workspace');
155
- this.currentAgentName = path.basename(task.repoPath || defaultWorkspaceEarly);
155
+ const localAgentName = path.basename(task.repoPath || defaultWorkspaceEarly);
156
+ // Also set instance vars for backward compat (metrics getter, heartbeat, etc.)
157
+ this.currentAgentId = localAgentId;
158
+ this.currentAgentName = localAgentName;
156
159
  // Track agent folder for rules parsing in heartbeat
157
160
  const cwd = task.repoPath || defaultWorkspaceEarly;
158
- this.agentFolderMap.set(this.currentAgentId, cwd);
161
+ this.agentFolderMap.set(localAgentId, cwd);
159
162
  if (task.mode !== 'tuna') {
160
163
  console.log(`[ClaudeCode] Agent Team mode — direct chat with Claude CLI`);
161
164
  ws.sendProgress(task.id, 'executing', { startedAt: new Date().toISOString() });
@@ -179,7 +182,7 @@ export class ClaudeCodeAdapter {
179
182
  if (process.env.MEM0_SSH_HOST && task.description.length >= 20) {
180
183
  try {
181
184
  const { callMem0SearchMemory } = await import('../mcp/setup.js');
182
- const memories = await callMem0SearchMemory(task.description, this.currentAgentName, 5);
185
+ const memories = await callMem0SearchMemory(task.description, localAgentName, 5);
183
186
  if (memories.length > 0) {
184
187
  const memoryContext = memories.map(m => `- ${m}`).join('\n');
185
188
  userMessage = `${task.description}\n\n<past_learnings>\nRelevant lessons from previous tasks:\n${memoryContext}\n</past_learnings>`;
@@ -207,10 +210,11 @@ export class ClaudeCodeAdapter {
207
210
  if (round === 0) {
208
211
  writeAgentFolderMcpConfig(cwd, this.agentConfig);
209
212
  // Seed memoryCount from Mem0 so it survives daemon restarts (non-blocking)
210
- fetchMem0Count(this.currentAgentName).then(count => {
211
- if (count > this.metrics.memoryCount) {
212
- this.metrics.memoryCount = count;
213
- console.log(`[Metrics] Seeded memoryCount=${count} from Mem0 for "${this.currentAgentName}"`);
213
+ fetchMem0Count(localAgentName).then(count => {
214
+ const m = this.getMetricsForAgent(localAgentId);
215
+ if (count > m.memoryCount) {
216
+ m.memoryCount = count;
217
+ console.log(`[Metrics] Seeded memoryCount=${count} from Mem0 for "${localAgentName}"`);
214
218
  }
215
219
  }).catch(() => { });
216
220
  }
@@ -328,7 +332,7 @@ export class ClaudeCodeAdapter {
328
332
  startedAt: firstChunkIso || undefined,
329
333
  });
330
334
  ws.sendTaskFailed(task.id, result.result);
331
- this.trackMetrics('failed', totalDurationMs);
335
+ this.trackMetrics('failed', totalDurationMs, localAgentId);
332
336
  console.log(`[ClaudeCode] Agent Team task ${task.id} failed in round ${round + 1}`);
333
337
  this.runReflection(task, result.result, 'failed', cwd).catch(() => { });
334
338
  return;
@@ -389,10 +393,10 @@ export class ClaudeCodeAdapter {
389
393
  durationMs: totalDurationMs,
390
394
  sessionId,
391
395
  });
392
- this.trackMetrics('done', totalDurationMs);
396
+ this.trackMetrics('done', totalDurationMs, localAgentId);
393
397
  const timeoutOutput = lastTaskOutput || 'Task completed (no follow-up)';
394
398
  this.runReflection(task, timeoutOutput, 'done', task.repoPath)
395
- .then(() => this.runSelfImprovement(task.repoPath))
399
+ .then(() => this.runSelfImprovement(task.repoPath, task.agentId))
396
400
  .catch(() => { });
397
401
  return;
398
402
  }
@@ -423,11 +427,11 @@ export class ClaudeCodeAdapter {
423
427
  });
424
428
  await new Promise(resolve => setTimeout(resolve, 150));
425
429
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
426
- this.trackMetrics('done', totalDurationMs);
430
+ this.trackMetrics('done', totalDurationMs, localAgentId);
427
431
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
428
432
  // Post-task reflection with actual output (non-blocking)
429
433
  this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath)
430
- .then(() => this.runSelfImprovement(task.repoPath))
434
+ .then(() => this.runSelfImprovement(task.repoPath, task.agentId))
431
435
  .catch(() => { });
432
436
  }
433
437
  finally {
@@ -903,7 +907,7 @@ export class ClaudeCodeAdapter {
903
907
  const overlap = phrasesA.filter(p => phrasesB.has(p)).length;
904
908
  return overlap / phrasesA.length > 0.4;
905
909
  }
906
- async runSelfImprovement(cwd) {
910
+ async runSelfImprovement(cwd, agentId) {
907
911
  if (!process.env.MEM0_SSH_HOST)
908
912
  return;
909
913
  this.taskCount++;
@@ -982,14 +986,15 @@ export class ClaudeCodeAdapter {
982
986
  const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
983
987
  fs.writeFileSync(claudeMdPath, existingContent + separator + `${SECTION_HEADER}\n${rulesBlock}\n`);
984
988
  }
985
- this.metrics.patternsLearnedCount += toAdd.length;
986
- this.metrics.rulesCount += toAdd.length;
987
- this.metrics.lastPatternAt = new Date().toISOString();
988
- this.metrics.latestLearnedRule = toAdd[toAdd.length - 1].rule.substring(0, 500);
989
+ const m = agentId ? this.getMetricsForAgent(agentId) : this.metrics;
990
+ m.patternsLearnedCount += toAdd.length;
991
+ m.rulesCount += toAdd.length;
992
+ m.lastPatternAt = new Date().toISOString();
993
+ m.latestLearnedRule = toAdd[toAdd.length - 1].rule.substring(0, 500);
989
994
  console.log(`[Self-Improve] Added ${toAdd.length} new rules to CLAUDE.md:`);
990
995
  toAdd.forEach(p => console.log(`[Self-Improve] - ${p.rule}`));
991
996
  // Sync learned rules to heartbeat metrics
992
- this.parseLearnedRules(claudeMdPath);
997
+ this.parseLearnedRules(claudeMdPath, agentId);
993
998
  }
994
999
  catch (err) {
995
1000
  console.warn(`[Self-Improve] Failed:`, err instanceof Error ? err.message : err);
@@ -999,7 +1004,7 @@ export class ClaudeCodeAdapter {
999
1004
  * Parse "## Learned Rules" section from CLAUDE.md and store in learnedRulesMap.
1000
1005
  * Rule format: `- Rule text (confidence: 0.7)`
1001
1006
  */
1002
- parseLearnedRules(claudeMdPath) {
1007
+ parseLearnedRules(claudeMdPath, agentId) {
1003
1008
  try {
1004
1009
  if (!fs.existsSync(claudeMdPath))
1005
1010
  return;
@@ -1044,8 +1049,9 @@ export class ClaudeCodeAdapter {
1044
1049
  });
1045
1050
  }
1046
1051
  if (rules.length > 0) {
1047
- this.learnedRulesMap.set(this.currentAgentId, rules);
1048
- this.metrics.rulesCount = rules.length;
1052
+ const resolvedAgentId = agentId || this.currentAgentId;
1053
+ this.learnedRulesMap.set(resolvedAgentId, rules);
1054
+ this.getMetricsForAgent(resolvedAgentId).rulesCount = rules.length;
1049
1055
  console.log(`[Self-Improve] Parsed ${rules.length} rules from CLAUDE.md for heartbeat sync`);
1050
1056
  }
1051
1057
  }
@@ -1054,20 +1060,21 @@ export class ClaudeCodeAdapter {
1054
1060
  }
1055
1061
  }
1056
1062
  /** Track task completion metrics (public for daemon resume path). */
1057
- trackMetricsPublic(status, durationMs) {
1058
- this.trackMetrics(status, durationMs);
1063
+ trackMetricsPublic(status, durationMs, agentId) {
1064
+ this.trackMetrics(status, durationMs, agentId);
1059
1065
  }
1060
1066
  /** Track task completion metrics. */
1061
- trackMetrics(status, durationMs) {
1062
- this.metrics.taskCount++;
1067
+ trackMetrics(status, durationMs, agentId) {
1068
+ const m = agentId ? this.getMetricsForAgent(agentId) : this.metrics;
1069
+ m.taskCount++;
1063
1070
  if (status === 'done')
1064
- this.metrics.successCount++;
1071
+ m.successCount++;
1065
1072
  else
1066
- this.metrics.failCount++;
1067
- this.metrics.totalDurationMs += durationMs;
1068
- this.metrics.avgDurationMs = Math.round(this.metrics.totalDurationMs / this.metrics.taskCount);
1069
- this.metrics.lastTaskAt = new Date().toISOString();
1070
- console.log(`[Metrics] Tasks: ${this.metrics.successCount}✓ ${this.metrics.failCount}✗ | Avg: ${(this.metrics.avgDurationMs / 1000).toFixed(0)}s | Reflections: ${this.metrics.reflectionCount} | Patterns: ${this.metrics.patternsLearnedCount}`);
1073
+ m.failCount++;
1074
+ m.totalDurationMs += durationMs;
1075
+ m.avgDurationMs = Math.round(m.totalDurationMs / m.taskCount);
1076
+ m.lastTaskAt = new Date().toISOString();
1077
+ console.log(`[Metrics] Tasks: ${m.successCount}✓ ${m.failCount}✗ | Avg: ${(m.avgDurationMs / 1000).toFixed(0)}s | Reflections: ${m.reflectionCount} | Patterns: ${m.patternsLearnedCount}`);
1071
1078
  }
1072
1079
  async dispose() {
1073
1080
  // No persistent resources to clean up
@@ -778,7 +778,7 @@ ${skillContent.slice(0, 15000)}`;
778
778
  outputFormat: 'stream-json',
779
779
  includePartialMessages: true,
780
780
  agentTeam: true,
781
- maxTurns: 50,
781
+ maxTurns: 200,
782
782
  resumeSessionId: sessionId,
783
783
  signal: abort.signal,
784
784
  inputFiles: currentInputFiles,
@@ -813,6 +813,24 @@ ${skillContent.slice(0, 15000)}`;
813
813
  if (data.type === 'system' && data.subtype === 'init') {
814
814
  sessionId = data.session_id;
815
815
  }
816
+ // Patch missing stream chunks from assistant message (same as handleTask)
817
+ if (data.type === 'assistant' && data.message) {
818
+ const msg = data.message;
819
+ const content = msg.content;
820
+ if (content) {
821
+ const fullText = content
822
+ .filter(b => b.type === 'text' && b.text)
823
+ .map(b => b.text)
824
+ .join('');
825
+ if (fullText && fullText.length > turnAccumulatedText.length) {
826
+ const missed = fullText.slice(turnAccumulatedText.length);
827
+ if (missed.length > 0) {
828
+ turnAccumulatedText = fullText;
829
+ wsClient.sendPMStream(taskId, missed);
830
+ }
831
+ }
832
+ }
833
+ }
816
834
  },
817
835
  });
818
836
  wsClient.sendPMStreamEnd(taskId, streamMsgId);
@@ -867,8 +885,8 @@ ${skillContent.slice(0, 15000)}`;
867
885
  // Track metrics + reflection on the adapter
868
886
  if (adapter.type === 'claude-code') {
869
887
  const ccAdapter = adapter;
870
- ccAdapter.trackMetricsPublic('done', totalDurationMs);
871
- ccAdapter.runReflection({ id: taskId, description: firstMessage, repoPath: cwd, enableReflection: true }, lastResumeOutput || 'Task completed (no follow-up)', 'done', cwd).then(() => ccAdapter.runSelfImprovement(cwd)).catch(() => { });
888
+ ccAdapter.trackMetricsPublic('done', totalDurationMs, savedState.agentId);
889
+ ccAdapter.runReflection({ id: taskId, description: firstMessage, repoPath: cwd, enableReflection: true }, lastResumeOutput || 'Task completed (no follow-up)', 'done', cwd).then(() => ccAdapter.runSelfImprovement(cwd, savedState.agentId)).catch(() => { });
872
890
  }
873
891
  return;
874
892
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"