Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
import type { MessageBlock } from "@rakazo/contracts";
|
|
import { isToolActivityBlock } from "@rakazo/core";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
botMessageOutcomeFromMidTurn,
|
|
clampUserProgressMessage,
|
|
extractNarrationText,
|
|
finalBlocksAfterMidTurnProgress,
|
|
isProgressMessageTruncated,
|
|
isUserProgressClientNonce,
|
|
USER_PROGRESS_MESSAGE_MAX_LENGTH,
|
|
userProgressClientNonce,
|
|
} from "./user-progress.js";
|
|
|
|
describe("clampUserProgressMessage", () => {
|
|
it("trims and rejects empty text", () => {
|
|
expect(clampUserProgressMessage(" hello ")).toBe("hello");
|
|
expect(clampUserProgressMessage(" ")).toBe("");
|
|
});
|
|
|
|
it("clamps long progress beats", () => {
|
|
const clamped = clampUserProgressMessage("x".repeat(USER_PROGRESS_MESSAGE_MAX_LENGTH + 40));
|
|
expect(clamped).toHaveLength(USER_PROGRESS_MESSAGE_MAX_LENGTH);
|
|
expect(clamped.endsWith("…")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("isProgressMessageTruncated", () => {
|
|
it("is false for text that fits", () => {
|
|
expect(isProgressMessageTruncated("short update")).toBe(false);
|
|
expect(isProgressMessageTruncated("x".repeat(USER_PROGRESS_MESSAGE_MAX_LENGTH))).toBe(false);
|
|
});
|
|
|
|
it("is true for text that would be cut off by clampUserProgressMessage", () => {
|
|
const long = "x".repeat(USER_PROGRESS_MESSAGE_MAX_LENGTH + 1);
|
|
expect(isProgressMessageTruncated(long)).toBe(true);
|
|
expect(clampUserProgressMessage(long).endsWith("…")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("extractNarrationText", () => {
|
|
it("pulls text blocks and current text while keeping tool activity", () => {
|
|
const { text, remaining } = extractNarrationText(
|
|
[
|
|
{ kind: "text", text: "Checking calendars. " },
|
|
{ kind: "steps", steps: [{ label: "Web search", count: 1 }] },
|
|
{ kind: "text", text: "Found three options." },
|
|
],
|
|
" Still looking.",
|
|
);
|
|
expect(text).toBe("Checking calendars. Found three options. Still looking.");
|
|
expect(remaining).toEqual([{ kind: "steps", steps: [{ label: "Web search", count: 1 }] }]);
|
|
});
|
|
});
|
|
|
|
describe("finalBlocksAfterMidTurnProgress", () => {
|
|
it("drops a hollow final message that is only hidden tool activity", () => {
|
|
const steps: MessageBlock = { kind: "steps", steps: [{ label: "Shell", count: 2 }] };
|
|
expect(finalBlocksAfterMidTurnProgress([steps], true)).toEqual([]);
|
|
expect(finalBlocksAfterMidTurnProgress([steps], false)).toEqual([steps]);
|
|
});
|
|
|
|
it("keeps a final answer alongside tool activity", () => {
|
|
const blocks: MessageBlock[] = [
|
|
{ kind: "steps", steps: [{ label: "Web search", count: 1 }] },
|
|
{ kind: "text", text: "You are free Tuesday afternoon." },
|
|
];
|
|
expect(finalBlocksAfterMidTurnProgress(blocks, true)).toEqual(blocks);
|
|
expect(isToolActivityBlock(blocks[0]!)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("botMessageOutcomeFromMidTurn", () => {
|
|
it("prefers the final reply as a result", () => {
|
|
expect(botMessageOutcomeFromMidTurn("All set.", ["Checking calendars…"])).toEqual({
|
|
text: "All set.",
|
|
intent: "result",
|
|
});
|
|
});
|
|
|
|
it("returns mid-turn progress as status when there is no final reply", () => {
|
|
expect(
|
|
botMessageOutcomeFromMidTurn("", ["Checking calendars…", "Found three free slots."]),
|
|
).toEqual({
|
|
text: "Checking calendars…\n\nFound three free slots.",
|
|
intent: "status",
|
|
});
|
|
});
|
|
|
|
it("returns null when nothing was posted", () => {
|
|
expect(botMessageOutcomeFromMidTurn(" ", [])).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("userProgressClientNonce", () => {
|
|
it("tags mid-turn progress messages for reconciler detection", () => {
|
|
const nonce = userProgressClientNonce("run-1", 0);
|
|
expect(nonce.startsWith("user-progress:run-1:0:")).toBe(true);
|
|
expect(isUserProgressClientNonce(nonce)).toBe(true);
|
|
expect(userProgressClientNonce("run-1", 0)).not.toBe(nonce);
|
|
expect(isUserProgressClientNonce(null)).toBe(false);
|
|
expect(isUserProgressClientNonce("other")).toBe(false);
|
|
});
|
|
});
|