test(lib): cover agent-activity status labels (57→65 tests)
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 <tool>...', and the no-tool/unknown-type fallthroughs. typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
49
src/lib/status-labels.test.ts
Normal file
49
src/lib/status-labels.test.ts
Normal file
@@ -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 <tool>..." rather than a blank or wrong status.
|
||||||
|
|
||||||
|
const part = (over: Record<string, unknown>) => 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 <tool>...'", () => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
27
src/lib/status-labels.ts
Normal file
27
src/lib/status-labels.ts
Normal file
@@ -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<string, string> = {
|
||||||
|
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..."
|
||||||
|
}
|
||||||
@@ -3,35 +3,13 @@ import { useConnections } from "./connections"
|
|||||||
import { useSessions } from "./sessions"
|
import { useSessions } from "./sessions"
|
||||||
import { send as notify } from "../lib/notifications"
|
import { send as notify } from "../lib/notifications"
|
||||||
import { sanitizeBody } from "../lib/notify-format"
|
import { sanitizeBody } from "../lib/notify-format"
|
||||||
|
import { statusFromPart } from "../lib/status-labels"
|
||||||
import { addBreadcrumb } from "../lib/sentry"
|
import { addBreadcrumb } from "../lib/sentry"
|
||||||
import type { Client, Part, Session, Message } from "../lib/sdk"
|
import type { Client, Part, Session, Message } from "../lib/sdk"
|
||||||
|
|
||||||
// Session status from the server
|
// Session status from the server
|
||||||
type SessionStatus = { type: "idle" } | { type: "busy" } | { type: "retry"; attempt: number; message: string }
|
type SessionStatus = { type: "idle" } | { type: "busy" } | { type: "retry"; attempt: number; message: string }
|
||||||
|
|
||||||
// Tool status labels derived from part type
|
|
||||||
const TOOL_STATUS: Record<string, string> = {
|
|
||||||
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 {
|
interface EventsState {
|
||||||
connected: boolean
|
connected: boolean
|
||||||
reconnectAttempts: number
|
reconnectAttempts: number
|
||||||
|
|||||||
Reference in New Issue
Block a user