From 6013d1d86d578ea1c998864d5a909108c3647c96 Mon Sep 17 00:00:00 2001 From: engineer Date: Wed, 3 Jun 2026 14:22:39 -0700 Subject: [PATCH] =?UTF-8?q?test(lib):=20cover=20agent-activity=20status=20?= =?UTF-8?q?labels=20(57=E2=86=9265=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract TOOL_STATUS + statusFromPart from stores/events.ts into a pure status-labels.ts (type-only Part import, erased at runtime; events.ts delegates). 8 tests pin the live 'what is the agent doing' labels: reasoning/text/known-tool mappings, shared labels (search/edit groups), unknown-tool degrade to 'Running ...', and the no-tool/unknown-type fallthroughs. typecheck clean. Co-Authored-By: Claude Opus 4.8 --- src/lib/status-labels.test.ts | 49 +++++++++++++++++++++++++++++++++++ src/lib/status-labels.ts | 27 +++++++++++++++++++ src/stores/events.ts | 24 +---------------- 3 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 src/lib/status-labels.test.ts create mode 100644 src/lib/status-labels.ts diff --git a/src/lib/status-labels.test.ts b/src/lib/status-labels.test.ts new file mode 100644 index 0000000..e27b6f3 --- /dev/null +++ b/src/lib/status-labels.test.ts @@ -0,0 +1,49 @@ +import { test } from "node:test" +import assert from "node:assert/strict" +import { statusFromPart, TOOL_STATUS } from "./status-labels.ts" + +// These labels are shown live while the agent works. A new tool type must degrade +// to a sensible "Running ..." rather than a blank or wrong status. + +const part = (over: Record) => over as any + +test("reasoning parts show Thinking", () => { + assert.equal(statusFromPart(part({ type: "reasoning" })), "Thinking...") +}) + +test("text parts show Writing", () => { + assert.equal(statusFromPart(part({ type: "text" })), "Writing...") +}) + +test("known tools map to their friendly label", () => { + assert.equal(statusFromPart(part({ type: "tool", tool: "grep" })), "Searching codebase...") + assert.equal(statusFromPart(part({ type: "tool", tool: "bash" })), "Running command...") + assert.equal(statusFromPart(part({ type: "tool", tool: "edit" })), "Making edits...") +}) + +test("read/list/grep/glob and edit/write/apply_patch share labels", () => { + for (const t of ["list", "grep", "glob"]) { + assert.equal(statusFromPart(part({ type: "tool", tool: t })), "Searching codebase...") + } + for (const t of ["edit", "write", "apply_patch"]) { + assert.equal(statusFromPart(part({ type: "tool", tool: t })), "Making edits...") + } +}) + +test("an unknown tool degrades to 'Running ...'", () => { + assert.equal(statusFromPart(part({ type: "tool", tool: "frobnicate" })), "Running frobnicate...") +}) + +test("a tool part with no tool name falls through to the generic label", () => { + assert.equal(statusFromPart(part({ type: "tool" })), "Working...") +}) + +test("an unrecognized part type falls through to Working", () => { + assert.equal(statusFromPart(part({ type: "file" })), "Working...") +}) + +test("every TOOL_STATUS entry is reachable via statusFromPart", () => { + for (const [tool, label] of Object.entries(TOOL_STATUS)) { + assert.equal(statusFromPart(part({ type: "tool", tool })), label) + } +}) diff --git a/src/lib/status-labels.ts b/src/lib/status-labels.ts new file mode 100644 index 0000000..f17c2e4 --- /dev/null +++ b/src/lib/status-labels.ts @@ -0,0 +1,27 @@ +// Human-readable status labels shown while the agent works ("Searching codebase..."). +// Extracted from stores/events.ts so the mapping is unit-testable in isolation. +// `import type` is erased at runtime, so this module pulls in no RN/SDK code. +import type { Part } from "./sdk" + +// Tool status labels derived from part type. +export const TOOL_STATUS: Record = { + read: "Gathering context...", + list: "Searching codebase...", + grep: "Searching codebase...", + glob: "Searching codebase...", + webfetch: "Searching web...", + edit: "Making edits...", + write: "Making edits...", + apply_patch: "Making edits...", + bash: "Running command...", + task: "Delegating...", + todowrite: "Planning...", + todoread: "Planning...", +} + +export function statusFromPart(part: Part): string { + if (part.type === "reasoning") return "Thinking..." + if (part.type === "tool" && part.tool) return TOOL_STATUS[part.tool] || `Running ${part.tool}...` + if (part.type === "text") return "Writing..." + return "Working..." +} diff --git a/src/stores/events.ts b/src/stores/events.ts index 044ab86..cbab02e 100644 --- a/src/stores/events.ts +++ b/src/stores/events.ts @@ -3,35 +3,13 @@ import { useConnections } from "./connections" import { useSessions } from "./sessions" import { send as notify } from "../lib/notifications" import { sanitizeBody } from "../lib/notify-format" +import { statusFromPart } from "../lib/status-labels" import { addBreadcrumb } from "../lib/sentry" import type { Client, Part, Session, Message } from "../lib/sdk" // Session status from the server type SessionStatus = { type: "idle" } | { type: "busy" } | { type: "retry"; attempt: number; message: string } -// Tool status labels derived from part type -const TOOL_STATUS: Record = { - read: "Gathering context...", - list: "Searching codebase...", - grep: "Searching codebase...", - glob: "Searching codebase...", - webfetch: "Searching web...", - edit: "Making edits...", - write: "Making edits...", - apply_patch: "Making edits...", - bash: "Running command...", - task: "Delegating...", - todowrite: "Planning...", - todoread: "Planning...", -} - -function statusFromPart(part: Part): string { - if (part.type === "reasoning") return "Thinking..." - if (part.type === "tool" && part.tool) return TOOL_STATUS[part.tool] || `Running ${part.tool}...` - if (part.type === "text") return "Writing..." - return "Working..." -} - interface EventsState { connected: boolean reconnectAttempts: number