tuna-agent 0.1.28 → 0.1.30

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.
@@ -1,13 +1,28 @@
1
1
  import type { AgentAdapter, AgentType } from './types.js';
2
2
  import type { AgentConfig, TaskAssignment, InputResponse } from '../types/index.js';
3
3
  import type { AgentWebSocketClient } from '../daemon/ws-client.js';
4
+ export interface AgentMetrics {
5
+ taskCount: number;
6
+ successCount: number;
7
+ failCount: number;
8
+ totalDurationMs: number;
9
+ avgDurationMs: number;
10
+ reflectionCount: number;
11
+ reflectionSkipCount: number;
12
+ patternsLearnedCount: number;
13
+ lastTaskAt: string | null;
14
+ upSince: string;
15
+ }
4
16
  export declare class ClaudeCodeAdapter implements AgentAdapter {
5
17
  readonly type: AgentType;
6
18
  readonly displayName = "Claude Code";
7
19
  private readonly agentConfig;
8
20
  private taskCount;
9
21
  private static readonly PATTERN_CHECK_INTERVAL;
22
+ private metrics;
10
23
  constructor(config: AgentConfig);
24
+ /** Get current agent metrics snapshot. */
25
+ getMetrics(): Record<string, unknown>;
11
26
  checkHealth(): Promise<{
12
27
  ok: boolean;
13
28
  message: string;
@@ -33,5 +48,7 @@ export declare class ClaudeCodeAdapter implements AgentAdapter {
33
48
  * Runs every N tasks to evolve the agent's permanent knowledge.
34
49
  */
35
50
  runSelfImprovement(cwd: string): Promise<void>;
51
+ /** Track task completion metrics. */
52
+ private trackMetrics;
36
53
  dispose(): Promise<void>;
37
54
  }
@@ -14,9 +14,26 @@ export class ClaudeCodeAdapter {
14
14
  agentConfig;
15
15
  taskCount = 0;
16
16
  static PATTERN_CHECK_INTERVAL = 5; // Check patterns every N tasks
17
+ // Agent performance metrics
18
+ metrics = {
19
+ taskCount: 0,
20
+ successCount: 0,
21
+ failCount: 0,
22
+ totalDurationMs: 0,
23
+ avgDurationMs: 0,
24
+ reflectionCount: 0,
25
+ reflectionSkipCount: 0,
26
+ patternsLearnedCount: 0,
27
+ lastTaskAt: null,
28
+ upSince: new Date().toISOString(),
29
+ };
17
30
  constructor(config) {
18
31
  this.agentConfig = config;
19
32
  }
33
+ /** Get current agent metrics snapshot. */
34
+ getMetrics() {
35
+ return { ...this.metrics };
36
+ }
20
37
  async checkHealth() {
21
38
  try {
22
39
  execSync('which claude', { stdio: 'ignore' });
@@ -202,6 +219,7 @@ export class ClaudeCodeAdapter {
202
219
  startedAt: firstChunkIso || undefined,
203
220
  });
204
221
  ws.sendTaskFailed(task.id, result.result);
222
+ this.trackMetrics('failed', totalDurationMs);
205
223
  console.log(`[ClaudeCode] Agent Team task ${task.id} failed in round ${round + 1}`);
206
224
  this.runReflection(task, result.result, 'failed', cwd).catch(() => { });
207
225
  return;
@@ -256,6 +274,12 @@ export class ClaudeCodeAdapter {
256
274
  if (err instanceof Error && err.message === '__FOLLOW_UP_TIMEOUT__') {
257
275
  console.log(`[ClaudeCode] No follow-up after ${FOLLOW_UP_TIMEOUT_MS / 1000}s — closing task`);
258
276
  pendingInputResolvers.delete(task.id);
277
+ ws.sendTaskDone(task.id, {
278
+ result: 'Agent Team task completed',
279
+ durationMs: totalDurationMs,
280
+ sessionId,
281
+ });
282
+ this.trackMetrics('done', totalDurationMs);
259
283
  const timeoutOutput = lastTaskOutput || 'Task completed (no follow-up)';
260
284
  this.runReflection(task, timeoutOutput, 'done', task.repoPath)
261
285
  .then(() => this.runSelfImprovement(task.repoPath))
@@ -289,6 +313,7 @@ export class ClaudeCodeAdapter {
289
313
  });
290
314
  await new Promise(resolve => setTimeout(resolve, 150));
291
315
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
316
+ this.trackMetrics('done', totalDurationMs);
292
317
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
293
318
  // Post-task reflection with actual output (non-blocking)
294
319
  this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath)
@@ -690,12 +715,14 @@ export class ClaudeCodeAdapter {
690
715
  const { callMem0Reflect, callMem0AddMemory } = await import('../mcp/setup.js');
691
716
  const aiReflection = await callMem0Reflect(task.description, resultSummary, status);
692
717
  if (!aiReflection) {
718
+ this.metrics.reflectionSkipCount++;
693
719
  console.log(`[Reflection] No meaningful lesson — skipping storage for task ${task.id}`);
694
720
  return;
695
721
  }
696
722
  // Step 2: Store the AI-generated reflection in Mem0
697
723
  console.log(`[Reflection] Storing: "${aiReflection.substring(0, 100)}..."`);
698
724
  await callMem0AddMemory(aiReflection, this.agentConfig.name);
725
+ this.metrics.reflectionCount++;
699
726
  console.log(`[Reflection] Stored for task ${task.id}`);
700
727
  }
701
728
  catch (err) {
@@ -783,6 +810,7 @@ export class ClaudeCodeAdapter {
783
810
  const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
784
811
  fs.writeFileSync(claudeMdPath, existingContent + separator + `${SECTION_HEADER}\n${rulesBlock}\n`);
785
812
  }
813
+ this.metrics.patternsLearnedCount += newPatterns.length;
786
814
  console.log(`[Self-Improve] Added ${newPatterns.length} new rules to CLAUDE.md:`);
787
815
  newPatterns.forEach(p => console.log(`[Self-Improve] - ${p.rule}`));
788
816
  }
@@ -790,6 +818,18 @@ export class ClaudeCodeAdapter {
790
818
  console.warn(`[Self-Improve] Failed:`, err instanceof Error ? err.message : err);
791
819
  }
792
820
  }
821
+ /** Track task completion metrics. */
822
+ trackMetrics(status, durationMs) {
823
+ this.metrics.taskCount++;
824
+ if (status === 'done')
825
+ this.metrics.successCount++;
826
+ else
827
+ this.metrics.failCount++;
828
+ this.metrics.totalDurationMs += durationMs;
829
+ this.metrics.avgDurationMs = Math.round(this.metrics.totalDurationMs / this.metrics.taskCount);
830
+ this.metrics.lastTaskAt = new Date().toISOString();
831
+ 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}`);
832
+ }
793
833
  async dispose() {
794
834
  // No persistent resources to clean up
795
835
  }
@@ -24,6 +24,10 @@ export interface AgentAdapter {
24
24
  * plan → execute → report progress/results via ws.
25
25
  */
26
26
  handleTask(task: TaskAssignment, ws: AgentWebSocketClient, pendingInputResolvers: Map<string, (response: InputResponse) => void>, signal?: AbortSignal, pendingPermissionResolvers?: Map<string, (approved: boolean) => void>): Promise<void>;
27
+ /**
28
+ * Get current agent performance metrics.
29
+ */
30
+ getMetrics?(): Record<string, unknown>;
27
31
  /**
28
32
  * Clean up resources on shutdown.
29
33
  */
@@ -750,6 +750,10 @@ ${skillContent.slice(0, 15000)}`;
750
750
  wsClient.send({ action: 'agent_ready' });
751
751
  }
752
752
  }
753
+ // Wire up agent metrics to heartbeat
754
+ if (adapter.getMetrics) {
755
+ ws.setMetricsProvider(() => adapter.getMetrics());
756
+ }
753
757
  ws.connect();
754
758
  const shutdown = () => {
755
759
  console.log('\n[Daemon] Shutting down...');
@@ -10,11 +10,14 @@ export declare class AgentWebSocketClient {
10
10
  private running;
11
11
  private onMessage;
12
12
  private onAuthFailed?;
13
+ private getAgentMetrics?;
13
14
  /** Pending flow command callbacks keyed by request ID */
14
15
  private _flowCallbacks;
15
16
  constructor(config: AgentConfig, onMessage: MessageHandler, onAuthFailed?: (code: number, reason: string) => void);
16
17
  connect(): void;
17
18
  disconnect(): void;
19
+ /** Register a callback to get agent metrics for heartbeat. */
20
+ setMetricsProvider(fn: () => Record<string, unknown>): void;
18
21
  /**
19
22
  * Send a message to the API server.
20
23
  */
@@ -15,6 +15,7 @@ export class AgentWebSocketClient {
15
15
  running = false;
16
16
  onMessage;
17
17
  onAuthFailed;
18
+ getAgentMetrics;
18
19
  /** Pending flow command callbacks keyed by request ID */
19
20
  _flowCallbacks = new Map();
20
21
  constructor(config, onMessage, onAuthFailed) {
@@ -41,6 +42,10 @@ export class AgentWebSocketClient {
41
42
  this.ws = null;
42
43
  }
43
44
  }
45
+ /** Register a callback to get agent metrics for heartbeat. */
46
+ setMetricsProvider(fn) {
47
+ this.getAgentMetrics = fn;
48
+ }
44
49
  /**
45
50
  * Send a message to the API server.
46
51
  */
@@ -273,11 +278,15 @@ export class AgentWebSocketClient {
273
278
  this._stopHeartbeat();
274
279
  this.heartbeatTimer = setInterval(() => {
275
280
  const agentType = this.config.agentType || 'claude-code';
276
- this.send({
281
+ const heartbeat = {
277
282
  action: 'heartbeat',
278
283
  system_load: getSystemLoad(),
279
284
  capabilities: detectCapabilities(agentType),
280
- });
285
+ };
286
+ if (this.getAgentMetrics) {
287
+ heartbeat.agent_metrics = this.getAgentMetrics();
288
+ }
289
+ this.send(heartbeat);
281
290
  }, 30000);
282
291
  }
283
292
  _stopHeartbeat() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"