Commit eddb00cc99ad

Vincent Demeester <vincent@sbr.pm>
2026-02-16 14:18:45
Phase 4: Status tool with TDD (RED-GREEN)
RED phase: - Created 5 failing tests for status tool - Tests covered: metadata, parameters, execution, content format - Verified tests fail with expected errors GREEN phase: - Implemented statusTool as AgentTool object - Returns system uptime, memory usage, load average - Formats data as human-readable text - All 5 tests pass ✅ Total: 20 tests passing (15 agent wrapper + 5 status tool) Note: Using AgentTool directly instead of ExtensionAPI (Daneel is standalone service, not pi-coding-agent extension)
Changed files (2)
src/pi/tools/status.test.ts
@@ -0,0 +1,70 @@
+/**
+ * Tests for Status Tool
+ * 
+ * RED phase: Write these tests first, verify they fail
+ * GREEN phase: Implement minimal code to pass
+ * REFACTOR phase: Improve structure while keeping tests green
+ */
+
+import { describe, it, expect } from "vitest";
+import { statusTool } from "./status.js";
+
+describe("Status Tool", () => {
+  it("should have correct tool metadata", () => {
+    expect(statusTool.name).toBe("status");
+    expect(statusTool.label).toBeDefined();
+    expect(statusTool.description).toBeDefined();
+    expect(statusTool.description).toContain("system");
+  });
+
+  it("should have no required parameters", () => {
+    expect(statusTool.parameters).toBeDefined();
+    // Empty object schema = no parameters
+  });
+
+  it("should execute and return system status", async () => {
+    const result = await statusTool.execute(
+      "test-call-id",
+      {},
+      new AbortController().signal,
+      () => {}
+    );
+
+    expect(result).toBeDefined();
+    expect(result.content).toBeDefined();
+    expect(Array.isArray(result.content)).toBe(true);
+    expect(result.content.length).toBeGreaterThan(0);
+  });
+
+  it("should return text content with system info", async () => {
+    const result = await statusTool.execute(
+      "test-call-id",
+      {},
+      new AbortController().signal,
+      () => {}
+    );
+
+    const textContent = result.content.find((c: any) => c.type === "text");
+    expect(textContent).toBeDefined();
+    
+    const text = (textContent as any).text.toLowerCase();
+    expect(text).toContain("uptime");
+    expect(text).toContain("memory");
+    expect(text).toContain("load");
+  });
+
+  it("should include numeric values in status", async () => {
+    const result = await statusTool.execute(
+      "test-call-id",
+      {},
+      new AbortController().signal,
+      () => {}
+    );
+
+    const textContent = result.content.find((c: any) => c.type === "text");
+    const text = (textContent as any).text;
+    
+    // Should contain numbers (uptime seconds, memory MB, load average)
+    expect(text).toMatch(/\d+/);
+  });
+});
src/pi/tools/status.ts
@@ -0,0 +1,68 @@
+/**
+ * Status Tool - Show system status (uptime, memory, load)
+ */
+
+import { Type } from "@sinclair/typebox";
+import type { AgentTool } from "@mariozechner/pi-agent-core";
+import * as os from "os";
+
+export const statusTool: AgentTool = {
+  name: "status",
+  label: "System Status",
+  description: "Show system status including uptime, memory usage, and load average",
+  parameters: Type.Object({}),
+  execute: async (toolCallId, params, signal, onUpdate) => {
+      // Get system information
+      const uptimeSeconds = os.uptime();
+      const totalMemory = os.totalmem();
+      const freeMemory = os.freemem();
+      const usedMemory = totalMemory - freeMemory;
+      const memoryUsagePercent = ((usedMemory / totalMemory) * 100).toFixed(1);
+      const loadAvg = os.loadavg();
+
+      // Format uptime
+      const hours = Math.floor(uptimeSeconds / 3600);
+      const minutes = Math.floor((uptimeSeconds % 3600) / 60);
+      const uptimeFormatted = `${hours}h ${minutes}m`;
+
+      // Format memory
+      const totalMemoryGB = (totalMemory / (1024 * 1024 * 1024)).toFixed(2);
+      const usedMemoryGB = (usedMemory / (1024 * 1024 * 1024)).toFixed(2);
+
+      const statusText = `System Status:
+
+Uptime: ${uptimeFormatted} (${Math.floor(uptimeSeconds)}s)
+
+Memory:
+  Total: ${totalMemoryGB} GB
+  Used:  ${usedMemoryGB} GB (${memoryUsagePercent}%)
+  Free:  ${(freeMemory / (1024 * 1024 * 1024)).toFixed(2)} GB
+
+Load Average:
+  1 min:  ${loadAvg[0].toFixed(2)}
+  5 min:  ${loadAvg[1].toFixed(2)}
+  15 min: ${loadAvg[2].toFixed(2)}
+
+Platform: ${os.platform()} ${os.arch()}
+CPUs: ${os.cpus().length}`;
+
+      return {
+        content: [
+          {
+            type: "text" as const,
+            text: statusText,
+          },
+        ],
+        details: {
+          uptime: uptimeSeconds,
+          memory: {
+            total: totalMemory,
+            used: usedMemory,
+            free: freeMemory,
+            usagePercent: parseFloat(memoryUsagePercent),
+          },
+          load: loadAvg,
+        },
+      };
+  },
+};