tuna-agent 0.1.28 → 0.1.29

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;
@@ -289,6 +307,7 @@ export class ClaudeCodeAdapter {
289
307
  });
290
308
  await new Promise(resolve => setTimeout(resolve, 150));
291
309
  ws.sendPMMessage(task.id, { sender: 'pm', content: 'Task completed.' });
310
+ this.trackMetrics('done', totalDurationMs);
292
311
  console.log(`[ClaudeCode] Agent Team task ${task.id} completed (${(totalDurationMs / 1000).toFixed(1)}s)`);
293
312
  // Post-task reflection with actual output (non-blocking)
294
313
  this.runReflection(task, lastTaskOutput || 'Task completed without text output', 'done', task.repoPath)
@@ -690,12 +709,14 @@ export class ClaudeCodeAdapter {
690
709
  const { callMem0Reflect, callMem0AddMemory } = await import('../mcp/setup.js');
691
710
  const aiReflection = await callMem0Reflect(task.description, resultSummary, status);
692
711
  if (!aiReflection) {
712
+ this.metrics.reflectionSkipCount++;
693
713
  console.log(`[Reflection] No meaningful lesson — skipping storage for task ${task.id}`);
694
714
  return;
695
715
  }
696
716
  // Step 2: Store the AI-generated reflection in Mem0
697
717
  console.log(`[Reflection] Storing: "${aiReflection.substring(0, 100)}..."`);
698
718
  await callMem0AddMemory(aiReflection, this.agentConfig.name);
719
+ this.metrics.reflectionCount++;
699
720
  console.log(`[Reflection] Stored for task ${task.id}`);
700
721
  }
701
722
  catch (err) {
@@ -783,6 +804,7 @@ export class ClaudeCodeAdapter {
783
804
  const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
784
805
  fs.writeFileSync(claudeMdPath, existingContent + separator + `${SECTION_HEADER}\n${rulesBlock}\n`);
785
806
  }
807
+ this.metrics.patternsLearnedCount += newPatterns.length;
786
808
  console.log(`[Self-Improve] Added ${newPatterns.length} new rules to CLAUDE.md:`);
787
809
  newPatterns.forEach(p => console.log(`[Self-Improve] - ${p.rule}`));
788
810
  }
@@ -790,6 +812,18 @@ export class ClaudeCodeAdapter {
790
812
  console.warn(`[Self-Improve] Failed:`, err instanceof Error ? err.message : err);
791
813
  }
792
814
  }
815
+ /** Track task completion metrics. */
816
+ trackMetrics(status, durationMs) {
817
+ this.metrics.taskCount++;
818
+ if (status === 'done')
819
+ this.metrics.successCount++;
820
+ else
821
+ this.metrics.failCount++;
822
+ this.metrics.totalDurationMs += durationMs;
823
+ this.metrics.avgDurationMs = Math.round(this.metrics.totalDurationMs / this.metrics.taskCount);
824
+ this.metrics.lastTaskAt = new Date().toISOString();
825
+ 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}`);
826
+ }
793
827
  async dispose() {
794
828
  // No persistent resources to clean up
795
829
  }
@@ -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.29",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"