A reply that works through tools now shows its latest tool step under
"is thinking…" (not its closing note, which is the answer). While a reply
is due, the open conversation checks the session's latest messages every
few seconds (api/admin/bots/session-messages) and replays any it missed
as server:bot-message, so the thread, rail and unread all take it and the
thinking row can't stay up until a reload. MCP and custom tool steps read
in words ("Using Exa search (web search exa)").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import type { HarnessCandidate } from "@/src/harness/types";
|
|
import describeJobStep from "./describe-job-step";
|
|
|
|
const candidate = { model: { slug: "deepseek/deepseek-chat:free" } } as HarnessCandidate;
|
|
|
|
describe("describeJobStep", () => {
|
|
test("says what each tool does once, never its arguments", () => {
|
|
expect(
|
|
describeJobStep({
|
|
index: 2,
|
|
text: null,
|
|
candidate,
|
|
tool_calls: [
|
|
{ id: "a", name: "sandbox_execute_bash", arguments: '{"command":"cat secret"}' },
|
|
{ id: "b", name: "sandbox_execute_bash", arguments: "{}" },
|
|
{ id: "c", name: "update_memory", arguments: "{}" },
|
|
],
|
|
}),
|
|
).toEqual({
|
|
kind: "tool",
|
|
text: "Running a command, Updating memory",
|
|
metadata: {
|
|
step: 2,
|
|
model: "deepseek/deepseek-chat:free",
|
|
tools: ["sandbox_execute_bash", "update_memory"],
|
|
},
|
|
});
|
|
});
|
|
|
|
test("prefers what the model said it's doing", () => {
|
|
expect(
|
|
describeJobStep({
|
|
index: 0,
|
|
text: "Opening the Gmail tab.\nThen reading it.",
|
|
candidate,
|
|
tool_calls: [{ id: "a", name: "browser_navigate", arguments: "{}" }],
|
|
})?.text,
|
|
).toBe("Opening the Gmail tab.");
|
|
});
|
|
|
|
test("labels tool families and falls back to the name", () => {
|
|
expect(
|
|
describeJobStep({
|
|
index: 0,
|
|
text: "",
|
|
candidate,
|
|
tool_calls: [
|
|
{ id: "a", name: "browser_navigate", arguments: "{}" },
|
|
{ id: "b", name: "browser_click", arguments: "{}" },
|
|
{ id: "c", name: "get_weather", arguments: "{}" },
|
|
],
|
|
})?.text,
|
|
).toBe("Using the browser, Using get weather");
|
|
});
|
|
|
|
test("names an MCP server's tool in words", () => {
|
|
expect(
|
|
describeJobStep({
|
|
index: 0,
|
|
text: null,
|
|
candidate,
|
|
tool_calls: [{ id: "a", name: "exa_search__web_search_exa", arguments: "{}" }],
|
|
})?.text,
|
|
).toBe("Using Exa search (web search exa)");
|
|
});
|
|
|
|
test("keeps the first line of a text-only turn, cut to one line", () => {
|
|
const step = describeJobStep({
|
|
index: 0,
|
|
text: `\n ${"a".repeat(250)}\nSecond line`,
|
|
candidate,
|
|
tool_calls: [],
|
|
});
|
|
|
|
expect(step?.kind).toBe("note");
|
|
expect(step?.text).toHaveLength(200);
|
|
expect(step?.text.endsWith("…")).toBe(true);
|
|
});
|
|
|
|
test("skips an empty turn", () => {
|
|
expect(describeJobStep({ index: 0, text: " ", candidate, tool_calls: [] })).toBeNull();
|
|
});
|
|
});
|