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
|
-
//
|
|
153
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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.
|
|
1048
|
-
this.
|
|
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
|
|
1067
|
+
trackMetrics(status, durationMs, agentId) {
|
|
1068
|
+
const m = agentId ? this.getMetricsForAgent(agentId) : this.metrics;
|
|
1069
|
+
m.taskCount++;
|
|
1063
1070
|
if (status === 'done')
|
|
1064
|
-
|
|
1071
|
+
m.successCount++;
|
|
1065
1072
|
else
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
console.log(`[Metrics] Tasks: ${
|
|
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
|
package/dist/daemon/index.js
CHANGED
|
@@ -778,7 +778,7 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
778
778
|
outputFormat: 'stream-json',
|
|
779
779
|
includePartialMessages: true,
|
|
780
780
|
agentTeam: true,
|
|
781
|
-
maxTurns:
|
|
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
|
}
|